Initial release

This commit is contained in:
civ
2026-08-16 18:24:52 +07:00
commit 876886a39a
13244 changed files with 2353959 additions and 0 deletions
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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