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,29 @@
set(TargetName ImageStitcher)
set(${TargetName}_sources
Readme.md
source/app.cpp
source/document.cpp
source/document.h
source/documentcontroller.cpp
source/documentcontroller.h
source/imageframesview.cpp
source/imageframesview.h
source/startupcontroller.cpp
source/startupcontroller.h
)
set(${TargetName}_RESOURCES
"resource/Window.uidesc"
"resource/StartupWindow.uidesc"
)
vstgui_add_executable(${TargetName} "${${TargetName}_sources}")
vstgui_add_resources(${TargetName} "${${TargetName}_RESOURCES}")
vstgui_set_target_bundle_id(${TargetName} "com.steinberg.vstgui.imagestitcher")
vstgui_set_target_infoplist(${TargetName} "resource/Info.plist")
vstgui_set_target_rcfile(${TargetName} "resource/imagestitcher.rc")
vstgui_set_cxx_version(${TargetName} ${VSTGUI_CXX_VERSION})
target_include_directories(${TargetName} PRIVATE ../../../)
set_target_properties(${TargetName} PROPERTIES ${APP_PROPERTIES} ${VSTGUI_TOOLS_FOLDER})
@@ -0,0 +1,5 @@
# Tool: ImageStitcher
Many controls in VSTGUI uses stacked bitmaps. Per example the COnOffButton has two states and depending on the state the upper half of the bitmap is shown, or the lower half.
This tool helps in creating these bitmaps by generating one stitched PNG out of many PNG's.
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>ImageStitcher</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSHumanReadableCopyright</key>
<string>Written 2018 by Arne Scheffler.</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>imagestitch</string>
</array>
<key>CFBundleTypeName</key>
<string>ImageStitch Document</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
</array>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.content</string>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>ImageStitch Document</string>
<key>UTTypeIdentifier</key>
<string>com.steinberg.vstgui.imagestitch.document</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>imagestitch</string>
</array>
</dict>
</dict>
</array>
</dict>
</plist>
@@ -0,0 +1 @@
BNDL????
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1005 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,349 @@
{
"vstgui-ui-description": {
"version": "1",
"bitmaps": {},
"fonts": {
"Title": {
"font-name": "Arial",
"size": "36"
},
"VSTGUI": {
"font-name": "Arial",
"size": "9"
},
"Welcome": {
"font-name": "Arial",
"size": "18"
}
},
"colors": {
"Focus": "#63bfffc8",
"Text": "#d0d0d0ff",
"WindowFrame": "#a6a6a6ff"
},
"gradients": {
"BackgroundGradient": [
{
"rgba": "#484848ff",
"start": "0.1360608552631579204472700439509935677052"
},
{
"rgba": "#272727b4",
"start": "1"
}
],
"Default TextButton Gradient": [
{
"rgba": "#dcdcdcff",
"start": "0"
},
{
"rgba": "#b4b4b4ff",
"start": "1"
}
],
"Default TextButton Gradient Highlighted": [
{
"rgba": "#b4b4b4ff",
"start": "0"
},
{
"rgba": "#646464ff",
"start": "1"
}
]
},
"control-tags": {
"CloseWindow": "2",
"CreateNewDocument": "0",
"OpenDocument": "1"
},
"custom": {
"UIGridController": {
"Grids": "1x 1,5x 5,10x 10,12x 12,15x 15",
"Size": "10, 10"
},
"UITemplateController": {
"SelectedTemplate": "Window"
},
"UIEditController": {
"EditViewScale": "1",
"EditorSize": "0, 0, 982, 685",
"SplitViewSize_0_0": "0.638846737481031823335797525942325592041",
"SplitViewSize_0_1": "0.3308042488619120091719594256574055179954",
"SplitViewSize_1_0": "0.4977238239757207916014181137143168598413",
"SplitViewSize_1_1": "0.4946889226100151804033089320000726729631",
"SplitViewSize_2_0": "0.7046843177189409113125861949811223894358",
"SplitViewSize_2_1": "0.2902240325865580450326319805753882974386",
"TabSwitchValue": "6",
"Version": "1"
},
"UIAttributesController": {},
"UIViewCreatorDataSource": {
"SelectedRow": "8"
},
"UIFontsDataSource": {
"SelectedRow": "1"
},
"UIBitmapsDataSource": {
"SelectedRow": "-1"
},
"UIColorsDataSource": {
"SelectedRow": "1"
},
"UIGradientsDataSource": {
"SelectedRow": "0"
},
"FocusDrawing": {
"color": "Focus",
"enabled": "true",
"width": "2"
},
"UIDescFilePath": {
"path":
"/Users/scheffle/Source/vstgui/vstgui/tools/imagestitcher/resource/StartupWindow.uidesc"
}
},
"templates": {
"Window": {
"attributes": {
"autosize": "left right top bottom ",
"background-color": "~ WhiteCColor",
"background-color-draw-style": "filled and stroked",
"class": "CViewContainer",
"maxSize": "560, 170",
"minSize": "560, 170",
"mouse-enabled": "true",
"opacity": "1",
"origin": "0, 0",
"size": "560, 170",
"transparent": "true",
"wants-focus": "false"
},
"children": {
"CGradientView": {
"attributes": {
"class": "CGradientView",
"draw-antialiased": "true",
"frame-color": "WindowFrame",
"frame-width": "-1",
"gradient": "BackgroundGradient",
"gradient-angle": "180",
"gradient-style": "radial",
"mouse-enabled": "false",
"opacity": "1",
"origin": "0, 0",
"radial-center": "0.5, 0.5",
"radial-radius": "1",
"round-rect-radius": "3",
"size": "560, 170",
"transparent": "false",
"wants-focus": "false"
}
},
"CTextLabel": {
"attributes": {
"back-color": "~ BlackCColor",
"background-offset": "0, 0",
"class": "CTextLabel",
"default-value": "0.5",
"font": "Title",
"font-antialias": "true",
"font-color": "Text",
"frame-color": "~ BlackCColor",
"frame-width": "1",
"max-value": "1",
"min-value": "0",
"mouse-enabled": "false",
"opacity": "1",
"origin": "0, 20",
"round-rect-radius": "6",
"shadow-color": "~ RedCColor",
"size": "560, 50",
"style-3D-in": "false",
"style-3D-out": "false",
"style-no-draw": "false",
"style-no-frame": "false",
"style-no-text": "false",
"style-round-rect": "false",
"style-shadow-text": "false",
"text-alignment": "center",
"text-inset": "0, 0",
"text-rotation": "0",
"text-shadow-offset": "1, 1",
"title": "ImageStitcher",
"transparent": "true",
"value-precision": "2",
"wants-focus": "false",
"wheel-inc-value": "0.1"
}
},
"CTextLabel": {
"attributes": {
"back-color": "~ BlackCColor",
"background-offset": "0, 0",
"class": "CTextLabel",
"default-value": "0.5",
"font": "Welcome",
"font-antialias": "true",
"font-color": "Text",
"frame-color": "WindowFrame",
"frame-width": "1",
"max-value": "1",
"min-value": "0",
"mouse-enabled": "false",
"opacity": "1",
"origin": "0, 70",
"round-rect-radius": "6",
"shadow-color": "~ RedCColor",
"size": "560, 30",
"style-3D-in": "false",
"style-3D-out": "false",
"style-no-draw": "false",
"style-no-frame": "false",
"style-no-text": "false",
"style-round-rect": "false",
"style-shadow-text": "false",
"text-alignment": "center",
"text-inset": "0, 0",
"text-rotation": "0",
"text-shadow-offset": "1, 1",
"title": "Welcome!",
"transparent": "true",
"value-precision": "2",
"wants-focus": "false",
"wheel-inc-value": "0.1"
}
},
"CTextButton": {
"attributes": {
"class": "CTextButton",
"control-tag": "CreateNewDocument",
"default-value": "0.5",
"font": "~ SystemFont",
"frame-color": "~ BlackCColor",
"frame-color-highlighted": "~ BlackCColor",
"frame-width": "1",
"gradient": "Default TextButton Gradient",
"gradient-highlighted": "Default TextButton Gradient Highlighted",
"icon-position": "left",
"icon-text-margin": "0",
"kick-style": "false",
"max-value": "1",
"min-value": "0",
"mouse-enabled": "true",
"opacity": "1",
"origin": "120, 110",
"round-radius": "4",
"size": "150, 20",
"text-alignment": "center",
"text-color": "~ BlackCColor",
"text-color-highlighted": "~ WhiteCColor",
"title": "Create new Document",
"transparent": "false",
"wants-focus": "true",
"wheel-inc-value": "0.1"
}
},
"CTextButton": {
"attributes": {
"class": "CTextButton",
"control-tag": "OpenDocument",
"default-value": "0.5",
"font": "~ SystemFont",
"frame-color": "~ BlackCColor",
"frame-color-highlighted": "~ BlackCColor",
"frame-width": "1",
"gradient": "Default TextButton Gradient",
"gradient-highlighted": "Default TextButton Gradient Highlighted",
"icon-position": "left",
"icon-text-margin": "0",
"kick-style": "false",
"max-value": "1",
"min-value": "0",
"mouse-enabled": "true",
"opacity": "1",
"origin": "290, 110",
"round-radius": "4",
"size": "150, 20",
"text-alignment": "center",
"text-color": "~ BlackCColor",
"text-color-highlighted": "~ WhiteCColor",
"title": "Open Document",
"transparent": "false",
"wants-focus": "true",
"wheel-inc-value": "0.1"
}
},
"CTextButton": {
"attributes": {
"class": "CTextButton",
"control-tag": "CloseWindow",
"default-value": "0.5",
"font": "~ SystemFont",
"frame-color": "~ BlackCColor",
"frame-color-highlighted": "~ BlackCColor",
"frame-width": "1",
"gradient": "Default TextButton Gradient",
"gradient-highlighted": "Default TextButton Gradient Highlighted",
"icon-position": "left",
"icon-text-margin": "0",
"kick-style": "false",
"max-value": "1",
"min-value": "0",
"mouse-enabled": "true",
"opacity": "1",
"origin": "240, 140",
"round-radius": "4",
"size": "80, 20",
"text-alignment": "center",
"text-color": "~ BlackCColor",
"text-color-highlighted": "~ WhiteCColor",
"title": "Quit",
"transparent": "false",
"wants-focus": "true",
"wheel-inc-value": "0.1"
}
},
"CTextLabel": {
"attributes": {
"back-color": "~ BlackCColor",
"background-offset": "0, 0",
"class": "CTextLabel",
"default-value": "0.5",
"font": "VSTGUI",
"font-antialias": "true",
"font-color": "~ WhiteCColor",
"frame-color": "~ BlackCColor",
"frame-width": "1",
"max-value": "1",
"min-value": "0",
"mouse-enabled": "true",
"opacity": "1",
"origin": "0, 0",
"round-rect-radius": "6",
"shadow-color": "~ RedCColor",
"size": "560, 30",
"style-3D-in": "false",
"style-3D-out": "false",
"style-no-draw": "false",
"style-no-frame": "false",
"style-no-text": "false",
"style-round-rect": "false",
"style-shadow-text": "false",
"text-alignment": "center",
"text-inset": "0, 0",
"text-rotation": "0",
"text-shadow-offset": "1, 1",
"title": "VSTGUI",
"transparent": "true",
"value-precision": "2",
"wants-focus": "false",
"wheel-inc-value": "0.1"
}
}
}
}
}
}
}
File diff suppressed because one or more lines are too long
@@ -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