Initial release
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
// This file is part of VSTGUI. It is subject to the license terms
|
||||
// in the LICENSE file found in the top-level directory of this
|
||||
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
|
||||
|
||||
#include "documentcontroller.h"
|
||||
#include "startupcontroller.h"
|
||||
#include "vstgui/standalone/include/helpers/appdelegate.h"
|
||||
#include "vstgui/standalone/include/helpers/menubuilder.h"
|
||||
#include "vstgui/standalone/include/helpers/windowlistener.h"
|
||||
#include "vstgui/standalone/include/iapplication.h"
|
||||
#include "vstgui/standalone/include/icommand.h"
|
||||
#include "vstgui/standalone/include/iuidescwindow.h"
|
||||
#include "vstgui/uidescription/cstream.h"
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace VSTGUI {
|
||||
namespace ImageStitcher {
|
||||
|
||||
using namespace VSTGUI::Standalone;
|
||||
using namespace VSTGUI::Standalone::Application;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
class ImageStitcherAppDelegate : public DelegateAdapter,
|
||||
public ICommandHandler,
|
||||
public MenuBuilderAdapter,
|
||||
public WindowListenerAdapter
|
||||
{
|
||||
public:
|
||||
ImageStitcherAppDelegate ()
|
||||
: DelegateAdapter ({"ImageStitcher", "1.0.0", VSTGUI_STANDALONE_APP_URI})
|
||||
{
|
||||
}
|
||||
|
||||
bool openFiles (const std::vector<UTF8String>& paths) override
|
||||
{
|
||||
for (auto& path : paths)
|
||||
{
|
||||
if (auto docContext = DocumentContext::loadDocument (path.getString ()))
|
||||
{
|
||||
if (auto controller = DocumentWindowController::make (docContext))
|
||||
{
|
||||
controller->showWindow ();
|
||||
controller->registerWindowListener (this);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void finishLaunching () override
|
||||
{
|
||||
auto& app = IApplication::instance ();
|
||||
app.registerCommand (Commands::NewDocument, 'n');
|
||||
app.registerCommand (Commands::OpenDocument, 'o');
|
||||
app.registerCommand (Commands::SaveDocument, 's');
|
||||
app.registerCommand (Commands::SaveDocumentAs, 'S');
|
||||
app.registerCommand (ExportCommand, 'e');
|
||||
|
||||
if (app.getWindows ().empty ())
|
||||
{
|
||||
showStartupController ();
|
||||
}
|
||||
}
|
||||
|
||||
void onQuit () override { inQuit = true; }
|
||||
|
||||
void onClosed (const IWindow& window) override
|
||||
{
|
||||
if (!inQuit && IApplication::instance ().getWindows ().empty ())
|
||||
{
|
||||
showStartupController ();
|
||||
}
|
||||
}
|
||||
|
||||
void doNewDocumentCommand ()
|
||||
{
|
||||
auto docContext = DocumentContext::makeEmptyDocument ();
|
||||
if (auto controller = DocumentWindowController::make (docContext))
|
||||
{
|
||||
controller->showWindow ();
|
||||
controller->registerWindowListener (this);
|
||||
|
||||
controller->doSaveAs ([controller] (bool success) {
|
||||
if (!success)
|
||||
controller->closeWindow ();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void doOpenDocument ()
|
||||
{
|
||||
auto docContext = DocumentContext::makeEmptyDocument ();
|
||||
if (auto controller = DocumentWindowController::make (docContext))
|
||||
{
|
||||
controller->showWindow ();
|
||||
controller->registerWindowListener (this);
|
||||
controller->doOpenDocument ([controller] (bool success) {
|
||||
if (!success)
|
||||
controller->closeWindow ();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
bool canHandleCommand (const Command& command) override
|
||||
{
|
||||
if (command == Commands::NewDocument)
|
||||
return true;
|
||||
if (command == Commands::OpenDocument)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool handleCommand (const Command& command) override
|
||||
{
|
||||
if (command == Commands::NewDocument)
|
||||
{
|
||||
doNewDocumentCommand ();
|
||||
return true;
|
||||
}
|
||||
if (command == Commands::OpenDocument)
|
||||
{
|
||||
doOpenDocument ();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool prependMenuSeparator (const Interface& context, const Command& cmd) const override
|
||||
{
|
||||
if (cmd == ExportCommand || cmd == Commands::CloseWindow)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
SortFunction getCommandGroupSortFunction (const Interface& context,
|
||||
const UTF8String& group) const override
|
||||
{
|
||||
if (group == CommandGroup::File)
|
||||
{
|
||||
return [] (const UTF8String& lhs, const UTF8String& rhs) {
|
||||
static auto order = {Commands::NewDocument.name, Commands::OpenDocument.name,
|
||||
Commands::SaveDocument.name, Commands::SaveDocumentAs.name,
|
||||
ExportCommand.name, Commands::CloseWindow.name};
|
||||
auto leftIndex = std::find (order.begin (), order.end (), lhs);
|
||||
auto rightIndex = std::find (order.begin (), order.end (), rhs);
|
||||
return std::distance (leftIndex, rightIndex) > 0;
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool inQuit {false};
|
||||
};
|
||||
|
||||
static Init gAppDelegate (std::make_unique<ImageStitcherAppDelegate> (),
|
||||
{{ConfigKey::UseCompressedUIDescriptionFiles, 1}});
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // ImageStitcher
|
||||
} // VSTGUI
|
||||
+254
@@ -0,0 +1,254 @@
|
||||
// This file is part of VSTGUI. It is subject to the license terms
|
||||
// in the LICENSE file found in the top-level directory of this
|
||||
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
|
||||
|
||||
#include "document.h"
|
||||
#include "vstgui/lib/cpoint.h"
|
||||
#include "vstgui/uidescription/cstream.h"
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace VSTGUI {
|
||||
namespace ImageStitcher {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
Optional<Path> getRelativePath (const Path& root, const Path& path)
|
||||
{
|
||||
if (path.find (root) == 0)
|
||||
{
|
||||
return {path.substr (root.size ())};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
Optional<Path> getDirectoryName (const Path& path)
|
||||
{
|
||||
auto pos = path.find_last_of (PathSeparator);
|
||||
if (pos == Path::npos || pos == path.size ())
|
||||
return {};
|
||||
return {path.substr (0, pos + strlen (PathSeparator))};
|
||||
}
|
||||
|
||||
static constexpr uint32_t PersistentIdentifer = 'imst';
|
||||
static constexpr uint32_t PersistentIdentiferNew = 'ist2';
|
||||
static constexpr uint32_t PersistentVersion = 1;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // anonymous
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
Optional<std::pair<uint32_t, uint32_t>> getImageSize (const Path& path)
|
||||
{
|
||||
CFileStream stream;
|
||||
if (!stream.open (path.data (), CFileStream::kReadMode | CFileStream::kBinaryMode,
|
||||
kBigEndianByteOrder))
|
||||
return {};
|
||||
uint32_t value;
|
||||
if (!(stream >> value) || value != 0x89504E47)
|
||||
return {};
|
||||
if (!(stream >> value) || value != 0x0D0A1A0A)
|
||||
return {};
|
||||
if (!(stream >> value) || value != 0x0000000D)
|
||||
return {};
|
||||
if (!(stream >> value) || value != 0x49484452)
|
||||
return {};
|
||||
uint32_t width = 0;
|
||||
if (!(stream >> width))
|
||||
return {};
|
||||
uint32_t height = 0;
|
||||
if (!(stream >> height))
|
||||
return {};
|
||||
|
||||
return {{width, height}};
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
DocumentContextPtr DocumentContext::makeEmptyDocument ()
|
||||
{
|
||||
auto doc = std::make_shared<Document> ();
|
||||
doc->path = "Untitled.imagestitch";
|
||||
auto docContext = std::make_shared<DocumentContext> (doc);
|
||||
return docContext;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
DocumentContextPtr DocumentContext::loadDocument (const Path& path)
|
||||
{
|
||||
auto rootPath = getDirectoryName (path);
|
||||
if (!rootPath)
|
||||
return nullptr;
|
||||
|
||||
CFileStream stream;
|
||||
if (!stream.open (path.data (), CFileStream::kReadMode | CFileStream::kBinaryMode,
|
||||
kLittleEndianByteOrder))
|
||||
return nullptr;
|
||||
|
||||
uint32_t identifier;
|
||||
if (!(stream >> identifier))
|
||||
return nullptr;
|
||||
if (!(identifier == PersistentIdentifer || identifier == PersistentIdentiferNew))
|
||||
return nullptr;
|
||||
|
||||
auto doc = std::make_shared<Document> ();
|
||||
doc->path = path;
|
||||
|
||||
if (identifier == PersistentIdentiferNew)
|
||||
{
|
||||
uint32_t persistentVersion = 0;
|
||||
if (!(stream >> persistentVersion))
|
||||
return nullptr;
|
||||
if (!(stream >> doc->numFramesPerRow))
|
||||
return nullptr;
|
||||
}
|
||||
if (!(stream >> doc->width))
|
||||
return nullptr;
|
||||
if (!(stream >> doc->height))
|
||||
return nullptr;
|
||||
uint32_t numPaths;
|
||||
if (!(stream >> numPaths))
|
||||
return nullptr;
|
||||
for (uint32_t i = 0; i < numPaths; ++i)
|
||||
{
|
||||
Path p;
|
||||
if (!(stream >> p))
|
||||
return nullptr;
|
||||
Path fullPath;
|
||||
if (!pathIsAbsolute (p))
|
||||
fullPath += *rootPath;
|
||||
fullPath += p;
|
||||
doc->imagePaths.emplace_back (std::move (fullPath));
|
||||
}
|
||||
|
||||
auto docContext = std::make_shared<DocumentContext> (doc);
|
||||
return docContext;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
bool DocumentContext::save ()
|
||||
{
|
||||
auto rootPath = getDirectoryName (doc->path);
|
||||
if (!rootPath)
|
||||
return false;
|
||||
|
||||
CFileStream stream;
|
||||
if (!stream.open (doc->path.data (),
|
||||
CFileStream::kWriteMode | CFileStream::kBinaryMode |
|
||||
CFileStream::kTruncateMode,
|
||||
kLittleEndianByteOrder))
|
||||
return false;
|
||||
|
||||
if (!(stream << PersistentIdentiferNew))
|
||||
return false;
|
||||
if (!(stream << PersistentVersion))
|
||||
return false;
|
||||
if (!(stream << doc->numFramesPerRow))
|
||||
return false;
|
||||
if (!(stream << doc->width))
|
||||
return false;
|
||||
if (!(stream << doc->height))
|
||||
return false;
|
||||
uint32_t numImages = static_cast<uint32_t> (doc->imagePaths.size ());
|
||||
if (!(stream << numImages))
|
||||
return false;
|
||||
for (auto& path : doc->imagePaths)
|
||||
{
|
||||
if (auto relPath = getRelativePath (*rootPath, path))
|
||||
stream << *relPath;
|
||||
else
|
||||
stream << path;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
DocumentContext::DocumentContext (const DocumentPtr& doc) : doc (doc)
|
||||
{
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void DocumentContext::replaceDocument (const DocumentPtr& newDoc)
|
||||
{
|
||||
doc = newDoc;
|
||||
size_t index = 0;
|
||||
for (auto& path : doc->imagePaths)
|
||||
{
|
||||
listeners.forEach ([&] (auto& l) { l->onImagePathAdded (path, index); });
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
bool DocumentContext::setPath (const std::string& p)
|
||||
{
|
||||
doc->path = p;
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
bool DocumentContext::setNumFramesPerRow (uint16_t numFrames)
|
||||
{
|
||||
doc->numFramesPerRow = numFrames;
|
||||
listeners.forEach ([&] (auto& l) { l->onNumFramesPerRowChanged (numFrames); });
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
auto DocumentContext::removeImagePathAtIndex (size_t index) -> Result
|
||||
{
|
||||
if (doc->imagePaths.size () < index)
|
||||
return Result::InvalidIndex;
|
||||
auto it = doc->imagePaths.begin ();
|
||||
std::advance (it, index);
|
||||
auto path = *it;
|
||||
doc->imagePaths.erase (it);
|
||||
if (doc->imagePaths.empty ())
|
||||
doc->width = doc->height = 0;
|
||||
listeners.forEach ([&] (auto& l) { l->onImagePathRemoved (path, index); });
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
auto DocumentContext::insertImagePathAtIndex (size_t index, const Path& path) -> Result
|
||||
{
|
||||
auto imageSize = getImageSize (path);
|
||||
if (!imageSize)
|
||||
return Result::InvalidImage;
|
||||
if (!doc->imagePaths.empty ())
|
||||
{
|
||||
if (!(imageSize->first == doc->width && imageSize->second == doc->height))
|
||||
return Result::ImageSizeMismatch;
|
||||
}
|
||||
auto it = doc->imagePaths.begin ();
|
||||
if (index >= doc->imagePaths.size ())
|
||||
it = doc->imagePaths.end ();
|
||||
else
|
||||
std::advance (it, index);
|
||||
doc->imagePaths.insert (it, path);
|
||||
if (doc->imagePaths.size () == 1)
|
||||
{
|
||||
doc->width = imageSize->first;
|
||||
doc->height = imageSize->second;
|
||||
}
|
||||
listeners.forEach ([&] (auto& l) { l->onImagePathAdded (path, index); });
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void DocumentContext::addListener (IDocumentListener* listener)
|
||||
{
|
||||
listeners.add (listener);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void DocumentContext::removeListener (IDocumentListener* listener)
|
||||
{
|
||||
listeners.remove (listener);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // ImageStitcher
|
||||
} // VSTGUI
|
||||
@@ -0,0 +1,99 @@
|
||||
// This file is part of VSTGUI. It is subject to the license terms
|
||||
// in the LICENSE file found in the top-level directory of this
|
||||
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "vstgui/lib/dispatchlist.h"
|
||||
#include "vstgui/lib/optional.h"
|
||||
#include "vstgui/lib/vstguibase.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace VSTGUI {
|
||||
namespace ImageStitcher {
|
||||
|
||||
using Path = std::string;
|
||||
using PathList = std::vector<Path>;
|
||||
|
||||
#if WINDOWS
|
||||
static constexpr const auto PathSeparator = "\\";
|
||||
#else
|
||||
static constexpr const auto PathSeparator = "/";
|
||||
#endif
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
Optional<std::pair<uint32_t, uint32_t>> getImageSize (const Path& path);
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
struct Document
|
||||
{
|
||||
Path path;
|
||||
PathList imagePaths;
|
||||
uint32_t width {0};
|
||||
uint32_t height {0};
|
||||
uint16_t numFramesPerRow {1};
|
||||
};
|
||||
using DocumentPtr = std::shared_ptr<Document>;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
struct IDocumentListener
|
||||
{
|
||||
virtual void onImagePathAdded (const Path& newPath, size_t index) = 0;
|
||||
virtual void onImagePathRemoved (const Path& newPath, size_t index) = 0;
|
||||
virtual void onNumFramesPerRowChanged (uint16_t newNumFramesPerRow) = 0;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
enum class DocumentContextResult
|
||||
{
|
||||
Success,
|
||||
InvalidIndex,
|
||||
InvalidImage,
|
||||
ImageSizeMismatch,
|
||||
};
|
||||
|
||||
struct DocumentContext;
|
||||
using DocumentContextPtr = std::shared_ptr<DocumentContext>;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
struct DocumentContext
|
||||
{
|
||||
using Result = DocumentContextResult;
|
||||
|
||||
static DocumentContextPtr makeEmptyDocument ();
|
||||
static DocumentContextPtr loadDocument (const Path& path);
|
||||
|
||||
DocumentContext (const DocumentPtr& doc);
|
||||
|
||||
void replaceDocument (const DocumentPtr& doc);
|
||||
|
||||
bool save ();
|
||||
|
||||
bool setPath (const Path& p);
|
||||
bool setNumFramesPerRow (uint16_t numFrames);
|
||||
Result removeImagePathAtIndex (size_t index);
|
||||
Result insertImagePathAtIndex (size_t index, const Path& path);
|
||||
|
||||
const DocumentPtr& getDocument () const { return doc; }
|
||||
const Path& getPath () const noexcept { return doc->path; }
|
||||
const PathList& getImagePaths () const noexcept { return doc->imagePaths; }
|
||||
uint32_t getWidth () const noexcept { return doc->width; }
|
||||
uint32_t getHeight () const noexcept { return doc->height; }
|
||||
uint16_t getNumFramesPerRow () const noexcept { return doc->numFramesPerRow; }
|
||||
|
||||
void addListener (IDocumentListener* listener);
|
||||
void removeListener (IDocumentListener* listener);
|
||||
|
||||
private:
|
||||
DocumentPtr doc;
|
||||
DispatchList<IDocumentListener*> listeners;
|
||||
};
|
||||
using DocumentContextPtr = std::shared_ptr<DocumentContext>;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // ImageStitcher
|
||||
} // VSTGUI
|
||||
+746
@@ -0,0 +1,746 @@
|
||||
// This file is part of VSTGUI. It is subject to the license terms
|
||||
// in the LICENSE file found in the top-level directory of this
|
||||
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
|
||||
|
||||
#include "documentcontroller.h"
|
||||
#include "imageframesview.h"
|
||||
#include "vstgui/lib/cbitmap.h"
|
||||
#include "vstgui/lib/cdatabrowser.h"
|
||||
#include "vstgui/lib/cgradientview.h"
|
||||
#include "vstgui/lib/coffscreencontext.h"
|
||||
#include "vstgui/lib/controls/cmoviebitmap.h"
|
||||
#include "vstgui/lib/cscrollview.h"
|
||||
#include "vstgui/lib/csplitview.h"
|
||||
#include "vstgui/lib/platform/iplatformbitmap.h"
|
||||
#include "vstgui/lib/platform/platformfactory.h"
|
||||
#include "vstgui/standalone/include/helpers/uidesc/modelbinding.h"
|
||||
#include "vstgui/standalone/include/helpers/value.h"
|
||||
#include "vstgui/standalone/include/ialertbox.h"
|
||||
#include "vstgui/standalone/include/iapplication.h"
|
||||
#include "vstgui/standalone/include/iasync.h"
|
||||
#include "vstgui/uidescription/cstream.h"
|
||||
#include "vstgui/uidescription/delegationcontroller.h"
|
||||
#include "vstgui/uidescription/iuidescription.h"
|
||||
#include "vstgui/uidescription/uiattributes.h"
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace VSTGUI {
|
||||
namespace ImageStitcher {
|
||||
|
||||
using namespace VSTGUI::Standalone;
|
||||
|
||||
static CFileExtension pngFileExtension ("PNG File", "png", "image/png", 0, "public.png");
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
class ImageViewController : public DelegationController
|
||||
{
|
||||
public:
|
||||
using Proc = std::function<void (ImageFramesView*)>;
|
||||
ImageViewController (Proc&& proc, IController* parent)
|
||||
: DelegationController (parent), proc (std::move (proc))
|
||||
{
|
||||
}
|
||||
|
||||
CView* createView (const UIAttributes& attributes, const IUIDescription* description) override
|
||||
{
|
||||
if (auto name = attributes.getAttributeValue (IUIDescription::kCustomViewName))
|
||||
{
|
||||
if (*name == "ImageView")
|
||||
{
|
||||
auto imageView = new ImageFramesView ();
|
||||
CColor color;
|
||||
if (description->getColor ("Focus", color))
|
||||
imageView->setSelectionColor (color);
|
||||
if (description->getColor ("font.color", color))
|
||||
imageView->setTextColor (color);
|
||||
return imageView;
|
||||
}
|
||||
}
|
||||
return controller->createView (attributes, description);
|
||||
}
|
||||
|
||||
CView* verifyView (CView* view, const UIAttributes& attr, const IUIDescription* desc) override
|
||||
{
|
||||
if (auto name = attr.getAttributeValue (IUIDescription::kCustomViewName))
|
||||
{
|
||||
if (*name == "ImageView")
|
||||
{
|
||||
proc (dynamic_cast<ImageFramesView*> (view));
|
||||
return view;
|
||||
}
|
||||
}
|
||||
return controller->verifyView (view, attr, desc);
|
||||
}
|
||||
|
||||
private:
|
||||
Proc proc;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
class MovieBitmapController : public DelegationController
|
||||
{
|
||||
public:
|
||||
using Proc = std::function<void (CMovieBitmap*)>;
|
||||
MovieBitmapController (Proc&& proc, IController* parent)
|
||||
: DelegationController (parent), proc (std::move (proc))
|
||||
{
|
||||
}
|
||||
|
||||
CView* verifyView (CView* view, const UIAttributes& attr, const IUIDescription* desc) override
|
||||
{
|
||||
if (auto mb = dynamic_cast<CMovieBitmap*> (view))
|
||||
{
|
||||
proc (mb);
|
||||
return view;
|
||||
}
|
||||
return controller->verifyView (view, attr, desc);
|
||||
}
|
||||
|
||||
private:
|
||||
Proc proc;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
class SplitViewController : public DelegationController, public ISplitViewController
|
||||
{
|
||||
public:
|
||||
SplitViewController (IController* parent, const IUIDescription* desc)
|
||||
: DelegationController (parent), desc (desc)
|
||||
{
|
||||
}
|
||||
|
||||
bool getSplitViewSizeConstraint (int32_t index, CCoord& minSize, CCoord& maxSize,
|
||||
CSplitView* splitView) override
|
||||
{
|
||||
if (index == 0)
|
||||
{
|
||||
minSize = 150;
|
||||
maxSize = -1;
|
||||
return true;
|
||||
}
|
||||
if (index == 1)
|
||||
{
|
||||
minSize = 250;
|
||||
maxSize = -1;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
ISplitViewSeparatorDrawer* getSplitViewSeparatorDrawer (CSplitView* splitView) override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
bool storeViewSize (int32_t index, const CCoord& size, CSplitView* splitView) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
bool restoreViewSize (int32_t index, CCoord& size, CSplitView* splitView) override
|
||||
{
|
||||
if (!gradientAdded)
|
||||
{
|
||||
if (auto view = desc->createView ("SplitViewSeperatorView", this))
|
||||
{
|
||||
if (auto container = view->asViewContainer ())
|
||||
{
|
||||
auto gradientView = container->getView (0);
|
||||
gradientView->removeAttribute ('cvcr');
|
||||
container->removeView (gradientView, false);
|
||||
auto viewSize = splitView->getViewSize ();
|
||||
auto sepWidth = splitView->getSeparatorWidth ();
|
||||
gradientView->setViewSize (CRect (0, 0, sepWidth, viewSize.getHeight ()));
|
||||
splitView->addViewToSeparator (0, gradientView);
|
||||
gradientAdded = true;
|
||||
}
|
||||
view->forget ();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
const IUIDescription* desc {nullptr};
|
||||
bool gradientAdded {false};
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
std::shared_ptr<DocumentWindowController> DocumentWindowController::make (
|
||||
const DocumentContextPtr& doc)
|
||||
{
|
||||
auto controller = std::make_shared<DocumentWindowController> (doc);
|
||||
|
||||
UIDesc::Config config;
|
||||
config.uiDescFileName = "Window.uidesc";
|
||||
config.viewName = "Window";
|
||||
config.windowConfig.title = getDisplayFilename (doc->getPath ());
|
||||
config.windowConfig.autoSaveFrameName = "DocumentController";
|
||||
config.windowConfig.groupIdentifier = "Document";
|
||||
config.windowConfig.style.border ().close ().size ().centered ();
|
||||
config.customization = controller;
|
||||
config.modelBinding = controller->createModelBinding ();
|
||||
|
||||
controller->window = UIDesc::makeWindow (config);
|
||||
return controller;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
DocumentWindowController::DocumentWindowController (const DocumentContextPtr& doc)
|
||||
: docContext (doc)
|
||||
{
|
||||
for (auto index = 0u; index < docContext->getImagePaths ().size (); ++index)
|
||||
onImagePathAdded (docContext->getImagePaths ()[index], index);
|
||||
docContext->addListener (this);
|
||||
docIsDirty = false;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
DocumentWindowController::~DocumentWindowController () noexcept
|
||||
{
|
||||
docContext->removeListener (this);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void DocumentWindowController::showWindow ()
|
||||
{
|
||||
if (window)
|
||||
window->show ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void DocumentWindowController::closeWindow ()
|
||||
{
|
||||
if (window)
|
||||
window->close ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void DocumentWindowController::registerWindowListener (IWindowListener* listener)
|
||||
{
|
||||
if (window)
|
||||
window->registerWindowListener (listener);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
UIDesc::ModelBindingPtr DocumentWindowController::createModelBinding ()
|
||||
{
|
||||
auto binding = UIDesc::ModelBindingCallbacks::make ();
|
||||
binding->addValue (Value::make ("AddPath"), UIDesc::ValueCalls::onAction ([this] (auto& v) {
|
||||
this->doAddPathCommand ();
|
||||
v.performEdit (0.);
|
||||
}));
|
||||
binding->addValue (Value::make ("RemovePath"), UIDesc::ValueCalls::onAction ([this] (auto& v) {
|
||||
this->doRemovePathCommand ();
|
||||
v.performEdit (0.);
|
||||
}));
|
||||
|
||||
binding->addValue (Value::make ("Export"), UIDesc::ValueCalls::onAction ([this] (auto& v) {
|
||||
this->doExport ();
|
||||
v.performEdit (0.);
|
||||
}));
|
||||
|
||||
displayFrameValue = Value::makeStepValue ("DisplayFrame", 1, 1);
|
||||
binding->addValue (displayFrameValue);
|
||||
|
||||
auto animationRunning = Value::make ("RunAnimation");
|
||||
binding->addValue (animationRunning, UIDesc::ValueCalls::onPerformEdit ([this] (auto& v) {
|
||||
if (v.getValue () >= 0.5)
|
||||
this->doStartAnimation ();
|
||||
else
|
||||
this->doStopAnimation ();
|
||||
}));
|
||||
animationTimeValue = Value::make ("AnimationTime", 0, Value::makeRangeConverter (16, 500, 0));
|
||||
binding->addValue (animationTimeValue,
|
||||
UIDesc::ValueCalls::onPerformEdit ([this, animationRunning] (auto& v) {
|
||||
if (animationRunning->getValue () >= 0.5)
|
||||
this->doStartAnimation ();
|
||||
}));
|
||||
numFramesPerRowValue =
|
||||
Value::make ("NumFramesPerRow", 0, Value::makeRangeConverter (1, 32767, 0));
|
||||
binding->addValue (numFramesPerRowValue, UIDesc::ValueCalls::onPerformEdit ([this] (auto& v) {
|
||||
this->docContext->setNumFramesPerRow (static_cast<uint16_t> (
|
||||
std::round (v.getConverter ().normalizedToPlain (v.getValue ()))));
|
||||
}));
|
||||
return binding;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
IController* DocumentWindowController::createController (const UTF8StringView& name,
|
||||
IController* parent,
|
||||
const IUIDescription* uiDesc)
|
||||
{
|
||||
if (name == "ImageViewController")
|
||||
return new ImageViewController (
|
||||
[&] (ImageFramesView* view) {
|
||||
imageView = view;
|
||||
imageView->setImageList (&imageList);
|
||||
imageView->setDocContext (docContext);
|
||||
},
|
||||
parent);
|
||||
if (name == "MovieBitmapController")
|
||||
return new MovieBitmapController ([&] (CMovieBitmap* view) { movieBitmapView = view; },
|
||||
parent);
|
||||
if (name == "SplitViewController")
|
||||
return new SplitViewController (parent, uiDesc);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void DocumentWindowController::onUIDescriptionParsed (const IUIDescription* uiDesc)
|
||||
{
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void DocumentWindowController::onSetContentView (IWindow& w, const SharedPointer<CFrame>& cv)
|
||||
{
|
||||
contentView = cv;
|
||||
if (imageView)
|
||||
imageView->setImageList (&imageList);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void DocumentWindowController::onClosed (const IWindow& w)
|
||||
{
|
||||
vstgui_assert (&w == window.get ());
|
||||
window = nullptr;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
bool DocumentWindowController::canClose (const IWindow&)
|
||||
{
|
||||
if (docIsDirty)
|
||||
{
|
||||
AlertBoxConfig alert;
|
||||
alert.headline = "Do you want to save the changes made to the document \"";
|
||||
alert.headline += getDisplayFilename (docContext->getPath ());
|
||||
alert.headline += "\"?";
|
||||
alert.description = "Your changes will be lost if you don't save them.";
|
||||
alert.defaultButton = "Save";
|
||||
alert.secondButton = "Cancel";
|
||||
alert.thirdButton = "Don't Save";
|
||||
auto result = IApplication::instance ().showAlertBox (alert);
|
||||
switch (result)
|
||||
{
|
||||
case AlertResult::DefaultButton:
|
||||
{
|
||||
if (pathIsAbsolute (docContext->getPath ()))
|
||||
{
|
||||
doSave ();
|
||||
return true;
|
||||
}
|
||||
doSaveAs ([this] (bool success) {
|
||||
if (success)
|
||||
window->close ();
|
||||
});
|
||||
return false;
|
||||
}
|
||||
case AlertResult::SecondButton: return false;
|
||||
default: return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
static bool exportImage (const SharedPointer<CBitmap>& image, UTF8StringPtr path)
|
||||
{
|
||||
auto platformBitmap = image->getPlatformBitmap ();
|
||||
vstgui_assert (platformBitmap);
|
||||
auto buffer = getPlatformFactory ().createBitmapMemoryPNGRepresentation (platformBitmap);
|
||||
CFileStream stream;
|
||||
if (!stream.open (path, CFileStream::kWriteMode | CFileStream::kBinaryMode |
|
||||
CFileStream::kTruncateMode))
|
||||
return false;
|
||||
return stream.writeRaw (buffer.data (), static_cast<uint32_t> (buffer.size ())) ==
|
||||
buffer.size ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void DocumentWindowController::doExport ()
|
||||
{
|
||||
auto fs =
|
||||
owned (CNewFileSelector::create (contentView, CNewFileSelector::Style::kSelectSaveFile));
|
||||
if (!fs)
|
||||
return;
|
||||
fs->setTitle ("Export Stitched Image");
|
||||
fs->setDefaultExtension (pngFileExtension);
|
||||
// TODO: set filename depending on doc name
|
||||
fs->run ([this] (CNewFileSelector* fs) {
|
||||
if (fs->getNumSelectedFiles () == 0)
|
||||
return;
|
||||
if (auto image = createStitchedBitmap ())
|
||||
{
|
||||
if (!exportImage (image, fs->getSelectedFile (0)))
|
||||
{
|
||||
AlertBoxForWindowConfig alert;
|
||||
alert.window = window;
|
||||
alert.headline = "Export failed";
|
||||
IApplication::instance ().showAlertBoxForWindow (alert);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void DocumentWindowController::doSave ()
|
||||
{
|
||||
if (docContext->save ())
|
||||
docIsDirty = false;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void DocumentWindowController::doSaveAs (std::function<void (bool saved)>&& customAction)
|
||||
{
|
||||
auto fs =
|
||||
owned (CNewFileSelector::create (contentView, CNewFileSelector::Style::kSelectSaveFile));
|
||||
if (!fs)
|
||||
return;
|
||||
fs->setTitle ("Choose Save Destination");
|
||||
fs->setDefaultExtension (imageStitchExtension);
|
||||
fs->setInitialDirectory (docContext->getPath ().data ());
|
||||
fs->setDefaultSaveName (getDisplayFilename (docContext->getPath ()).data ());
|
||||
fs->run ([this, customAction = std::move (customAction)] (CNewFileSelector * fs) {
|
||||
if (fs->getNumSelectedFiles () == 0)
|
||||
{
|
||||
customAction (false);
|
||||
return;
|
||||
}
|
||||
docContext->setPath (fs->getSelectedFile (0));
|
||||
if (docContext->save ())
|
||||
{
|
||||
window->setTitle (getDisplayFilename (docContext->getPath ()));
|
||||
window->setRepresentedPath (UTF8String (docContext->getPath ()));
|
||||
docIsDirty = false;
|
||||
customAction (true);
|
||||
}
|
||||
else
|
||||
customAction (false);
|
||||
});
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void DocumentWindowController::onImagePathAdded (const Path& newPath, size_t index)
|
||||
{
|
||||
auto platformBitmap = getPlatformFactory ().createBitmapFromPath (newPath.data ());
|
||||
if (!platformBitmap)
|
||||
{
|
||||
CPoint size (docContext->getWidth (), docContext->getHeight ());
|
||||
platformBitmap = getPlatformFactory().createBitmap (size);
|
||||
}
|
||||
auto it = imageList.begin ();
|
||||
if (index >= imageList.size ())
|
||||
it = imageList.end ();
|
||||
else
|
||||
std::advance (it, index);
|
||||
imageList.insert (it, {makeOwned<CBitmap> (platformBitmap), newPath, false});
|
||||
setDirty ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void DocumentWindowController::onImagePathRemoved (const Path& newPath, size_t index)
|
||||
{
|
||||
auto it = imageList.begin ();
|
||||
std::advance (it, index);
|
||||
imageList.erase (it);
|
||||
setDirty ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void DocumentWindowController::onNumFramesPerRowChanged (uint16_t newNumFramesPerRow)
|
||||
{
|
||||
setDirty ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void DocumentWindowController::doOpenDocument (std::function<void (bool saved)>&& customAction)
|
||||
{
|
||||
auto fs = owned (CNewFileSelector::create (contentView));
|
||||
if (!fs)
|
||||
return;
|
||||
fs->setTitle ("Choose Document");
|
||||
fs->setDefaultExtension (imageStitchExtension);
|
||||
fs->run ([this, customAction = std::move (customAction)] (CNewFileSelector * fs) {
|
||||
if (fs->getNumSelectedFiles () == 0)
|
||||
{
|
||||
customAction (false);
|
||||
return;
|
||||
}
|
||||
if (auto newDocContext = DocumentContext::loadDocument (fs->getSelectedFile (0)))
|
||||
{
|
||||
docContext->replaceDocument (newDocContext->getDocument ());
|
||||
window->setTitle (getDisplayFilename (docContext->getPath ()));
|
||||
window->setRepresentedPath (UTF8String (docContext->getPath ()));
|
||||
docIsDirty = false;
|
||||
customAction (true);
|
||||
}
|
||||
else
|
||||
customAction (false);
|
||||
});
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void DocumentWindowController::doAddPathCommand ()
|
||||
{
|
||||
auto fs = owned (CNewFileSelector::create (contentView));
|
||||
if (!fs)
|
||||
return;
|
||||
fs->setAllowMultiFileSelection (true);
|
||||
fs->setTitle ("Choose Images");
|
||||
fs->setDefaultExtension (pngFileExtension);
|
||||
fs->run ([this] (CNewFileSelector* fs) {
|
||||
auto numFiles = fs->getNumSelectedFiles ();
|
||||
if (numFiles == 0)
|
||||
return;
|
||||
std::string alertDescription;
|
||||
size_t pos = lastSelectedPos ();
|
||||
doDeselectAllCommand ();
|
||||
for (auto i = 0u; i < numFiles; ++i)
|
||||
{
|
||||
auto path = fs->getSelectedFile (i);
|
||||
auto result = docContext->insertImagePathAtIndex (pos, path);
|
||||
if (result != DocumentContextResult::Success)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case DocumentContextResult::ImageSizeMismatch:
|
||||
{
|
||||
alertDescription += "Image Size Mismatch :";
|
||||
alertDescription += path;
|
||||
alertDescription += "\n";
|
||||
break;
|
||||
}
|
||||
case DocumentContextResult::InvalidImage:
|
||||
{
|
||||
alertDescription += "Invalid Image :";
|
||||
alertDescription += path;
|
||||
alertDescription += "\n";
|
||||
break;
|
||||
}
|
||||
case DocumentContextResult::InvalidIndex:
|
||||
{
|
||||
alertDescription +=
|
||||
"Internal Error (DocumentContextResult::InvalidIndex) adding ";
|
||||
alertDescription += path;
|
||||
alertDescription += "\n";
|
||||
break;
|
||||
}
|
||||
case DocumentContextResult::Success: break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
imageList[pos].selected = true;
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
if (!alertDescription.empty ())
|
||||
{
|
||||
AlertBoxForWindowConfig alert;
|
||||
alert.window = window;
|
||||
alert.headline = "Error adding images!";
|
||||
alert.description = alertDescription;
|
||||
IApplication::instance ().showAlertBoxForWindow (alert);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void DocumentWindowController::doRemovePathCommand ()
|
||||
{
|
||||
std::vector<size_t> indices;
|
||||
for (auto index = 0u; index < imageList.size (); ++index)
|
||||
{
|
||||
if (imageList[index].selected)
|
||||
{
|
||||
indices.push_back (index);
|
||||
}
|
||||
}
|
||||
for (auto it = indices.rbegin (); it != indices.rend (); ++it)
|
||||
docContext->removeImagePathAtIndex (*it);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void DocumentWindowController::doStartAnimation ()
|
||||
{
|
||||
auto& converter = animationTimeValue->getConverter ();
|
||||
auto time =
|
||||
static_cast<uint32_t> (converter.normalizedToPlain (animationTimeValue->getValue ()));
|
||||
timer = makeOwned<CVSTGUITimer> (
|
||||
[this] (auto) {
|
||||
auto v = displayFrameValue->getValue ();
|
||||
v += 1. / imageList.size ();
|
||||
if (v + std::numeric_limits<double>::epsilon () >= 1.)
|
||||
v = 0.;
|
||||
displayFrameValue->beginEdit ();
|
||||
displayFrameValue->performEdit (v);
|
||||
displayFrameValue->endEdit ();
|
||||
},
|
||||
time);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void DocumentWindowController::doStopAnimation ()
|
||||
{
|
||||
if (!timer)
|
||||
return;
|
||||
|
||||
timer->stop ();
|
||||
timer = nullptr;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void DocumentWindowController::doSelectAllCommand ()
|
||||
{
|
||||
for (auto& image : imageList)
|
||||
image.selected = true;
|
||||
if (imageView)
|
||||
imageView->invalid ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void DocumentWindowController::doDeselectAllCommand ()
|
||||
{
|
||||
for (auto& image : imageList)
|
||||
image.selected = false;
|
||||
if (imageView)
|
||||
imageView->invalid ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
bool DocumentWindowController::somethingSelected () const
|
||||
{
|
||||
for (auto& image : imageList)
|
||||
{
|
||||
if (image.selected)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
size_t DocumentWindowController::lastSelectedPos () const
|
||||
{
|
||||
auto it =
|
||||
std::find_if (imageList.rbegin (), imageList.rend (), [] (auto& e) { return e.selected; });
|
||||
if (it == imageList.rend ())
|
||||
return imageList.size ();
|
||||
return std::distance (imageList.begin (), it.base ());
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
bool DocumentWindowController::canHandleCommand (const Command& command)
|
||||
{
|
||||
if (command == Commands::Delete)
|
||||
return somethingSelected ();
|
||||
if (command == Commands::SelectAll)
|
||||
return !imageList.empty ();
|
||||
if (command == ExportCommand)
|
||||
return !imageList.empty ();
|
||||
if (command == Commands::SaveDocumentAs)
|
||||
return !imageList.empty ();
|
||||
if (command == Commands::SaveDocument)
|
||||
return !imageList.empty () && pathIsAbsolute (docContext->getPath ());
|
||||
return false;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
bool DocumentWindowController::handleCommand (const Command& command)
|
||||
{
|
||||
if (command == Commands::Delete)
|
||||
{
|
||||
doRemovePathCommand ();
|
||||
return true;
|
||||
}
|
||||
if (command == Commands::SelectAll)
|
||||
{
|
||||
doSelectAllCommand ();
|
||||
return true;
|
||||
}
|
||||
if (command == ExportCommand)
|
||||
{
|
||||
doExport ();
|
||||
return true;
|
||||
}
|
||||
if (command == Commands::SaveDocument)
|
||||
{
|
||||
doSave ();
|
||||
return true;
|
||||
}
|
||||
if (command == Commands::SaveDocumentAs)
|
||||
{
|
||||
doSaveAs ();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
SharedPointer<CBitmap> DocumentWindowController::createStitchedBitmap ()
|
||||
{
|
||||
if (!contentView || docContext->getImagePaths ().empty ())
|
||||
return nullptr;
|
||||
auto numCols = docContext->getNumFramesPerRow ();
|
||||
auto numRows = std::ceil (static_cast<double> (docContext->getImagePaths ().size ()) / numCols);
|
||||
|
||||
CRect r;
|
||||
CPoint size (docContext->getWidth (), docContext->getHeight ());
|
||||
r.setSize (size);
|
||||
size.x *= numCols;
|
||||
size.y *= numRows;
|
||||
|
||||
auto offscreen = COffscreenContext::create (size);
|
||||
if (!offscreen)
|
||||
return nullptr;
|
||||
|
||||
offscreen->beginDraw ();
|
||||
auto col = 0;
|
||||
for (const auto& image : imageList)
|
||||
{
|
||||
image.bitmap->draw (offscreen, r);
|
||||
if (++col >= numCols)
|
||||
{
|
||||
col = 0;
|
||||
r.left = 0;
|
||||
r.offset (0, docContext->getHeight ());
|
||||
}
|
||||
else
|
||||
{
|
||||
r.offset (docContext->getWidth (), 0);
|
||||
}
|
||||
}
|
||||
offscreen->endDraw ();
|
||||
|
||||
auto multiFrameBitmap =
|
||||
makeOwned<CMultiFrameBitmap> (offscreen->getBitmap ()->getPlatformBitmap ());
|
||||
auto res = multiFrameBitmap->setMultiFrameDesc (
|
||||
{CPoint (docContext->getWidth (), docContext->getHeight ()),
|
||||
static_cast<uint16_t> (imageList.size ()), numCols});
|
||||
vstgui_assert (res, "Multi Frame Bitmap Description invalid!");
|
||||
return multiFrameBitmap;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void DocumentWindowController::setDirty ()
|
||||
{
|
||||
docIsDirty = true;
|
||||
if (asyncUpdateTriggered)
|
||||
return;
|
||||
asyncUpdateTriggered = true;
|
||||
Async::schedule (Async::mainQueue (), [this] () {
|
||||
if (auto v = displayFrameValue->dynamicCast<IMutableStepValue> ())
|
||||
v->setNumSteps (static_cast<uint32_t> (imageList.size ()));
|
||||
if (imageView)
|
||||
imageView->setImageList (&imageList);
|
||||
if (movieBitmapView)
|
||||
{
|
||||
movieBitmapView->setBackground (createStitchedBitmap ());
|
||||
auto size = movieBitmapView->getViewSize ();
|
||||
size.setWidth (docContext->getWidth ());
|
||||
size.setHeight (docContext->getHeight ());
|
||||
movieBitmapView->setViewSize (size);
|
||||
}
|
||||
asyncUpdateTriggered = false;
|
||||
});
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // ImageStitcher
|
||||
} // VSTGUI
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
// This file is part of VSTGUI. It is subject to the license terms
|
||||
// in the LICENSE file found in the top-level directory of this
|
||||
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "document.h"
|
||||
#include "vstgui/lib/cbitmap.h"
|
||||
#include "vstgui/lib/cfileselector.h"
|
||||
#include "vstgui/lib/cvstguitimer.h"
|
||||
#include "vstgui/standalone/include/helpers/windowcontroller.h"
|
||||
#include "vstgui/standalone/include/icommand.h"
|
||||
#include "vstgui/standalone/include/iuidescwindow.h"
|
||||
#include "vstgui/standalone/include/ivalue.h"
|
||||
#include <vector>
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace VSTGUI {
|
||||
namespace ImageStitcher {
|
||||
|
||||
class ImageFramesView;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
static constexpr IdStringPtr ExportStr = "Export...";
|
||||
static const Standalone::Command ExportCommand {Standalone::CommandGroup::File, ExportStr};
|
||||
static CFileExtension imageStitchExtension ("Image Stitch File", "imagestitch", "", 0, "");
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
struct Image
|
||||
{
|
||||
SharedPointer<CBitmap> bitmap;
|
||||
Path path;
|
||||
bool selected {false};
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1910 // Can be removed when dropping VS 2015 Support
|
||||
Image (SharedPointer<CBitmap> bitmap, Path path, bool selected)
|
||||
: bitmap (bitmap), path (path), selected (selected)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
};
|
||||
using ImageList = std::vector<Image>;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
class DocumentWindowController : public Standalone::WindowControllerAdapter,
|
||||
public Standalone::UIDesc::ICustomization,
|
||||
public Standalone::ICommandHandler,
|
||||
public IDocumentListener
|
||||
{
|
||||
public:
|
||||
static std::shared_ptr<DocumentWindowController> make (const DocumentContextPtr& doc);
|
||||
|
||||
DocumentWindowController (const DocumentContextPtr& doc);
|
||||
~DocumentWindowController () noexcept;
|
||||
|
||||
const DocumentContextPtr& getDoc () const noexcept { return docContext; }
|
||||
SharedPointer<CBitmap> createStitchedBitmap ();
|
||||
|
||||
void showWindow ();
|
||||
void closeWindow ();
|
||||
void registerWindowListener (Standalone::IWindowListener* listener);
|
||||
|
||||
void doSaveAs (std::function<void(bool saved)>&& customAction = [] (bool) {});
|
||||
void doOpenDocument (std::function<void(bool saved)>&& customAction = [] (bool) {});
|
||||
|
||||
private:
|
||||
void onImagePathAdded (const Path& newPath, size_t index) override;
|
||||
void onImagePathRemoved (const Path& newPath, size_t index) override;
|
||||
void onNumFramesPerRowChanged (uint16_t newNumFramesPerRow) override;
|
||||
|
||||
IController* createController (const UTF8StringView& name, IController* parent,
|
||||
const IUIDescription* uiDesc) override;
|
||||
void onUIDescriptionParsed (const IUIDescription* uiDesc) override;
|
||||
void onSetContentView (Standalone::IWindow& w, const SharedPointer<CFrame>& cv) override;
|
||||
void onClosed (const Standalone::IWindow& window) override;
|
||||
bool canClose (const Standalone::IWindow& window) override;
|
||||
Standalone::UIDesc::ModelBindingPtr createModelBinding ();
|
||||
|
||||
bool canHandleCommand (const Standalone::Command& command) override;
|
||||
bool handleCommand (const Standalone::Command& command) override;
|
||||
|
||||
void doAddPathCommand ();
|
||||
void doRemovePathCommand ();
|
||||
void doSelectAllCommand ();
|
||||
void doDeselectAllCommand ();
|
||||
void doStartAnimation ();
|
||||
void doStopAnimation ();
|
||||
void doExport ();
|
||||
void doSave ();
|
||||
|
||||
bool somethingSelected () const;
|
||||
size_t lastSelectedPos () const;
|
||||
|
||||
void setDirty ();
|
||||
|
||||
DocumentContextPtr docContext;
|
||||
CFrame* contentView {nullptr};
|
||||
ImageFramesView* imageView {nullptr};
|
||||
CMovieBitmap* movieBitmapView {nullptr};
|
||||
Standalone::WindowPtr window;
|
||||
Standalone::ValuePtr displayFrameValue;
|
||||
Standalone::ValuePtr animationTimeValue;
|
||||
Standalone::ValuePtr numFramesPerRowValue;
|
||||
SharedPointer<CVSTGUITimer> timer;
|
||||
ImageList imageList;
|
||||
bool asyncUpdateTriggered {false};
|
||||
bool docIsDirty {false};
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
inline std::string getDisplayFilename (const Path& path)
|
||||
{
|
||||
if (path.empty ())
|
||||
return "Untitled";
|
||||
auto pos = path.find_last_of (PathSeparator);
|
||||
if (pos == Path::npos)
|
||||
return path;
|
||||
return path.substr (pos + 1);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // ImageStitcher
|
||||
} // VSTGUI
|
||||
+643
@@ -0,0 +1,643 @@
|
||||
// This file is part of VSTGUI. It is subject to the license terms
|
||||
// in the LICENSE file found in the top-level directory of this
|
||||
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
|
||||
|
||||
#include "imageframesview.h"
|
||||
#include "vstgui/lib/cdrawcontext.h"
|
||||
#include "vstgui/lib/cdropsource.h"
|
||||
#include "vstgui/lib/cframe.h"
|
||||
#include "vstgui/lib/cscrollview.h"
|
||||
#include "vstgui/lib/dragging.h"
|
||||
#include "vstgui/lib/coffscreencontext.h"
|
||||
#include "vstgui/standalone/include/ialertbox.h"
|
||||
#include "vstgui/standalone/include/iapplication.h"
|
||||
#include "vstgui/standalone/include/iasync.h"
|
||||
#include <cassert>
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace VSTGUI {
|
||||
namespace ImageStitcher {
|
||||
|
||||
using namespace VSTGUI::Standalone;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
ImageFramesView::ImageFramesView () : CView (CRect (0, 0, 10, 10))
|
||||
{
|
||||
font = makeOwned<CFontDesc> (*kSystemFont);
|
||||
font->setSize (8);
|
||||
setSelectionColor (MakeCColor (164, 205, 255, 255));
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void ImageFramesView::setDocContext (const DocumentContextPtr& dc)
|
||||
{
|
||||
docContext = dc;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void ImageFramesView::setImageList (ImageList* list)
|
||||
{
|
||||
imageList = list;
|
||||
updateViewSize ();
|
||||
invalid ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void ImageFramesView::makeRectVisible (CRect r) const
|
||||
{
|
||||
if (!isAttached ())
|
||||
return;
|
||||
if (auto scrollView = dynamic_cast<CScrollView*> (getParentView ()->getParentView ()))
|
||||
{
|
||||
scrollView->makeRectVisible (r);
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void ImageFramesView::updateViewSize ()
|
||||
{
|
||||
if (imageList)
|
||||
{
|
||||
CRect r = getViewSize ();
|
||||
if (imageList->empty ())
|
||||
{
|
||||
r.setSize ({0., 0.});
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto image = imageList->front ().bitmap)
|
||||
{
|
||||
auto size = image->getSize ();
|
||||
size.y += titleHeight;
|
||||
rowHeight = size.y;
|
||||
size.y *= imageList->size ();
|
||||
size.y += 15; // back list drop zone
|
||||
r.setSize (size);
|
||||
}
|
||||
}
|
||||
if (isAttached ())
|
||||
{
|
||||
if (auto scrollView = dynamic_cast<CScrollView*> (getParentView ()->getParentView ()))
|
||||
{
|
||||
auto parentSize = scrollView->getViewSize ();
|
||||
if (parentSize.getWidth () > r.getWidth ())
|
||||
r.setWidth (parentSize.getWidth ());
|
||||
if (parentSize.getHeight () > r.getHeight ())
|
||||
r.setHeight (parentSize.getHeight ());
|
||||
}
|
||||
}
|
||||
setMouseableArea (r);
|
||||
setViewSize (r);
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void ImageFramesView::parentSizeChanged ()
|
||||
{
|
||||
updateViewSize ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void ImageFramesView::setSelectionColor (CColor color)
|
||||
{
|
||||
activeSelectionColor = inactiveSelectionColor = color;
|
||||
double h, s, l;
|
||||
inactiveSelectionColor.toHSL (h, s, l);
|
||||
s = 0.;
|
||||
inactiveSelectionColor.fromHSL (h, s, l);
|
||||
invalid ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void ImageFramesView::setTextColor (CColor color)
|
||||
{
|
||||
textColor = color;
|
||||
selectedTextColor = textColor;
|
||||
struct HSL
|
||||
{
|
||||
double h, s, l;
|
||||
};
|
||||
HSL asc, tc;
|
||||
activeSelectionColor.toHSL (asc.h, asc.s, asc.l);
|
||||
textColor.toHSL (tc.h, tc.s, tc.l);
|
||||
if (std::abs (asc.l - tc.l) < 0.5)
|
||||
{
|
||||
tc.l = asc.l - 0.5;
|
||||
if (tc.l < 0)
|
||||
tc.l = 1. - tc.l;
|
||||
selectedTextColor.fromHSL (tc.h, tc.s, tc.l);
|
||||
}
|
||||
invalid ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void ImageFramesView::drawRect (CDrawContext* context, const CRect& _updateRect)
|
||||
{
|
||||
if (!imageList || imageList->empty ())
|
||||
return;
|
||||
auto topLeft = getViewSize ().getTopLeft ();
|
||||
CDrawContext::Transform tm (*context, CGraphicsTransform ().translate (topLeft));
|
||||
|
||||
CRect updateRect (_updateRect);
|
||||
updateRect.offsetInverse (topLeft);
|
||||
|
||||
context->setFillColor (getFrame ()->getFocusView () == this ? activeSelectionColor :
|
||||
inactiveSelectionColor);
|
||||
|
||||
context->setFontColor (textColor);
|
||||
context->setFont (font);
|
||||
context->setDrawMode (kAntiAliasing);
|
||||
|
||||
CRect r;
|
||||
auto imageSize = imageList->front ().bitmap->getSize ();
|
||||
r.setSize (imageSize);
|
||||
r.setWidth (getWidth ());
|
||||
int32_t index = 0;
|
||||
for (auto& image : *imageList)
|
||||
{
|
||||
if (image.selected)
|
||||
{
|
||||
CRect sr (r);
|
||||
sr.bottom += titleHeight;
|
||||
if (updateRect.rectOverlap (sr))
|
||||
context->drawRect (sr, kDrawFilled);
|
||||
}
|
||||
CRect ir (r);
|
||||
ir.setWidth (imageSize.x);
|
||||
ir.offset (r.getWidth () / 2. - imageSize.x / 2., 0);
|
||||
if (updateRect.rectOverlap (ir))
|
||||
image.bitmap->draw (context, ir);
|
||||
if (!image.path.empty ())
|
||||
{
|
||||
CRect tr (r);
|
||||
tr.top = tr.bottom;
|
||||
tr.bottom += titleHeight - 1;
|
||||
if (updateRect.rectOverlap (tr))
|
||||
{
|
||||
auto name = getDisplayFilename (image.path);
|
||||
context->setFontColor (image.selected ? selectedTextColor : textColor);
|
||||
context->drawString (name.data (), tr);
|
||||
}
|
||||
}
|
||||
if (index == dropIndicatorPos)
|
||||
{
|
||||
context->setFrameColor (kRedCColor);
|
||||
context->setLineWidth (2.);
|
||||
context->drawLine (r.getTopLeft (), r.getTopRight ());
|
||||
}
|
||||
r.offset (0, rowHeight);
|
||||
++index;
|
||||
}
|
||||
if (dropIndicatorPos == static_cast<int32_t> (imageList->size ()))
|
||||
{
|
||||
context->setFrameColor (kRedCColor);
|
||||
context->setLineWidth (2.);
|
||||
context->drawLine (r.getTopLeft (), r.getTopRight ());
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void ImageFramesView::takeFocus ()
|
||||
{
|
||||
CView::takeFocus ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void ImageFramesView::looseFocus ()
|
||||
{
|
||||
CView::looseFocus ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
int32_t ImageFramesView::firstSelectedIndex () const
|
||||
{
|
||||
for (auto index = 0u; index < imageList->size (); ++index)
|
||||
{
|
||||
if (imageList->at (index).selected)
|
||||
return static_cast<int32_t> (index);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
int32_t ImageFramesView::lastSelectedIndex () const
|
||||
{
|
||||
for (int32_t index = static_cast<int32_t> (imageList->size ()) - 1; index >= 0; --index)
|
||||
{
|
||||
if (imageList->at (index).selected)
|
||||
return index;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
CPoint ImageFramesView::sizeOfOneRow () const
|
||||
{
|
||||
CPoint size;
|
||||
size.x = getWidth ();
|
||||
size.y = rowHeight;
|
||||
return size;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
CRect ImageFramesView::indexToRect (size_t index) const
|
||||
{
|
||||
CRect r;
|
||||
if (imageList->empty ())
|
||||
return r;
|
||||
auto size = sizeOfOneRow ();
|
||||
r.setSize (size);
|
||||
r.offset (0, size.y * index);
|
||||
r.offset (getViewSize ().getTopLeft ());
|
||||
return r;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
int32_t ImageFramesView::posToIndex (CPoint where) const
|
||||
{
|
||||
if (imageList->empty ())
|
||||
return 0;
|
||||
CRect r = indexToRect (0);
|
||||
for (auto index = 0u; index < imageList->size (); ++index)
|
||||
{
|
||||
if (r.pointInside (where))
|
||||
return index;
|
||||
r.offset (0, r.getHeight ());
|
||||
}
|
||||
return static_cast<int32_t> (imageList->size ());
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void ImageFramesView::selectExclusive (size_t index)
|
||||
{
|
||||
for (auto i = 0u; i < imageList->size (); ++i)
|
||||
{
|
||||
auto& image = imageList->at (i);
|
||||
if (image.selected != (i == index))
|
||||
{
|
||||
image.selected = (i == index);
|
||||
invalidRect (indexToRect (i));
|
||||
}
|
||||
}
|
||||
makeRectVisible (indexToRect (index));
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void ImageFramesView::enlargeSelection (size_t index)
|
||||
{
|
||||
// find nearest selected item
|
||||
int32_t nearest = std::numeric_limits<int32_t>::max ();
|
||||
for (auto i = 0u; i < imageList->size (); ++i)
|
||||
{
|
||||
auto& image = imageList->at (i);
|
||||
if (!image.selected)
|
||||
continue;
|
||||
auto diff = static_cast<int32_t> (i) - static_cast<int32_t> (index);
|
||||
if (std::abs (diff) < std::abs (nearest))
|
||||
nearest = diff;
|
||||
}
|
||||
if (nearest == std::numeric_limits<int32_t>::max ())
|
||||
nearest = -static_cast<int32_t> (index);
|
||||
|
||||
if (nearest < 0)
|
||||
{
|
||||
for (auto i = index + nearest; i <= index; ++i)
|
||||
{
|
||||
if (!imageList->at (i).selected)
|
||||
{
|
||||
imageList->at (i).selected = true;
|
||||
invalidRect (indexToRect (i));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (nearest > 0)
|
||||
{
|
||||
for (auto i = index; i < index + nearest; ++i)
|
||||
{
|
||||
if (!imageList->at (i).selected)
|
||||
{
|
||||
imageList->at (i).selected = true;
|
||||
invalidRect (indexToRect (i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr size_t DragPackageID = 'isdp';
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
bool ImageFramesView::getIndicesFromDataPackage (IDataPackage* package, std::vector<size_t>* result)
|
||||
{
|
||||
if (package->getDataType (0) == IDataPackage::kBinary)
|
||||
{
|
||||
const void* buffer;
|
||||
IDataPackage::Type type;
|
||||
if (auto dataSize = package->getData (0, buffer, type))
|
||||
{
|
||||
auto data = reinterpret_cast<const size_t*> (buffer);
|
||||
if (data[0] == DragPackageID)
|
||||
{
|
||||
if (result)
|
||||
{
|
||||
for (auto index = 1u; index < dataSize / sizeof (size_t); ++index)
|
||||
{
|
||||
result->emplace_back (data[index]);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
std::vector<Path> ImageFramesView::getDragPngImagePaths (IDataPackage* drag)
|
||||
{
|
||||
std::vector<Path> result;
|
||||
auto count = drag->getCount ();
|
||||
for (auto i = 0u; i < count; ++i)
|
||||
{
|
||||
if (drag->getDataType (i) != IDataPackage::kFilePath)
|
||||
continue;
|
||||
const void* buffer;
|
||||
IDataPackage::Type type;
|
||||
auto size = drag->getData (i, buffer, type);
|
||||
Path p (reinterpret_cast<const char*> (buffer), size);
|
||||
if (getImageSize (p))
|
||||
result.emplace_back (std::move (p));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
bool ImageFramesView::dragHasPngImages (IDataPackage* drag)
|
||||
{
|
||||
auto count = drag->getCount ();
|
||||
for (auto i = 0u; i < count; ++i)
|
||||
{
|
||||
if (drag->getDataType (i) != IDataPackage::kFilePath)
|
||||
continue;
|
||||
const void* buffer;
|
||||
IDataPackage::Type type;
|
||||
auto size = drag->getData (i, buffer, type);
|
||||
Path p (reinterpret_cast<const char*> (buffer), size);
|
||||
if (getImageSize (p))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void ImageFramesView::reorderImages (size_t position, bool doCopy, std::vector<size_t>& indices)
|
||||
{
|
||||
std::sort (indices.begin (), indices.end ());
|
||||
std::vector<Path> paths;
|
||||
if (doCopy)
|
||||
{
|
||||
for (auto index : indices)
|
||||
paths.emplace_back (imageList->at (index).path);
|
||||
for (auto& path : paths)
|
||||
{
|
||||
docContext->insertImagePathAtIndex (position, path);
|
||||
++position;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto it = indices.rbegin (); it != indices.rend (); ++it)
|
||||
{
|
||||
auto index = *it;
|
||||
paths.emplace_back (imageList->at (index).path);
|
||||
docContext->removeImagePathAtIndex (index);
|
||||
if (index < position)
|
||||
--position;
|
||||
}
|
||||
for (auto& path : paths)
|
||||
docContext->insertImagePathAtIndex (position, path);
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void ImageFramesView::addImages (size_t position, const std::vector<std::string>& imagePaths)
|
||||
{
|
||||
std::string alertDescription;
|
||||
for (auto& path : imagePaths)
|
||||
{
|
||||
auto res = docContext->insertImagePathAtIndex (position, path);
|
||||
switch (res)
|
||||
{
|
||||
case DocumentContextResult::Success:
|
||||
{
|
||||
++position;
|
||||
break;
|
||||
}
|
||||
case DocumentContextResult::ImageSizeMismatch:
|
||||
{
|
||||
alertDescription += "Image Size Mismatch :";
|
||||
alertDescription += path;
|
||||
alertDescription += "\n";
|
||||
break;
|
||||
}
|
||||
case DocumentContextResult::InvalidImage:
|
||||
case DocumentContextResult::InvalidIndex:
|
||||
{
|
||||
alertDescription += "Unexpected Error\n";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!alertDescription.empty ())
|
||||
{
|
||||
Async::schedule (Async::mainQueue (), [alertDescription] () {
|
||||
AlertBoxConfig alert;
|
||||
alert.headline = "Error adding images!";
|
||||
alert.description = alertDescription;
|
||||
IApplication::instance ().showAlertBox (alert);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
CMouseEventResult ImageFramesView::onMouseDown (CPoint& where, const CButtonState& buttons)
|
||||
{
|
||||
assert (imageList);
|
||||
if (buttons.isLeftButton ())
|
||||
{
|
||||
bool exclusive = buttons.getModifierState () != kControl;
|
||||
bool shift = buttons.getModifierState () == kShift;
|
||||
auto index = posToIndex (where);
|
||||
if (index >= 0 && index < static_cast<int32_t> (imageList->size ()))
|
||||
{
|
||||
if (shift)
|
||||
{
|
||||
enlargeSelection (index);
|
||||
}
|
||||
else if (exclusive)
|
||||
{
|
||||
if (!imageList->at (index).selected)
|
||||
{
|
||||
selectExclusive (index);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
imageList->at (index).selected = !imageList->at (index).selected;
|
||||
invalidRect (indexToRect (index));
|
||||
}
|
||||
}
|
||||
if (auto frame = getFrame ())
|
||||
frame->setFocusView (this);
|
||||
dragStartMouseObserver.init (where);
|
||||
return kMouseEventHandled;
|
||||
}
|
||||
return kMouseEventNotHandled;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
CMouseEventResult ImageFramesView::onMouseMoved (CPoint& where, const CButtonState& buttons)
|
||||
{
|
||||
if (buttons.isLeftButton ())
|
||||
{
|
||||
if (dragStartMouseObserver.shouldStartDrag (where))
|
||||
{
|
||||
std::vector<size_t> indices;
|
||||
indices.emplace_back (DragPackageID);
|
||||
for (auto index = 0u; index < imageList->size (); ++index)
|
||||
{
|
||||
if (imageList->at (index).selected)
|
||||
indices.emplace_back (index);
|
||||
}
|
||||
if (indices.size () > 1)
|
||||
{
|
||||
auto dropSource = makeOwned<CDropSource> ();
|
||||
dropSource->add (indices.data (),
|
||||
static_cast<uint32_t> (indices.size () * sizeof (size_t)),
|
||||
IDataPackage::kBinary);
|
||||
DragDescription dragDesc (dropSource);
|
||||
auto imageSize = imageList->front ().bitmap->getSize ();
|
||||
imageSize.y *= indices.size () - 1;
|
||||
dragDesc.bitmap = renderBitmapOffscreen (
|
||||
imageSize, getFrame ()->getScaleFactor (), [&] (CDrawContext& context) {
|
||||
CRect r;
|
||||
r.setSize (imageList->front ().bitmap->getSize ());
|
||||
for (auto index : indices)
|
||||
{
|
||||
if (index == DragPackageID)
|
||||
continue;
|
||||
if (auto image = imageList->at (index).bitmap)
|
||||
{
|
||||
image->draw (&context, r);
|
||||
}
|
||||
r.offset (0, r.getHeight ());
|
||||
}
|
||||
});
|
||||
doDrag (dragDesc);
|
||||
}
|
||||
}
|
||||
return kMouseEventHandled;
|
||||
}
|
||||
return kMouseEventNotHandled;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
bool ImageFramesView::onDrop (DragEventData eventData)
|
||||
{
|
||||
if (dropIndicatorPos == -1)
|
||||
return false;
|
||||
size_t dropPosition = static_cast<size_t> (dropIndicatorPos);
|
||||
std::vector<size_t> indices;
|
||||
if (getIndicesFromDataPackage (eventData.drag, &indices))
|
||||
{
|
||||
auto doCopy = eventData.modifiers.has (ModifierKey::Alt);
|
||||
reorderImages (dropPosition, doCopy, indices);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto imagePaths = getDragPngImagePaths (eventData.drag);
|
||||
if (!imagePaths.empty ())
|
||||
{
|
||||
addImages (dropPosition, imagePaths);
|
||||
}
|
||||
}
|
||||
|
||||
invalid ();
|
||||
dropIndicatorPos = -1;
|
||||
dragHasImages = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
DragOperation ImageFramesView::onDragEnter (DragEventData eventData)
|
||||
{
|
||||
if (getIndicesFromDataPackage (eventData.drag))
|
||||
return DragOperation::Move;
|
||||
else if (dragHasPngImages (eventData.drag))
|
||||
{
|
||||
dragHasImages = true;
|
||||
return DragOperation::Copy;
|
||||
}
|
||||
return DragOperation::None;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void ImageFramesView::onDragLeave (DragEventData eventData)
|
||||
{
|
||||
dropIndicatorPos = -1;
|
||||
dragHasImages = false;
|
||||
invalid ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
DragOperation ImageFramesView::onDragMove (DragEventData eventData)
|
||||
{
|
||||
if (dragHasImages || getIndicesFromDataPackage (eventData.drag))
|
||||
{
|
||||
eventData.pos.offset (0, rowHeight / 2);
|
||||
auto newIndex = posToIndex (eventData.pos);
|
||||
if (newIndex != dropIndicatorPos)
|
||||
{
|
||||
dropIndicatorPos = newIndex;
|
||||
invalid ();
|
||||
}
|
||||
auto doCopy = dragHasImages ? true : eventData.modifiers.has (ModifierKey::Alt);
|
||||
return doCopy ? DragOperation::Copy : DragOperation::Move;
|
||||
}
|
||||
return DragOperation::None;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void ImageFramesView::onKeyboardEvent (KeyboardEvent& event)
|
||||
{
|
||||
if (event.type != EventType::KeyDown || event.virt == VirtualKey::None || !imageList || imageList->empty ())
|
||||
return;
|
||||
switch (event.virt)
|
||||
{
|
||||
case VirtualKey::Up:
|
||||
{
|
||||
auto index = firstSelectedIndex ();
|
||||
if (index <= 0)
|
||||
index = static_cast<int32_t> (imageList->size ());
|
||||
--index;
|
||||
selectExclusive (static_cast<size_t> (index));
|
||||
event.consumed = true;
|
||||
break;
|
||||
}
|
||||
case VirtualKey::Down:
|
||||
{
|
||||
auto index = lastSelectedIndex ();
|
||||
if (index == static_cast<int32_t> (imageList->size ()) - 1)
|
||||
index = 0;
|
||||
else
|
||||
++index;
|
||||
selectExclusive (static_cast<size_t> (index));
|
||||
event.consumed = true;
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // ImageStitcher
|
||||
} // VSTGUI
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
// This file is part of VSTGUI. It is subject to the license terms
|
||||
// in the LICENSE file found in the top-level directory of this
|
||||
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "documentcontroller.h"
|
||||
#include "vstgui/lib/cbitmap.h"
|
||||
#include "vstgui/lib/ccolor.h"
|
||||
#include "vstgui/lib/cfont.h"
|
||||
#include "vstgui/lib/cview.h"
|
||||
#include "vstgui/lib/dragging.h"
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace VSTGUI {
|
||||
namespace ImageStitcher {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
class ImageFramesView : public CView, public IDropTarget
|
||||
{
|
||||
public:
|
||||
ImageFramesView ();
|
||||
|
||||
void setDocContext (const DocumentContextPtr& dc);
|
||||
void setImageList (ImageList* list);
|
||||
void setSelectionColor (CColor color);
|
||||
void setTextColor (CColor color);
|
||||
|
||||
void takeFocus () override;
|
||||
void looseFocus () override;
|
||||
private:
|
||||
static std::vector<Path> getDragPngImagePaths (IDataPackage* drag);
|
||||
static bool dragHasPngImages (IDataPackage* drag);
|
||||
static bool getIndicesFromDataPackage (IDataPackage* package,
|
||||
std::vector<size_t>* result = nullptr);
|
||||
|
||||
CPoint sizeOfOneRow () const;
|
||||
CRect indexToRect (size_t index) const;
|
||||
int32_t posToIndex (CPoint where) const;
|
||||
int32_t firstSelectedIndex () const;
|
||||
int32_t lastSelectedIndex () const;
|
||||
void makeRectVisible (CRect r) const;
|
||||
void selectExclusive (size_t index);
|
||||
void enlargeSelection (size_t index);
|
||||
void updateViewSize ();
|
||||
void reorderImages (size_t position, bool doCopy, std::vector<size_t>& indices);
|
||||
void addImages (size_t position, const std::vector<Path>& imagePaths);
|
||||
void parentSizeChanged () override;
|
||||
void drawRect (CDrawContext* context, const CRect& _updateRect) override;
|
||||
CMouseEventResult onMouseDown (CPoint& where, const CButtonState& buttons) override;
|
||||
CMouseEventResult onMouseMoved (CPoint& where, const CButtonState& buttons) override;
|
||||
SharedPointer<IDropTarget> getDropTarget () override { return this; }
|
||||
DragOperation onDragEnter (DragEventData eventData) override;
|
||||
DragOperation onDragMove (DragEventData eventData) override;
|
||||
void onDragLeave (DragEventData eventData) override;
|
||||
bool onDrop (DragEventData eventData) override;
|
||||
void onKeyboardEvent (KeyboardEvent& event) override;
|
||||
|
||||
CColor activeSelectionColor;
|
||||
CColor inactiveSelectionColor;
|
||||
CColor textColor {kBlackCColor};
|
||||
CColor selectedTextColor {kBlackCColor};
|
||||
CCoord titleHeight {8};
|
||||
CCoord rowHeight {0};
|
||||
DragStartMouseObserver dragStartMouseObserver;
|
||||
SharedPointer<CFontDesc> font;
|
||||
DocumentContextPtr docContext;
|
||||
ImageList* imageList {nullptr};
|
||||
|
||||
int32_t dropIndicatorPos {-1};
|
||||
bool dragHasImages {false};
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // ImageStitcher
|
||||
} // VSTGUI
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
// This file is part of VSTGUI. It is subject to the license terms
|
||||
// in the LICENSE file found in the top-level directory of this
|
||||
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
|
||||
|
||||
#include "startupcontroller.h"
|
||||
#include "vstgui/standalone/include/helpers/uidesc/customization.h"
|
||||
#include "vstgui/standalone/include/helpers/uidesc/modelbinding.h"
|
||||
#include "vstgui/standalone/include/helpers/value.h"
|
||||
#include "vstgui/standalone/include/helpers/windowcontroller.h"
|
||||
#include "vstgui/standalone/include/iappdelegate.h"
|
||||
#include "vstgui/standalone/include/iapplication.h"
|
||||
#include "vstgui/standalone/include/iasync.h"
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
|
||||
using namespace VSTGUI::Standalone;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace VSTGUI {
|
||||
namespace ImageStitcher {
|
||||
namespace {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
class StartupWindowController : public WindowControllerAdapter,
|
||||
public UIDesc::CustomizationAdapter,
|
||||
public ICommandHandler
|
||||
{
|
||||
public:
|
||||
static std::shared_ptr<StartupWindowController> getInstance ()
|
||||
{
|
||||
if (!instance)
|
||||
{
|
||||
instance = std::make_shared<StartupWindowController> ();
|
||||
|
||||
UIDesc::Config config;
|
||||
config.uiDescFileName = "StartupWindow.uidesc";
|
||||
config.viewName = "Window";
|
||||
config.windowConfig.style.transparent ()
|
||||
.close ()
|
||||
.centered ()
|
||||
.movableByWindowBackground ();
|
||||
config.modelBinding = instance->createModelBinding ();
|
||||
config.customization = instance;
|
||||
|
||||
instance->window = UIDesc::makeWindow (config);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
void showWindow ()
|
||||
{
|
||||
window->show ();
|
||||
window->activate ();
|
||||
}
|
||||
|
||||
private:
|
||||
void createNewDocument ()
|
||||
{
|
||||
Async::schedule (Async::mainQueue (), [] () {
|
||||
auto commandHandler =
|
||||
IApplication::instance ().getDelegate ().dynamicCast<ICommandHandler> ();
|
||||
assert (commandHandler);
|
||||
commandHandler->handleCommand (Commands::NewDocument);
|
||||
});
|
||||
}
|
||||
|
||||
void openDocument ()
|
||||
{
|
||||
Async::schedule (Async::mainQueue (), [] () {
|
||||
auto commandHandler =
|
||||
IApplication::instance ().getDelegate ().dynamicCast<ICommandHandler> ();
|
||||
assert (commandHandler);
|
||||
commandHandler->handleCommand (Commands::OpenDocument);
|
||||
});
|
||||
}
|
||||
|
||||
UIDesc::ModelBindingPtr createModelBinding ()
|
||||
{
|
||||
auto binding = UIDesc::ModelBindingCallbacks::make ();
|
||||
binding->addValue (Value::make ("CreateNewDocument"),
|
||||
UIDesc::ValueCalls::onAction ([this] (auto& v) {
|
||||
v.performEdit (0.);
|
||||
this->handleCommand (Commands::NewDocument);
|
||||
}));
|
||||
binding->addValue (Value::make ("OpenDocument"),
|
||||
UIDesc::ValueCalls::onAction ([this] (auto& v) {
|
||||
v.performEdit (0.);
|
||||
this->handleCommand (Commands::OpenDocument);
|
||||
}));
|
||||
binding->addValue (Value::make ("CloseWindow"),
|
||||
UIDesc::ValueCalls::onAction ([] (auto& v) {
|
||||
v.performEdit (0.);
|
||||
IApplication::instance ().quit ();
|
||||
}));
|
||||
return binding;
|
||||
}
|
||||
|
||||
bool canHandleCommand (const Command& command) override
|
||||
{
|
||||
if (command == Commands::OpenDocument)
|
||||
return true;
|
||||
if (command == Commands::NewDocument)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool handleCommand (const Command& command) override
|
||||
{
|
||||
if (command == Commands::OpenDocument)
|
||||
{
|
||||
openDocument ();
|
||||
window->close ();
|
||||
return true;
|
||||
}
|
||||
if (command == Commands::NewDocument)
|
||||
{
|
||||
createNewDocument ();
|
||||
window->close ();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void onClosed (const IWindow&) override
|
||||
{
|
||||
window = nullptr;
|
||||
instance = nullptr;
|
||||
if (IApplication::instance ().getWindows ().empty ())
|
||||
IApplication::instance ().quit ();
|
||||
}
|
||||
|
||||
static std::shared_ptr<StartupWindowController> instance;
|
||||
|
||||
WindowPtr window;
|
||||
};
|
||||
std::shared_ptr<StartupWindowController> StartupWindowController::instance;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // anonymous
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void showStartupController ()
|
||||
{
|
||||
StartupWindowController::getInstance ()->showWindow ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // ImageStitcher
|
||||
} // VSTGUI
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// This file is part of VSTGUI. It is subject to the license terms
|
||||
// in the LICENSE file found in the top-level directory of this
|
||||
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace VSTGUI {
|
||||
namespace ImageStitcher {
|
||||
|
||||
void showStartupController ();
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // ImageStitcher
|
||||
} // VSTGUI
|
||||
Reference in New Issue
Block a user