Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
383 lines (331 sloc) 16 KB
#include <chrono>
#include <thread>
#include <list>
#include <GLFW/glfw3.h>
#include <third_party/ImGuiColorTextEdit/TextEditor.h>
#include <third_party/imgui/examples/opengl2_example/imgui_impl_glfw_gl2.h>
#include "host_app.h"
#include "rcrl/rcrl.h"
using namespace std;
#define RCRL_LIVE_DEMO 0
bool g_console_visible = !RCRL_LIVE_DEMO;
// my own callback - need to add the new line symbols to make ImGuiColorTextEdit work when 'enter' is pressed
void My_ImGui_ImplGlfwGL2_KeyCallback(GLFWwindow* w, int key, int scancode, int action, int mods) {
// calling the callback from the imgui/glfw integration only if not a dash because when writing an underscore (with shift down)
// ImGuiColorTextEdit does a paste - see this for more info: https://github.com/BalazsJako/ImGuiColorTextEdit/issues/18
if(key != '-')
ImGui_ImplGlfwGL2_KeyCallback(w, key, scancode, action, mods);
// add the '\n' char when 'enter' is pressed - for ImGuiColorTextEdit
ImGuiIO& io = ImGui::GetIO();
if(key == GLFW_KEY_ENTER && !io.KeyCtrl && (action == GLFW_PRESS || action == GLFW_REPEAT))
io.AddInputCharacter((unsigned short)'\n');
// console toggle
if(!io.WantCaptureKeyboard && !io.WantTextInput && key == GLFW_KEY_GRAVE_ACCENT &&
(action == GLFW_PRESS || action == GLFW_REPEAT))
g_console_visible = !g_console_visible;
}
int main() {
// Setup window
glfwSetErrorCallback([](int error, const char* description) { fprintf(stderr, "%d %s", error, description); });
if(!glfwInit())
return 1;
GLFWwindow* window = glfwCreateWindow(1280, 1024, "Read-Compile-Run-Loop - REPL for C++", nullptr, nullptr);
glfwMakeContextCurrent(window);
// Setup ImGui binding
ImGui::CreateContext();
ImGui_ImplGlfwGL2_Init(window, true);
// remove rounding of the console window
ImGuiStyle& style = ImGui::GetStyle();
style.WindowRounding = 0.f;
ImGuiIO& io = ImGui::GetIO();
io.Fonts->AddFontFromFileTTF(CMAKE_SOURCE_DIR "/src/third_party/imgui/misc/fonts/Cousine-Regular.ttf", 17.f);
// overwrite with my own callback
glfwSetKeyCallback(window, My_ImGui_ImplGlfwGL2_KeyCallback);
// an editor instance - for the already submitted code
TextEditor history;
history.SetLanguageDefinition(TextEditor::LanguageDefinition::CPlusPlus());
history.SetReadOnly(true);
// this is the precompiled header for the plugin in this demo project so it's contents are always there for the plugin
history.SetText("#include \"precompiled_for_plugin.h\"\n");
// an editor instance - compiler output - with an empty language definition and a custom palette
TextEditor compiler_output;
compiler_output.SetLanguageDefinition(TextEditor::LanguageDefinition());
compiler_output.SetReadOnly(true);
auto custom_palette = TextEditor::GetDarkPalette();
custom_palette[(int)TextEditor::PaletteIndex::MultiLineComment] = 0xcccccccc; // text is treated as such by default
compiler_output.SetPalette(custom_palette);
// holds the standard output from while loading the plugin - same as the compiler output as a theme
TextEditor program_output(compiler_output);
// an editor instance - for the core being currently written
TextEditor editor;
editor.SetLanguageDefinition(TextEditor::LanguageDefinition::CPlusPlus());
#if !RCRL_LIVE_DEMO
// set some initial code
editor.SetText(R"raw(// global
#include "host_app.h"
// once
cout << "Hello world!" << endl;
// global
#include <vector>
auto getVec() { return vector<int>({1, 2, 3}); }
// vars
auto vec = getVec();
// once
cout << vec.size() << endl;
)raw");
#endif // RCRL_LIVE_DEMO
// holds the exit code from the last compilation - there was an error when not 0
int last_compiler_exitcode = 0;
// if the default mode was used for the first section
bool used_default_mode = false;
rcrl::Mode default_mode = rcrl::ONCE;
// limiting to 50 fps because on some systems the whole machine started lagging when the demo was turned on
using frames = chrono::duration<int64_t, ratio<1, 60>>;
auto nextFrame = chrono::system_clock::now() + frames{0};
// add objects in scene
for(int i = 0; i < 4; ++i) {
for(int k = 0; k < 4; ++k) {
auto& obj = addObject(-7.5f + k * 5, -4.5f + i * 3);
obj.colorize(float(i % 2), float(k % 2), 0);
}
}
// main loop
while(!glfwWindowShouldClose(window)) {
// poll for events
glfwPollEvents();
ImGui_ImplGlfwGL2_NewFrame();
// handle window stretching
int display_w, display_h;
glfwGetFramebufferSize(window, &display_w, &display_h);
int window_w, window_h;
glfwGetWindowSize(window, &window_w, &window_h);
// console should be always fixed
ImGui::SetNextWindowSize({(float)window_w, -1.f}, ImGuiCond_Always);
ImGui::SetNextWindowPos({0.f, 0.f}, ImGuiCond_Always);
// sets breakpoints on the program_output instance of the text editor widget - used to highlight new output
auto do_breakpoints_on_output = [&](int old_line_count, const std::string& new_output) {
TextEditor::Breakpoints bps;
if(old_line_count == program_output.GetTotalLines() && new_output.size())
bps.insert(old_line_count);
for(auto curr_line = old_line_count; curr_line < program_output.GetTotalLines(); ++curr_line)
bps.insert(curr_line);
program_output.SetBreakpoints(bps);
};
if(g_console_visible &&
ImGui::Begin("console", nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) {
const auto text_field_height = ImGui::GetTextLineHeight() * 14;
const float left_right_ratio = 0.45f;
// top left part
ImGui::BeginChild("history code", ImVec2(window_w * left_right_ratio, text_field_height));
auto hcpos = history.GetCursorPosition();
ImGui::Text("Executed code: %3d/%-3d %3d lines", hcpos.mLine + 1, hcpos.mColumn + 1, editor.GetTotalLines());
history.Render("History");
ImGui::EndChild();
ImGui::SameLine();
// top right part
ImGui::BeginChild("compiler output", ImVec2(0, text_field_height));
auto new_output = rcrl::get_new_compiler_output();
if(new_output.size()) {
auto total_output = compiler_output.GetText() + new_output;
// scan for errors through the lines and highlight them with markers
auto curr_pos = 0;
auto line = 1;
auto first_error_marker_line = 0;
TextEditor::ErrorMarkers error_markers;
do {
auto new_curr_pos_1 = total_output.find("error", curr_pos + 1); // add 1 to skip new lines
auto new_curr_pos_2 = total_output.find("\n", curr_pos + 1); // add 1 to skip new lines
if(new_curr_pos_1 < new_curr_pos_2) {
error_markers.insert(make_pair(line, ""));
if(!first_error_marker_line)
first_error_marker_line = line;
}
if(new_curr_pos_2 < new_curr_pos_1) {
line++;
}
curr_pos = min(new_curr_pos_1, new_curr_pos_2);
} while(size_t(curr_pos) != string::npos);
compiler_output.SetErrorMarkers(error_markers);
// update compiler output
compiler_output.SetText(move(total_output));
if(first_error_marker_line)
compiler_output.SetCursorPosition({first_error_marker_line, 1});
else
compiler_output.SetCursorPosition({compiler_output.GetTotalLines(), 1});
}
if(last_compiler_exitcode)
ImGui::TextColored({1, 0, 0, 1}, "Compiler output - ERROR!");
else
ImGui::Text("Compiler output: ");
ImGui::SameLine();
auto cocpos = compiler_output.GetCursorPosition();
ImGui::Text("%3d/%-3d %3d lines", cocpos.mLine + 1, cocpos.mColumn + 1, compiler_output.GetTotalLines());
compiler_output.Render("Compiler output");
ImGui::EndChild();
// bottom left part
ImGui::BeginChild("source code", ImVec2(window_w * left_right_ratio, text_field_height));
auto ecpos = editor.GetCursorPosition();
ImGui::Text("RCRL Console: %3d/%-3d %3d lines | %s", ecpos.mLine + 1, ecpos.mColumn + 1, editor.GetTotalLines(),
editor.CanUndo() ? "*" : " ");
editor.Render("Code");
ImGui::EndChild();
ImGui::SameLine();
// bottom right part
ImGui::BeginChild("program output", ImVec2(0, text_field_height));
auto ocpos = program_output.GetCursorPosition();
ImGui::Text("Program output: %3d/%-3d %3d lines", ocpos.mLine + 1, ocpos.mColumn + 1,
program_output.GetTotalLines());
program_output.Render("Output");
ImGui::EndChild();
// bottom buttons
ImGui::Text("Default mode:");
ImGui::SameLine();
ImGui::RadioButton("global", (int*)&default_mode, rcrl::GLOBAL);
ImGui::SameLine();
ImGui::RadioButton("vars", (int*)&default_mode, rcrl::VARS);
ImGui::SameLine();
ImGui::RadioButton("once", (int*)&default_mode, rcrl::ONCE);
ImGui::SameLine();
auto compile = ImGui::Button("Compile and run");
ImGui::SameLine();
if(ImGui::Button("Cleanup Plugins") && !rcrl::is_compiling()) {
compiler_output.SetText("");
auto output_from_cleanup = rcrl::cleanup_plugins(true);
auto old_line_count = program_output.GetTotalLines();
program_output.SetText(program_output.GetText() + output_from_cleanup);
program_output.SetCursorPosition({program_output.GetTotalLines(), 0});
last_compiler_exitcode = 0;
history.SetText("#include \"precompiled_for_plugin.h\"\n");
// highlight the new stdout lines
do_breakpoints_on_output(old_line_count, output_from_cleanup);
}
ImGui::SameLine();
if(ImGui::Button("Clear Output"))
program_output.SetText("");
ImGui::SameLine();
ImGui::Dummy({20, 0});
ImGui::SameLine();
#if !RCRL_LIVE_DEMO
ImGui::Text("Use Ctrl+Enter to submit code");
#endif // RCRL_LIVE_DEMO
// if the user has submitted code for compilation
#if RCRL_LIVE_DEMO
extern std::list<const char*> fragments;
static bool fragment_popped = false;
if(!rcrl::is_compiling() && !fragment_popped && fragments.size() && io.KeysDown[GLFW_KEY_ENTER] && io.KeyCtrl) {
editor.SetText(fragments.front());
fragments.pop_front();
fragment_popped = true;
}
#else // RCRL_LIVE_DEMO
compile |= (io.KeysDown[GLFW_KEY_ENTER] && io.KeyCtrl);
#endif // RCRL_LIVE_DEMO
if(compile && !rcrl::is_compiling() && editor.GetText().size() > 1) {
// clear compiler output
compiler_output.SetText("");
auto getCodePrefix = [&]() {
#ifdef __APPLE__ // related to this: https://github.com/onqtam/rcrl/issues/4
static bool first_time_called = true;
if(first_time_called) {
first_time_called = false;
return string("//global\n#include \"precompiled_for_plugin.h\"\n") +
(default_mode == rcrl::GLOBAL ? "// global\n" :
(default_mode == rcrl::VARS ? "// vars\n" : "// once\n"));
}
#endif
return string("");
};
// submit to the RCRL engine
if(rcrl::submit_code(getCodePrefix() + editor.GetText(), default_mode, &used_default_mode)) {
// make the editor code untouchable while compiling
editor.SetReadOnly(true);
} else {
last_compiler_exitcode = 1;
}
#if RCRL_LIVE_DEMO
fragment_popped = false;
#endif // RCRL_LIVE_DEMO
}
ImGui::End();
}
// if there is a spawned compiler process and it has just finished
if(rcrl::try_get_exit_status_from_compile(last_compiler_exitcode)) {
// we can edit the code again
editor.SetReadOnly(false);
if(last_compiler_exitcode) {
// errors occurred - set cursor to the last line of the erroneous code
editor.SetCursorPosition({editor.GetTotalLines(), 0});
} else {
// append to the history and focus last line
history.SetCursorPosition({history.GetTotalLines(), 1});
auto history_text = history.GetText();
// add a new line (if one is missing) to the code that will go to the history for readability
if(history_text.size() && history_text.back() != '\n')
history_text += '\n';
// if the default mode was used - add an extra comment before the code to the history for clarity
if(used_default_mode)
history_text += default_mode == rcrl::GLOBAL ? "// global\n" :
(default_mode == rcrl::VARS ? "// vars\n" : "// once\n");
history.SetText(history_text + editor.GetText());
// load the new plugin
auto output_from_loading = rcrl::copy_and_load_new_plugin(true);
auto old_line_count = program_output.GetTotalLines();
program_output.SetText(program_output.GetText() + output_from_loading);
// TODO: used for auto-scroll but the cursor in the editor is removed (unfocused) sometimes from this...
//program_output.SetCursorPosition({program_output.GetTotalLines(), 0});
// highlight the new stdout lines
do_breakpoints_on_output(old_line_count, output_from_loading);
// clear the editor
editor.SetText("\r"); // an empty string "" breaks it for some reason...
editor.SetCursorPosition({0, 0});
}
}
// rendering
glViewport(0, 0, display_w, display_h);
glClearColor(0.45f, 0.55f, 0.60f, 1.00f);
glClear(GL_COLOR_BUFFER_BIT);
glPushMatrix();
glLoadIdentity();
glScalef(0.1f, 0.1f * display_w / display_h, 0.1f);
for(auto& obj : getObjects())
obj.draw();
glPopMatrix();
// finalize rendering
ImGui::Render();
ImGui_ImplGlfwGL2_RenderDrawData(ImGui::GetDrawData());
glfwSwapBuffers(window);
// do the frame rate limiting
this_thread::sleep_until(nextFrame);
nextFrame += frames{1};
}
// cleanup
rcrl::cleanup_plugins();
ImGui_ImplGlfwGL2_Shutdown();
ImGui::DestroyContext();
glfwTerminate();
return 0;
}
#if RCRL_LIVE_DEMO
std::list<const char*> fragments = {
R"raw(// global
#include "host_app.h"
)raw", R"raw(// vars
auto& obj = addObject(0, -2);
)raw", R"raw(// once
obj.colorize(1, 1, 1);
)raw", R"raw(// once
obj.translate(0, -4);
obj.set_speed(7);
)raw", R"raw(// once
for(auto& curr : getObjects())
curr.set_speed(0.1);
)raw", R"raw(// global
struct MyType {
MyType() { cout << "hello" << endl; }
~MyType() { cout << "bye" << endl; }
};
// vars
MyType asd;
)raw"};
#endif // RCRL_LIVE_DEMO