Clipboard handling

This commit is contained in:
Ilia Demianenko 2023-09-11 01:59:23 -07:00 committed by Martin Fouilleul
parent 7026919b38
commit a5567da82c
16 changed files with 273 additions and 28 deletions

View File

@ -333,7 +333,10 @@ ORCA_EXPORT void oc_on_frame_refresh(void)
OC_UI_STYLE_LAYOUT_SPACING);
oc_ui_container("buttons", 0)
{
oc_ui_button("Button A");
if(oc_ui_button("Break the law").clicked)
{
oc_clipboard_set_string((oc_str8)OC_STR8_LIT("Check out my cool website"));
}
oc_ui_button("Button B");
oc_ui_button("Button C");
oc_ui_button("Button D");

View File

@ -133,7 +133,6 @@ def bindgen(apiName, spec, **kwargs):
s += '\t\tOC_ASSERT((char*)__retPtr + sizeof(' + retTypeCName + ') <= ((char*)_mem + m3_GetMemorySize(runtime)), "return pointer is out of bounds");\n'
s += '\t}\n'
for argIndex, arg in enumerate(decl['args']):
argName = arg['name']

View File

@ -58,6 +58,7 @@ typedef enum
OC_EVENT_MOUSE_WHEEL,
OC_EVENT_MOUSE_ENTER,
OC_EVENT_MOUSE_LEAVE,
OC_EVENT_CLIPBOARD_PASTE,
OC_EVENT_WINDOW_RESIZE,
OC_EVENT_WINDOW_MOVE,
OC_EVENT_WINDOW_FOCUS,
@ -479,6 +480,8 @@ void oc_window_set_size(oc_vec2 size);
void ORCA_IMPORT(oc_request_quit)(void);
oc_key_code ORCA_IMPORT(oc_scancode_to_keycode)(oc_scan_code scanCode);
void oc_clipboard_set_string(oc_str8 string);
#endif // !defined(OC_PLATFORM_ORCA) || !(OC_PLATFORM_ORCA)
#ifdef __cplusplus

View File

@ -1243,6 +1243,7 @@ void oc_clipboard_set_string(oc_str8 string)
NSString* nsString = [[NSString alloc] initWithBytes:string.ptr length:string.len encoding:NSUTF8StringEncoding];
NSPasteboard* pb = [NSPasteboard generalPasteboard];
[pb clearContents];
[pb writeObjects:[[NSArray alloc] initWithObjects:nsString, nil]];
}
}

View File

