fractorium/Source/Fractorium/FractoriumLibrary.cpp

1052 lines
45 KiB
C++
Raw Normal View History

#include "FractoriumPch.h"
#include "Fractorium.h"
/// <summary>
/// Initialize the library tree UI.
/// </summary>
void Fractorium::InitLibraryUI()
{
ui.LibraryTree->SetMainWindow(this);
//Making the TreeItemChanged() events use a direct connection is absolutely critical.
connect(ui.LibraryTree, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(OnEmberTreeItemChanged(QTreeWidgetItem*, int)), Qt::DirectConnection);
connect(ui.LibraryTree, SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)), this, SLOT(OnEmberTreeItemDoubleClicked(QTreeWidgetItem*, int)), Qt::QueuedConnection);
connect(ui.LibraryTree, SIGNAL(itemActivated(QTreeWidgetItem*, int)), this, SLOT(OnEmberTreeItemDoubleClicked(QTreeWidgetItem*, int)), Qt::QueuedConnection);
connect(ui.SequenceTree, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(OnSequenceTreeItemChanged(QTreeWidgetItem*, int)), Qt::DirectConnection);
connect(ui.SequenceStartPreviewsButton, SIGNAL(clicked(bool)), this, SLOT(OnSequenceStartPreviewsButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.SequenceStopPreviewsButton, SIGNAL(clicked(bool)), this, SLOT(OnSequenceStopPreviewsButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.SequenceAllButton, SIGNAL(clicked(bool)), this, SLOT(OnSequenceAllButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.SequenceGenerateButton, SIGNAL(clicked(bool)), this, SLOT(OnSequenceGenerateButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.SequenceRenderButton, SIGNAL(clicked(bool)), this, SLOT(OnSequenceRenderButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.SequenceAnimateButton, SIGNAL(clicked(bool)), this, SLOT(OnSequenceAnimateButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.SequenceClearButton, SIGNAL(clicked(bool)), this, SLOT(OnSequenceClearButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.SequenceSaveButton, SIGNAL(clicked(bool)), this, SLOT(OnSequenceSaveButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.SequenceOpenButton, SIGNAL(clicked(bool)), this, SLOT(OnSequenceOpenButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.SequenceRandomizeStaggerCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnSequenceRandomizeStaggerCheckBoxStateChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceRandomizeFramesPerRotCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnSequenceRandomizeFramesPerRotCheckBoxStateChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceRandomizeRotationsCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnSequenceRandomizeRotationsCheckBoxStateChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceRandomizeBlendFramesCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnSequenceRandomizeBlendFramesCheckBoxStateChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceRandomizeRotationsPerBlendCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnSequenceRandomizeRotationsPerBlendCheckBoxStateChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceStaggerSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnSequenceStaggerSpinBoxChanged(double)), Qt::QueuedConnection);
connect(ui.SequenceRandomStaggerMaxSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnSequenceRandomStaggerMaxSpinBoxChanged(double)), Qt::QueuedConnection);
connect(ui.SequenceStartFlameSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnSequenceStartFlameSpinBoxChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceStopFlameSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnSequenceStopFlameSpinBoxChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceFramesPerRotSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnSequenceFramesPerRotSpinBoxChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceRandomFramesPerRotMaxSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnSequenceRandomFramesPerRotMaxSpinBoxChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceRotationsSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnSequenceRotationsSpinBoxChanged(double)), Qt::QueuedConnection);
connect(ui.SequenceRandomRotationsMaxSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnSequenceRandomRotationsMaxSpinBoxChanged(double)), Qt::QueuedConnection);
connect(ui.SequenceBlendFramesSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnSequenceBlendFramesSpinBoxChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceRandomBlendMaxFramesSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnSequenceRandomBlendMaxFramesSpinBoxChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceRotationsPerBlendSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnSequenceRandomRotationsPerBlendSpinBoxChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceRotationsPerBlendMaxSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnSequenceRandomRotationsPerBlendMaxSpinBoxChanged(int)), Qt::QueuedConnection);
//Stagger
ui.SequenceStaggerSpinBox->setValue(m_Settings->Stagger());//Lower.
ui.SequenceStaggerSpinBox->setMaximum(std::numeric_limits<int>::max());//Lower max = upper.
ui.SequenceRandomStaggerMaxSpinBox->setValue(m_Settings->StaggerMax());//Upper.
ui.SequenceRandomStaggerMaxSpinBox->setMinimum(m_Settings->Stagger());//Upper min = lower max.
//Frames per rotation.
ui.SequenceFramesPerRotSpinBox->setValue(m_Settings->FramesPerRot());//Lower.
ui.SequenceFramesPerRotSpinBox->setMaximum(std::numeric_limits<int>::max());//Lower max = upper.
ui.SequenceRandomFramesPerRotMaxSpinBox->setValue(m_Settings->FramesPerRotMax());//Upper.
ui.SequenceRandomFramesPerRotMaxSpinBox->setMinimum(m_Settings->FramesPerRot());//Upper min = lower max.
//Rotations.
ui.SequenceRotationsSpinBox->setValue(m_Settings->Rotations());//Lower.
ui.SequenceRotationsSpinBox->setMaximum(std::numeric_limits<int>::max());//Lower max = upper.
ui.SequenceRandomRotationsMaxSpinBox->setValue(m_Settings->RotationsMax());//Upper.
ui.SequenceRandomRotationsMaxSpinBox->setMinimum(m_Settings->Rotations());//Upper min = lower max.
//Blend frames.
ui.SequenceBlendFramesSpinBox->setValue(m_Settings->BlendFrames());//Lower.
ui.SequenceBlendFramesSpinBox->setMaximum(std::numeric_limits<int>::max());//Lower max = upper.
ui.SequenceRandomBlendMaxFramesSpinBox->setValue(m_Settings->BlendFramesMax());//Upper.
ui.SequenceRandomBlendMaxFramesSpinBox->setMinimum(m_Settings->BlendFrames());//Upper min = lower max.
//Rotations per blend.
ui.SequenceRotationsPerBlendSpinBox->setValue(m_Settings->RotationsPerBlend());//Lower.
ui.SequenceRotationsPerBlendSpinBox->setMaximum(std::numeric_limits<int>::max());//Lower max = upper.
ui.SequenceRotationsPerBlendMaxSpinBox->setValue(m_Settings->RotationsPerBlendMax());//Upper.
ui.SequenceRotationsPerBlendMaxSpinBox->setMinimum(m_Settings->RotationsPerBlend());//Upper min = lower max.
//Linear.
ui.SequenceLinearCheckBox->setChecked(m_Settings->Linear());
//Animation FPS.
ui.SequenceAnimationFpsSpinBox->setValue(m_Settings->AnimationFps());
}
/// <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 (const 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(NAME_COL, i == index ? Qt::Checked : Qt::Unchecked);
}
}
}
}
/// <summary>
/// Get the index of the currently selected ember in the library tree.
/// </summary>
/// <param name="isChecked">Whether to search for items that are checked or items that are only selected</param>
/// <returns>A pair containing the index of the item clicked and a pointer to the item</param>
vector<pair<size_t, QTreeWidgetItem*>> Fractorium::GetCurrentEmberIndex(bool isChecked)
{
int index = 0;
QTreeWidgetItem* item = nullptr;
const auto tree = ui.LibraryTree;
vector<pair<size_t, QTreeWidgetItem*>> v;
if (const auto top = tree->topLevelItem(0))
{
for (int i = 0; i < top->childCount(); i++)//Iterate through all of the children, which will represent the open embers.
{
item = top->child(index);
if (item && item->isSelected())
{
if (isChecked)
{
if (item->checkState(NAME_COL) == Qt::Checked)
v.push_back(make_pair(index, item));
}
else
v.push_back(make_pair(index, item));
}
index++;
}
}
return v;
}
/// <summary>
/// Slot function to be called via QMetaObject::invokeMethod() to update preview images in the preview thread.
/// </summary>
/// <param name="item">The item double clicked on</param>
/// <param name="v">The vector holding the RGBA bitmap</param>
/// <param name="w">The width of the bitmap</param>
/// <param name="h">The height of the bitmap</param>
void Fractorium::SetTreeItemData(EmberTreeWidgetItemBase* item, vv4F& v, uint w, uint h)
{
m_PreviewVec.resize(size_t(w) * size_t(h) * 4);
Rgba32ToRgba8(v.data(), m_PreviewVec.data(), w, h, m_Settings->Transparency());
item->SetImage(m_PreviewVec, w, h);
}
/// <summary>
/// Set all libary tree entries to the name of the corresponding ember they represent.
/// Set all libary tree entries to point to the underlying ember they represent.
/// </summary>
/// <param name="update">A bitfield representing the type of synchronizing to do. Update one or more of index, name or pointer.</param>
template <typename T>
void FractoriumEmberController<T>::SyncLibrary(eLibraryUpdate update)
{
auto it = m_EmberFile.m_Embers.begin();
const auto tree = m_Fractorium->ui.LibraryTree;
if (const auto top = tree->topLevelItem(0))
{
for (int i = 0; i < top->childCount() && it != m_EmberFile.m_Embers.end(); ++i, ++it)//Iterate through all of the children, which will represent the open embers.
{
if (auto emberItem = dynamic_cast<EmberTreeWidgetItem<T>*>(top->child(i)))//Cast the child widget to the EmberTreeWidgetItem type.
{
if (static_cast<uint>(update) & static_cast<uint>(eLibraryUpdate::INDEX))
it->m_Index = i;
if (static_cast<uint>(update) & static_cast<uint>(eLibraryUpdate::NAME))
emberItem->setText(NAME_COL, QString::fromStdString(it->m_Name));
if (static_cast<uint>(update) & static_cast<uint>(eLibraryUpdate::POINTER))
emberItem->SetEmberPointer(&(*it));
if (emberItem->checkState(NAME_COL) == Qt::Checked)
m_EmberFilePointer = emberItem->GetEmber();
emberItem->setText(INDEX_COL, ToString(i));
}
}
}
}
/// <summary>
/// Fill the library tree with the names of the embers in the
/// currently opened file.
/// Start preview render thread.
/// </summary>
/// <param name="selectIndex">After the tree is filled, select this index. Pass -1 to omit selecting an index.</param>
template <typename T>
void FractoriumEmberController<T>::FillLibraryTree(int selectIndex)
{
StopAllPreviewRenderers();
const uint size = PREVIEW_SIZE;
vector<unsigned char> empy_preview(size * size * 4);
const auto tree = m_Fractorium->ui.LibraryTree;
tree->clear();
auto fileItem = new QTreeWidgetItem(tree);
QFileInfo info(m_EmberFile.m_Filename);
fileItem->setText(NAME_COL, info.fileName());
fileItem->setToolTip(NAME_COL, m_EmberFile.m_Filename);
fileItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled);
uint i = 0;
for (auto& it : m_EmberFile.m_Embers)
{
auto emberItem = new EmberTreeWidgetItem<T>(&it, fileItem);
auto istr = ToString(i++);
emberItem->setText(INDEX_COL, istr);
if (it.m_Name.empty())
emberItem->setText(NAME_COL, istr);
else
emberItem->setText(NAME_COL, it.m_Name.c_str());
emberItem->setToolTip(NAME_COL, emberItem->text(NAME_COL));
emberItem->SetImage(empy_preview, size, size);
}
if (selectIndex != -1)
m_Fractorium->SelectLibraryItem(selectIndex);
m_Fractorium->SyncFileCountToSequenceCount();
RenderLibraryPreviews(0, static_cast<uint>(m_EmberFile.Size()));
tree->expandAll();
}
/// <summary>
/// Update the library tree with the newly added embers (most likely from pasting) and
/// only render previews for the new ones, without clearing the entire tree.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::UpdateLibraryTree()
{
const uint size = PREVIEW_SIZE;
vector<unsigned char> empy_preview(size * size * 4);
const auto tree = m_Fractorium->ui.LibraryTree;
if (auto top = tree->topLevelItem(0))
{
const int origChildCount = top->childCount();
int i = origChildCount;
for (auto it = Advance(m_EmberFile.m_Embers.begin(), i); it != m_EmberFile.m_Embers.end(); ++it)
{
auto emberItem = new EmberTreeWidgetItem<T>(&(*it), top);
auto istr = ToString(i++);
emberItem->setText(INDEX_COL, istr);
if (it->m_Name.empty())
emberItem->setText(NAME_COL, istr);
else
emberItem->setText(NAME_COL, it->m_Name.c_str());
emberItem->setToolTip(NAME_COL, emberItem->text(NAME_COL));
emberItem->SetImage(empy_preview, size, size);
}
//When adding elements, ensure all indices are sequential.
SyncLibrary(eLibraryUpdate::INDEX);
m_Fractorium->SyncFileCountToSequenceCount();
RenderLibraryPreviews(origChildCount, static_cast<uint>(m_EmberFile.Size()));
}
}
/// <summary>
/// Copy the text of the item which was changed to the name of the current ember.
/// Ensure all names are unique in the opened file.
/// This seems to be called spuriously, so we do a check inside to make sure
/// the text was actually changed.
/// We also have to wrap the dynamic_cast call in a try/catch block because this can
/// be called on a widget that has already been deleted.
/// </summary>
/// <param name="item">The libary tree item changed</param>
/// <param name="col">The column clicked, ignored.</param>
template <typename T>
void FractoriumEmberController<T>::EmberTreeItemChanged(QTreeWidgetItem* item, int col)
{
try
{
const auto tree = m_Fractorium->ui.LibraryTree;
if (auto emberItem = dynamic_cast<EmberTreeWidgetItem<T>*>(item))
{
auto oldName = emberItem->GetEmber()->m_Name;//First preserve the previous name.
auto newName = emberItem->text(NAME_COL).toStdString();
//Checking/unchecking other items shouldn't perform the processing below.
//If nothing changed, nothing to do.
if (!emberItem->isSelected() && newName == oldName)
return;
if (newName.empty())//Prevent empty string.
{
emberItem->UpdateEditText();
return;
}
emberItem->UpdateEmberName();//Copy edit text to the ember's name variable.
m_EmberFile.MakeNamesUnique();//Ensure all names remain unique.
SyncLibrary(eLibraryUpdate::NAME);//Copy all ember names to the tree items since some might have changed to be made unique.
newName = emberItem->GetEmber()->m_Name;//Get the new, final, unique name.
if (m_EmberFilePointer && m_EmberFilePointer == emberItem->GetEmber() && oldName != newName)//If the ember edited was the current one, and the name was indeed changed, update the name of the current one.
{
m_Ember.m_Name = newName;
m_LastSaveCurrent = "";//Reset will force the dialog to show on the next save current since the user probably wants a different name.
}
}
else if (const auto parentItem = dynamic_cast<QTreeWidgetItem*>(item))
{
const auto text = parentItem->text(NAME_COL);
if (text != "")
{
m_EmberFile.m_Filename = text;
m_LastSaveAll = "";//Reset will force the dialog to show on the next save all since the user probably wants a different name.
}
}
}
catch (const std::exception& e)
{
qDebug() << "FractoriumEmberController<T>::EmberTreeItemChanged() : Exception thrown: " << e.what();
}
}
void Fractorium::OnEmberTreeItemChanged(QTreeWidgetItem* item, int col)
{
if (item && ui.LibraryTree->topLevelItemCount())//This can sometimes be spurriously called even when the tree is empty.
m_Controller->EmberTreeItemChanged(item, col);
}
/// <summary>
/// Set the current ember to the selected item.
/// Clears the undo state.
/// Resets the rendering process.
/// Called when the user double clicks on a library tree item.
/// This will get called twice for some reason, so the check state is checked to prevent duplicate processing.
/// </summary>
/// <param name="item">The item double clicked on</param>
/// <param name="col">The column clicked</param>
template <typename T>
void FractoriumEmberController<T>::EmberTreeItemDoubleClicked(QTreeWidgetItem* item, int col)
{
if (item->checkState(col) == Qt::Unchecked)
SetEmber(m_Fractorium->ui.LibraryTree->currentIndex().row(), false);
}
void Fractorium::OnEmberTreeItemDoubleClicked(QTreeWidgetItem* item, int col)
{
if (ui.LibraryTree->topLevelItemCount())//This can sometimes be spurriously called even when the tree is empty.
m_Controller->EmberTreeItemDoubleClicked(item, col);
}
/// <summary>
/// Move a possibly disjoint selection of library items from their indices,
/// to another index.
/// </summary>
/// <param name="items">The selected list of items to move</param>
/// <param name="destRow">The destination index to move the item to</param>
template <typename T>
void FractoriumEmberController<T>::MoveLibraryItems(const QModelIndexList& items, int destRow)
{
int i = 0;
const auto startRow = items[0].row();
const auto tree = m_Fractorium->ui.LibraryTree;
const auto top = tree->topLevelItem(0);
list<string> names;
for (auto& item : items)
if (auto temp = m_EmberFile.Get(item.row()))
names.push_back(temp->m_Name);
auto b = m_EmberFile.m_Embers.begin();
const auto result = Gather(b, m_EmberFile.m_Embers.end(), Advance(b, destRow), [&](const Ember<T>& ember)
{
auto position = std::find(names.begin(), names.end(), ember.m_Name);
if (position != names.end())
{
names.erase(position);
return true;
}
return false;
});
tree->update();
SyncLibrary(eLibraryUpdate(static_cast<uint>(eLibraryUpdate::INDEX) | static_cast<uint>(eLibraryUpdate::POINTER)));
//SyncLibrary(eLibraryUpdate(eLibraryUpdate::INDEX | eLibraryUpdate::POINTER | eLibraryUpdate::NAME));
}
/// <summary>
/// Delete the currently selected items in the tree.
/// Note this is not necessarilly the current ember, it's just the items
/// in the tree that are selected.
/// </summary>
/// <param name="v">A vector of pairs, each containing the index of the item selected and a pointer to the item</param>
template <typename T>
void FractoriumEmberController<T>::Delete(const vector<pair<size_t, QTreeWidgetItem*>>& v)
{
size_t offset = 0;
uint last = 0;
for (auto& p : v)
{
if (p.second && m_EmberFile.Delete(p.first - offset))
{
last = uint(p.first - offset);
delete p.second;
SyncLibrary(eLibraryUpdate::INDEX);
m_Fractorium->SyncFileCountToSequenceCount();
}
offset++;
}
//Select the next item in the tree closest to the last one that was deleted.
if (const auto top = m_Fractorium->ui.LibraryTree->topLevelItem(0))
{
last = std::min<uint>(top->childCount() - 1, last);
if (auto item = dynamic_cast<EmberTreeWidgetItem<T>*>(top->child(last)))
if (item->GetEmber()->m_Name != m_Ember.m_Name)
EmberTreeItemDoubleClicked(item, 0);
}
}
/// <summary>
/// Called when the user presses and releases the delete key while the library tree has the focus,
/// and an item is selected.
/// </summary>
/// <param name="v">A vector of pairs, each containing the index of the item selected and a pointer to the item</param>
void Fractorium::OnDelete(const vector<pair<size_t, QTreeWidgetItem*>>& v)
{
m_Controller->Delete(v);
}
/// <summary>
/// Stop the preview renderer if it's already running.
/// Clear all of the existing preview images, then start the preview rendering thread.
/// Optionally only render previews for a subset of all open embers.
/// </summary>
/// <param name="start">The 0-based index to start rendering previews for</param>
/// <param name="end">The 0-based index which is one beyond the last ember to render a preview for</param>
template <typename T>
void FractoriumEmberController<T>::RenderPreviews(QTreeWidget* tree, TreePreviewRenderer<T>* renderer, EmberFile<T>& file, uint start, uint end)
{
renderer->Stop();
if (start == UINT_MAX && end == UINT_MAX)
{
// Animated item might be at index 0, previews go in last item.
if (const auto top = tree->topLevelItem(tree->topLevelItemCount() - 1))
{
const auto childCount = top->childCount();
vector<unsigned char> emptyPreview(PREVIEW_SIZE * PREVIEW_SIZE * 4);
for (int i = 0; i < childCount; i++)
if (auto treeItem = dynamic_cast<EmberTreeWidgetItemBase*>(top->child(i)))
treeItem->SetImage(emptyPreview, PREVIEW_SIZE, PREVIEW_SIZE);
}
renderer->Render(0, uint(file.Size()));
}
else
renderer->Render(start, end);
}
/// <summary>
/// Wrapper around calling RenderPreviews with the appropriate values passed in for the previews in the main library tree.
/// </summary>
/// <param name="start">The 0-based index to start rendering previews for</param>
/// <param name="end">The 0-based index which is one beyond the last ember to render a preview for</param>
template <typename T>
void FractoriumEmberController<T>::RenderLibraryPreviews(uint start, uint end)
{
RenderPreviews(m_Fractorium->ui.LibraryTree, m_LibraryPreviewRenderer.get(), m_EmberFile, start, end);
}
template <typename T>
void FractoriumEmberController<T>::StopLibraryPreviewRender()
{
m_LibraryPreviewRenderer->Stop();
QApplication::processEvents();
}
/// <summary>
/// Thing wrapper around StopLibraryPreviewRender() and StopSequencePreviewRender() to stop both preview renderers.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::StopAllPreviewRenderers()
{
StopLibraryPreviewRender();
StopSequencePreviewRender();
}
template <typename T>
void FractoriumEmberController<T>::AddAnimationItem()
{
auto fileItem = new QTreeWidgetItem(m_Fractorium->ui.SequenceTree);
fileItem->setText(NAME_COL, "Rendered Animation");
fileItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
auto emberItem = new EmberTreeWidgetItemBase(fileItem);
emberItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
emberItem->setToolTip(INDEX_COL, "Animated Frame");
const uint size = PREVIEW_SIZE;
vector<unsigned char> empy_preview(size * size * 4);
emberItem->SetImage(empy_preview, size, size);
}
/// <summary>
/// Fill the sequence tree with the names of the embers in the
/// currently generated sequence.
/// Start the sequence preview render thread.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::FillSequenceTree()
{
StopAllPreviewRenderers();
const uint size = PREVIEW_SIZE;
vector<unsigned char> empy_preview(size * size * 4);
const auto tree = m_Fractorium->ui.SequenceTree;
tree->clear();
//Add extra TreeWidget for animation at index 0.
AddAnimationItem();
m_AnimateTimer->stop();
auto fileItem = new QTreeWidgetItem(tree);
QFileInfo info(m_SequenceFile.m_Filename);
fileItem->setText(NAME_COL, info.fileName());
fileItem->setToolTip(NAME_COL, m_SequenceFile.m_Filename);
fileItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable);
uint i = 0;
for (auto& it : m_SequenceFile.m_Embers)
{
auto emberItem = new EmberTreeWidgetItemBase(fileItem);
auto istr = ToString(i++);
if (it.m_Name.empty())
emberItem->setText(NAME_COL, istr);
else
emberItem->setText(NAME_COL, it.m_Name.c_str());
emberItem->setText(INDEX_COL, istr);
emberItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
emberItem->setToolTip(NAME_COL, emberItem->text(NAME_COL));
emberItem->SetImage(empy_preview, size, size);
}
tree->expandAll();
//Hide, then show the animation item.
tree->collapseItem(tree->topLevelItem(0));
RenderSequencePreviews(0, uint(m_SequenceFile.Size()));
if (const auto animation = tree->topLevelItem(0))
{
animation->setExpanded(true);
m_AnimateFrame = 0;
m_AnimateTimer->start(1000 / m_Fractorium->ui.SequenceAnimationFpsSpinBox->value());
}
}
/// <summary>
/// Copy the text of the root item to the name of the sequence file.
/// Called whenever the text of the root item is changed.
/// </summary>
/// <param name="item">The root sequence tree item which changed</param>
/// <param name="col">The column clicked, ignored.</param>
template <typename T>
void FractoriumEmberController<T>::SequenceTreeItemChanged(QTreeWidgetItem* item, int col)
{
if (item == m_Fractorium->ui.SequenceTree->topLevelItem(1))
{
auto text = item->text(NAME_COL);
if (text != "")
m_SequenceFile.m_Filename = text;
}
}
void Fractorium::OnSequenceTreeItemChanged(QTreeWidgetItem* item, int col)
{
if (item && ui.SequenceTree->topLevelItemCount())
m_Controller->SequenceTreeItemChanged(item, col);
}
/// <summary>
/// Wrapper around calling RenderPreviews with the appropriate values passed in for the previews in the sequence tree.
/// Called when Render Previews is clicked.
/// </summary>
/// <param name="start">Ignored, render all.</param>
/// <param name="end">Ignored, render all.</param>
template <typename T>
void FractoriumEmberController<T>::RenderSequencePreviews(uint start, uint end)
{
RenderPreviews(m_Fractorium->ui.SequenceTree, m_SequencePreviewRenderer.get(), m_SequenceFile, start, end);
}
void Fractorium::OnSequenceStartPreviewsButtonClicked(bool checked) { m_Controller->RenderSequencePreviews(); }
/// <summary>
/// Stop rendering the sequence previews.
/// Called when Stop Previews is clicked.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::StopSequencePreviewRender()
{
m_SequencePreviewRenderer->Stop();
QApplication::processEvents();
}
void Fractorium::OnSequenceStopPreviewsButtonClicked(bool checked) { m_Controller->StopSequencePreviewRender(); }
/// <summary>
/// Set the start and stop spin boxes to 0 and the length of the ember file minus 1, respectively.
/// Called whenever the count of the current file changes or when All is clicked.
/// </summary>
void Fractorium::SyncFileCountToSequenceCount()
{
if (const auto top = ui.LibraryTree->topLevelItem(0))
{
const int count = top->childCount() - 1;
ui.LibraryTree->headerItem()->setText(NAME_COL, "Current Flame File (" + QString::number(top->childCount()) + ")");
ui.SequenceStartFlameSpinBox->setMinimum(0);
ui.SequenceStartFlameSpinBox->setMaximum(count);
ui.SequenceStartFlameSpinBox->setValue(0);
ui.SequenceStopFlameSpinBox->setMinimum(0);
ui.SequenceStopFlameSpinBox->setMaximum(count);
ui.SequenceStopFlameSpinBox->setValue(count);
}
}
void Fractorium::OnSequenceAllButtonClicked(bool checked) { SyncFileCountToSequenceCount(); }
/// <summary>
/// Generate an animation sequence and place it in the sequence tree. This code is
/// mostly similar to that of EmberGenome.
/// It differs in a few ways:
/// The number of frames used in a rotation and in blending can differ. In EmberGenome, they are the same.
/// The number of rotations, frames used in rotations and frames used in blending can all be randomized.
/// Called when the Generate button is clicked.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::SequenceGenerateButtonClicked()
{
StopAllPreviewRenderers();
SaveCurrentToOpenedFile(false);
Ember<T> result;
auto& ui = m_Fractorium->ui;
auto s = m_Fractorium->m_Settings;
//Bools for determining whether to use hard coded vs. random values.
const bool randStagger = ui.SequenceRandomizeStaggerCheckBox->isChecked();
const bool randFramesRot = ui.SequenceRandomizeFramesPerRotCheckBox->isChecked();
const bool randRot = ui.SequenceRandomizeRotationsCheckBox->isChecked();
const bool randBlend = ui.SequenceRandomizeBlendFramesCheckBox->isChecked();
const bool randBlendRot = ui.SequenceRandomizeRotationsPerBlendCheckBox->isChecked();
//The direction to rotate the loops.
const bool loopsCw = ui.SequenceRotationsCWCheckBox->isChecked();
const bool loopsBlendCw = ui.SequenceRotationsPerBlendCWCheckBox->isChecked();
//Whether to stagger, default is 1 which means no stagger.
const double stagger = ui.SequenceStaggerSpinBox->value();
const double staggerMax = ui.SequenceRandomStaggerMaxSpinBox->value();
//Rotations on keyframes.
const double rots = ui.SequenceRotationsSpinBox->value();
const double rotsMax = ui.SequenceRandomRotationsMaxSpinBox->value();
//Number of frames it takes to rotate a keyframe.
const int framesPerRot = ui.SequenceFramesPerRotSpinBox->value();
const int framesPerRotMax = ui.SequenceRandomFramesPerRotMaxSpinBox->value();
//Number of frames it takes to interpolate.
const int framesBlend = ui.SequenceBlendFramesSpinBox->value();
const int framesBlendMax = ui.SequenceRandomBlendMaxFramesSpinBox->value();
//Number of rotations performed during interpolation.
const int rotsPerBlend = ui.SequenceRotationsPerBlendSpinBox->value();
const int rotsPerBlendMax = ui.SequenceRotationsPerBlendMaxSpinBox->value();
const size_t start = ui.SequenceStartFlameSpinBox->value();
const size_t stop = ui.SequenceStopFlameSpinBox->value();
const size_t startCount = ui.SequenceStartCountSpinBox->value();
const bool linear = ui.SequenceLinearCheckBox->isChecked();
const size_t keyFrames = (stop - start) + 1;
size_t frameCount = 0;
double frames = 0;
vector<pair<size_t, size_t>> devices;//Dummy.
EmberReport emberReport;
ostringstream os;
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)
{
if ((!rots || !framesPerRot) && !framesBlend)
{
QMessageBox::critical(m_Fractorium, "Animation sequence parameters error",
"Rotations and Frames per rot, or blend frames must be positive and non-zero");
return;
}
--User changes -Remove the Type field from the variations tree and instead just put the type indicator icon next to the variation name. -Double clicking to toggle variation parameter spinners now resets the value to the default if there is one, else it uses zero. If it is already using the default, it is toggled to 0. -Add a new button to toggle xaos on and off. -When duplicating a flame, insert it immediately after the one being duplicated instead of at the end of the file. -When switching between flames in a file, keep the same xform index selected rather than resetting it to the first xform each time. -Create a threaded writer for the final render and EmberAnimate so the rendering process does not get delayed by file saving which may take a long time. -Remove warning which said "Frames per rot cannot be greater than one while Rotations is zero" when generating a sequence. -Add the Circle_Rand variation from Chaotica. -Add tool tips to clarify the following items: --Auto Unique Filenames checkbox in the options dialog. --Xaos table headers. --Bug fixes -Generating sequences using the following variations would be done incorrectly: circletrans1, collideoscope, crob, curlsp, glynnsim1, glynnsim2, hypercrop, julian, julian, mobiusn, nblur, waves2, wavesn. -Adding/removing nodes from the color curve had accidentally been disabled. -The applied xaos weight table was not showing normalized weight values. -Changing the size of a flame was not observing the Apply To All checkbox. -Do not clamp the Rotate field to +/-180, because this causes the rotation to switch from CW to CCW during sequence generation. Instead, leave it exactly as the user entered it so the rotations proceed in the same direction.
2023-11-22 00:58:22 -05:00
//if (framesPerRot > 1 && !rots)//Because framesPerRot control has a min value of 1, check greater than 1. Also don't need to check the inverse like in EmberGenome.
//{
//QMessageBox::critical(m_Fractorium, "Animation sequence parameters error",
// "Frames per rot cannot be greater than one while Rotations is zero. Setting it to 1.");
//ui.SequenceFramesPerRotSpinBox->setValue(1);
//return;
//}
}
SheepTools<T, float> tools(palettePath, EmberCommon::CreateRenderer<T>(eRendererType::CPU_RENDERER, devices, false, 0, emberReport));
tools.SetSpinParams(!linear,
stagger,//Will be set again below if random is used.
0,
0,
s->Nick().toStdString(),
s->Url().toStdString(),
s->Id().toStdString(),
"",
0,
0);
if (randFramesRot)
frames = ui.SequenceRandomFramesPerRotMaxSpinBox->value();
--User changes -Remove the Type field from the variations tree and instead just put the type indicator icon next to the variation name. -Double clicking to toggle variation parameter spinners now resets the value to the default if there is one, else it uses zero. If it is already using the default, it is toggled to 0. -Add a new button to toggle xaos on and off. -When duplicating a flame, insert it immediately after the one being duplicated instead of at the end of the file. -When switching between flames in a file, keep the same xform index selected rather than resetting it to the first xform each time. -Create a threaded writer for the final render and EmberAnimate so the rendering process does not get delayed by file saving which may take a long time. -Remove warning which said "Frames per rot cannot be greater than one while Rotations is zero" when generating a sequence. -Add the Circle_Rand variation from Chaotica. -Add tool tips to clarify the following items: --Auto Unique Filenames checkbox in the options dialog. --Xaos table headers. --Bug fixes -Generating sequences using the following variations would be done incorrectly: circletrans1, collideoscope, crob, curlsp, glynnsim1, glynnsim2, hypercrop, julian, julian, mobiusn, nblur, waves2, wavesn. -Adding/removing nodes from the color curve had accidentally been disabled. -The applied xaos weight table was not showing normalized weight values. -Changing the size of a flame was not observing the Apply To All checkbox. -Do not clamp the Rotate field to +/-180, because this causes the rotation to switch from CW to CCW during sequence generation. Instead, leave it exactly as the user entered it so the rotations proceed in the same direction.
2023-11-22 00:58:22 -05:00
else if (rots)
frames = ui.SequenceFramesPerRotSpinBox->value();
--User changes -Remove the Type field from the variations tree and instead just put the type indicator icon next to the variation name. -Double clicking to toggle variation parameter spinners now resets the value to the default if there is one, else it uses zero. If it is already using the default, it is toggled to 0. -Add a new button to toggle xaos on and off. -When duplicating a flame, insert it immediately after the one being duplicated instead of at the end of the file. -When switching between flames in a file, keep the same xform index selected rather than resetting it to the first xform each time. -Create a threaded writer for the final render and EmberAnimate so the rendering process does not get delayed by file saving which may take a long time. -Remove warning which said "Frames per rot cannot be greater than one while Rotations is zero" when generating a sequence. -Add the Circle_Rand variation from Chaotica. -Add tool tips to clarify the following items: --Auto Unique Filenames checkbox in the options dialog. --Xaos table headers. --Bug fixes -Generating sequences using the following variations would be done incorrectly: circletrans1, collideoscope, crob, curlsp, glynnsim1, glynnsim2, hypercrop, julian, julian, mobiusn, nblur, waves2, wavesn. -Adding/removing nodes from the color curve had accidentally been disabled. -The applied xaos weight table was not showing normalized weight values. -Changing the size of a flame was not observing the Apply To All checkbox. -Do not clamp the Rotate field to +/-180, because this causes the rotation to switch from CW to CCW during sequence generation. Instead, leave it exactly as the user entered it so the rotations proceed in the same direction.
2023-11-22 00:58:22 -05:00
else
frames = 1;
if (randRot)
frames *= ui.SequenceRandomRotationsMaxSpinBox->value();
else
frames *= ui.SequenceRotationsSpinBox->value();
if (randBlend)
frames += ui.SequenceRandomBlendMaxFramesSpinBox->value();
else
frames += ui.SequenceBlendFramesSpinBox->value();
frames *= keyFrames;
frames += startCount;
os << setfill('0') << setprecision(0) << fixed;
m_SequenceFile.Clear();
m_SequenceFile.m_Filename = EmberFile<T>::DefaultFilename("Sequence_");
double blend;
size_t frame;
Ember<T> embers[2];//Spin needs contiguous array below, and this will also get modified, so a copy is needed to avoid modifying the embers in the original file.
const auto padding = streamsize(std::log10(frames)) + 1;
auto it = Advance(m_EmberFile.m_Embers.begin(), start);
for (size_t i = start; i <= stop && it != m_EmberFile.m_Embers.end(); i++, ++it)
{
const auto rotations = randRot ? m_Rand.Frand<double>(rots, rotsMax) : rots;
embers[0] = *it;
if (rotations > 0)
{
const auto rotFrames = randFramesRot ? m_Rand.Frand<double>(framesPerRot, framesPerRotMax) : framesPerRot;
const auto roundFrames = size_t(std::round(rotFrames * rotations));
for (frame = 0; frame < roundFrames; frame++)
{
blend = frame / rotFrames;
tools.Spin(embers[0], nullptr, result, startCount + frameCount++, blend, loopsCw);//Result is cleared and reassigned each time inside of Spin().
FormatName(result, os, padding);
m_SequenceFile.m_Embers.push_back(result);
}
//The loop above will have rotated just shy of a complete rotation.
//Rotate the next step and save in result, but do not print.
//result will be the starting point for the interp phase below.
frame = roundFrames;
blend = frame / rotFrames;
tools.Spin(embers[0], nullptr, result, startCount + frameCount, blend, loopsCw);//Do not increment frameCount here.
FormatName(result, os, padding);
}
if (i < stop)
{
if (rotations > 0)//Store the last result as the flame to interpolate from. This applies for whole or fractional values of opt.Loops().
embers[0] = result;
auto it2 = it;//Need a quick temporary to avoid modifying it, which is used in the loop.
embers[1] = *(++it2);//Get the next ember to be used with blending below.
const auto blendFrames = randBlend ? m_Rand.Frand<double>(framesBlend, framesBlendMax) : framesBlend;
const auto d = randBlendRot ? m_Rand.Frand<double>(rotsPerBlend, rotsPerBlendMax) : double(rotsPerBlend);
const auto rpb = size_t(std::round(d));
if (randStagger)
tools.Stagger(m_Rand.Frand<double>(stagger, staggerMax));
for (frame = 0; frame < blendFrames; frame++)
{
const auto seqFlag = frame == 0 || (frame == blendFrames - 1);
blend = frame / double(blendFrames);
result.Clear();
tools.SpinInter(&embers[0], nullptr, result, startCount + frameCount++, seqFlag, blend, rpb, loopsBlendCw);
FormatName(result, os, padding);
m_SequenceFile.m_Embers.push_back(result);
}
}
}
it = Advance(m_EmberFile.m_Embers.begin(), stop);
tools.Spin(*it, nullptr, result, startCount + frameCount, 0, loopsBlendCw);
FormatName(result, os, padding);
m_SequenceFile.m_Embers.push_back(result);
FillSequenceTree();//The sequence has been generated, now create preview thumbnails.
}
void Fractorium::OnSequenceGenerateButtonClicked(bool checked) { m_Controller->SequenceGenerateButtonClicked(); }
/// <summary>
/// Show the final render dialog and load the sequence into it.
/// This will automatically check the Render All and Render as Animation sequence checkboxes.
/// Called when the Render Sequence button is clicked.
/// </summary>
/// <param name="checked">Ignored.</param>
void Fractorium::OnSequenceRenderButtonClicked(bool checked)
{
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.");
SetupFinalRenderDialog();
if (m_FinalRenderDialog)
m_FinalRenderDialog->Show(true);//Show with a bool specifying that it came from the sequence generator.
}
}
/// <summary>
/// Animate the sequence
/// </summary>
template <typename T>
void FractoriumEmberController<T>::SequenceAnimateNextFrame()
{
const auto tree = m_Fractorium->ui.SequenceTree;
if (const auto renders = tree->topLevelItem(1))
{
if (renders->childCount())
{
const auto animate = dynamic_cast<EmberTreeWidgetItemBase*>(tree->topLevelItem(0)->child(0));
const auto frame = m_AnimateFrame++ % renders->childCount();
const auto nth = dynamic_cast<EmberTreeWidgetItemBase*>(renders->child(frame));
if (animate && nth)
{
if (!nth->m_Rendered)
{
m_AnimateFrame = 0;
}
else
{
animate->m_Pixmap = QPixmap(nth->m_Pixmap);
animate->setData(NAME_COL, Qt::DecorationRole, animate->m_Pixmap);
}
}
}
}
}
/// <summary>
/// Animate the sequence
/// </summary>
template <typename T>
void FractoriumEmberController<T>::SequenceAnimateButtonClicked()
{
if (const auto animation = m_Fractorium->ui.SequenceTree->topLevelItem(0))
{
if (animation->isExpanded() && m_AnimateTimer->isActive())
{
animation->setExpanded(false);
m_AnimateTimer->stop();
}
else
{
animation->setExpanded(true);
m_AnimateFrame = 0;
m_AnimateTimer->start(1000 / m_Fractorium->ui.SequenceAnimationFpsSpinBox->value());
}
}
}
void Fractorium::OnSequenceAnimateButtonClicked(bool checked) { m_Controller->SequenceAnimateButtonClicked(); }
/// <summary>
/// Clear the sequence.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::SequenceClearButtonClicked()
{
m_SequencePreviewRenderer->Stop();
m_Fractorium->ui.SequenceTree->clear();
}
void Fractorium::OnSequenceClearButtonClicked(bool checked) { m_Controller->SequenceClearButtonClicked(); }
/// <summary>
/// Save the sequence to a file.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::SequenceSaveButtonClicked()
{
auto s = m_Fractorium->m_Settings;
QString filename = m_Fractorium->SetupSaveXmlDialog(m_SequenceFile.m_Filename);
if (filename != "")
{
EmberToXml<T> writer;
QFileInfo fileInfo(filename);
for (auto& ember : m_SequenceFile.m_Embers)
ApplyXmlSavingTemplate(ember);
if (writer.Save(filename.toStdString().c_str(), m_SequenceFile.m_Embers, 0, true, true, false, false, false))
s->SaveFolder(fileInfo.canonicalPath());
else
m_Fractorium->ShowCritical("Save Failed", "Could not save sequence file, try saving to a different folder.");
}
}
void Fractorium::OnSequenceSaveButtonClicked(bool checked) { m_Controller->SequenceSaveButtonClicked(); }
/// <summary>
/// Open one or more sequence file, concatenate them all, place them in the sequence
/// tree and begin rendering the sequence previews.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::SequenceOpenButtonClicked()
{
m_SequencePreviewRenderer->Stop();
auto filenames = m_Fractorium->SetupOpenXmlDialog();
if (!filenames.empty())
{
size_t i;
EmberFile<T> emberFile;
XmlToEmber<T> parser;
vector<Ember<T>> embers;
vector<string> errors;
emberFile.m_Filename = filenames[0];
for (auto& filename : filenames)
{
embers.clear();
if (parser.Parse(filename.toStdString().c_str(), embers, true) && !embers.empty())
{
for (i = 0; i < embers.size(); i++)
if (embers[i].m_Name == "" || embers[i].m_Name == "No name")//Ensure it has a name.
embers[i].m_Name = ToString<qulonglong>(i).toStdString();
emberFile.m_Embers.insert(emberFile.m_Embers.end(), embers.begin(), embers.end());
errors = parser.ErrorReport();
}
else
{
errors = parser.ErrorReport();
m_Fractorium->ShowCritical("Open Failed", "Could not open sequence file, see info tab for details.");
}
if (!errors.empty())
m_Fractorium->ErrorReportToQTextEdit(errors, m_Fractorium->ui.InfoFileOpeningTextEdit, false);//Concat errors from all files.
}
if (emberFile.Size() > 0)//Ensure at least something was read.
{
emberFile.MakeNamesUnique();
m_SequenceFile = std::move(emberFile);//Move the temp to avoid creating dupes because we no longer need it.
FillSequenceTree();
}
}
}
void Fractorium::OnSequenceOpenButtonClicked(bool checked) { m_Controller->SequenceOpenButtonClicked(); }
/// <summary>
/// Constrain all min/max spinboxes when the max spinboxes are enabled/disabled via the random checkbox.
/// </summary>
void Fractorium::OnSequenceRandomizeStaggerCheckBoxStateChanged(int state)
{
ui.SequenceRandomStaggerMaxSpinBox->setMinimum(ui.SequenceStaggerSpinBox->value());
ui.SequenceStaggerSpinBox->setMaximum(state ? ui.SequenceRandomStaggerMaxSpinBox->value() : std::numeric_limits<int>::max());
ui.SequenceRandomStaggerMaxSpinBox->setEnabled(state);
}
void Fractorium::OnSequenceRandomizeFramesPerRotCheckBoxStateChanged(int state)
{
ui.SequenceRandomFramesPerRotMaxSpinBox->setMinimum(ui.SequenceFramesPerRotSpinBox->value());
ui.SequenceFramesPerRotSpinBox->setMaximum(state ? ui.SequenceRandomFramesPerRotMaxSpinBox->value() : std::numeric_limits<int>::max());
ui.SequenceRandomFramesPerRotMaxSpinBox->setEnabled(state);
}
void Fractorium::OnSequenceRandomizeRotationsCheckBoxStateChanged(int state)
{
ui.SequenceRandomRotationsMaxSpinBox->setMinimum(ui.SequenceRotationsSpinBox->value());
ui.SequenceRotationsSpinBox->setMaximum(state ? ui.SequenceRandomRotationsMaxSpinBox->value() : std::numeric_limits<int>::max());
ui.SequenceRandomRotationsMaxSpinBox->setEnabled(state);
}
void Fractorium::OnSequenceRandomizeBlendFramesCheckBoxStateChanged(int state)
{
ui.SequenceRandomBlendMaxFramesSpinBox->setMinimum(ui.SequenceBlendFramesSpinBox->value());
ui.SequenceBlendFramesSpinBox->setMaximum(state ? ui.SequenceRandomBlendMaxFramesSpinBox->value() : std::numeric_limits<int>::max());
ui.SequenceRandomBlendMaxFramesSpinBox->setEnabled(state);
}
void Fractorium::OnSequenceRandomizeRotationsPerBlendCheckBoxStateChanged(int state)
{
ui.SequenceRotationsPerBlendMaxSpinBox->setMinimum(ui.SequenceRotationsPerBlendSpinBox->value());
ui.SequenceRotationsPerBlendSpinBox->setMaximum(state ? ui.SequenceRotationsPerBlendMaxSpinBox->value() : std::numeric_limits<int>::max());
ui.SequenceRotationsPerBlendMaxSpinBox->setEnabled(state);
}
/// <summary>
/// Constrain all min/max spinboxes.
/// </summary>
void Fractorium::OnSequenceStaggerSpinBoxChanged(double d) { if (ui.SequenceRandomizeStaggerCheckBox->isChecked()) ui.SequenceRandomStaggerMaxSpinBox->setMinimum(d); }
void Fractorium::OnSequenceRandomStaggerMaxSpinBoxChanged(double d) { if (ui.SequenceRandomStaggerMaxSpinBox->hasFocus()) ui.SequenceStaggerSpinBox->setMaximum(d); }
void Fractorium::OnSequenceStartFlameSpinBoxChanged(int d) { ui.SequenceStopFlameSpinBox->setMinimum(d); }
void Fractorium::OnSequenceStopFlameSpinBoxChanged(int d) { if (ui.SequenceStopFlameSpinBox->hasFocus()) ui.SequenceStartFlameSpinBox->setMaximum(d); }
void Fractorium::OnSequenceFramesPerRotSpinBoxChanged(int d) { if (ui.SequenceRandomizeFramesPerRotCheckBox->isChecked()) ui.SequenceRandomFramesPerRotMaxSpinBox->setMinimum(d); }
void Fractorium::OnSequenceRandomFramesPerRotMaxSpinBoxChanged(int d) { if (ui.SequenceRandomFramesPerRotMaxSpinBox->hasFocus()) ui.SequenceFramesPerRotSpinBox->setMaximum(d); }
void Fractorium::OnSequenceRotationsSpinBoxChanged(double d) { if (ui.SequenceRandomizeRotationsCheckBox->isChecked()) ui.SequenceRandomRotationsMaxSpinBox->setMinimum(d); }
void Fractorium::OnSequenceRandomRotationsMaxSpinBoxChanged(double d) { if (ui.SequenceRandomRotationsMaxSpinBox->hasFocus()) ui.SequenceRotationsSpinBox->setMaximum(d); }
void Fractorium::OnSequenceBlendFramesSpinBoxChanged(int d) { if (ui.SequenceRandomizeBlendFramesCheckBox->isChecked()) ui.SequenceRandomBlendMaxFramesSpinBox->setMinimum(d); }
void Fractorium::OnSequenceRandomBlendMaxFramesSpinBoxChanged(int d) { if (ui.SequenceRandomBlendMaxFramesSpinBox->hasFocus()) ui.SequenceBlendFramesSpinBox->setMaximum(d); }
void Fractorium::OnSequenceRandomRotationsPerBlendSpinBoxChanged(int d) { if (ui.SequenceRandomizeRotationsPerBlendCheckBox->isChecked()) ui.SequenceRotationsPerBlendMaxSpinBox->setMinimum(d); }
void Fractorium::OnSequenceRandomRotationsPerBlendMaxSpinBoxChanged(int d) { if (ui.SequenceRotationsPerBlendMaxSpinBox->hasFocus()) ui.SequenceRotationsPerBlendSpinBox->setMaximum(d); }
/// <summary>
/// Save all sequence settings to match the values in the controls.
/// </summary>
void Fractorium::SyncSequenceSettings()
{
m_Settings->Stagger(ui.SequenceStaggerSpinBox->value());
m_Settings->StaggerMax(ui.SequenceRandomStaggerMaxSpinBox->value());
m_Settings->FramesPerRot(ui.SequenceFramesPerRotSpinBox->value());
m_Settings->FramesPerRotMax(ui.SequenceRandomFramesPerRotMaxSpinBox->value());
m_Settings->Rotations(ui.SequenceRotationsSpinBox->value());
m_Settings->RotationsMax(ui.SequenceRandomRotationsMaxSpinBox->value());
m_Settings->BlendFrames(ui.SequenceBlendFramesSpinBox->value());
m_Settings->BlendFramesMax(ui.SequenceRandomBlendMaxFramesSpinBox->value());
m_Settings->RotationsPerBlend(ui.SequenceRotationsPerBlendSpinBox->value());
m_Settings->RotationsPerBlendMax(ui.SequenceRotationsPerBlendMaxSpinBox->value());
m_Settings->Linear(ui.SequenceLinearCheckBox->isChecked());
m_Settings->AnimationFps(ui.SequenceAnimationFpsSpinBox->value());
}
template class FractoriumEmberController<float>;
#ifdef DO_DOUBLE
template class FractoriumEmberController<double>;
#endif