Skip to content

Commit 4b92e80

Browse files
committed
Copy and paste working!
1 parent e026056 commit 4b92e80

6 files changed

Lines changed: 141 additions & 49 deletions

File tree

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
#include <iostream>
21
#ifdef __EMSCRIPTEN__
3-
4-
#ifndef EMSCRIPTEN_BROWSER_CLIPBOARD_H_INCLUDED
5-
#define EMSCRIPTEN_BROWSER_CLIPBOARD_H_INCLUDED
2+
#pragma once
63

74
#include <string>
85
#include <emscripten.h>
@@ -21,7 +18,7 @@ namespace emscripten_browser_clipboard {
2118

2219
/////////////////////////////////// Interface //////////////////////////////////
2320

24-
using paste_handler = void(*)(std::string const&, void*);
21+
using paste_handler = void(*)(std::string&&, void*);
2522
using copy_handler = char const* (*)(void*);
2623

2724
inline void paste(paste_handler callback, void* callback_data = nullptr);
@@ -30,23 +27,23 @@ namespace emscripten_browser_clipboard {
3027

3128
///////////////////////////////// Implementation ///////////////////////////////
3229

33-
namespace {
30+
namespace detail {
3431

3532
EM_JS_INLINE(void, paste_js, (paste_handler callback, void* callback_data), {
3633
/// Register the given callback to handle paste events. Callback data pointer is passed through to the callback.
3734
/// Paste handler callback signature is:
38-
/// void my_handler(std::string const &paste_data, void *callback_data = nullptr);
39-
document.addEventListener('paste', function (event) {
40-
Module["ccall"]('paste_return', 'number',['string', 'number', 'number'],[event.clipboardData.getData('text/plain'), callback, callback_data]);
35+
/// void my_handler(std::string &&paste_data, void *callback_data = nullptr);
36+
document.addEventListener('paste', (event) => {
37+
Module["ccall"]('emscripten_browser_clipboard_detail_paste_return', 'number',['string', 'number', 'number'],[event.clipboardData.getData('text/plain'), callback, callback_data]);
4138
});
4239
});
4340

4441
EM_JS_INLINE(void, copy_js, (copy_handler callback, void* callback_data), {
4542
/// Register the given callback to handle copy events. Callback data pointer is passed through to the callback.
4643
/// Copy handler callback signature is:
4744
/// char const *my_handler(void *callback_data = nullptr);
48-
document.addEventListener('copy', function (event){
49-
const content_ptr = Module["ccall"]('copy_return', 'number',['number', 'number'],[callback, callback_data]);
45+
document.addEventListener('copy', (event) => {
46+
const content_ptr = Module["ccall"]('emscripten_browser_clipboard_detail_copy_return', 'number',['number', 'number'],[callback, callback_data]);
5047
event.clipboardData.setData('text/plain', UTF8ToString(content_ptr));
5148
event.preventDefault();
5249
});
@@ -57,48 +54,46 @@ namespace emscripten_browser_clipboard {
5754
navigator.clipboard.writeText(UTF8ToString(content_ptr));
5855
});
5956

60-
}
57+
} // namespace detail
6158

6259
inline void paste(paste_handler callback, void* callback_data) {
6360
/// C++ wrapper for javascript paste call
64-
paste_js(callback, callback_data);
61+
detail::paste_js(callback, callback_data);
6562
}
6663

6764
inline void copy(copy_handler callback, void* callback_data) {
6865
/// C++ wrapper for javascript copy call
69-
copy_js(callback, callback_data);
66+
detail::copy_js(callback, callback_data);
7067
}
7168

7269
inline void copy(std::string const& content) {
7370
/// C++ wrapper for javascript copy call
74-
copy_async_js(content.c_str());
71+
detail::copy_async_js(content.c_str());
7572
}
7673

77-
namespace {
74+
namespace detail {
7875

7976
extern "C" {
8077

81-
EMSCRIPTEN_KEEPALIVE inline int paste_return(char const* paste_data, paste_handler callback, void* callback_data);
78+
EMSCRIPTEN_KEEPALIVE inline int emscripten_browser_clipboard_detail_paste_return(char const* paste_data, paste_handler callback, void* callback_data);
8279

83-
EMSCRIPTEN_KEEPALIVE inline int paste_return(char const* paste_data, paste_handler callback, void* callback_data) {
80+
EMSCRIPTEN_KEEPALIVE inline int emscripten_browser_clipboard_detail_paste_return(char const* paste_data, paste_handler callback, void* callback_data) {
8481
/// Call paste callback - this function is called from javascript when the paste event occurs
8582
callback(paste_data, callback_data);
8683
return 1;
8784
}
8885

89-
EMSCRIPTEN_KEEPALIVE inline char const* copy_return(copy_handler callback, void* callback_data);
86+
EMSCRIPTEN_KEEPALIVE inline char const* emscripten_browser_clipboard_detail_copy_return(copy_handler callback, void* callback_data);
9087

91-
EMSCRIPTEN_KEEPALIVE inline char const* copy_return(copy_handler callback, void* callback_data) {
92-
/// Call paste callback - this function is called from javascript when the paste event occurs
88+
EMSCRIPTEN_KEEPALIVE inline char const* emscripten_browser_clipboard_detail_copy_return(copy_handler callback, void* callback_data) {
89+
/// Call copy callback - this function is called from javascript when the copy event occurs
9390
return callback(callback_data);
9491
}
9592

9693
}
9794

98-
}
99-
100-
}
95+
} // namespace detail
10196

102-
#endif // EMSCRIPTEN_BROWSER_CLIPBOARD_H_INCLUDED
97+
} // namespace emscripten_browser_clipboard
10398

10499
#endif

devlog.md

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
1-
A month later, we're in the world wide web!
1+
These seven hours of devlogging time are mostly due to three things: partial derivatives, non-elementary functions and getting this project to work on the web. As you'll see, we got two out of three on this one!
22

3-
From the beginning of the project, I kept telling myself that I would, eventually, port it to Web Assembly. Many architectural choices were made with this in mind (not using compute shaders, sticking to version 330 features, using ImGUI for the frontend, not using a file system, etc).
4-
I just knew that this would be sort of a headache, and kept delaying it, but it comes a time at our lives where we have to accept that not everyone wants to download a sketchy binary from github. So we had to port it to web assembly!
3+
First, our tool supports derivatives relative to z (denoted as `d/dz`). The rules for it are simple: anything that _isn't_ dependant on `z` is 0, and for things entirely dependant on it, we apply some rules according to a table.
4+
The problem is that `z = x + iy`, and each variable `x,y` is partially dependant on `z`. They can't be equal to 0, since `d/dz(x + iy) = d/dz(z) != d/dz(0)`. To fix this, we set some "sanity" rules, and set `d/dz(x) = 0.5` and `d/dz(y) = -0.5i`. This fixes our problem!
55

6-
For those not familiar with the tech, WebAssembly (wasm, for short), is essentially the low-level language of the web. The advantage is that it's faster than javascript, and many languages can compile down to it - and that includes C++!
7-
Our graphics core is entirely made in OpenGL, but thankfully, a tool named Emscripten also handles this conversion by turning OpenGL into WebGL (the web equivalent). So, we just had to rewrite our code to handle things specific to the web.
6+
Second, some users requested non-elementary functions (that is, functions we can only ever approximate, since their definitions are infinite). A good time was spent finding good, fast approximations, and most importantly fixing their 'explosions' to infinite, which at times resulted in their values becoming NaN. This is partially solved by first evaluating the function's logarithm (which grows far, faar slower), and exponentiating it back for its regular value.
87

9-
Our main change was the textures that we were using to send data to our shaders. Originally, this was done through `samplerBuffer`s, which don't exist in the version OpenGL runs. This made us have to change our data to 2D textures, `sampler2D`. This requires a little more math to get data from the texture, but it's small enough to be a change applied both to the desktop and web version (to avoid refactoring headache).
8+
Third, the web thing. I wanted to get two things working:
9+
1. Exporting plots as images. This worked _graciously_ well. It took me less than half an hour to get it working.
10+
2. Copy and pasting in the browser. I have wasted at least five hours of my life trying to get this to work. I've implemented every solution I've found in the internet to solve it. I reverse engineered an online library to see how it works and put it in my project. I can't get it to work. This is heavily due to the 'security' layer in web assembly that makes it very, very difficult to get text from the OS into the browser. After many hours, I officially gave up.
1011

11-
The rest is changing the main loop logic (to be step-based), the event callbacks, and creeating a basic UI. Attached, our code running in the website!
12-
13-
Take a look for yourself in: https://sekqies.github.io/complex-plotter/
12+
Attached, some plots of our new functions!
1413

1514
**Commits**
16-
[04e5437](https://url.jam06452.uk/1w417ue): Working web assembly build - ImGUI not working
17-
[181a62c](https://url.jam06452.uk/up08en): Finished web assembly porting
18-
[5ea3105](https://url.jam06452.uk/1s6zjmu): Add web deploy
19-
...and 7 other commits fixing github
15+
3f015c0: Add exporting plots as images
16+
f3271a2: Added popup when exporting
17+
7255bf4: Implemented the zeta and gamma function

src/graphics/ui.cpp

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,55 @@
77
#include <iostream>
88

99
#include <imgui.h>
10+
#include <imgui_internal.h>
11+
#include <imstb_textedit.h>
1012
#include <imgui_stdlib.h>
1113

1214
#include <interactions/export.h>
1315

1416

17+
#ifdef __EMSCRIPTEN__
18+
#include <emscripten_browser_clipboard.h>
19+
20+
#endif
21+
22+
1523
using string = std::string;
1624
using glm::vec2;
1725

1826
const float DEBOUNCE_DELAY = 0.05f;
1927

28+
29+
30+
31+
32+
static int FunctionInputCallback(ImGuiInputTextCallbackData* data) {
33+
FunctionState* state = (FunctionState*)data->UserData;
34+
if (data->HasSelection()) {
35+
state->selected_text = state->expression.substr(
36+
data->SelectionStart,
37+
data->SelectionEnd - data->SelectionStart
38+
);
39+
}
40+
else {
41+
state->selected_text = "";
42+
}
43+
44+
#ifdef __EMSCRIPTEN__
45+
if (just_pasted) {
46+
if (data->HasSelection()) {
47+
data->DeleteChars(data->SelectionStart, data->SelectionEnd - data->SelectionStart);
48+
}
49+
data->InsertChars(data->CursorPos, clip_content.c_str());
50+
just_pasted = false;
51+
clip_content.clear();
52+
state->needs_reparse = true;
53+
}
54+
#endif
55+
return 0;
56+
}
57+
58+
2059
namespace UI {
2160
bool Button(const char* label, const ImVec2& size = ImVec2(0, 0)) {
2261
bool clicked = ImGui::Button(label, size);
@@ -139,7 +178,22 @@ void render_inspector_overlay(const PickerResult& hover, ViewState& view_state)
139178
}
140179

141180

181+
142182
void render_and_update(FunctionState& state, ViewState& view_state, unsigned int& op_tex, unsigned int& const_tex, Shader& interpreter_shader, CompilerShader& compiler_shader) {
183+
#ifdef __EMSCRIPTEN__
184+
static bool clipboard_initialized = false;
185+
if (!clipboard_initialized) {
186+
emscripten_browser_clipboard::copy([](void* callback_data) -> const char* {
187+
FunctionState* s = static_cast<FunctionState*>(callback_data);
188+
if (!s->selected_text.empty()) {
189+
return s->selected_text.c_str();
190+
}
191+
return s->expression.c_str();
192+
}, (void*)&state);
193+
194+
clipboard_initialized = true;
195+
}
196+
#endif
143197
state.is_3d = view_state.is_3d;
144198
ImGui::Begin("Function Editor", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
145199
ImGui::SameLine();
@@ -157,11 +211,30 @@ void render_and_update(FunctionState& state, ViewState& view_state, unsigned int
157211
ImGui::SameLine();
158212

159213
static bool auto_reparse = true;
160-
161214
bool pressed_enter = ImGui::InputText("##source",
162215
&state.expression,
163-
ImGuiInputTextFlags_EnterReturnsTrue);
164-
216+
ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackAlways,
217+
FunctionInputCallback,
218+
(void*)&state);
219+
if (ImGui::IsItemActive()) {
220+
ImGuiInputTextState* edit_state = ImGui::GetInputTextState(ImGui::GetActiveID());
221+
222+
if (edit_state && edit_state->HasSelection()) {
223+
int start = edit_state->GetSelectionStart();
224+
int end = edit_state->GetSelectionEnd();
225+
226+
int real_start = std::min(start, end);
227+
int real_end = std::max(start, end);
228+
229+
if (real_start < (int)state.expression.length()) {
230+
int len = std::min((int)state.expression.length() - real_start, real_end - real_start);
231+
state.selected_text = state.expression.substr(real_start, len);
232+
}
233+
}
234+
else {
235+
state.selected_text = "";
236+
}
237+
}
165238
bool typed = ImGui::IsItemEdited();
166239

167240
if (typed && !pressed_enter && auto_reparse) {
@@ -233,6 +306,12 @@ void render_and_update(FunctionState& state, ViewState& view_state, unsigned int
233306
ImGui::Unindent();
234307
}
235308
}
309+
if (ImGui::Button("Test Clipboard Callbacks")) {
310+
ImGui::SetClipboardText("Testing 123");
311+
312+
const char* text = ImGui::GetClipboardText();
313+
std::cout << "Manual check: " << (text ? text : "NULL") << std::endl;
314+
}
236315

237316
if (UI::CollapsingHeader("3D Keybinds")) {
238317
ImGui::BulletText("WASD: Move");

src/graphics/ui.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ struct PickerResult;
1212
typedef struct FunctionState {
1313
Shader* current_shader = nullptr;
1414
std::string expression = "z";
15+
std::string selected_text = "";
1516
std::string error_message = "";
1617
float last_typing_time = 0.0f;
1718
bool needs_reparse = true;

src/graphics/ui_init.cpp

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
#include <graphics/ui_init.h>
2+
#include <string>
3+
#include <iostream>
4+
#include <iomanip>
5+
#ifdef __EMSCRIPTEN__
6+
#include <emscripten_browser_clipboard.h>
7+
#include <emscripten.h>
8+
9+
#endif
210

311

412

@@ -10,14 +18,18 @@ void init_imgui(GLFWwindow* window) {
1018
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
1119
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
1220
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
13-
1421
ImGui_ImplGlfw_InitForOpenGL(window, true);
15-
#ifdef __EMSCRIPTEN__
16-
ImGui_ImplOpenGL3_Init("#version 300 es");
17-
ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange;
18-
#else
19-
ImGui_ImplOpenGL3_Init();
20-
#endif
22+
#ifdef __EMSCRIPTEN__
23+
24+
emscripten_browser_clipboard::paste([](std::string&& paste_data, void* callback_data) {
25+
clip_content = std::move(paste_data);
26+
just_pasted = true;
27+
}, nullptr);
28+
29+
ImGui_ImplOpenGL3_Init("#version 300 es");
30+
#else
31+
ImGui_ImplOpenGL3_Init();
32+
#endif
2133
}
2234

2335
void init_imgui_loop() {

src/graphics/ui_init.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,11 @@ void init_imgui_loop();
1111

1212
void render_imgui();
1313

14-
void shutdown_imgui();
14+
void shutdown_imgui();
15+
16+
17+
#ifdef __EMSCRIPTEN__
18+
#include <string>
19+
inline bool just_pasted = false;
20+
inline std::string clip_content;
21+
#endif

0 commit comments

Comments
 (0)