@ -14,6 +14,7 @@
#include "orca.h"
#include "runtime.h"
#include "runtime_clipboard.c"
#include "runtime_io.c"
#include "runtime_memory.c"
@ -117,6 +118,16 @@ void oc_bridge_window_set_size(oc_vec2 size)
oc_window_set_content_size(__orcaApp.window, size);
}
oc_wasm_str8 oc_bridge_clipboard_get_string(oc_wasm_addr wasmArena)
{
return oc_runtime_clipboard_get_string(&__orcaApp.clipboard, wasmArena);
}
void oc_bridge_clipboard_set_string(oc_wasm_str8 value)
{
oc_runtime_clipboard_set_string(&__orcaApp.clipboard, value);
}
void oc_bridge_log(oc_log_level level,
int fileLen,
char* file,
@ -553,7 +564,6 @@ i32 orca_runloop(void* user)
oc_event* event = 0;
while((event = oc_next_event(oc_scratch())) != 0)
{
if(app->debugOverlay.show)
{
oc_ui_process_event(event);
@ -561,19 +571,41 @@ i32 orca_runloop(void* user)
if(exports[OC_EXPORT_RAW_EVENT])
{
#ifndef M3_BIG_ENDIAN
oc_event* eventPtr = (oc_event*)oc_wasm_address_to_ptr(app->env.rawEventOffset, sizeof(oc_event));
memcpy(eventPtr, event, sizeof(*event));
const void* args[1] = { &app->env.rawEventOffset };
M3Result res = m3_Call(exports[OC_EXPORT_RAW_EVENT], 1, args);
if(res)
oc_arena_scope scratch = oc_scratch_begin();
oc_event* clipboardEvent = oc_runtime_clipboard_process_event_begin(&__orcaApp.clipboard, scratch.arena, event);
oc_event* events[2];
u64 eventsCount;
if(clipboardEvent != 0)
{
ORCA_WASM3_ABORT(app->env.m3Runtime, res, "Runtime error");
events[0] = clipboardEvent;
events[1] = event;
eventsCount = 2;
}
else
{
events[0] = event;
eventsCount = 1;
}
for(int i = 0; i < eventsCount; i++)
{
#ifndef M3_BIG_ENDIAN
oc_event* eventPtr = (oc_event*)oc_wasm_address_to_ptr(app->env.rawEventOffset, sizeof(oc_event));
memcpy(eventPtr, events[i], sizeof(*events[i]));
const void* args[1] = { &app->env.rawEventOffset };
M3Result res = m3_Call(exports[OC_EXPORT_RAW_EVENT], 1, args);
if(res)
{
ORCA_WASM3_ABORT(app->env.m3Runtime, res, "Runtime error");
}
#else
oc_log_error("oc_on_raw_event() is not supported on big endian platforms");
oc_log_error("oc_on_raw_event() is not supported on big endian platforms");
#endif
}
oc_runtime_clipboard_process_event_end(&__orcaApp.clipboard);
oc_scratch_end(scratch);
}
switch(event->type)

View File

@ -9,6 +9,8 @@
#define __RUNTIME_H_
#include "platform/platform_io_internal.h"
#include "runtime_memory.h"
#include "runtime_clipboard.h"
#include "m3_compile.h"
#include "m3_env.h"
@ -121,6 +123,7 @@ typedef struct oc_runtime
oc_wasm_env env;
oc_runtime_clipboard clipboard;
} oc_runtime;
oc_runtime* oc_runtime_get(void);

96
src/runtime_clipboard.c Normal file
View File

@ -0,0 +1,96 @@
/*************************************************************************
*
* Orca
* Copyright 2023 Martin Fouilleul and the Orca project contributors
* See LICENSE.txt for licensing information
*
**************************************************************************/
#include "runtime_clipboard.h"
#if OC_PLATFORM_WINDOWS || OC_PLATFORM_MACOS
oc_wasm_str8 oc_runtime_clipboard_get_string(oc_runtime_clipboard* clipboard, oc_wasm_addr wasmArena)
{
oc_wasm_str8 result = { 0 };
if(clipboard->isGetAllowed)
{
oc_arena_scope scratch = oc_scratch_begin();
oc_str8 value = oc_clipboard_get_string(scratch.arena);
oc_wasm_addr valueAddr = oc_wasm_arena_push(wasmArena, value.len);
char* valuePtr = (char*)oc_wasm_address_to_ptr(valueAddr, value.len);
memcpy(valuePtr, value.ptr, value.len);
oc_scratch_end(scratch);
result = (oc_wasm_str8){ .ptr = valueAddr,
.len = value.len };
}
else
{
oc_log_warning("Clipboard contents can only be get from within the paste event handler\n");
}
return (result);
}
static f64 OC_CLIPBOARD_SET_TIMEOUT = 1;
void oc_runtime_clipboard_set_string(oc_runtime_clipboard* clipboard, oc_wasm_str8 value)
{
f64 time = oc_clock_time(OC_CLOCK_MONOTONIC);
if(time < clipboard->setAllowedUntil)
{
oc_str8 nativeValue = oc_wasm_str8_to_native(value);
oc_clipboard_set_string(nativeValue);
}
else
{
oc_log_warning("Clipboard contents can only be set within %f second(s) of a paste shortcut press\n", OC_CLIPBOARD_SET_TIMEOUT);
}
}
oc_event* oc_runtime_clipboard_process_event_begin(oc_runtime_clipboard* clipboard, oc_arena* arena, oc_event* origEvent)
{
oc_event* resultEvent = 0;
if(origEvent->type == OC_EVENT_KEYBOARD_KEY)
{
bool isPressedOrRepeated = origEvent->key.action == OC_KEY_PRESS || origEvent->key.action == OC_KEY_REPEAT;
oc_keymod_flags rawMods = origEvent->key.mods & ~OC_KEYMOD_MAIN_MODIFIER;
#if OC_PLATFORM_WINDOWS
bool cutOrCopied = isPressedOrRepeated
&& ((origEvent->key.keyCode == OC_KEY_X && rawMods == OC_KEYMOD_CTRL)
|| (origEvent->key.keyCode == OC_KEY_DELETE && rawMods == OC_KEYMOD_SHIFT)
|| (origEvent->key.keyCode == OC_KEY_C && rawMods == OC_KEYMOD_CTRL)
|| (origEvent->key.keyCode == OC_KEY_INSERT && rawMods == OC_KEYMOD_CTRL));
bool pasted = isPressedOrRepeated
&& ((origEvent->key.keyCode == OC_KEY_V && rawMods == OC_KEYMOD_CTRL)
|| (origEvent->key.keyCode == OC_KEY_INSERT && rawMods == OC_KEYMOD_SHIFT));
#elif OC_PLATFORM_MACOS
bool cutOrCopied = isPressedOrRepeated
&& ((origEvent->key.keyCode == OC_KEY_X && rawMods == OC_KEYMOD_CMD)
|| (origEvent->key.keyCode == OC_KEY_C && rawMods == OC_KEYMOD_CMD));
bool pasted = isPressedOrRepeated
&& origEvent->key.keyCode == OC_KEY_V && rawMods == OC_KEYMOD_CMD;
#endif
if(cutOrCopied)
{
clipboard->setAllowedUntil = oc_clock_time(OC_CLOCK_MONOTONIC) + OC_CLIPBOARD_SET_TIMEOUT;
}
if(pasted)
{
clipboard->isGetAllowed = true;
resultEvent = oc_arena_push_type(arena, oc_event);
*resultEvent = (oc_event){ .window = origEvent->window,
.type = OC_EVENT_CLIPBOARD_PASTE };
}
}
return (resultEvent);
}
void oc_runtime_clipboard_process_event_end(oc_runtime_clipboard* clipboard)
{
clipboard->isGetAllowed = false;
}
#else
#error Default clipboard handling is not supported on this platform"
#endif

31
src/runtime_clipboard.h Normal file
View File

@ -0,0 +1,31 @@
/*************************************************************************
*
* Orca
* Copyright 2023 Martin Fouilleul and the Orca project contributors
* See LICENSE.txt for licensing information
*
**************************************************************************/
#ifndef __RUNTIME_CLIPBOARD_H_
#define __RUNTIME_CLIPBOARD_H_
#include "runtime_memory.h"
typedef struct oc_runtime_clipboard
{
bool isGetAllowed;
f64 setAllowedUntil;
} oc_runtime_clipboard;
#if OC_PLATFORM_WINDOWS || OC_PLATFORM_MACOS
oc_wasm_str8 oc_runtime_clipboard_get_string(oc_runtime_clipboard* clipboard, oc_wasm_addr wasmArena);
void oc_runtime_clipboard_set_string(oc_runtime_clipboard* clipboard, oc_wasm_str8 value);
oc_event* oc_runtime_clipboard_process_event_begin(oc_runtime_clipboard* clipboard, oc_arena* arena, oc_event* origEvent);
void oc_runtime_clipboard_process_event_end(oc_runtime_clipboard* clipboard);
#else
#error Default clipboard handling is not supported on this platform"
#endif
#endif //__RUNTIME_CLIPBOARD_H_

View File

@ -134,7 +134,8 @@ oc_wasm_file_open_with_dialog_result oc_file_open_with_dialog_bridge(oc_wasm_add
oc_list_for(&nativeResult.selection, elt, oc_file_open_with_dialog_elt, listElt)
{
oc_wasm_file_open_with_dialog_elt* wasmElt = oc_wasm_arena_push(wasmArena, sizeof(oc_wasm_file_open_with_dialog_elt));
oc_wasm_addr wasmEltAddr = oc_wasm_arena_push(wasmArena, sizeof(oc_wasm_file_open_with_dialog_elt));
oc_wasm_file_open_with_dialog_elt* wasmElt = oc_wasm_address_to_ptr(wasmEltAddr, sizeof(oc_wasm_file_open_with_dialog_elt));
wasmElt->file = elt->file;
oc_wasm_list_push_back(&result.selection, &wasmElt->listElt);

View File

@ -127,7 +127,7 @@ void oc_wasm_list_push_back(oc_wasm_list* list, oc_wasm_list_elt* elt)
// Wasm arenas helpers
//------------------------------------------------------------------------------------
void* oc_wasm_arena_push(oc_wasm_addr arena, u64 size)
oc_wasm_addr oc_wasm_arena_push(oc_wasm_addr arena, u64 size)
{
oc_wasm_env* env = oc_runtime_get_env();
@ -146,6 +146,6 @@ void* oc_wasm_arena_push(oc_wasm_addr arena, u64 size)
{
ORCA_WASM3_ABORT(env->m3Runtime, res, "Runtime error");
}
void* ptr = oc_wasm_address_to_ptr(retValues[0], size);
return (ptr);
return (retValues[0]);
}

View File

@ -8,8 +8,6 @@
#ifndef __RUNTIME_MEMORY_H_
#define __RUNTIME_MEMORY_H_
#include "runtime.h"
typedef u32 oc_wasm_addr;
typedef u32 oc_wasm_size;
@ -96,6 +94,6 @@ typedef struct oc_wasm_str8_list
// Wasm arenas helpers
//------------------------------------------------------------------------------------
void* oc_wasm_arena_push(oc_wasm_addr arena, u64 size);
oc_wasm_addr oc_wasm_arena_push(oc_wasm_addr arena, u64 size);
#endif //__RUNTIME_MEMORY_H_

View File

@ -63,6 +63,12 @@ static void oc_update_key_mods(oc_input_state* state, oc_keymod_flags mods)
state->keyboard.mods = mods;
}
static void oc_update_clipboard_paste(oc_input_state* state, oc_arena* arena)
{
state->clipboard.lastUpdate = state->frameCounter;
state->clipboard.pastedText = oc_clipboard_get_string(arena);
}
static void oc_update_mouse_move(oc_input_state* state, f32 x, f32 y, f32 deltaX, f32 deltaY)
{
u64 frameCounter = state->frameCounter;
@ -134,7 +140,7 @@ void oc_input_next_frame(oc_input_state* state)
state->frameCounter++;
}
void oc_input_process_event(oc_input_state* state, oc_event* event)
void oc_input_process_event(oc_input_state* state, oc_arena* arena, oc_event* event)
{
switch(event->type)
{
@ -154,6 +160,10 @@ void oc_input_process_event(oc_input_state* state, oc_event* event)
oc_update_key_mods(state, event->key.mods);
break;
case OC_EVENT_CLIPBOARD_PASTE:
oc_update_clipboard_paste(state, arena);
break;
case OC_EVENT_MOUSE_MOVE:
oc_update_mouse_move(state, event->mouse.x, event->mouse.y, event->mouse.deltaX, event->mouse.deltaY);
break;
@ -413,3 +423,18 @@ oc_str8 oc_input_text_utf8(oc_input_state* input, oc_arena* arena)
}
return (res);
}
bool oc_clipboard_pasted(oc_input_state* input)
{
return input->clipboard.lastUpdate == input->frameCounter;
}
oc_str8 oc_clipboard_pasted_text(oc_input_state* input)
{
oc_str8 res = { 0 };
if(input->clipboard.lastUpdate == input->frameCounter)
{
res = input->clipboard.pastedText;
}
return (res);
}

View File

@ -67,15 +67,22 @@ typedef struct oc_text_state
oc_str32 codePoints;
} oc_text_state;
typedef struct oc_clipboard_state
{
u64 lastUpdate;
oc_str8 pastedText;
} oc_clipboard_state;
typedef struct oc_input_state
{
u64 frameCounter;
oc_keyboard_state keyboard;
oc_mouse_state mouse;
oc_text_state text;
oc_clipboard_state clipboard;
} oc_input_state;
ORCA_API void oc_input_process_event(oc_input_state* state, oc_event* event);
ORCA_API void oc_input_process_event(oc_input_state* state, oc_arena* arena, oc_event* event);
ORCA_API void oc_input_next_frame(oc_input_state* state);
ORCA_API bool oc_key_down(oc_input_state* state, oc_key_code key);
@ -101,6 +108,9 @@ ORCA_API oc_vec2 oc_mouse_wheel(oc_input_state* state);
ORCA_API oc_str32 oc_input_text_utf32(oc_input_state* state, oc_arena* arena);
ORCA_API oc_str8 oc_input_text_utf8(oc_input_state* state, oc_arena* arena);
ORCA_API bool oc_clipboard_pasted(oc_input_state* state);
ORCA_API oc_str8 oc_clipboard_pasted_text(oc_input_state* state);
ORCA_API oc_keymod_flags oc_key_mods(oc_input_state* state);
#endif //__INPUT_STATE_H_

View File

@ -7,6 +7,7 @@
**************************************************************************/
#include "ui.h"
#include "math.h"
#include "app/app.h"
#include "platform/platform.h"
#include "platform/platform_clock.h"
#include "platform/platform_debug.h"
@ -329,7 +330,7 @@ void oc_ui_style_box_after(oc_ui_box* box, oc_ui_pattern pattern, oc_ui_style* s
void oc_ui_process_event(oc_event* event)
{
oc_ui_context* ui = oc_ui_get_context();
oc_input_process_event(&ui->input, event);
oc_input_process_event(&ui->input, &ui->frameArena, event);
}
oc_vec2 oc_ui_mouse_position(void)
@ -590,6 +591,8 @@ oc_ui_sig oc_ui_box_sig(oc_ui_box* box)
sig.dragging = box->dragging;
}
sig.pasted = oc_clipboard_pasted(input);
}
return (sig);
}
@ -1438,8 +1441,6 @@ void oc_ui_begin_frame(oc_vec2 size, oc_ui_style* defaultStyle, oc_ui_style_mask
{
oc_ui_context* ui = oc_ui_get_context();
oc_arena_clear(&ui->frameArena);
ui->frameCounter++;
f64 time = oc_clock_time(OC_CLOCK_MONOTONIC);
ui->lastFrameDuration = time - ui->frameTime;
@ -1515,6 +1516,7 @@ void oc_ui_end_frame(void)
}
}
oc_arena_clear(&ui->frameArena);
oc_input_next_frame(&ui->input);
}
@ -2699,7 +2701,6 @@ oc_str32 oc_ui_edit_delete_selection(oc_ui_context* ui, oc_str32 codepoints)
void oc_ui_edit_copy_selection_to_clipboard(oc_ui_context* ui, oc_str32 codepoints)
{
#if !OC_PLATFORM_ORCA
if(ui->editCursor == ui->editMark)
{
return;
@ -2709,15 +2710,13 @@ void oc_ui_edit_copy_selection_to_clipboard(oc_ui_context* ui, oc_str32 codepoin
oc_str32 selection = oc_str32_slice(codepoints, start, end);
oc_str8 string = oc_utf8_push_from_codepoints(&ui->frameArena, selection);
oc_clipboard_clear();
oc_clipboard_set_string(string);
#endif
}
oc_str32 oc_ui_edit_replace_selection_with_clipboard(oc_ui_context* ui, oc_str32 codepoints)
{
#if OC_PLATFORM_ORCA
oc_str32 result = { 0 };
oc_str32 result = codepoints;
#else
oc_str8 string = oc_clipboard_get_string(&ui->frameArena);
oc_str32 input = oc_utf8_push_to_codepoints(&ui->frameArena, string);
@ -3095,17 +3094,32 @@ const oc_ui_edit_command OC_UI_EDIT_COMMANDS_WINDOWS[] = {
.mods = OC_KEYMOD_CTRL,
.operation = OC_UI_EDIT_CUT,
.move = OC_UI_EDIT_MOVE_NONE },
{
.key = OC_KEY_DELETE,
.mods = OC_KEYMOD_SHIFT,
.operation = OC_UI_EDIT_CUT,
.move = OC_UI_EDIT_MOVE_NONE },
//NOTE(martin): copy
{
.key = OC_KEY_C,
.mods = OC_KEYMOD_CTRL,
.operation = OC_UI_EDIT_COPY,
.move = OC_UI_EDIT_MOVE_NONE },
{
.key = OC_KEY_INSERT,
.mods = OC_KEYMOD_CTRL,
.operation = OC_UI_EDIT_COPY,
.move = OC_UI_EDIT_MOVE_NONE },
//NOTE(martin): paste
{
.key = OC_KEY_V,
.mods = OC_KEYMOD_CTRL,
.operation = OC_UI_EDIT_PASTE,
.move = OC_UI_EDIT_MOVE_NONE },
{
.key = OC_KEY_INSERT,
.mods = OC_KEYMOD_SHIFT,
.operation = OC_UI_EDIT_PASTE,
.move = OC_UI_EDIT_MOVE_NONE }
};
@ -3696,6 +3710,13 @@ oc_ui_text_box_result oc_ui_text_box(const char* name, oc_arena* arena, oc_str8
}
}
if(sig.pasted)
{
oc_str8 pastedText = oc_clipboard_pasted_text(&ui->input);
oc_str32 input = oc_utf8_push_to_codepoints(&ui->frameArena, pastedText);
codepoints = oc_ui_edit_replace_selection_with_codepoints(ui, codepoints, input);
}
//NOTE(martin): check changed/accepted
if(oldCodepoints.ptr != codepoints.ptr)
{

View File

@ -456,6 +456,8 @@ typedef struct oc_ui_sig
bool dragging;
bool hovering;
bool pasted;
} oc_ui_sig;
typedef void (*oc_ui_box_draw_proc)(oc_ui_box* box, void* data);
@ -620,6 +622,8 @@ typedef struct oc_ui_context
oc_ui_edit_move editSelectionMode;
i32 editWordSelectionInitialCursor;
i32 editWordSelectionInitialMark;
bool clipboardRegistered;
oc_ui_theme* theme;
} oc_ui_context;

View File

@ -106,5 +106,23 @@
{ "name": "scanCode",
"type": {"name": "oc_scan_code", "tag": "i"}}
]
},
{
"name": "oc_clipboard_get_string",
"cname": "oc_bridge_clipboard_get_string",
"ret": { "name": "oc_str8", "cname": "oc_wasm_str8", "tag": "S"},
"args": [
{"name": "arena",
"type": {"name": "oc_arena*", "cname": "i32", "tag": "i"}}
]
},
{
"name": "oc_clipboard_set_string",
"cname": "oc_bridge_clipboard_set_string",
"ret": {"name": "void", "tag": "v"},
"args": [
{ "name": "value",
"type": {"name": "oc_str8", "cname": "oc_wasm_str8", "tag": "S"}}
]
}
]