Paste images from clipboard added
This commit is contained in:
parent
b6c771c862
commit
875a3760ed
6 changed files with 142 additions and 28 deletions
|
|
@ -17,6 +17,7 @@ namespace emscripten_browser_clipboard {
|
|||
|
||||
/////////////////////////////////// Interface //////////////////////////////////
|
||||
|
||||
using paste_image_handler = void(*)(std::string, void*);
|
||||
using paste_handler = void(*)(std::string&&, void*);
|
||||
using copy_handler = char const*(*)(void*);
|
||||
|
||||
|
|
@ -48,6 +49,42 @@ EM_JS_INLINE(void, paste_async_js, (paste_handler callback, void *callback_data)
|
|||
});
|
||||
});
|
||||
|
||||
EM_JS_INLINE(void, paste_async_image_js, (paste_image_handler callback, void *callback_data), {
|
||||
/// Register the given callback to handle paste events. Callback data pointer is passed through to the callback.
|
||||
/// Paste handler callback signature is:
|
||||
/// void my_handler(std::string const &paste_data, void *callback_data = nullptr);
|
||||
navigator.clipboard.read()
|
||||
.then(clipboardContents => {
|
||||
const imageMimetypesSupported = ["image/png", "image/jpeg", "image/gif", "image/webp", "image/svg+xml"];
|
||||
for (const item of clipboardContents) {
|
||||
for(const mimeType of imageMimetypesSupported) {
|
||||
if(item.types.includes(mimeType)) {
|
||||
item.getType(mimeType).then(blob => {
|
||||
const file_reader = new FileReader();
|
||||
file_reader.onload = (event) => {
|
||||
const uint8Arr = new Uint8Array(event.target.result);
|
||||
const data_ptr = Module["_malloc"](uint8Arr.length);
|
||||
const data_on_heap = new Uint8Array(Module["HEAPU8"].buffer, data_ptr, uint8Arr.length);
|
||||
data_on_heap.set(uint8Arr);
|
||||
Module["ccall"]('emscripten_browser_clipboard_detail_paste_image_return', 'number', ['number', 'number', 'number', 'number'], [data_on_heap.byteOffset, uint8Arr.length, callback, callback_data]);
|
||||
Module["_free"](data_ptr);
|
||||
};
|
||||
file_reader.readAsArrayBuffer(blob);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Failed to get blob from clipboard item: ', err);
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log('Could not find image mimetype in clipboard');
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Failed to read clipboard contents: ', err);
|
||||
});
|
||||
});
|
||||
|
||||
EM_JS_INLINE(void, copy_js, (copy_handler callback, void *callback_data), {
|
||||
/// Register the given callback to handle copy events. Callback data pointer is passed through to the callback.
|
||||
/// Copy handler callback signature is:
|
||||
|
|
@ -76,6 +113,11 @@ inline void paste_async(paste_handler callback, void *callback_data) {
|
|||
detail::paste_async_js(callback, callback_data);
|
||||
}
|
||||
|
||||
inline void paste_async_image(paste_image_handler callback, void *callback_data) {
|
||||
/// C++ wrapper for javascript paste call
|
||||
detail::paste_async_image_js(callback, callback_data);
|
||||
}
|
||||
|
||||
inline void copy(copy_handler callback, void *callback_data) {
|
||||
/// C++ wrapper for javascript copy call
|
||||
detail::copy_js(callback, callback_data);
|
||||
|
|
@ -98,6 +140,12 @@ EMSCRIPTEN_KEEPALIVE inline int emscripten_browser_clipboard_detail_paste_return
|
|||
return 1;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE inline int emscripten_browser_clipboard_detail_paste_image_return(char const *pasteData, int pasteSize, paste_image_handler callback, void *callback_data) {
|
||||
/// Call paste callback - this function is called from javascript when the paste event occurs
|
||||
callback(std::string(pasteData, pasteSize), callback_data);
|
||||
return 1;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE inline char const *emscripten_browser_clipboard_detail_copy_return(copy_handler callback, void *callback_data);
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE inline char const *emscripten_browser_clipboard_detail_copy_return(copy_handler callback, void *callback_data) {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,10 @@
|
|||
|
||||
#include "Tools/EraserTool.hpp"
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <EmscriptenHelpers/emscripten_browser_clipboard.h>
|
||||
#endif
|
||||
|
||||
DrawingProgram::DrawingProgram(World& initWorld):
|
||||
world(initWorld),
|
||||
drawCache(*this),
|
||||
|
|
@ -166,6 +170,30 @@ bool DrawingProgram::selection_action_menu(Vector2f popupPos) {
|
|||
selection.paste_clipboard(popupPos * t.final_gui_scale());
|
||||
shouldClose = true;
|
||||
}
|
||||
if(t.gui.text_button_left_transparent("Paste Image", "Paste Image")) {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
struct PasteData {
|
||||
std::weak_ptr<World> w;
|
||||
Vector2f screenPos;
|
||||
};
|
||||
static PasteData pasteData;
|
||||
pasteData.screenPos = popupPos;
|
||||
pasteData.w = make_weak_ptr(world.main.world);
|
||||
emscripten_browser_clipboard::paste_async_image([](std::string pasteData, void* callbackData){
|
||||
PasteData* p = (PasteData*)callbackData;
|
||||
std::shared_ptr<World> wLock = p->w.lock();
|
||||
if(wLock)
|
||||
wLock->drawProg.add_file_to_canvas_by_data("Image from clipboard", std::string(pasteData), p->screenPos);
|
||||
else
|
||||
Logger::get().log("INFO", "Loading image to canvas that has been destroyed");
|
||||
}, &pasteData);
|
||||
#else
|
||||
std::string imageData = world.main.input.get_clipboard_image_data_SDL();
|
||||
if(!imageData.empty())
|
||||
add_file_to_canvas_by_data("Image from clipboard", imageData, popupPos);
|
||||
#endif
|
||||
shouldClose = true;
|
||||
}
|
||||
if(selection.is_something_selected()) {
|
||||
if(t.gui.text_button_left_transparent("Copy", "Copy")) {
|
||||
selection.selection_to_clipboard();
|
||||
|
|
@ -486,9 +514,32 @@ void DrawingProgram::add_file_to_canvas_by_path(const std::filesystem::path& fil
|
|||
}
|
||||
|
||||
void DrawingProgram::add_file_to_canvas_by_path_execute(const std::filesystem::path& filePath, Vector2f dropPos) {
|
||||
NetworkingObjects::NetObjTemporaryPtr<ResourceData> imageTempPtr = world.rMan.add_resource_file(filePath);
|
||||
if(imageTempPtr) {
|
||||
NetworkingObjects::NetObjID imageID = imageTempPtr.get_net_id();
|
||||
if(layerMan.is_a_layer_being_edited()) {
|
||||
NetworkingObjects::NetObjTemporaryPtr<ResourceData> imageTempPtr = world.rMan.add_resource_file(filePath);
|
||||
if(imageTempPtr) {
|
||||
NetworkingObjects::NetObjID imageID = imageTempPtr.get_net_id();
|
||||
ResourceDisplay* display = world.rMan.get_display_data(imageID);
|
||||
Vector2f imTrueDim = display->get_dimensions();
|
||||
CanvasComponentContainer* newContainer = new CanvasComponentContainer(world.netObjMan, CanvasComponentType::IMAGE);
|
||||
ImageCanvasComponent& img = static_cast<ImageCanvasComponent&>(newContainer->get_comp());
|
||||
newContainer->coords = world.drawData.cam.c;
|
||||
float imWidth = imTrueDim.x() / (imTrueDim.x() + imTrueDim.y());
|
||||
float imHeight = imTrueDim.y() / (imTrueDim.x() + imTrueDim.y());
|
||||
Vector2f imDim = Vector2f{world.main.window.size.x() * imWidth, world.main.window.size.x() * imHeight} * display->get_dimension_scale();
|
||||
img.d.p1 = dropPos - imDim;
|
||||
img.d.p2 = dropPos + imDim;
|
||||
img.d.imageID = imageID;
|
||||
layerMan.add_component_to_layer_being_edited(newContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DrawingProgram::add_file_to_canvas_by_data(const std::string& fileName, std::string_view fileBuffer, Vector2f dropPos) {
|
||||
if(layerMan.is_a_layer_being_edited()) {
|
||||
ResourceData newResource;
|
||||
newResource.data = std::make_shared<std::string>(fileBuffer);
|
||||
newResource.name = fileName;
|
||||
NetworkingObjects::NetObjID imageID = world.rMan.add_resource(newResource).get_net_id();
|
||||
ResourceDisplay* display = world.rMan.get_display_data(imageID);
|
||||
Vector2f imTrueDim = display->get_dimensions();
|
||||
CanvasComponentContainer* newContainer = new CanvasComponentContainer(world.netObjMan, CanvasComponentType::IMAGE);
|
||||
|
|
@ -504,25 +555,6 @@ void DrawingProgram::add_file_to_canvas_by_path_execute(const std::filesystem::p
|
|||
}
|
||||
}
|
||||
|
||||
void DrawingProgram::add_file_to_canvas_by_data(const std::string& fileName, std::string_view fileBuffer, Vector2f dropPos) {
|
||||
ResourceData newResource;
|
||||
newResource.data = std::make_shared<std::string>(fileBuffer);
|
||||
newResource.name = fileName;
|
||||
NetworkingObjects::NetObjID imageID = world.rMan.add_resource(newResource).get_net_id();
|
||||
ResourceDisplay* display = world.rMan.get_display_data(imageID);
|
||||
Vector2f imTrueDim = display->get_dimensions();
|
||||
CanvasComponentContainer* newContainer = new CanvasComponentContainer(world.netObjMan, CanvasComponentType::IMAGE);
|
||||
ImageCanvasComponent& img = static_cast<ImageCanvasComponent&>(newContainer->get_comp());
|
||||
newContainer->coords = world.drawData.cam.c;
|
||||
float imWidth = imTrueDim.x() / (imTrueDim.x() + imTrueDim.y());
|
||||
float imHeight = imTrueDim.y() / (imTrueDim.x() + imTrueDim.y());
|
||||
Vector2f imDim = Vector2f{world.main.window.size.x() * imWidth, world.main.window.size.x() * imHeight} * display->get_dimension_scale();
|
||||
img.d.p1 = dropPos - imDim;
|
||||
img.d.p2 = dropPos + imDim;
|
||||
img.d.imageID = imageID;
|
||||
layerMan.add_component_to_layer_being_edited(newContainer);
|
||||
}
|
||||
|
||||
float DrawingProgram::drag_point_radius() {
|
||||
return 8.0f * world.main.toolbar.final_gui_scale();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ std::vector<CanvasComponentContainer::ObjInfoIterator> DrawingProgramLayerManage
|
|||
if(!newObjs.empty()) {
|
||||
auto editLayerPtr = editingLayer.lock();
|
||||
if(!editLayerPtr)
|
||||
return {};
|
||||
throw std::runtime_error("[DrawingProgramLayerManager::add_many_components_to_layer_being_edited] No layer selected!");
|
||||
auto toRet = editLayerPtr->get_layer().components->insert_ordered_list_and_send_create(editLayerPtr->get_layer().components, newObjs);
|
||||
add_undo_place_components(editingLayer.lock().get(), toRet);
|
||||
}
|
||||
|
|
@ -109,11 +109,10 @@ std::vector<CanvasComponentContainer::ObjInfoIterator> DrawingProgramLayerManage
|
|||
CanvasComponentContainer::ObjInfo* DrawingProgramLayerManager::add_component_to_layer_being_edited(CanvasComponentContainer* newObj) {
|
||||
auto editLayerPtr = editingLayer.lock();
|
||||
if(!editLayerPtr)
|
||||
return nullptr;
|
||||
throw std::runtime_error("[DrawingProgramLayerManager::add_component_to_layer_being_edited] No layer selected!");
|
||||
return &(*editLayerPtr->get_layer().components->push_back_and_send_create(editLayerPtr->get_layer().components, newObj));
|
||||
}
|
||||
|
||||
|
||||
void DrawingProgramLayerManager::load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version) {
|
||||
if(version >= VersionNumber(0, 4, 0)) {
|
||||
layerTreeRoot = drawP.world.netObjMan.make_obj_from_ptr<DrawingProgramLayerListItem>(new DrawingProgramLayerListItem());
|
||||
|
|
|
|||
|
|
@ -136,6 +136,33 @@ std::string InputManager::get_clipboard_str_SDL() {
|
|||
return toRet;
|
||||
}
|
||||
|
||||
std::string InputManager::get_clipboard_image_data_SDL() {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
static std::unordered_set<std::string> validMimetypes;
|
||||
if(validMimetypes.empty()) {
|
||||
validMimetypes.emplace("image/png");
|
||||
validMimetypes.emplace("image/gif");
|
||||
validMimetypes.emplace("image/jpeg");
|
||||
validMimetypes.emplace("image/svg+xml");
|
||||
validMimetypes.emplace("image/webp");
|
||||
}
|
||||
size_t mimeTypeSize;
|
||||
char** mimeTypes = SDL_GetClipboardMimeTypes(&mimeTypeSize);
|
||||
if(mimeTypes) {
|
||||
for(size_t i = 0; i < mimeTypeSize; i++) {
|
||||
if(validMimetypes.contains(mimeTypes[i])) {
|
||||
size_t clipboardDataSize;
|
||||
void* clipboardData = SDL_GetClipboardData(mimeTypes[i], &clipboardDataSize);
|
||||
std::string toRet(static_cast<char*>(clipboardData), clipboardDataSize);
|
||||
SDL_free(clipboardData);
|
||||
return toRet;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return std::string();
|
||||
}
|
||||
|
||||
void InputManager::set_clipboard_str(std::string_view s) {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
emscripten_browser_clipboard::copy(std::string(s));
|
||||
|
|
|
|||
|
|
@ -211,6 +211,7 @@ struct InputManager {
|
|||
void set_clipboard_str(std::string_view s);
|
||||
void set_clipboard_plain_and_richtext_pair(const std::pair<std::string, RichText::TextData>& plainAndRichtextPair);
|
||||
std::string get_clipboard_str_SDL();
|
||||
std::string get_clipboard_image_data_SDL();
|
||||
void call_text_paste(bool isRichTextPaste);
|
||||
void process_text_paste(const std::string& plainClipboardStr);
|
||||
|
||||
|
|
|
|||
|
|
@ -541,17 +541,24 @@ void Toolbar::top_toolbar() {
|
|||
main.world->drawProg.switch_to_tool(DrawingProgramToolType::SCREENSHOT);
|
||||
if(gui.text_button_left_transparent("add image or file to canvas", "Add Image/File to Canvas")) {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
static std::weak_ptr<World> worldWeakPtr;
|
||||
worldWeakPtr = make_weak_ptr(main.world);
|
||||
emscripten_browser_file::upload("*", [](std::string const& fileName, std::string const& mimeType, std::string_view buffer, void* callbackData) {
|
||||
if(!buffer.empty()) {
|
||||
World* world = (World*)callbackData;
|
||||
world->drawProg.add_file_to_canvas_by_data(fileName, buffer, world->main.window.size.cast<float>() / 2.0f);
|
||||
auto world = ((std::weak_ptr<World>*)callbackData)->lock();
|
||||
if(world)
|
||||
world->drawProg.add_file_to_canvas_by_data(fileName, buffer, world->main.window.size.cast<float>() / 2.0f);
|
||||
else
|
||||
Logger::get().log("INFO", "Loading image to canvas that has been destroyed");
|
||||
}
|
||||
}, main.world.get());
|
||||
}, &worldWeakPtr);
|
||||
#else
|
||||
open_file_selector("Open File", {{"Any File", "*"}}, [w = make_weak_ptr(main.world)](const std::filesystem::path& p, const auto& e) {
|
||||
auto wLock = w.lock();
|
||||
if(wLock)
|
||||
wLock->drawProg.add_file_to_canvas_by_path(p.string(), wLock->main.window.size.cast<float>() / 2.0f, false);
|
||||
else
|
||||
Logger::get().log("INFO", "Loading image to canvas that has been destroyed");
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue