Compare commits

...

5 Commits

Author SHA1 Message Date
Gaspar Capello bc39d7d38e
Merge 141849842f into 80fa065bd5 2025-07-25 19:59:54 +00:00
Gaspar Capello 141849842f Fix thumbnails for macOS Sequoia (fix #5009) 2025-07-25 16:58:06 -03:00
Christian Kaiser 80fa065bd5 [lua] Add sprite.undoHistory
build / build (Debug, macos-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, macos-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, noscripts, cli) (push) Has been cancelled Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Has been cancelled Details
2025-07-25 13:58:52 -03:00
David Capello de1ccb24dd [win] Don't drop text when IME dialog composition is accepted w/Enter
build / build (Debug, macos-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, macos-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, noscripts, cli) (push) Waiting to run Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Waiting to run Details
build-auto / build-auto (Debug, macos-latest) (push) Has been cancelled Details
build-auto / build-auto (Debug, ubuntu-latest) (push) Has been cancelled Details
build-auto / build-auto (Debug, windows-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, macos-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, ubuntu-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, windows-latest) (push) Has been cancelled Details
With #5230, now that we can show the IME dialog on Windows, when we
are selecting a specific word/composition in the IME dialog, if we
press Enter we'll receive that Enter onKeyUp(). It's better if we
process the Enter key onKeyDown() (as the IME enter key is not
received in that case).
2025-07-25 09:19:50 -03:00
David Capello 7d91c4b9d9 [win] Fix dead keys on Windows 2025-07-24 17:45:46 -03:00
15 changed files with 354 additions and 361 deletions

2
laf

@ -1 +1 @@
Subproject commit a2bb9ec7fb98354279a2c49870a4a47a67a8e86e
Subproject commit 8ec4b553f1618f7a4b47cdcf4cfc2663266111ac

View File

@ -86,10 +86,6 @@ if(ENABLE_STEAM)
add_subdirectory(steam)
endif()
if(ENABLE_DESKTOP_INTEGRATION)
add_subdirectory(desktop)
endif()
add_subdirectory(app)
######################################################################
@ -187,6 +183,10 @@ if(ENABLE_ASEPRITE_EXE)
add_executable(${main_target}
main/main.cpp
${main_resources})
if(ENABLE_DESKTOP_INTEGRATION)
add_subdirectory(desktop)
endif()
if(LAF_BACKEND STREQUAL "skia")
if(WIN32)
set_target_properties(${main_target} PROPERTIES WIN32_EXECUTABLE true)

View File

@ -13,6 +13,8 @@
#include "app/console.h"
#include "app/context.h"
#include "app/context_observer.h"
#include "app/doc.h"
#include "app/doc_undo.h"
#include "app/script/docobj.h"
#include "app/script/engine.h"
#include "app/script/luacpp.h"

View File

@ -64,6 +64,7 @@
#include "doc/tag.h"
#include "doc/tileset.h"
#include "doc/tilesets.h"
#include "undo/undo_state.h"
#include <algorithm>
@ -1029,6 +1030,42 @@ int Sprite_set_useLayerUuids(lua_State* L)
return 0;
}
int Sprite_get_undoHistory(lua_State* L)
{
const auto* sprite = get_docobj<Sprite>(L, 1);
const auto* doc = static_cast<Doc*>(sprite->document());
const auto* history = doc->undoHistory();
if (!history) {
lua_pushnil(L);
return 1;
}
const undo::UndoState* currentState = history->currentState();
const undo::UndoState* s = history->firstState();
const bool canRedo = history->canRedo();
bool pastCurrent = !currentState && canRedo;
int undoSteps = 0;
int redoSteps = 0;
while (s) {
if (pastCurrent && canRedo)
redoSteps++;
else if (currentState || !canRedo)
undoSteps++;
if (s == currentState || !currentState)
pastCurrent = true;
s = s->next();
}
lua_newtable(L);
setfield_integer(L, "undoSteps", undoSteps);
setfield_integer(L, "redoSteps", redoSteps);
return 1;
}
const luaL_Reg Sprite_methods[] = {
{ "__eq", Sprite_eq },
{ "resize", Sprite_resize },
@ -1094,6 +1131,7 @@ const Property Sprite_properties[] = {
{ "events", Sprite_get_events, nullptr },
{ "tileManagementPlugin", Sprite_get_tileManagementPlugin, Sprite_set_tileManagementPlugin },
{ "useLayerUuids", Sprite_get_useLayerUuids, Sprite_set_useLayerUuids },
{ "undoHistory", Sprite_get_undoHistory, nullptr },
{ nullptr, nullptr, nullptr }
};

View File

@ -443,12 +443,7 @@ bool WritingTextState::onSetCursor(Editor* editor, const gfx::Point& mouseScreen
return true;
}
bool WritingTextState::onKeyDown(Editor*, KeyMessage*)
{
return false;
}
bool WritingTextState::onKeyUp(Editor*, KeyMessage* msg)
bool WritingTextState::onKeyDown(Editor*, KeyMessage* msg)
{
// Cancel loop pressing Esc key
if (msg->scancode() == ui::kKeyEsc) {
@ -457,7 +452,17 @@ bool WritingTextState::onKeyUp(Editor*, KeyMessage* msg)
// Drop text pressing Enter key
else if (msg->scancode() == ui::kKeyEnter) {
drop();
return true;
}
return false;
}
bool WritingTextState::onKeyUp(Editor*, KeyMessage* msg)
{
// Note: We cannot process kKeyEnter key here to drop the text as it
// could be received after the Enter key is pressed in the IME
// dialog to accept the composition (not to accept the text). So we
// process kKeyEnter in onKeyDown().
return true;
}

View File

@ -1,37 +1,23 @@
# Desktop Integration
# Copyright (c) 2022 Igara Studio S.A.
# Copyright (c) 2022-2025 Igara Studio S.A.
find_library(QUARTZ_LIBRARY Quartz)
find_library(QUICKLOOK_LIBRARY QuickLookThumbnailing)
add_library(AsepriteThumbnailer SHARED
main.mm
thumbnail.mm)
set(extension_target AsepriteThumbnailer)
target_link_libraries(AsepriteThumbnailer
laf-base
add_executable(${extension_target}
appex/thumbnail.mm)
target_link_libraries(${extension_target}
dio-lib
render-lib
${QUARTZ_LIBRARY})
${QUICKLOOK_LIBRARY})
set_target_properties(AsepriteThumbnailer PROPERTIES
FRAMEWORK TRUE
MACOSX_FRAMEWORK_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist")
set_target_properties(${extension_target} PROPERTIES
BUNDLE TRUE
BUNDLE_EXTENSION appex
MACOSX_BUNDLE TRUE
LINK_FLAGS "-Wl,-e,_NSExtensionMain"
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_LIST_DIR}/appex/Info.plist")
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/lib/AsepriteThumbnailer.qlgenerator
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
COMMAND ${CMAKE_COMMAND} -E make_directory lib/AsepriteThumbnailer.qlgenerator
COMMAND ${CMAKE_COMMAND} -E make_directory lib/AsepriteThumbnailer.qlgenerator/Contents
COMMAND ${CMAKE_COMMAND} -E make_directory lib/AsepriteThumbnailer.qlgenerator/Contents/MacOS
COMMAND ${CMAKE_COMMAND} -E copy
lib/AsepriteThumbnailer.framework/Versions/A/AsepriteThumbnailer
lib/AsepriteThumbnailer.qlgenerator/Contents/MacOS
COMMAND ${CMAKE_COMMAND} -E copy
lib/AsepriteThumbnailer.framework/Versions/A/Resources/Info.plist
lib/AsepriteThumbnailer.qlgenerator/Contents
BYPRODUCTS ${CMAKE_BINARY_DIR}/lib/AsepriteThumbnailer.qlgenerator/Contents/MacOS/AsepriteThumbnailer
${CMAKE_BINARY_DIR}/lib/AsepriteThumbnailer.qlgenerator/Contents/Info.plist
DEPENDS AsepriteThumbnailer)
add_custom_target(AsepriteThumbnailer.qlgenerator
DEPENDS ${CMAKE_BINARY_DIR}/lib/AsepriteThumbnailer.qlgenerator)
add_dependencies(${main_target} ${extension_target})

View File

@ -1,58 +0,0 @@
<?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>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>QLGenerator</string>
<key>LSItemContentTypes</key>
<array>
<string>dyn.ah62d4rv4ge80c65f</string>
<string>dyn.ah62d4rv4ge80c65fsb3gw7df</string>
</array>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>AsepriteThumbnailer</string>
<key>CFBundleIdentifier</key>
<string>org.aseprite.AsepriteThumbnailer</string>
<key>CFBundleName</key>
<string>AsepriteThumbnailer</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>CFPlugInFactories</key>
<dict>
<key>A5E9417E-6E7A-4B2D-85A4-84E114D7A960</key>
<string>QuickLookGeneratorPluginFactory</string>
</dict>
<key>CFPlugInTypes</key>
<dict>
<key>5E2D9680-5022-40FA-B806-43349622E5B9</key>
<array>
<string>A5E9417E-6E7A-4B2D-85A4-84E114D7A960</string>
</array>
</dict>
<key>CFPlugInUnloadFunction</key>
<string></string>
<key>QLThumbnailMinimumSize</key>
<real>32</real>
<key>QLPreviewWidth</key>
<real>256</real>
<key>QLPreviewHeight</key>
<real>256</real>
<key>QLNeedsToBeRunInMainThread</key>
<true/>
<key>QLSupportsConcurrentRequests</key>
<false/>
<key>NSHumanReadableCopyright</key>
<string>Copyright © Igara Studio S.A. All rights reserved.</string>
</dict>
</plist>

View File

@ -0,0 +1,125 @@
<?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>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeIdentifier</key>
<string>org.aseprite.Aseprite.sprite</string>
<key>UTTypeDescription</key>
<string>Aseprite Sprite</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.image</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>aseprite</string>
<string>ase</string>
</array>
<key>public.mime-type</key>
<string>image/aseprite</string>
</dict>
</dict>
</array>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>aseprite</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>Document.icns</string>
<key>CFBundleTypeName</key>
<string>Aseprite Sprite</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>ase</string>
<string>bmp</string>
<string>flc</string>
<string>fli</string>
<string>gif</string>
<string>ico</string>
<string>jpeg</string>
<string>jpg</string>
<string>pcx</string>
<string>png</string>
<string>tga</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>Document.icns</string>
<key>CFBundleTypeName</key>
<string>Aseprite Sprite</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>aseprite-extension</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>Extension.icns</string>
<key>CFBundleTypeName</key>
<string>Aseprite Extension</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
</array>
<key>CFBundleName</key>
<string>Aseprite</string>
<key>CFBundleIdentifier</key>
<string>org.aseprite.Aseprite</string>
<key>BuildMachineOSBuild</key>
<string>24C101</string>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleDisplayName</key>
<string>Aseprite</string>
<key>CFBundleExecutable</key>
<string>aseprite</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleShortVersionString</key>
<string>$VERSION</string>
<key>CFBundleVersion</key>
<string>$VERSION</string>
<key>CFBundleIconFile</key>
<string>Aseprite.icns</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.graphics-design</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2001-2025, Igara Studio S.A.
All rights reserved.</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>NSRequiresAquaSystemAppearance</key>
<false/>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CSResourcesFileMapped</key>
<true/>
<key>LSHasPlugIns</key>
<true/>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>LSMinimumSystemVersion</key>
<string>10.15</string>
</dict>
</plist>

View File

@ -0,0 +1,10 @@
<?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>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,37 @@
<?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>CFBundleExecutable</key>
<string>AsepriteThumbnailer</string>
<key>CFBundleIdentifier</key>
<string>org.aseprite.Aseprite.AsepriteThumbnailer</string>
<key>CFBundleName</key>
<string>AsepriteThumbnailer</string>
<key>CFBundleShortVersionString</key>
<string>$VERSION</string>
<key>CFBundleVersion</key>
<string>$VERSION</string>
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>QLSupportedContentTypes</key>
<array>
<string>org.aseprite.aseprite.sprite</string>
</array>
<key>QLThumbnailMinimumDimension</key>
<integer>32</integer>
</dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.quicklook.thumbnail</string>
<key>NSExtensionPrincipalClass</key>
<string>ThumbnailProvider</string>
</dict>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2001-2025, Igara Studio S.A.
All rights reserved.</string>
</dict>
</plist>

View File

@ -2,9 +2,7 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.app-sandbox</key>
<true/>
</dict>
</plist>

View File

@ -1,11 +1,10 @@
// Desktop Integration
// Copyright (c) 2022 Igara Studio S.A.
// Copyright (c) 2022-2025 Igara Studio S.A.
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "thumbnail.h"
#include "app/file_system.h"
#include "dio/decode_delegate.h"
#include "dio/decode_file.h"
#include "dio/file_interface.h"
@ -13,8 +12,7 @@
#include "render/render.h"
#include <Cocoa/Cocoa.h>
#include <algorithm>
#include <QuickLookThumbnailing/QuickLookThumbnailing.h>
namespace desktop {
@ -87,7 +85,7 @@ public:
} // anonymous namespace
CGImageRef get_thumbnail(CFURLRef url, CFDictionaryRef options, CGSize maxSize)
CGImageRef get_thumbnail(CFURLRef url, CGSize maxSize)
{
auto data = [[NSData alloc] initWithContentsOfURL:(NSURL*)url];
if (!data)
@ -153,3 +151,42 @@ CGImageRef get_thumbnail(CFURLRef url, CFDictionaryRef options, CGSize maxSize)
}
} // namespace desktop
@interface ThumbnailProvider : QLThumbnailProvider
@end
@implementation ThumbnailProvider
- (void)provideThumbnailForFileRequest:(QLFileThumbnailRequest*)request
completionHandler:
(void (^)(QLThumbnailReply* _Nullable, NSError* _Nullable))handler
{
CFURLRef url = (__bridge CFURLRef)(request.fileURL);
CGSize maxSize = request.maximumSize;
CGImageRef cgImage = desktop::get_thumbnail(url, maxSize);
if (!cgImage) {
handler(nil, nil);
return;
}
CGSize imageSize = CGSizeMake(CGImageGetWidth(cgImage), CGImageGetHeight(cgImage));
handler([QLThumbnailReply replyWithContextSize:maxSize
currentContextDrawingBlock:^BOOL {
CGContextRef ctx = [[NSGraphicsContext currentContext] CGContext];
CGContextSaveGState(ctx);
CGFloat scale = MIN(maxSize.width / imageSize.width,
maxSize.height / imageSize.height);
CGSize scaledSize = CGSizeMake(imageSize.width * scale,
imageSize.height * scale);
CGRect drawRect = CGRectMake((maxSize.width - scaledSize.width) / 2,
(maxSize.height - scaledSize.height) / 2,
scaledSize.width,
scaledSize.height);
CGContextDrawImage(ctx, drawRect, cgImage);
CGContextRestoreGState(ctx);
CGImageRelease(cgImage);
return YES;
}],
nil);
}
@end

View File

@ -1,234 +0,0 @@
// Desktop Integration
// Copyright (c) 2022 Igara Studio S.A.
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include <CoreFoundation/CFPlugInCOM.h>
#include <CoreFoundation/CoreFoundation.h>
#include <CoreServices/CoreServices.h>
#include <QuickLook/QuickLook.h>
#include "base/debug.h"
#include "thumbnail.h"
// Just as a side note: We're using the same UUID as the Windows
// Aseprite thumbnailer.
//
// If you're going to use this code, remember to change this UUID and
// change it in the Info.plist file.
#define PLUGIN_ID "A5E9417E-6E7A-4B2D-85A4-84E114D7A960"
static HRESULT Plugin_QueryInterface(void*, REFIID, LPVOID*);
static ULONG Plugin_AddRef(void*);
static ULONG Plugin_Release(void*);
static OSStatus Plugin_GenerateThumbnailForURL(void*,
QLThumbnailRequestRef,
CFURLRef,
CFStringRef,
CFDictionaryRef,
CGSize);
static void Plugin_CancelThumbnailGeneration(void*, QLThumbnailRequestRef);
static OSStatus Plugin_GeneratePreviewForURL(void*,
QLPreviewRequestRef,
CFURLRef,
CFStringRef,
CFDictionaryRef);
static void Plugin_CancelPreviewGeneration(void*, QLPreviewRequestRef);
static QLGeneratorInterfaceStruct Plugin_vtbl = { // kQLGeneratorTypeID interface
// IUnknown
nullptr, // void* reserved
Plugin_QueryInterface,
Plugin_AddRef,
Plugin_Release,
// QLGeneratorInterface
Plugin_GenerateThumbnailForURL,
Plugin_CancelThumbnailGeneration,
Plugin_GeneratePreviewForURL,
Plugin_CancelPreviewGeneration
};
// TODO it would be nice to create a C++ smart pointer/wrapper for CFUUIDRef type
struct Plugin {
QLGeneratorInterfaceStruct* interface; // Must be a pointer
CFUUIDRef factoryID;
ULONG refCount = 1; // Starts with one reference when it's created
Plugin(CFUUIDRef factoryID)
: interface(new QLGeneratorInterfaceStruct(Plugin_vtbl))
, factoryID(factoryID)
{
CFPlugInAddInstanceForFactory(factoryID);
}
~Plugin()
{
delete interface;
if (factoryID) {
CFPlugInRemoveInstanceForFactory(factoryID);
CFRelease(factoryID);
}
}
// IUnknown impl
HRESULT QueryInterface(REFIID iid, LPVOID* ppv)
{
CFUUIDRef interfaceID = CFUUIDCreateFromUUIDBytes(kCFAllocatorDefault, iid);
if (CFEqual(interfaceID, kQLGeneratorCallbacksInterfaceID)) {
*ppv = this;
AddRef();
CFRelease(interfaceID);
return S_OK;
}
else {
*ppv = nullptr;
CFRelease(interfaceID);
return E_NOINTERFACE;
}
}
ULONG AddRef() { return ++refCount; }
ULONG Release()
{
if (refCount == 1) {
delete this;
return 0;
}
else {
ASSERT(refCount != 0);
return --refCount;
}
}
// QLGeneratorInterfaceStruct impl
static OSStatus GenerateThumbnailForURL(QLThumbnailRequestRef thumbnail,
CFURLRef url,
CFStringRef contentTypeUTI,
CFDictionaryRef options,
CGSize maxSize)
{
CGImageRef image = desktop::get_thumbnail(url, options, maxSize);
if (!image)
return -1;
QLThumbnailRequestSetImage(thumbnail, image, nullptr);
CGImageRelease(image);
return 0;
}
static void CancelThumbnailGeneration(QLThumbnailRequestRef thumbnail)
{
// TODO
}
OSStatus GeneratePreviewForURL(QLPreviewRequestRef preview,
CFURLRef url,
CFStringRef contentTypeUTI,
CFDictionaryRef options)
{
CGImageRef image = desktop::get_thumbnail(url, options, CGSizeMake(0, 0));
if (!image)
return -1;
int w = CGImageGetWidth(image);
int h = CGImageGetHeight(image);
int wh = std::min(w, h);
if (wh < 128) {
w = 128 * w / wh;
h = 128 * h / wh;
}
CGContextRef cg = QLPreviewRequestCreateContext(preview, CGSizeMake(w, h), YES, options);
CGContextSetInterpolationQuality(cg, kCGInterpolationNone);
CGContextDrawImage(cg, CGRectMake(0, 0, w, h), image);
QLPreviewRequestFlushContext(preview, cg);
CGImageRelease(image);
CGContextRelease(cg);
return 0;
}
void CancelPreviewGeneration(QLPreviewRequestRef preview)
{
// TODO
}
};
static HRESULT Plugin_QueryInterface(void* p, REFIID iid, LPVOID* ppv)
{
ASSERT(p);
return reinterpret_cast<Plugin*>(p)->QueryInterface(iid, ppv);
}
static ULONG Plugin_AddRef(void* p)
{
ASSERT(p);
return reinterpret_cast<Plugin*>(p)->AddRef();
}
static ULONG Plugin_Release(void* p)
{
ASSERT(p);
return reinterpret_cast<Plugin*>(p)->Release();
}
static OSStatus Plugin_GenerateThumbnailForURL(void* p,
QLThumbnailRequestRef thumbnail,
CFURLRef url,
CFStringRef contentTypeUTI,
CFDictionaryRef options,
CGSize maxSize)
{
ASSERT(p);
return reinterpret_cast<Plugin*>(p)->GenerateThumbnailForURL(thumbnail,
url,
contentTypeUTI,
options,
maxSize);
}
static void Plugin_CancelThumbnailGeneration(void* p, QLThumbnailRequestRef thumbnail)
{
ASSERT(p);
reinterpret_cast<Plugin*>(p)->CancelThumbnailGeneration(thumbnail);
}
static OSStatus Plugin_GeneratePreviewForURL(void* p,
QLPreviewRequestRef preview,
CFURLRef url,
CFStringRef contentTypeUTI,
CFDictionaryRef options)
{
ASSERT(p);
return reinterpret_cast<Plugin*>(p)->GeneratePreviewForURL(preview, url, contentTypeUTI, options);
}
static void Plugin_CancelPreviewGeneration(void* p, QLPreviewRequestRef preview)
{
ASSERT(p);
reinterpret_cast<Plugin*>(p)->CancelPreviewGeneration(preview);
}
// This is the only public entry point of the framework/plugin (the
// "QuickLookGeneratorPluginFactory" name is specified in the
// Info.list file): the factory of objects. Similar than the Win32 COM
// IClassFactory::CreateInstance()
//
// This function is used to create an instance of an object of
// kQLGeneratorTypeID type, which should implement the
// QLGeneratorInterfaceStruct interface.
extern "C" void* QuickLookGeneratorPluginFactory(CFAllocatorRef allocator, CFUUIDRef typeID)
{
if (CFEqual(typeID, kQLGeneratorTypeID)) {
CFUUIDRef uuid = CFUUIDCreateFromString(kCFAllocatorDefault, CFSTR(PLUGIN_ID));
auto plugin = new Plugin(uuid);
CFRelease(uuid);
return plugin;
}
return nullptr; // Unknown typeID
}

View File

@ -1,19 +0,0 @@
// Desktop Integration
// Copyright (c) 2022 Igara Studio S.A.
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef DESKTOP_THUMBNAIL_H_INCLUDED
#define DESKTOP_THUMBNAIL_H_INCLUDED
#include <CoreFoundation/CoreFoundation.h>
#include <CoreGraphics/CoreGraphics.h>
namespace desktop {
CGImageRef get_thumbnail(CFURLRef url, CFDictionaryRef options, CGSize maxSize);
} // namespace desktop
#endif

View File

@ -228,3 +228,69 @@ do
c = app.open(fn)
assert(c.tileManagementPlugin == nil)
end
-- Undo History
function test_undo_history()
local sprite = Sprite(1, 1)
assert(sprite.undoHistory.undoSteps == 0)
assert(sprite.undoHistory.redoSteps == 0)
sprite:resize(10, 10)
assert(sprite.undoHistory.undoSteps == 1)
assert(sprite.undoHistory.redoSteps == 0)
sprite:resize(10, 15)
assert(sprite.undoHistory.undoSteps == 2)
assert(sprite.undoHistory.redoSteps == 0)
sprite:resize(10, 30)
assert(sprite.undoHistory.undoSteps == 3)
assert(sprite.undoHistory.redoSteps == 0)
app.undo()
assert(sprite.undoHistory.undoSteps == 2)
assert(sprite.undoHistory.redoSteps == 1)
app.undo()
assert(sprite.undoHistory.undoSteps == 1)
assert(sprite.undoHistory.redoSteps == 2)
app.redo()
assert(sprite.undoHistory.undoSteps == 2)
assert(sprite.undoHistory.redoSteps == 1)
app.undo()
app.undo()
assert(sprite.undoHistory.undoSteps == 0)
assert(sprite.undoHistory.redoSteps == 3)
sprite:resize(10, 30)
if (app.preferences.undo.allow_nonlinear_history) then
assert(sprite.undoHistory.undoSteps == 4)
assert(sprite.undoHistory.redoSteps == 0)
else
assert(sprite.undoHistory.undoSteps == 1)
assert(sprite.undoHistory.redoSteps == 0)
end
end
do
local prevSetting = app.preferences.undo.allow_nonlinear_history
app.preferences.undo.allow_nonlinear_history = true
test_undo_history()
app.preferences.undo.allow_nonlinear_history = prevSetting
end
do
local prevSetting = app.preferences.undo.allow_nonlinear_history
app.preferences.undo.allow_nonlinear_history = false
test_undo_history()
app.preferences.undo.allow_nonlinear_history = prevSetting
end