--- /dev/null
+#include <string>
+#include <vector>
+#include <err.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include "imgui.h"
+#include "imgui_impl_glfw.h"
+#include "imgui_impl_opengl3.h"
+#include <GLFW/glfw3.h>
+
+static void glfw_error_callback(int error, const char* description) {
+ errx(1, "GLFW Error %d: %s", error, description);
+}
+
+static unsigned int physical_line_length = 30;
+static std::vector<std::string> physical_lines;
+static std::vector<std::string> logical_lines;
+static std::string next_line_buf;
+static size_t next_line_buf_used;
+static void add_line_(std::string& line) {
+ size_t num_phys_lines = (line.size() + (physical_line_length-1)) / physical_line_length;
+ if (num_phys_lines == 0)
+ physical_lines.push_back("");
+ for (size_t i=0; i<num_phys_lines; i++)
+ physical_lines.push_back(line.substr(i * physical_line_length, physical_line_length));
+}
+static void add_line(std::string line) {
+ logical_lines.push_back(line);
+ add_line_(line);
+}
+
+int main(void) {
+ fcntl(0, F_SETFL, fcntl(0, F_GETFL, 0) | O_NONBLOCK);
+
+ glfwSetErrorCallback(glfw_error_callback);
+ if (!glfwInit())
+ return 1;
+
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
+
+ float main_scale = ImGui_ImplGlfw_GetContentScaleForMonitor(glfwGetPrimaryMonitor());
+ GLFWwindow* window = glfwCreateWindow((int)(1280 * main_scale), (int)(800 * main_scale), "logscroll", nullptr, nullptr);
+ if (window == nullptr)
+ return 1;
+ glfwMakeContextCurrent(window);
+ glfwSwapInterval(1);
+
+ IMGUI_CHECKVERSION();
+ ImGui::CreateContext();
+ ImGuiIO& io = ImGui::GetIO(); (void)io;
+ io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
+ ImGui::StyleColorsDark();
+ ImGuiStyle& style = ImGui::GetStyle();
+ style.ScaleAllSizes(main_scale);
+ style.FontScaleDpi = main_scale;
+
+ ImGui_ImplGlfw_InitForOpenGL(window, true);
+ ImGui_ImplOpenGL3_Init(nullptr);
+
+ while (!glfwWindowShouldClose(window)) {
+ glfwPollEvents();
+
+ bool want_scroll_to_bottom = false;
+ while (1) {
+ next_line_buf.resize(next_line_buf_used + 512);
+ ssize_t read_res = read(0, next_line_buf.data()+next_line_buf_used, 512);
+ if (read_res < 0 && errno == EAGAIN)
+ break;
+ if (read_res == 0)
+ break;
+ if (read_res < 0)
+ err(1, "read failed");
+ next_line_buf_used += (size_t)read_res;
+
+ while (1) {
+ size_t newline_pos = next_line_buf.find('\n', 0);
+ if (newline_pos >= next_line_buf_used)
+ break;
+ add_line(next_line_buf.substr(0, newline_pos));
+ want_scroll_to_bottom = true;
+ next_line_buf = next_line_buf.substr(newline_pos+1);
+ next_line_buf_used -= newline_pos+1;
+ }
+ }
+
+ float line_height = ImGui::GetTextLineHeightWithSpacing();
+
+ // Start the Dear ImGui frame
+ ImGui_ImplOpenGL3_NewFrame();
+ ImGui_ImplGlfw_NewFrame();
+ ImGui::NewFrame();
+
+ //ImGui::ShowDemoWindow(nullptr);
+
+ const ImGuiViewport* viewport = ImGui::GetMainViewport();
+ unsigned int num_lines_in_window = viewport->Size.y / line_height;
+ ImGui::SetNextWindowPos(viewport->Pos);
+ ImGui::SetNextWindowSize(viewport->Size);
+
+ ImGui::SetNextWindowContentSize(ImVec2(0, line_height * (physical_lines.size()+1)));
+ if (1 && want_scroll_to_bottom) {
+ ssize_t scroll_y = (ssize_t)physical_lines.size() - (ssize_t)num_lines_in_window + 1;
+ if (scroll_y < 0)
+ scroll_y = 0;
+ ImGui::SetNextWindowScroll(ImVec2(0, line_height*scroll_y));
+ }
+ ImGui::Begin("logscroll", nullptr,
+ ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
+ ImGuiWindowFlags_AlwaysVerticalScrollbar);
+ unsigned int new_physical_line_length = ImGui::GetContentRegionAvail().x / ImGui::CalcTextSize("A").x;
+ if (physical_line_length != new_physical_line_length) {
+ physical_line_length = new_physical_line_length;
+ physical_lines.clear();
+ for (std::string& line : logical_lines)
+ add_line_(line);
+ }
+ size_t phys_line_startidx = ImGui::GetScrollY() / line_height;
+ size_t top_line_idx = phys_line_startidx - (phys_line_startidx % num_lines_in_window);
+ ImGui::SetCursorScreenPos(ImVec2(ImGui::GetCursorScreenPos().x, ImGui::GetCursorScreenPos().y + ImGui::GetScrollY()));
+ for (size_t i=top_line_idx; i<top_line_idx + num_lines_in_window; i++) {
+ if (i % num_lines_in_window == (phys_line_startidx - 1 + num_lines_in_window) % num_lines_in_window) {
+ ImDrawList *dl = ImGui::GetWindowDrawList();
+ ImVec2 p0 = ImGui::GetCursorScreenPos();
+ ImVec2 p1(p0.x + ImGui::GetContentRegionAvail().x, p0.y + line_height);
+ dl->AddRectFilled(p0, p1, ImGui::GetColorU32(ImVec4(1, 0, 0, 1)));
+ ImGui::Text(" ");
+ continue;
+ }
+ if (i == top_line_idx + num_lines_in_window)
+ break;
+ size_t cur_i = (i < phys_line_startidx) ? (i + num_lines_in_window) : i;
+ if (cur_i < physical_lines.size())
+ ImGui::Text("%s", physical_lines[cur_i].c_str());
+ else
+ ImGui::Text(" ");
+ }
+ ImGui::End();
+
+ // Rendering
+ ImGui::Render();
+ int display_w, display_h;
+ glfwGetFramebufferSize(window, &display_w, &display_h);
+ glViewport(0, 0, display_w, display_h);
+ glClearColor(0, 0, 0, 1);
+ glClear(GL_COLOR_BUFFER_BIT);
+ ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
+
+ glfwSwapBuffers(window);
+ }
+
+ // Cleanup
+ ImGui_ImplOpenGL3_Shutdown();
+ ImGui_ImplGlfw_Shutdown();
+ ImGui::DestroyContext();
+
+ glfwDestroyWindow(window);
+ glfwTerminate();
+
+ return 0;
+}