├── .github └── workflows │ └── build.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake └── GitVars.cmake ├── examples ├── CMakeLists.txt ├── emscripten0 │ ├── CMakeLists.txt │ ├── index-tmpl.html │ ├── main.cpp │ └── style.css ├── hnterm │ ├── CMakeLists.txt │ ├── README.md │ ├── hn-state.cpp │ ├── hn-state.h │ ├── impl-emscripten.cpp │ ├── impl-ncurses.cpp │ ├── index-tmpl.html │ ├── json.h │ ├── main.cpp │ └── style.css ├── imtui-common.h ├── imtui-demo.cpp ├── imtui-demo.h ├── ncurses0 │ ├── CMakeLists.txt │ └── main.cpp └── slack │ ├── CMakeLists.txt │ ├── README.md │ ├── index-tmpl.html │ ├── logs.h │ ├── main.cpp │ └── style.css ├── include └── imtui │ ├── imtui-impl-emscripten.h │ ├── imtui-impl-ncurses.h │ ├── imtui-impl-text.h │ └── imtui.h ├── src ├── CMakeLists.txt ├── imtui-impl-emscripten.cpp ├── imtui-impl-ncurses.cpp └── imtui-impl-text.cpp └── third-party └── CMakeLists.txt /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | 4 | jobs: 5 | ubuntu-20_04-gcc: 6 | runs-on: ubuntu-20.04 7 | 8 | strategy: 9 | matrix: 10 | build: [Debug, Release] 11 | 12 | steps: 13 | - name: Clone 14 | uses: actions/checkout@v1 15 | with: 16 | submodules: true 17 | 18 | - name: Dependencies 19 | run: sudo apt-get update && sudo apt-get install libncurses5-dev libcurl4-openssl-dev 20 | 21 | - name: Configure 22 | run: cmake . -DCMAKE_BUILD_TYPE=${{ matrix.build }} 23 | 24 | - name: Build 25 | run: make 26 | 27 | ubuntu-18_04-clang: 28 | runs-on: ubuntu-18.04 29 | 30 | strategy: 31 | matrix: 32 | build: [Debug, Release] 33 | 34 | steps: 35 | - name: Clone 36 | uses: actions/checkout@v1 37 | with: 38 | submodules: true 39 | 40 | - name: Dependencies 41 | run: sudo apt-get update && sudo apt-get install libncurses5-dev libcurl4-openssl-dev 42 | 43 | - name: Configure 44 | run: cmake . -DCMAKE_BUILD_TYPE=${{ matrix.build }} -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang 45 | 46 | - name: Build 47 | run: make 48 | 49 | macOS-latest: 50 | runs-on: macOS-latest 51 | 52 | strategy: 53 | matrix: 54 | build: [Debug, Release] 55 | 56 | steps: 57 | - name: Clone 58 | uses: actions/checkout@v1 59 | with: 60 | submodules: true 61 | 62 | - name: Dependencies 63 | run: echo "tmp" 64 | 65 | - name: Configure 66 | run: cmake . -DCMAKE_BUILD_TYPE=${{ matrix.build }} 67 | 68 | - name: Build 69 | run: make 70 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | compile_commands.json 2 | build 3 | build-* 4 | 5 | .cache 6 | .clangd 7 | .vscode 8 | .cquery_cache 9 | *.code-workspace 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third-party/imgui/imgui"] 2 | path = third-party/imgui/imgui 3 | url = https://github.com/ggerganov/imgui 4 | branch = imtui 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [Unreleased] 4 | 5 | - "Columns & Tables" demo window (0986676) 6 | 7 | ## [1.0.4] - 2021-04-03 8 | 9 | - Update Dear ImGui to v1.81 ([#24](https://github.com/ggerganov/imtui/pull/24)) 10 | - Fix CMake install targets 11 | 12 | ## [1.0.3] - 2021-01-09 13 | 14 | - Update Dear ImGui to v1.79 15 | - Add Windows support with MSYS2 + PDCurses (39f34ea) 16 | 17 | ## [1.0.0] - 2020-12-15 18 | 19 | - Initial release with Dear ImGui v1.77 backend 20 | 21 | [unreleased]: https://github.com/ggerganov/imtui/compare/v1.0.4...HEAD 22 | [1.0.4]: https://github.com/ggerganov/imtui/releases/tag/v1.0.4 23 | [1.0.3]: https://github.com/ggerganov/imtui/releases/tag/v1.0.3 24 | [1.0.0]: https://github.com/ggerganov/imtui/releases/tag/v1.0.0 25 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.10) 2 | project(imtui VERSION 1.0.3) 3 | 4 | set(CMAKE_EXPORT_COMPILE_COMMANDS "on") 5 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 6 | set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") 7 | 8 | if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) 9 | set(IMTUI_STANDALONE ON) 10 | include(cmake/GitVars.cmake) 11 | include(GNUInstallDirs) 12 | else() 13 | set(IMTUI_STANDALONE OFF) 14 | endif() 15 | 16 | if (EMSCRIPTEN) 17 | set(IMTUI_SUPPORT_NCURSES_DEFAULT OFF) 18 | set(IMTUI_SUPPORT_CURL_DEFAULT OFF) 19 | set(IMTUI_LIBRARY_TYPE STATIC) 20 | else() 21 | set(IMTUI_SUPPORT_NCURSES_DEFAULT ON) 22 | set(IMTUI_SUPPORT_CURL_DEFAULT ON) 23 | if (BUILD_SHARED_LIBS) 24 | set(IMTUI_LIBRARY_TYPE SHARED) 25 | else () 26 | set(IMTUI_LIBRARY_TYPE STATIC) 27 | endif() 28 | endif() 29 | 30 | # options 31 | 32 | option(IMTUI_ALL_WARNINGS "imtui: enable all compiler warnings" ${IMTUI_STANDALONE}) 33 | option(IMTUI_ALL_WARNINGS_3RD_PARTY "imtui: enable all compiler warnings in 3rd party libs" OFF) 34 | 35 | option(IMTUI_SANITIZE_THREAD "imtui: enable thread sanitizer" OFF) 36 | option(IMTUI_SANITIZE_ADDRESS "imtui: enable address sanitizer" OFF) 37 | option(IMTUI_SANITIZE_UNDEFINED "imtui: enable undefined sanitizer" OFF) 38 | 39 | option(IMTUI_SUPPORT_NCURSES "imtui: support for libncurses" ${IMTUI_SUPPORT_NCURSES_DEFAULT}) 40 | option(IMTUI_SUPPORT_CURL "imtui: support for libcurl" ${IMTUI_SUPPORT_CURL_DEFAULT}) 41 | 42 | option(IMTUI_BUILD_EXAMPLES "imtui: build examples" ${IMTUI_STANDALONE}) 43 | 44 | # sanitizers 45 | 46 | if (IMTUI_SANITIZE_THREAD) 47 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=thread") 48 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread") 49 | endif() 50 | 51 | if (IMTUI_SANITIZE_ADDRESS) 52 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer") 53 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") 54 | endif() 55 | 56 | if (IMTUI_SANITIZE_UNDEFINED) 57 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=undefined") 58 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined") 59 | endif() 60 | 61 | # dependencies 62 | 63 | if (IMTUI_SUPPORT_NCURSES) 64 | if (MINGW) 65 | set(CURSES_LIBRARIES pdcurses) 66 | else() 67 | find_package(Curses REQUIRED) 68 | CHECK_LIBRARY_EXISTS("${CURSES_NCURSES_LIBRARY}" 69 | nodelay "" CURSES_NCURSES_HAS_NODELAY) 70 | if(NOT CURSES_NCURSES_HAS_NODELAY) 71 | find_library(CURSES_EXTRA_LIBRARY tinfo) 72 | CHECK_LIBRARY_EXISTS("${CURSES_EXTRA_LIBRARY}" 73 | nodelay "" CURSES_TINFO_HAS_NODELAY) 74 | endif() 75 | if(CURSES_EXTRA_LIBRARY) 76 | set(CURSES_LIBRARIES ${CURSES_LIBRARIES} ${CURSES_EXTRA_LIBRARY}) 77 | endif() 78 | endif() 79 | endif() 80 | 81 | # main 82 | 83 | if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 84 | set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) 85 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "RelWithDebInfo") 86 | endif () 87 | 88 | add_subdirectory(third-party) 89 | 90 | if (IMTUI_ALL_WARNINGS) 91 | if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") 92 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic") 93 | else() 94 | # todo : windows 95 | endif() 96 | endif() 97 | 98 | add_subdirectory(src) 99 | 100 | if (IMTUI_STANDALONE AND IMTUI_BUILD_EXAMPLES) 101 | add_subdirectory(examples) 102 | endif() 103 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Georgi Gerganov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | imtui 2 | ===== 3 | [![Actions Status](https://github.com/ggerganov/imtui/workflows/CI/badge.svg)](https://github.com/ggerganov/imtui/actions) 4 | [![ImTui v1.0.4 badge][changelog-badge]][changelog] 5 | [![Dear ImGui version badge][imgui-version-badge]](https://github.com/ocornut/imgui) 6 | 7 | ImTui is an immediate mode text-based user interface library. Supports 256 ANSI colors and mouse/keyboard input. 8 | 9 |

10 | 11 | ImTui basic 12 | 13 |
14 | A very basic ImTui example 15 |

16 | 17 | --- 18 | 19 |

20 | 21 | Slack client 22 | 23 |
24 | Text-based client for Slack 25 |

26 | 27 | --- 28 | 29 |

30 | 31 | Tables 32 | 33 |
34 | Tables example 35 |

36 | 37 | --- 38 | 39 |

40 | 41 | HNTerm 42 | 43 |
44 | Text-based client for Hacker News 45 |

46 | 47 | --- 48 | 49 |

50 | 51 | WTF util 52 | 53 |
54 | Text-based configuration editor for the WTF Dashboard 55 |

56 | 57 | ## Live demo in the browser 58 | 59 | Even though this library is supposed to be used in the terminal, for convenience here is an [Emscripten](https://emscripten.org) build to demonstrate what it looks like, by simulating a console in the browser: 60 | 61 | - Demo 0: [imtui.ggerganov.com](https://imtui.ggerganov.com/) 62 | - Demo 1: [hnterm.ggerganov.com](https://hnterm.ggerganov.com/) 63 | - Demo 2: [wtf-tui.ggerganov.com](https://wtf-tui.ggerganov.com/) 64 | - Demo 3: [slack.ggerganov.com](https://slack.ggerganov.com/) 65 | 66 | Note: the demos work best with **Chrome** 67 | 68 | ## Details 69 | 70 | This library is 99.9% based on the popular [Dear ImGui](https://github.com/ocornut/imgui) library. ImTui simply provides an [ncurses](https://en.wikipedia.org/wiki/Ncurses) interface in order to draw and interact with widgets in the terminal. The entire Dear ImGui interface is available out-of-the-box. 71 | 72 | For basic usage of ImTui, check one of the available samples: 73 | 74 | - [example-ncurses0](https://github.com/ggerganov/imtui/blob/master/examples/ncurses0/main.cpp) 75 | - [example-emscripten0](https://github.com/ggerganov/imtui/blob/master/examples/emscripten0/main.cpp) 76 | - [hnterm](https://github.com/ggerganov/hnterm) - a simple tool to browse Hacker News in the terminal 77 | - [wtf-tui](https://github.com/ggerganov/wtf-tui) - text-based UI for configuring the WTF terminal dashboard 78 | - [slack](https://github.com/ggerganov/imtui/blob/master/examples/slack) - text-based mock UI for Slack 79 | 80 | ## Building 81 | 82 | ImTui depends only on `libncurses` 83 | 84 | ### Linux and Mac: 85 | 86 | ```bash 87 | git clone https://github.com/ggerganov/imtui --recursive 88 | cd imtui 89 | mkdir build && cd build 90 | cmake .. 91 | make 92 | 93 | ./bin/imtui-example-ncurses0 94 | ``` 95 | 96 | ### Windows: 97 | 98 | Partial Windows support is currently available using MSYS2 + MinGW + PDCurses: 99 | 100 | ``` 101 | # install required packages in an MSYS2 terminal: 102 | pacman -S git cmake make mingw-w64-x86_64-dlfcn mingw-w64-x86_64-gcc mingw-w64-x86_64-pdcurses mingw-w64-x86_64-curl 103 | 104 | # build 105 | git clone https://github.com/ggerganov/imtui --recursive 106 | cd imtui 107 | mkdir build && cd build 108 | cmake .. 109 | make 110 | 111 | ./bin/hnterm.exe 112 | ``` 113 | ![](https://user-images.githubusercontent.com/1991296/103576542-fa5aef80-4edb-11eb-8340-4bd60a1f9fba.gif) 114 | For more information, checkout the following discussion: [#19](https://github.com/ggerganov/imtui/discussions/19) 115 | 116 | ### Emscripten: 117 | 118 | ```bash 119 | git clone https://github.com/ggerganov/imtui --recursive 120 | cd imtui 121 | mkdir build && cd build 122 | emconfigure cmake .. 123 | make 124 | ``` 125 | 126 | [changelog]: ./CHANGELOG.md 127 | [changelog-badge]: https://img.shields.io/badge/changelog-ImTui%20v1.0.4-dummy 128 | [imgui-version-badge]: https://img.shields.io/badge/Powered%20by%20Dear%20ImGui-v1.81-blue.svg 129 | -------------------------------------------------------------------------------- /cmake/GitVars.cmake: -------------------------------------------------------------------------------- 1 | find_package(Git) 2 | 3 | # the commit's SHA1 4 | execute_process(COMMAND 5 | "${GIT_EXECUTABLE}" describe --match=NeVeRmAtCh --always --abbrev=8 6 | WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" 7 | OUTPUT_VARIABLE GIT_SHA1 8 | ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) 9 | 10 | # the date of the commit 11 | execute_process(COMMAND 12 | "${GIT_EXECUTABLE}" log -1 --format=%ad --date=local 13 | WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" 14 | OUTPUT_VARIABLE GIT_DATE 15 | ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) 16 | 17 | # the subject of the commit 18 | execute_process(COMMAND 19 | "${GIT_EXECUTABLE}" log -1 --format=%s 20 | WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" 21 | OUTPUT_VARIABLE GIT_COMMIT_SUBJECT 22 | ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) 23 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 11) 2 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 3 | 4 | find_package(Threads REQUIRED) 5 | 6 | add_library(imtui-examples-common ${IMTUI_LIBRARY_TYPE} 7 | imtui-demo.cpp 8 | ) 9 | 10 | target_link_libraries(imtui-examples-common PRIVATE 11 | imtui 12 | ) 13 | 14 | if (EMSCRIPTEN) 15 | add_subdirectory(emscripten0) 16 | add_subdirectory(hnterm) 17 | add_subdirectory(slack) 18 | return() 19 | endif() 20 | 21 | if (IMTUI_SUPPORT_NCURSES) 22 | add_subdirectory(ncurses0) 23 | add_subdirectory(slack) 24 | 25 | if (IMTUI_SUPPORT_CURL) 26 | if (IMTUI_SUPPORT_CURL) 27 | find_package(CURL REQUIRED) 28 | if (MINGW) 29 | set(CURL_LIBRARIES curl) 30 | endif() 31 | endif() 32 | 33 | add_subdirectory(hnterm) 34 | endif() 35 | endif() 36 | -------------------------------------------------------------------------------- /examples/emscripten0/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TARGET imtui-example-emscripten0) 2 | 3 | add_executable(${TARGET} 4 | main.cpp 5 | ) 6 | 7 | target_include_directories(${TARGET} PRIVATE 8 | .. 9 | ) 10 | 11 | target_link_libraries(${TARGET} PRIVATE 12 | imtui 13 | imtui-examples-common 14 | imtui-emscripten 15 | ) 16 | 17 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/index-tmpl.html ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/index.html @ONLY) 18 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/style.css ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/style.css @ONLY) 19 | -------------------------------------------------------------------------------- /examples/emscripten0/index-tmpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ImTui Emscripten example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
 
16 |
17 | 18 |
19 | 20 | | 21 | Build time: @GIT_DATE@ | 22 | Commit hash: @GIT_SHA1@ | 23 | Commit subject: @GIT_COMMIT_SUBJECT@ | 24 | 25 |
26 |
27 | View on GitHub 28 | 29 | 30 |
31 | 32 | 283 | 284 | 285 | 286 | -------------------------------------------------------------------------------- /examples/emscripten0/main.cpp: -------------------------------------------------------------------------------- 1 | #include "imtui/imtui.h" 2 | #include "imtui/imtui-impl-emscripten.h" 3 | 4 | #include "imtui-demo.h" 5 | 6 | #include 7 | 8 | ImTui::TScreen * g_screen = nullptr; 9 | 10 | extern "C" { 11 | EMSCRIPTEN_KEEPALIVE 12 | void render_frame() { 13 | ImTui_ImplText_NewFrame(); 14 | ImTui_ImplEmscripten_NewFrame(); 15 | 16 | ImGui::NewFrame(); 17 | 18 | ImGui::SetNextWindowPos(ImVec2(8, 28), ImGuiCond_Once); 19 | ImGui::SetNextWindowSize(ImVec2(50.0, 10.0), ImGuiCond_Once); 20 | ImGui::Begin("Hello, world!"); 21 | ImGui::Text("mx = %g, my = %g", ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y); 22 | ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); 23 | ImGui::End(); 24 | 25 | bool showDemoWindow = true; 26 | ImTui::ShowDemoWindow(&showDemoWindow); 27 | 28 | ImGui::Render(); 29 | 30 | ImTui_ImplText_RenderDrawData(ImGui::GetDrawData(), g_screen); 31 | } 32 | } 33 | 34 | int main() { 35 | IMGUI_CHECKVERSION(); 36 | ImGui::CreateContext(); 37 | 38 | ImTui_ImplText_Init(); 39 | g_screen = ImTui_ImplEmscripten_Init(true); 40 | 41 | return 0; 42 | } 43 | -------------------------------------------------------------------------------- /examples/emscripten0/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; background-color: black; 3 | -webkit-font-smoothing: subpixel-antialiased; 4 | font-smoothing: subpixel-antialiased; 5 | } 6 | #screen { 7 | margin: 0; 8 | padding: 0; 9 | font-size: 13px; 10 | height: 100%; 11 | font: sans-serif; 12 | } 13 | .no-sel { 14 | outline: none; 15 | -moz-user-select: none; 16 | -webkit-user-select: none; 17 | -webkit-touch-callout: none; 18 | -ms-user-select:none; 19 | user-select:none; 20 | -o-user-select:none; 21 | } 22 | .cell { 23 | pointer-events: none; 24 | } 25 | .cell-version { 26 | padding-left: 4px; 27 | padding-top: 0.5em; 28 | text-align: left; 29 | display: inline-block; 30 | float: left; 31 | color: rgba(255, 255, 255, 0.75); 32 | } 33 | .cell-about { 34 | padding-right: 24px; 35 | padding-top: 0.5em; 36 | text-align: right; 37 | display: inline-block; 38 | float: right; 39 | } 40 | .nav-link { 41 | text-decoration: none; 42 | color: rgba(255, 255, 255, 1.0); 43 | } 44 | svg { 45 | -webkit-filter: invert(100%); /* safari 6.0 - 9.0 */ 46 | filter: invert(100%); 47 | } 48 | 49 | .f0 {color:#000000} 50 | .f1 {color:#800000} 51 | .f2 {color:#008000} 52 | .f3 {color:#808000} 53 | .f4 {color:#000080} 54 | .f5 {color:#800080} 55 | .f6 {color:#008080} 56 | .f7 {color:#c0c0c0} 57 | .f8 {color:#808080} 58 | .f9 {color:#ff0000} 59 | .f10 {color:#00ff00} 60 | .f11 {color:#ffff00} 61 | .f12 {color:#0000ff} 62 | .f13 {color:#ff00ff} 63 | .f14 {color:#00ffff} 64 | .f15 {color:#ffffff} 65 | .f16 {color:#000000} 66 | .f17 {color:#00005f} 67 | .f18 {color:#000087} 68 | .f19 {color:#0000af} 69 | .f20 {color:#0000d7} 70 | .f21 {color:#0000ff} 71 | .f22 {color:#005f00} 72 | .f23 {color:#005f5f} 73 | .f24 {color:#005f87} 74 | .f25 {color:#005faf} 75 | .f26 {color:#005fd7} 76 | .f27 {color:#005fff} 77 | .f28 {color:#008700} 78 | .f29 {color:#00875f} 79 | .f30 {color:#008787} 80 | .f31 {color:#0087af} 81 | .f32 {color:#0087d7} 82 | .f33 {color:#0087ff} 83 | .f34 {color:#00af00} 84 | .f35 {color:#00af5f} 85 | .f36 {color:#00af87} 86 | .f37 {color:#00afaf} 87 | .f38 {color:#00afd7} 88 | .f39 {color:#00afff} 89 | .f40 {color:#00d700} 90 | .f41 {color:#00d75f} 91 | .f42 {color:#00d787} 92 | .f43 {color:#00d7af} 93 | .f44 {color:#00d7d7} 94 | .f45 {color:#00d7ff} 95 | .f46 {color:#00ff00} 96 | .f47 {color:#00ff5f} 97 | .f48 {color:#00ff87} 98 | .f49 {color:#00ffaf} 99 | .f50 {color:#00ffd7} 100 | .f51 {color:#00ffff} 101 | .f52 {color:#5f0000} 102 | .f53 {color:#5f005f} 103 | .f54 {color:#5f0087} 104 | .f55 {color:#5f00af} 105 | .f56 {color:#5f00d7} 106 | .f57 {color:#5f00ff} 107 | .f58 {color:#5f5f00} 108 | .f59 {color:#5f5f5f} 109 | .f60 {color:#5f5f87} 110 | .f61 {color:#5f5faf} 111 | .f62 {color:#5f5fd7} 112 | .f63 {color:#5f5fff} 113 | .f64 {color:#5f8700} 114 | .f65 {color:#5f875f} 115 | .f66 {color:#5f8787} 116 | .f67 {color:#5f87af} 117 | .f68 {color:#5f87d7} 118 | .f69 {color:#5f87ff} 119 | .f70 {color:#5faf00} 120 | .f71 {color:#5faf5f} 121 | .f72 {color:#5faf87} 122 | .f73 {color:#5fafaf} 123 | .f74 {color:#5fafd7} 124 | .f75 {color:#5fafff} 125 | .f76 {color:#5fd700} 126 | .f77 {color:#5fd75f} 127 | .f78 {color:#5fd787} 128 | .f79 {color:#5fd7af} 129 | .f80 {color:#5fd7d7} 130 | .f81 {color:#5fd7ff} 131 | .f82 {color:#5fff00} 132 | .f83 {color:#5fff5f} 133 | .f84 {color:#5fff87} 134 | .f85 {color:#5fffaf} 135 | .f86 {color:#5fffd7} 136 | .f87 {color:#5fffff} 137 | .f88 {color:#870000} 138 | .f89 {color:#87005f} 139 | .f90 {color:#870087} 140 | .f91 {color:#8700af} 141 | .f92 {color:#8700d7} 142 | .f93 {color:#8700ff} 143 | .f94 {color:#875f00} 144 | .f95 {color:#875f5f} 145 | .f96 {color:#875f87} 146 | .f97 {color:#875faf} 147 | .f98 {color:#875fd7} 148 | .f99 {color:#875fff} 149 | .f100 {color:#878700} 150 | .f101 {color:#87875f} 151 | .f102 {color:#878787} 152 | .f103 {color:#8787af} 153 | .f104 {color:#8787d7} 154 | .f105 {color:#8787ff} 155 | .f106 {color:#87af00} 156 | .f107 {color:#87af5f} 157 | .f108 {color:#87af87} 158 | .f109 {color:#87afaf} 159 | .f110 {color:#87afd7} 160 | .f111 {color:#87afff} 161 | .f112 {color:#87d700} 162 | .f113 {color:#87d75f} 163 | .f114 {color:#87d787} 164 | .f115 {color:#87d7af} 165 | .f116 {color:#87d7d7} 166 | .f117 {color:#87d7ff} 167 | .f118 {color:#87ff00} 168 | .f119 {color:#87ff5f} 169 | .f120 {color:#87ff87} 170 | .f121 {color:#87ffaf} 171 | .f122 {color:#87ffd7} 172 | .f123 {color:#87ffff} 173 | .f124 {color:#af0000} 174 | .f125 {color:#af005f} 175 | .f126 {color:#af0087} 176 | .f127 {color:#af00af} 177 | .f128 {color:#af00d7} 178 | .f129 {color:#af00ff} 179 | .f130 {color:#af5f00} 180 | .f131 {color:#af5f5f} 181 | .f132 {color:#af5f87} 182 | .f133 {color:#af5faf} 183 | .f134 {color:#af5fd7} 184 | .f135 {color:#af5fff} 185 | .f136 {color:#af8700} 186 | .f137 {color:#af875f} 187 | .f138 {color:#af8787} 188 | .f139 {color:#af87af} 189 | .f140 {color:#af87d7} 190 | .f141 {color:#af87ff} 191 | .f142 {color:#afaf00} 192 | .f143 {color:#afaf5f} 193 | .f144 {color:#afaf87} 194 | .f145 {color:#afafaf} 195 | .f146 {color:#afafd7} 196 | .f147 {color:#afafff} 197 | .f148 {color:#afd700} 198 | .f149 {color:#afd75f} 199 | .f150 {color:#afd787} 200 | .f151 {color:#afd7af} 201 | .f152 {color:#afd7d7} 202 | .f153 {color:#afd7ff} 203 | .f154 {color:#afff00} 204 | .f155 {color:#afff5f} 205 | .f156 {color:#afff87} 206 | .f157 {color:#afffaf} 207 | .f158 {color:#afffd7} 208 | .f159 {color:#afffff} 209 | .f160 {color:#d70000} 210 | .f161 {color:#d7005f} 211 | .f162 {color:#d70087} 212 | .f163 {color:#d700af} 213 | .f164 {color:#d700d7} 214 | .f165 {color:#d700ff} 215 | .f166 {color:#d75f00} 216 | .f167 {color:#d75f5f} 217 | .f168 {color:#d75f87} 218 | .f169 {color:#d75faf} 219 | .f170 {color:#d75fd7} 220 | .f171 {color:#d75fff} 221 | .f172 {color:#d78700} 222 | .f173 {color:#d7875f} 223 | .f174 {color:#d78787} 224 | .f175 {color:#d787af} 225 | .f176 {color:#d787d7} 226 | .f177 {color:#d787ff} 227 | .f178 {color:#d7af00} 228 | .f179 {color:#d7af5f} 229 | .f180 {color:#d7af87} 230 | .f181 {color:#d7afaf} 231 | .f182 {color:#d7afd7} 232 | .f183 {color:#d7afff} 233 | .f184 {color:#d7d700} 234 | .f185 {color:#d7d75f} 235 | .f186 {color:#d7d787} 236 | .f187 {color:#d7d7af} 237 | .f188 {color:#d7d7d7} 238 | .f189 {color:#d7d7ff} 239 | .f190 {color:#d7ff00} 240 | .f191 {color:#d7ff5f} 241 | .f192 {color:#d7ff87} 242 | .f193 {color:#d7ffaf} 243 | .f194 {color:#d7ffd7} 244 | .f195 {color:#d7ffff} 245 | .f196 {color:#ff0000} 246 | .f197 {color:#ff005f} 247 | .f198 {color:#ff0087} 248 | .f199 {color:#ff00af} 249 | .f200 {color:#ff00d7} 250 | .f201 {color:#ff00ff} 251 | .f202 {color:#ff5f00} 252 | .f203 {color:#ff5f5f} 253 | .f204 {color:#ff5f87} 254 | .f205 {color:#ff5faf} 255 | .f206 {color:#ff5fd7} 256 | .f207 {color:#ff5fff} 257 | .f208 {color:#ff8700} 258 | .f209 {color:#ff875f} 259 | .f210 {color:#ff8787} 260 | .f211 {color:#ff87af} 261 | .f212 {color:#ff87d7} 262 | .f213 {color:#ff87ff} 263 | .f214 {color:#ffaf00} 264 | .f215 {color:#ffaf5f} 265 | .f216 {color:#ffaf87} 266 | .f217 {color:#ffafaf} 267 | .f218 {color:#ffafd7} 268 | .f219 {color:#ffafff} 269 | .f220 {color:#ffd700} 270 | .f221 {color:#ffd75f} 271 | .f222 {color:#ffd787} 272 | .f223 {color:#ffd7af} 273 | .f224 {color:#ffd7d7} 274 | .f225 {color:#ffd7ff} 275 | .f226 {color:#ffff00} 276 | .f227 {color:#ffff5f} 277 | .f228 {color:#ffff87} 278 | .f229 {color:#ffffaf} 279 | .f230 {color:#ffffd7} 280 | .f231 {color:#ffffff} 281 | .f232 {color:#080808} 282 | .f233 {color:#121212} 283 | .f234 {color:#1c1c1c} 284 | .f235 {color:#262626} 285 | .f236 {color:#303030} 286 | .f237 {color:#3a3a3a} 287 | .f238 {color:#444444} 288 | .f239 {color:#4e4e4e} 289 | .f240 {color:#585858} 290 | .f241 {color:#626262} 291 | .f242 {color:#6c6c6c} 292 | .f243 {color:#767676} 293 | .f244 {color:#808080} 294 | .f245 {color:#8a8a8a} 295 | .f246 {color:#949494} 296 | .f247 {color:#9e9e9e} 297 | .f248 {color:#a8a8a8} 298 | .f249 {color:#b2b2b2} 299 | .f250 {color:#bcbcbc} 300 | .f251 {color:#c6c6c6} 301 | .f252 {color:#d0d0d0} 302 | .f253 {color:#dadada} 303 | .f254 {color:#e4e4e4} 304 | .f255 {color:#eeeeee} 305 | .b1 {background-color:#800000} 306 | .b2 {background-color:#008000} 307 | .b3 {background-color:#808000} 308 | .b4 {background-color:#000080} 309 | .b5 {background-color:#800080} 310 | .b6 {background-color:#008080} 311 | .b7 {background-color:#c0c0c0} 312 | .b8 {background-color:#808080} 313 | .b9 {background-color:#ff0000} 314 | .b10 {background-color:#00ff00} 315 | .b11 {background-color:#ffff00} 316 | .b12 {background-color:#0000ff} 317 | .b13 {background-color:#ff00ff} 318 | .b14 {background-color:#00ffff} 319 | .b15 {background-color:#ffffff} 320 | .b16 {background-color:#000000} 321 | .b17 {background-color:#00005f} 322 | .b18 {background-color:#000087} 323 | .b19 {background-color:#0000af} 324 | .b20 {background-color:#0000d7} 325 | .b21 {background-color:#0000ff} 326 | .b22 {background-color:#005f00} 327 | .b23 {background-color:#005f5f} 328 | .b24 {background-color:#005f87} 329 | .b25 {background-color:#005faf} 330 | .b26 {background-color:#005fd7} 331 | .b27 {background-color:#005fff} 332 | .b28 {background-color:#008700} 333 | .b29 {background-color:#00875f} 334 | .b30 {background-color:#008787} 335 | .b31 {background-color:#0087af} 336 | .b32 {background-color:#0087d7} 337 | .b33 {background-color:#0087ff} 338 | .b34 {background-color:#00af00} 339 | .b35 {background-color:#00af5f} 340 | .b36 {background-color:#00af87} 341 | .b37 {background-color:#00afaf} 342 | .b38 {background-color:#00afd7} 343 | .b39 {background-color:#00afff} 344 | .b40 {background-color:#00d700} 345 | .b41 {background-color:#00d75f} 346 | .b42 {background-color:#00d787} 347 | .b43 {background-color:#00d7af} 348 | .b44 {background-color:#00d7d7} 349 | .b45 {background-color:#00d7ff} 350 | .b46 {background-color:#00ff00} 351 | .b47 {background-color:#00ff5f} 352 | .b48 {background-color:#00ff87} 353 | .b49 {background-color:#00ffaf} 354 | .b50 {background-color:#00ffd7} 355 | .b51 {background-color:#00ffff} 356 | .b52 {background-color:#5f0000} 357 | .b53 {background-color:#5f005f} 358 | .b54 {background-color:#5f0087} 359 | .b55 {background-color:#5f00af} 360 | .b56 {background-color:#5f00d7} 361 | .b57 {background-color:#5f00ff} 362 | .b58 {background-color:#5f5f00} 363 | .b59 {background-color:#5f5f5f} 364 | .b60 {background-color:#5f5f87} 365 | .b61 {background-color:#5f5faf} 366 | .b62 {background-color:#5f5fd7} 367 | .b63 {background-color:#5f5fff} 368 | .b64 {background-color:#5f8700} 369 | .b65 {background-color:#5f875f} 370 | .b66 {background-color:#5f8787} 371 | .b67 {background-color:#5f87af} 372 | .b68 {background-color:#5f87d7} 373 | .b69 {background-color:#5f87ff} 374 | .b70 {background-color:#5faf00} 375 | .b71 {background-color:#5faf5f} 376 | .b72 {background-color:#5faf87} 377 | .b73 {background-color:#5fafaf} 378 | .b74 {background-color:#5fafd7} 379 | .b75 {background-color:#5fafff} 380 | .b76 {background-color:#5fd700} 381 | .b77 {background-color:#5fd75f} 382 | .b78 {background-color:#5fd787} 383 | .b79 {background-color:#5fd7af} 384 | .b80 {background-color:#5fd7d7} 385 | .b81 {background-color:#5fd7ff} 386 | .b82 {background-color:#5fff00} 387 | .b83 {background-color:#5fff5f} 388 | .b84 {background-color:#5fff87} 389 | .b85 {background-color:#5fffaf} 390 | .b86 {background-color:#5fffd7} 391 | .b87 {background-color:#5fffff} 392 | .b88 {background-color:#870000} 393 | .b89 {background-color:#87005f} 394 | .b90 {background-color:#870087} 395 | .b91 {background-color:#8700af} 396 | .b92 {background-color:#8700d7} 397 | .b93 {background-color:#8700ff} 398 | .b94 {background-color:#875f00} 399 | .b95 {background-color:#875f5f} 400 | .b96 {background-color:#875f87} 401 | .b97 {background-color:#875faf} 402 | .b98 {background-color:#875fd7} 403 | .b99 {background-color:#875fff} 404 | .b100 {background-color:#878700} 405 | .b101 {background-color:#87875f} 406 | .b102 {background-color:#878787} 407 | .b103 {background-color:#8787af} 408 | .b104 {background-color:#8787d7} 409 | .b105 {background-color:#8787ff} 410 | .b106 {background-color:#87af00} 411 | .b107 {background-color:#87af5f} 412 | .b108 {background-color:#87af87} 413 | .b109 {background-color:#87afaf} 414 | .b110 {background-color:#87afd7} 415 | .b111 {background-color:#87afff} 416 | .b112 {background-color:#87d700} 417 | .b113 {background-color:#87d75f} 418 | .b114 {background-color:#87d787} 419 | .b115 {background-color:#87d7af} 420 | .b116 {background-color:#87d7d7} 421 | .b117 {background-color:#87d7ff} 422 | .b118 {background-color:#87ff00} 423 | .b119 {background-color:#87ff5f} 424 | .b120 {background-color:#87ff87} 425 | .b121 {background-color:#87ffaf} 426 | .b122 {background-color:#87ffd7} 427 | .b123 {background-color:#87ffff} 428 | .b124 {background-color:#af0000} 429 | .b125 {background-color:#af005f} 430 | .b126 {background-color:#af0087} 431 | .b127 {background-color:#af00af} 432 | .b128 {background-color:#af00d7} 433 | .b129 {background-color:#af00ff} 434 | .b130 {background-color:#af5f00} 435 | .b131 {background-color:#af5f5f} 436 | .b132 {background-color:#af5f87} 437 | .b133 {background-color:#af5faf} 438 | .b134 {background-color:#af5fd7} 439 | .b135 {background-color:#af5fff} 440 | .b136 {background-color:#af8700} 441 | .b137 {background-color:#af875f} 442 | .b138 {background-color:#af8787} 443 | .b139 {background-color:#af87af} 444 | .b140 {background-color:#af87d7} 445 | .b141 {background-color:#af87ff} 446 | .b142 {background-color:#afaf00} 447 | .b143 {background-color:#afaf5f} 448 | .b144 {background-color:#afaf87} 449 | .b145 {background-color:#afafaf} 450 | .b146 {background-color:#afafd7} 451 | .b147 {background-color:#afafff} 452 | .b148 {background-color:#afd700} 453 | .b149 {background-color:#afd75f} 454 | .b150 {background-color:#afd787} 455 | .b151 {background-color:#afd7af} 456 | .b152 {background-color:#afd7d7} 457 | .b153 {background-color:#afd7ff} 458 | .b154 {background-color:#afff00} 459 | .b155 {background-color:#afff5f} 460 | .b156 {background-color:#afff87} 461 | .b157 {background-color:#afffaf} 462 | .b158 {background-color:#afffd7} 463 | .b159 {background-color:#afffff} 464 | .b160 {background-color:#d70000} 465 | .b161 {background-color:#d7005f} 466 | .b162 {background-color:#d70087} 467 | .b163 {background-color:#d700af} 468 | .b164 {background-color:#d700d7} 469 | .b165 {background-color:#d700ff} 470 | .b166 {background-color:#d75f00} 471 | .b167 {background-color:#d75f5f} 472 | .b168 {background-color:#d75f87} 473 | .b169 {background-color:#d75faf} 474 | .b170 {background-color:#d75fd7} 475 | .b171 {background-color:#d75fff} 476 | .b172 {background-color:#d78700} 477 | .b173 {background-color:#d7875f} 478 | .b174 {background-color:#d78787} 479 | .b175 {background-color:#d787af} 480 | .b176 {background-color:#d787d7} 481 | .b177 {background-color:#d787ff} 482 | .b178 {background-color:#d7af00} 483 | .b179 {background-color:#d7af5f} 484 | .b180 {background-color:#d7af87} 485 | .b181 {background-color:#d7afaf} 486 | .b182 {background-color:#d7afd7} 487 | .b183 {background-color:#d7afff} 488 | .b184 {background-color:#d7d700} 489 | .b185 {background-color:#d7d75f} 490 | .b186 {background-color:#d7d787} 491 | .b187 {background-color:#d7d7af} 492 | .b188 {background-color:#d7d7d7} 493 | .b189 {background-color:#d7d7ff} 494 | .b190 {background-color:#d7ff00} 495 | .b191 {background-color:#d7ff5f} 496 | .b192 {background-color:#d7ff87} 497 | .b193 {background-color:#d7ffaf} 498 | .b194 {background-color:#d7ffd7} 499 | .b195 {background-color:#d7ffff} 500 | .b196 {background-color:#ff0000} 501 | .b197 {background-color:#ff005f} 502 | .b198 {background-color:#ff0087} 503 | .b199 {background-color:#ff00af} 504 | .b200 {background-color:#ff00d7} 505 | .b201 {background-color:#ff00ff} 506 | .b202 {background-color:#ff5f00} 507 | .b203 {background-color:#ff5f5f} 508 | .b204 {background-color:#ff5f87} 509 | .b205 {background-color:#ff5faf} 510 | .b206 {background-color:#ff5fd7} 511 | .b207 {background-color:#ff5fff} 512 | .b208 {background-color:#ff8700} 513 | .b209 {background-color:#ff875f} 514 | .b210 {background-color:#ff8787} 515 | .b211 {background-color:#ff87af} 516 | .b212 {background-color:#ff87d7} 517 | .b213 {background-color:#ff87ff} 518 | .b214 {background-color:#ffaf00} 519 | .b215 {background-color:#ffaf5f} 520 | .b216 {background-color:#ffaf87} 521 | .b217 {background-color:#ffafaf} 522 | .b218 {background-color:#ffafd7} 523 | .b219 {background-color:#ffafff} 524 | .b220 {background-color:#ffd700} 525 | .b221 {background-color:#ffd75f} 526 | .b222 {background-color:#ffd787} 527 | .b223 {background-color:#ffd7af} 528 | .b224 {background-color:#ffd7d7} 529 | .b225 {background-color:#ffd7ff} 530 | .b226 {background-color:#ffff00} 531 | .b227 {background-color:#ffff5f} 532 | .b228 {background-color:#ffff87} 533 | .b229 {background-color:#ffffaf} 534 | .b230 {background-color:#ffffd7} 535 | .b231 {background-color:#ffffff} 536 | .b232 {background-color:#080808} 537 | .b233 {background-color:#121212} 538 | .b234 {background-color:#1c1c1c} 539 | .b235 {background-color:#262626} 540 | .b236 {background-color:#303030} 541 | .b237 {background-color:#3a3a3a} 542 | .b238 {background-color:#444444} 543 | .b239 {background-color:#4e4e4e} 544 | .b240 {background-color:#585858} 545 | .b241 {background-color:#626262} 546 | .b242 {background-color:#6c6c6c} 547 | .b243 {background-color:#767676} 548 | .b244 {background-color:#808080} 549 | .b245 {background-color:#8a8a8a} 550 | .b246 {background-color:#949494} 551 | .b247 {background-color:#9e9e9e} 552 | .b248 {background-color:#a8a8a8} 553 | .b249 {background-color:#b2b2b2} 554 | .b250 {background-color:#bcbcbc} 555 | .b251 {background-color:#c6c6c6} 556 | .b252 {background-color:#d0d0d0} 557 | .b253 {background-color:#dadada} 558 | .b254 {background-color:#e4e4e4} 559 | .b255 {background-color:#eeeeee} 560 | 561 | -------------------------------------------------------------------------------- /examples/hnterm/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 17) 2 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 3 | 4 | set(TARGET hnterm) 5 | 6 | option(HNTERM_ENABLE_API_CACHE "hnterm: enable API caching" OFF) 7 | 8 | if (EMSCRIPTEN) 9 | set (CMAKE_CXX_FLAGS "-s ALLOW_MEMORY_GROWTH=1 -s FETCH=1 -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0") 10 | 11 | add_executable(${TARGET} 12 | main.cpp 13 | hn-state.cpp 14 | impl-emscripten.cpp 15 | ) 16 | 17 | target_include_directories(${TARGET} PRIVATE 18 | .. 19 | ) 20 | 21 | target_link_libraries(${TARGET} PRIVATE 22 | imtui 23 | imtui-examples-common 24 | imtui-emscripten 25 | ) 26 | 27 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/index-tmpl.html ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/index.html @ONLY) 28 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/style.css ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/style.css @ONLY) 29 | else() 30 | add_executable(${TARGET} 31 | main.cpp 32 | hn-state.cpp 33 | impl-ncurses.cpp 34 | ) 35 | 36 | target_include_directories(${TARGET} PRIVATE 37 | .. 38 | ${CURL_INCLUDE_DIR} 39 | ) 40 | 41 | target_link_libraries(${TARGET} PRIVATE 42 | imtui-ncurses 43 | imtui-examples-common 44 | ${CURL_LIBRARIES} 45 | ) 46 | endif() 47 | 48 | if (HNTERM_ENABLE_API_CACHE) 49 | target_compile_definitions(${TARGET} PRIVATE ENABLE_API_CACHE) 50 | endif() 51 | -------------------------------------------------------------------------------- /examples/hnterm/README.md: -------------------------------------------------------------------------------- 1 | *Note: this example has been moved in a standalone repository - https://github.com/ggerganov/hnterm* 2 | 3 | *Future development will continue there* 4 | 5 | # hnterm 6 | 7 | Interactive browsing of [Hacker News](https://news.ycombinator.com/news) in the terminal 8 | 9 | [![hnterm-demo](https://asciinema.org/a/gJakwNnEcgmGzZYiYIzFA1n8R.svg)](https://asciinema.org/a/gJakwNnEcgmGzZYiYIzFA1n8R) 10 | 11 | ![hnterm-dark](https://i.imgur.com/JMuM62I.png) 12 | 13 | ## Live demo in the browser 14 | 15 | The Emscripten port of HNTerm uses Emscripten's Fetch API instead of `libcurl` to perform requests to the [HN API](https://github.com/HackerNews/API). 16 | 17 | Demo: [hnterm.ggerganov.com](https://hnterm.ggerganov.com/) *(not suitable for mobile devices)* 18 | 19 | ## Details 20 | 21 | HNTerm is a small console application written in C++ for browsing [Hacker News](https://news.ycombinator.com/news). It queries the official [HN API](https://github.com/HackerNews/API) and interactively displays the current stories and comments. It uses `libcurl` to perform the GET requests to the API. The UI is rendered with [ImTui](https://github.com/ggerganov/imtui). HNTerm fetches only the content that is currently visible on the screen. The window splits allow browsing multiple stories/comment sections at the same time. 22 | 23 | ## Building 24 | 25 | ### Linux and Mac: 26 | 27 | ```bash 28 | git clone https://github.com/ggerganov/imtui --recursive 29 | cd imtui 30 | mkdir build && cd build 31 | cmake .. 32 | make 33 | 34 | ./bin/hnterm 35 | ``` 36 | -------------------------------------------------------------------------------- /examples/hnterm/hn-state.cpp: -------------------------------------------------------------------------------- 1 | /*! \file hn-state.cpp 2 | * \brief HN::State 3 | */ 4 | 5 | #include "hn-state.h" 6 | 7 | #include "json.h" 8 | 9 | extern void requestJSONForURI_impl(std::string uri); 10 | extern std::string getJSONForURI_impl(const std::string & uri); 11 | extern uint64_t getTotalBytesDownloaded(); 12 | extern int getNFetches(); 13 | extern void updateRequests_impl(); 14 | extern uint64_t t_s(); 15 | 16 | namespace { 17 | 18 | void replaceAll(std::string & s, const std::string & search, const std::string & replace) { 19 | for (size_t pos = 0; ; pos += replace.length()) { 20 | pos = s.find(search, pos); 21 | if (pos == std::string::npos) break; 22 | s.erase(pos, search.length()); 23 | s.insert(pos, replace); 24 | } 25 | } 26 | 27 | std::string getJSONForURI(const std::string & uri) { 28 | auto responseString = getJSONForURI_impl(uri); 29 | if (responseString == "") return ""; 30 | 31 | return responseString; 32 | } 33 | 34 | } 35 | 36 | namespace HN { 37 | 38 | // todo : optimize this 39 | std::string parseHTML(std::string str) { 40 | ::replaceAll(str, "

", "\n"); 41 | 42 | std::string res; 43 | bool inTag = false; 44 | for (auto & ch : str) { 45 | if (ch == '<') { 46 | inTag = true; 47 | } else if (ch == '>') { 48 | inTag = false; 49 | } else { 50 | if (inTag == false) { 51 | res += ch; 52 | } 53 | } 54 | } 55 | 56 | ::replaceAll(res, "/", "/"); 57 | ::replaceAll(res, "'", "'"); 58 | ::replaceAll(res, ">", ">"); 59 | ::replaceAll(res, "–", "-"); 60 | ::replaceAll(res, "“", "\""); 61 | ::replaceAll(res, "”", "\""); 62 | ::replaceAll(res, "‘", "'"); 63 | ::replaceAll(res, "’", "'"); 64 | ::replaceAll(res, "„", "'"); 65 | ::replaceAll(res, """, "\""); 66 | ::replaceAll(res, "&", "&"); 67 | ::replaceAll(res, "—", "-"); 68 | 69 | return res; 70 | } 71 | 72 | URI getItemURI(ItemId id) { 73 | return kAPIItem + std::to_string(id) + ".json"; 74 | } 75 | 76 | ItemType getItemType(const ItemData & itemData) { 77 | if (itemData.find("type") == itemData.end()) return ItemType::Unknown; 78 | 79 | auto strType = itemData.at("type"); 80 | 81 | if (strType == "story") return ItemType::Story; 82 | if (strType == "comment") return ItemType::Comment; 83 | if (strType == "job") return ItemType::Job; 84 | if (strType == "poll") return ItemType::Poll; 85 | if (strType == "pollopt") return ItemType::PollOpt; 86 | 87 | return ItemType::Unknown; 88 | } 89 | 90 | void parseStory(const ItemData & data, Story & res) { 91 | try { 92 | res.by = data.at("by"); 93 | } catch (...) { 94 | res.by = "[deleted]"; 95 | } 96 | try { 97 | res.descendants = std::stoi(data.at("descendants")); 98 | } catch (...) { 99 | res.descendants = 0; 100 | } 101 | try { 102 | res.id = std::stoi(data.at("id")); 103 | } catch (...) { 104 | res.id = 0; 105 | } 106 | try { 107 | res.kids = JSON::parseIntArray(data.at("kids")); 108 | } catch (...) { 109 | res.kids.clear(); 110 | } 111 | try { 112 | res.score = std::stoi(data.at("score")); 113 | } catch (...) { 114 | res.score = 0; 115 | } 116 | try { 117 | res.time = std::stoll(data.at("time")); 118 | } catch (...) { 119 | res.time = 0; 120 | } 121 | try { 122 | res.text = parseHTML(data.at("text")); 123 | } catch (...) { 124 | res.text = ""; 125 | } 126 | try { 127 | res.title = parseHTML(data.at("title")); 128 | } catch (...) { 129 | res.title = ""; 130 | } 131 | try { 132 | res.url = data.at("url"); 133 | res.domain = ""; 134 | int slash = 0; 135 | for (auto & ch : res.url) { 136 | if (ch == '/') { 137 | ++slash; 138 | continue; 139 | } 140 | if (slash > 2) break; 141 | if (slash > 1) res.domain += ch; 142 | } 143 | } catch (...) { 144 | res.url = ""; 145 | res.domain = ""; 146 | } 147 | } 148 | 149 | void parseComment(const ItemData & data, Comment & res) { 150 | try { 151 | res.by = data.at("by"); 152 | } catch (...) { 153 | res.by = "[deleted]"; 154 | } 155 | try { 156 | res.id = std::stoi(data.at("id")); 157 | } catch (...) { 158 | res.id = 0; 159 | } 160 | try { 161 | res.kids = JSON::parseIntArray(data.at("kids")); 162 | } catch (...) { 163 | res.kids.clear(); 164 | } 165 | try { 166 | res.parent = std::stoi(data.at("parent")); 167 | } catch (...) { 168 | res.parent = 0; 169 | } 170 | try { 171 | res.text = parseHTML(data.at("text")); 172 | } catch (...) { 173 | res.text = ""; 174 | } 175 | try { 176 | res.time = std::stoll(data.at("time")); 177 | } catch (...) { 178 | res.time = 0; 179 | } 180 | } 181 | 182 | void parseJob(const ItemData & data, Job & res) { 183 | try { 184 | res.by = data.at("by"); 185 | } catch (...) { 186 | res.by = "[deleted]"; 187 | } 188 | try { 189 | res.id = std::stoi(data.at("id")); 190 | } catch (...) { 191 | res.id = 0; 192 | } 193 | try { 194 | res.score = std::stoi(data.at("score")); 195 | } catch (...) { 196 | res.score = 0; 197 | } 198 | try { 199 | res.time = std::stoll(data.at("time")); 200 | } catch (...) { 201 | res.time = 0; 202 | } 203 | try { 204 | res.title = parseHTML(data.at("title")); 205 | } catch (...) { 206 | res.title = ""; 207 | } 208 | try { 209 | res.url = data.at("url"); 210 | res.domain = ""; 211 | int slash = 0; 212 | for (auto & ch : res.url) { 213 | if (ch == '/') { 214 | ++slash; 215 | continue; 216 | } 217 | if (slash > 2) break; 218 | if (slash > 1) res.domain += ch; 219 | } 220 | } catch (...) { 221 | res.url = ""; 222 | res.domain = ""; 223 | } 224 | } 225 | 226 | ItemIds getStoriesIds(const URI & uri) { 227 | return JSON::parseIntArray(getJSONForURI(uri)); 228 | } 229 | 230 | ItemIds getChangedItemsIds() { 231 | auto data = JSON::parseJSONMap(getJSONForURI(HN::kAPIUpdates)); 232 | return JSON::parseIntArray(data["items"]); 233 | } 234 | 235 | 236 | bool State::update(const ItemIds & toRefresh) { 237 | bool updated = false; 238 | 239 | auto now = ::t_s(); 240 | 241 | if (timeout(now, lastUpdatePoll_s)) { 242 | requestJSONForURI(HN::kAPITopStories); 243 | //requestJSONForURI(HN::kAPIBestStories); 244 | requestJSONForURI(HN::kAPIShowStories); 245 | requestJSONForURI(HN::kAPIAskStories); 246 | requestJSONForURI(HN::kAPINewStories); 247 | requestJSONForURI(HN::kAPIUpdates); 248 | 249 | lastUpdatePoll_s = ::t_s(); 250 | updated = true; 251 | } else { 252 | nextUpdate = lastUpdatePoll_s + 30 - now; 253 | } 254 | 255 | { 256 | { 257 | auto ids = HN::getStoriesIds(HN::kAPITopStories); 258 | if (ids.empty() == false) { 259 | idsTop = std::move(ids); 260 | updated = true; 261 | } 262 | } 263 | 264 | //{ 265 | // auto ids = HN::getStoriesIds(HN::kAPIBestStories); 266 | // if (ids.empty() == false) { 267 | // idsBest = std::move(ids); 268 | // } 269 | //} 270 | 271 | { 272 | auto ids = HN::getStoriesIds(HN::kAPIShowStories); 273 | if (ids.empty() == false) { 274 | idsShow = std::move(ids); 275 | updated = true; 276 | } 277 | } 278 | 279 | { 280 | auto ids = HN::getStoriesIds(HN::kAPIAskStories); 281 | if (ids.empty() == false) { 282 | idsAsk = std::move(ids); 283 | updated = true; 284 | } 285 | } 286 | 287 | { 288 | auto ids = HN::getStoriesIds(HN::kAPINewStories); 289 | if (ids.empty() == false) { 290 | idsNew = std::move(ids); 291 | updated = true; 292 | } 293 | } 294 | } 295 | 296 | { 297 | auto ids = HN::getChangedItemsIds(); 298 | for (auto id : ids) { 299 | if (items.find(id) == items.end()) continue; 300 | items[id].needUpdate = true; 301 | items[id].needRequest = true; 302 | } 303 | } 304 | 305 | for (auto id : toRefresh) { 306 | if (items[id].needRequest == false) continue; 307 | 308 | requestJSONForURI(getItemURI(id)); 309 | items[id].needRequest = false; 310 | updated = true; 311 | } 312 | 313 | for (auto id : toRefresh) { 314 | if (items[id].needUpdate == false) continue; 315 | 316 | const auto json = getJSONForURI(getItemURI(id)); 317 | if (json == "") continue; 318 | 319 | const auto data = JSON::parseJSONMap(json); 320 | const auto type = getItemType(data); 321 | auto & item = items[id]; 322 | switch (type) { 323 | case ItemType::Unknown: 324 | { 325 | } 326 | break; 327 | case ItemType::Story: 328 | { 329 | if (std::holds_alternative(item.data) == false) { 330 | item.data = Story(); 331 | } 332 | 333 | Story & story = std::get(item.data); 334 | if (item.needUpdate) { 335 | parseStory(data, story); 336 | item.needUpdate = false; 337 | updated = true; 338 | } 339 | } 340 | break; 341 | case ItemType::Comment: 342 | { 343 | if (std::holds_alternative(item.data) == false) { 344 | item.data = Comment(); 345 | } 346 | 347 | Comment & story = std::get(item.data); 348 | if (item.needUpdate) { 349 | parseComment(data, story); 350 | item.needUpdate = false; 351 | updated = true; 352 | } 353 | } 354 | break; 355 | case ItemType::Job: 356 | { 357 | if (std::holds_alternative(item.data) == false) { 358 | item.data = Job(); 359 | } 360 | 361 | Job & job = std::get(item.data); 362 | if (item.needUpdate) { 363 | parseJob(data, job); 364 | item.needUpdate = false; 365 | updated = true; 366 | } 367 | } 368 | break; 369 | case ItemType::Poll: 370 | { 371 | // temp 372 | item.needUpdate = false; 373 | } 374 | break; 375 | case ItemType::PollOpt: 376 | { 377 | // temp 378 | item.needUpdate = false; 379 | } 380 | break; 381 | }; 382 | 383 | break; 384 | } 385 | 386 | nFetches = getNFetches(); 387 | totalBytesDownloaded = getTotalBytesDownloaded(); 388 | 389 | updateRequests_impl(); 390 | 391 | return updated; 392 | } 393 | 394 | void State::forceUpdate(const ItemIds & toUpdate) { 395 | auto tNow_s = t_s(); 396 | for (auto id : toUpdate) { 397 | if (items.find(id) == items.end()) continue; 398 | if (tNow_s - items[id].lastForceUpdate_s > 60) { 399 | items[id].needUpdate = true; 400 | items[id].needRequest = true; 401 | 402 | items[id].lastForceUpdate_s = tNow_s; 403 | } 404 | } 405 | } 406 | 407 | bool State::timeout(uint64_t now, uint64_t last) const { 408 | return now - last > 30; 409 | } 410 | 411 | std::string State::timeSince(uint64_t t) const { 412 | auto delta = t_s() - t; 413 | if (delta < 60) return std::to_string(delta) + " seconds"; 414 | if (delta < 3600) return std::to_string(delta/60) + " minutes"; 415 | if (delta < 24*3600) return std::to_string(delta/3600) + " hours"; 416 | return std::to_string(delta/24/3600) + " days"; 417 | } 418 | 419 | void State::requestJSONForURI(std::string uri) { 420 | snprintf(curURI, 512, "%s", uri.c_str()); 421 | 422 | requestJSONForURI_impl(std::move(uri)); 423 | } 424 | 425 | } 426 | -------------------------------------------------------------------------------- /examples/hnterm/hn-state.h: -------------------------------------------------------------------------------- 1 | /*! \file hn-state.h 2 | * \brief HN::State 3 | */ 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace HN { 14 | 15 | using URI = std::string; 16 | using ItemId = int; 17 | using ItemIds = std::vector; 18 | using ItemData = std::map; 19 | 20 | static const std::string kCmdPrefix = "curl -s -k "; 21 | 22 | static const URI kAPIItem = "https://hacker-news.firebaseio.com/v0/item/"; 23 | static const URI kAPITopStories = "https://hacker-news.firebaseio.com/v0/topstories.json"; 24 | static const URI kAPINewStories = "https://hacker-news.firebaseio.com/v0/newstories.json"; 25 | //static const URI kAPIBestStories = "https://hacker-news.firebaseio.com/v0/beststories.json"; 26 | static const URI kAPIAskStories = "https://hacker-news.firebaseio.com/v0/askstories.json"; 27 | static const URI kAPIShowStories = "https://hacker-news.firebaseio.com/v0/showstories.json"; 28 | static const URI kAPIJobStories = "https://hacker-news.firebaseio.com/v0/jobstories.json"; 29 | static const URI kAPIUpdates = "https://hacker-news.firebaseio.com/v0/updates.json"; 30 | 31 | struct Story { 32 | std::string by = ""; 33 | int descendants = 0; 34 | ItemId id = 0; 35 | ItemIds kids; 36 | int score = 0; 37 | uint64_t time = 0; 38 | std::string text = ""; 39 | std::string title = ""; 40 | std::string url = ""; 41 | std::string domain = ""; 42 | }; 43 | 44 | struct Comment { 45 | std::string by = ""; 46 | ItemId id = 0; 47 | ItemIds kids; 48 | ItemId parent = 0; 49 | std::string text = ""; 50 | uint64_t time = 0; 51 | }; 52 | 53 | struct Job { 54 | std::string by = ""; 55 | ItemId id = 0; 56 | int score = 0; 57 | uint64_t time = 0; 58 | std::string title = ""; 59 | std::string url = ""; 60 | std::string domain = ""; 61 | }; 62 | 63 | struct Poll {}; 64 | struct PollOpt {}; 65 | 66 | enum class ItemType : int { 67 | Unknown, 68 | Story, 69 | Comment, 70 | Job, 71 | Poll, 72 | PollOpt, 73 | }; 74 | 75 | struct Item { 76 | ItemType type = ItemType::Unknown; 77 | 78 | bool needUpdate = true; 79 | bool needRequest = true; 80 | 81 | uint64_t lastForceUpdate_s = 0; 82 | 83 | std::variant data; 84 | }; 85 | 86 | struct State { 87 | bool update(const ItemIds & toRefresh); 88 | void forceUpdate(const ItemIds & toUpdate); 89 | 90 | bool timeout(uint64_t now, uint64_t last) const; 91 | std::string timeSince(uint64_t t) const; 92 | 93 | ItemIds idsTop; 94 | //ItemIds idsBest; 95 | ItemIds idsShow; 96 | ItemIds idsAsk; 97 | ItemIds idsNew; 98 | 99 | std::map items; 100 | 101 | int nFetches = 0; 102 | uint64_t totalBytesDownloaded = 0; 103 | 104 | uint64_t lastUpdatePoll_s = 0; 105 | 106 | char curURI[512]; 107 | int nextUpdate = 0; 108 | 109 | private: 110 | void requestJSONForURI(std::string uri); 111 | }; 112 | 113 | } 114 | 115 | -------------------------------------------------------------------------------- /examples/hnterm/impl-emscripten.cpp: -------------------------------------------------------------------------------- 1 | /*! \file impl-emscripten.cpp 2 | * \brief Enter description here. 3 | */ 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | static int g_nFetches; 13 | static uint64_t g_totalBytesDownloaded = 0; 14 | 15 | // not sure if this mutex is needed, but just in case 16 | static std::mutex g_mutex; 17 | static std::map g_fetchCache; 18 | 19 | uint64_t t_s() { 20 | return emscripten_date_now()*0.001f; 21 | } 22 | 23 | bool hnInit() { 24 | return true; 25 | } 26 | 27 | void hnFree() { 28 | } 29 | 30 | int openInBrowser(std::string uri) { 31 | std::string cmd = "window.open('" + uri + "');"; 32 | emscripten_run_script(cmd.c_str()); 33 | 34 | return 0; 35 | } 36 | 37 | void downloadSucceeded(emscripten_fetch_t *fetch) { 38 | g_totalBytesDownloaded += fetch->numBytes; 39 | 40 | //printf("Finished downloading %llu bytes from URL %s.\n", fetch->numBytes, fetch->url); 41 | { 42 | std::lock_guard lock(g_mutex); 43 | g_fetchCache[fetch->url] = std::string(fetch->data, fetch->numBytes-1); 44 | } 45 | emscripten_fetch_close(fetch); 46 | } 47 | 48 | void downloadFailed(emscripten_fetch_t *fetch) { 49 | fprintf(stderr, "Downloading %s failed, HTTP failure status code: %d.\n", fetch->url, fetch->status); 50 | emscripten_fetch_close(fetch); 51 | } 52 | 53 | std::string getJSONForURI_impl(const std::string & uri) { 54 | if (auto it = g_fetchCache.find(uri); it != g_fetchCache.end()) { 55 | auto res = std::move(g_fetchCache[uri]); 56 | g_fetchCache.erase(it); 57 | 58 | return res; 59 | } 60 | 61 | return ""; 62 | } 63 | 64 | uint64_t getTotalBytesDownloaded() { 65 | return g_totalBytesDownloaded; 66 | } 67 | 68 | int getNFetches() { 69 | return g_nFetches; 70 | } 71 | 72 | void requestJSONForURI_impl(std::string uri) { 73 | ++g_nFetches; 74 | 75 | emscripten_fetch_attr_t attr; 76 | emscripten_fetch_attr_init(&attr); 77 | strcpy(attr.requestMethod, "GET"); 78 | attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; 79 | attr.onsuccess = downloadSucceeded; 80 | attr.onerror = downloadFailed; 81 | emscripten_fetch(&attr, uri.c_str()); 82 | } 83 | 84 | void updateRequests_impl() { 85 | } 86 | -------------------------------------------------------------------------------- /examples/hnterm/impl-ncurses.cpp: -------------------------------------------------------------------------------- 1 | /*! \file impl-ncurses.cpp 2 | * \brief Enter description here. 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #ifndef WIN32 12 | #include 13 | #endif 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #define MAX_PARALLEL 5 23 | 24 | //#define DEBUG_SIGPIPE 25 | 26 | #if defined(DEBUG_SIGPIPE) || defined(ENABLE_API_CACHE) 27 | #include 28 | #endif 29 | 30 | namespace { 31 | #ifdef ENABLE_API_CACHE 32 | inline std::string getCacheFname(const std::string & uri) { 33 | std::string fname = "cache-" + uri; 34 | for (auto & ch : fname) { 35 | if ((ch >= 'a' && ch <= 'z') || 36 | (ch >= 'A' && ch <= 'Z') || 37 | (ch >= '0' && ch <= '9')) { 38 | continue; 39 | } 40 | ch = '-'; 41 | } 42 | 43 | return fname; 44 | } 45 | #endif 46 | } 47 | 48 | void sigpipe_handler([[maybe_unused]] int signal) { 49 | #ifdef DEBUG_SIGPIPE 50 | std::ofstream fout("SIGPIPE.OCCURED"); 51 | fout << signal << std::endl; 52 | fout.close(); 53 | #endif 54 | } 55 | 56 | struct Data { 57 | CURL *eh = NULL; 58 | bool running = false; 59 | std::string uri = ""; 60 | std::string content = ""; 61 | }; 62 | 63 | static CURLM *g_cm; 64 | 65 | static int g_nFetches = 0; 66 | static uint64_t g_totalBytesDownloaded = 0; 67 | static std::deque g_fetchQueue; 68 | static std::map g_fetchCache; 69 | static std::array g_fetchData; 70 | 71 | uint64_t t_s() { 72 | return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); // duh .. 73 | } 74 | 75 | static size_t writeFunction(void *ptr, size_t size, size_t nmemb, Data* data) { 76 | size_t bytesDownloaded = size*nmemb; 77 | g_totalBytesDownloaded += bytesDownloaded; 78 | 79 | data->content.append((char*) ptr, bytesDownloaded); 80 | 81 | #ifdef ENABLE_API_CACHE 82 | auto fname = ::getCacheFname(data->uri); 83 | 84 | std::ofstream fout(fname); 85 | fout.write(data->content.c_str(), data->content.size()); 86 | fout.close(); 87 | #endif 88 | 89 | g_fetchCache[data->uri] = std::move(data->content); 90 | data->content.clear(); 91 | 92 | return bytesDownloaded; 93 | } 94 | 95 | static void addTransfer(CURLM *cm, int idx, std::string && uri) { 96 | if (g_fetchData[idx].eh == NULL) { 97 | g_fetchData[idx].eh = curl_easy_init(); 98 | } 99 | 100 | CURL *eh = g_fetchData[idx].eh; 101 | curl_easy_setopt(eh, CURLOPT_URL, uri.c_str()); 102 | curl_easy_setopt(eh, CURLOPT_PRIVATE, &g_fetchData[idx]); 103 | curl_easy_setopt(eh, CURLOPT_WRITEFUNCTION, writeFunction); 104 | curl_easy_setopt(eh, CURLOPT_WRITEDATA, &g_fetchData[idx]); 105 | curl_multi_add_handle(cm, eh); 106 | } 107 | 108 | bool hnInit() { 109 | #ifndef _WIN32 110 | struct sigaction sh; 111 | struct sigaction osh; 112 | 113 | sh.sa_handler = &sigpipe_handler; //Can set to SIG_IGN 114 | // Restart interrupted system calls 115 | sh.sa_flags = SA_RESTART; 116 | 117 | // Block every signal during the handler 118 | sigemptyset(&sh.sa_mask); 119 | 120 | if (sigaction(SIGPIPE, &sh, &osh) < 0) { 121 | return false; 122 | } 123 | #endif 124 | 125 | curl_global_init(CURL_GLOBAL_ALL); 126 | g_cm = curl_multi_init(); 127 | 128 | curl_multi_setopt(g_cm, CURLMOPT_MAXCONNECTS, (long)MAX_PARALLEL); 129 | 130 | return true; 131 | } 132 | 133 | void hnFree() { 134 | curl_multi_cleanup(g_cm); 135 | curl_global_cleanup(); 136 | } 137 | 138 | int openInBrowser(std::string uri) { 139 | #ifdef __APPLE__ 140 | std::string cmd = "open " + uri + " > /dev/null 2>&1"; 141 | #else 142 | std::string cmd = "xdg-open " + uri + " > /dev/null 2>&1"; 143 | #endif 144 | return system(cmd.c_str()); 145 | } 146 | 147 | std::string getJSONForURI_impl(const std::string & uri) { 148 | if (auto it = g_fetchCache.find(uri); it != g_fetchCache.end()) { 149 | auto res = std::move(g_fetchCache[uri]); 150 | g_fetchCache.erase(it); 151 | 152 | return res; 153 | } 154 | 155 | return ""; 156 | } 157 | 158 | uint64_t getTotalBytesDownloaded() { 159 | return g_totalBytesDownloaded; 160 | } 161 | 162 | uint64_t getNFetches() { 163 | return g_nFetches; 164 | } 165 | 166 | void requestJSONForURI_impl(std::string uri) { 167 | g_fetchQueue.push_back(std::move(uri)); 168 | } 169 | 170 | void updateRequests_impl() { 171 | CURLMsg *msg; 172 | int msgs_left = -1; 173 | 174 | while ((msg = curl_multi_info_read(g_cm, &msgs_left))) { 175 | if (msg->msg == CURLMSG_DONE) { 176 | Data* data; 177 | CURL *e = msg->easy_handle; 178 | curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &data); 179 | data->running = false; 180 | curl_multi_remove_handle(g_cm, e); 181 | //curl_easy_cleanup(e); 182 | //data->eh = NULL; 183 | } else { 184 | fprintf(stderr, "E: CURLMsg (%d)\n", msg->msg); 185 | } 186 | } 187 | 188 | int still_alive = 1; 189 | 190 | curl_multi_perform(g_cm, &still_alive); 191 | 192 | while (still_alive < MAX_PARALLEL && g_fetchQueue.size() > 0) { 193 | long unsigned int idx = 0; 194 | while (g_fetchData[idx].running) { 195 | ++idx; 196 | if (idx == g_fetchData.size()) break; 197 | } 198 | if (idx == g_fetchData.size()) break; 199 | 200 | auto uri = std::move(g_fetchQueue.front()); 201 | g_fetchQueue.pop_front(); 202 | 203 | #ifdef ENABLE_API_CACHE 204 | auto fname = ::getCacheFname(uri); 205 | 206 | std::ifstream fin(fname); 207 | if (fin.is_open() && fin.good()) { 208 | std::string str((std::istreambuf_iterator(fin)), std::istreambuf_iterator()); 209 | fin.close(); 210 | 211 | g_fetchCache[uri] = std::move(str); 212 | 213 | continue; 214 | } 215 | #endif 216 | 217 | ++g_nFetches; 218 | 219 | g_fetchData[idx].running = true; 220 | g_fetchData[idx].uri = uri; 221 | addTransfer(g_cm, idx, std::move(uri)); 222 | 223 | ++still_alive; 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /examples/hnterm/index-tmpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HNTerm - Browser Hacker News in the terminal 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |

20 |
 
21 |
22 | 23 |
24 | 25 | | 26 | Build time: @GIT_DATE@ | 27 | Commit hash: @GIT_SHA1@ | 28 | Commit subject: @GIT_COMMIT_SUBJECT@ | 29 | 30 |
31 |
32 | View on GitHub 33 | 34 | 35 |
36 | 37 | 296 | 297 | 298 | 299 | -------------------------------------------------------------------------------- /examples/hnterm/json.h: -------------------------------------------------------------------------------- 1 | /*! \file json.h 2 | * \brief Json parsing 3 | */ 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace JSON { 12 | 13 | std::vector parseIntArray(const std::string & json) { 14 | std::vector res; 15 | if (json[0] != '[') return res; 16 | 17 | int n = json.size(); 18 | int curid = 0; 19 | for (int i = 1; i < n; ++i) { 20 | if (json[i] == ' ') continue; 21 | if (json[i] == ',') { 22 | res.push_back(curid); 23 | curid = 0; 24 | continue; 25 | } 26 | 27 | if (json[i] == ']') { 28 | res.push_back(curid); 29 | break; 30 | } 31 | 32 | curid = 10*curid + json[i] - 48; 33 | } 34 | 35 | return res; 36 | } 37 | 38 | std::map parseJSONMap(const std::string & json) { 39 | std::map res; 40 | if (json[0] != '{') return res; 41 | 42 | bool hasKey = false; 43 | bool inToken = false; 44 | 45 | std::string strKey = ""; 46 | std::string strVal = ""; 47 | 48 | int n = json.size(); 49 | for (int i = 1; i < n; ++i) { 50 | if (!inToken) { 51 | if (json[i] == ' ') continue; 52 | if (json[i] == '"' && json[i-1] != '\\') { 53 | inToken = true; 54 | continue; 55 | } 56 | } else { 57 | if (json[i] == '"' && json[i-1] != '\\') { 58 | if (hasKey == false) { 59 | hasKey = true; 60 | ++i; 61 | while (json[i] == ' ') ++i; 62 | ++i; // : 63 | while (json[i] == ' ') ++i; 64 | if (json[i] == '[') { 65 | while (json[i] != ']') { 66 | strVal += json[i++]; 67 | } 68 | strVal += ']'; 69 | hasKey = false; 70 | res[strKey] = strVal; 71 | strKey = ""; 72 | strVal = ""; 73 | } else if (json[i] != '\"') { 74 | while (json[i] != ',' && json[i] != '}') { 75 | strVal += json[i++]; 76 | } 77 | hasKey = false; 78 | res[strKey] = strVal; 79 | strKey = ""; 80 | strVal = ""; 81 | } else { 82 | inToken = true; 83 | continue; 84 | } 85 | } else { 86 | hasKey = false; 87 | res[strKey] = strVal; 88 | strKey = ""; 89 | strVal = ""; 90 | } 91 | inToken = false; 92 | continue; 93 | } 94 | if (hasKey == false) { 95 | strKey += json[i]; 96 | } else { 97 | strVal += json[i]; 98 | } 99 | } 100 | } 101 | 102 | return res; 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /examples/hnterm/main.cpp: -------------------------------------------------------------------------------- 1 | /*! \file main.cpp 2 | * \brief HNTerm - browse Hacker News in the terminal 3 | */ 4 | 5 | #include "imtui/imtui.h" 6 | 7 | #include "hn-state.h" 8 | 9 | #ifdef __EMSCRIPTEN__ 10 | 11 | #include "imtui/imtui-impl-emscripten.h" 12 | 13 | #include 14 | #include 15 | 16 | #else 17 | 18 | #define EMSCRIPTEN_KEEPALIVE 19 | 20 | #include "imtui/imtui-impl-ncurses.h" 21 | 22 | #endif 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | // global vars 31 | bool g_updated = false; 32 | ImTui::TScreen * g_screen = nullptr; 33 | 34 | // platform specific functions 35 | extern bool hnInit(); 36 | extern void hnFree(); 37 | extern int openInBrowser(std::string uri); 38 | 39 | // helper functions 40 | namespace { 41 | 42 | [[maybe_unused]] 43 | std::map parseCmdArguments(int argc, char ** argv) { 44 | int last = argc; 45 | std::map res; 46 | for (int i = 1; i < last; ++i) { 47 | res[argv[i]] = ""; 48 | if (argv[i][0] == '-') { 49 | if (strlen(argv[i]) > 1) { 50 | res[std::string(1, argv[i][1])] = strlen(argv[i]) > 2 ? argv[i] + 2 : ""; 51 | } 52 | } 53 | } 54 | 55 | return res; 56 | } 57 | 58 | } 59 | 60 | namespace UI { 61 | 62 | enum class WindowContent : int { 63 | Top, 64 | //Best, 65 | Show, 66 | Ask, 67 | New, 68 | Count, 69 | }; 70 | 71 | enum class StoryListMode : int { 72 | Micro, 73 | Normal, 74 | Spread, 75 | COUNT, 76 | }; 77 | 78 | enum class ColorScheme : int { 79 | Default, 80 | Dark, 81 | Green, 82 | COUNT, 83 | }; 84 | 85 | std::map kContentStr = { 86 | { WindowContent::Top, "Top" }, 87 | //{ WindowContent::Best, "Best" }, 88 | { WindowContent::Show, "Show" }, 89 | { WindowContent::Ask, "Ask" }, 90 | { WindowContent::New, "New" }, 91 | }; 92 | 93 | struct WindowData { 94 | WindowContent content; 95 | bool showComments = false; 96 | HN::ItemId selectedStoryId = 0; 97 | int hoveredStoryId = 0; 98 | int hoveredCommentId = 0; 99 | int maxStories = 10; 100 | }; 101 | 102 | struct State { 103 | int hoveredWindowId = 0; 104 | int statusWindowHeight = 4; 105 | 106 | ColorScheme colorScheme = ColorScheme::Dark; 107 | StoryListMode storyListMode = StoryListMode::Normal; 108 | #ifdef __EMSCRIPTEN__ 109 | bool showHelpWelcome = true; 110 | #else 111 | bool showHelpWelcome = false; 112 | #endif 113 | bool showHelpModal = false; 114 | bool showStatusWindow = true; 115 | 116 | int nWindows = 2; 117 | 118 | char statusWindowHeader[512]; 119 | 120 | std::map collapsed; 121 | 122 | void changeColorScheme(bool inc = true) { 123 | if (inc) { 124 | colorScheme = (ColorScheme)(((int) colorScheme + 1) % ((int)ColorScheme::COUNT)); 125 | } 126 | 127 | ImVec4* colors = ImGui::GetStyle().Colors; 128 | switch (colorScheme) { 129 | case ColorScheme::Default: 130 | { 131 | colors[ImGuiCol_Text] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); 132 | colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); 133 | colors[ImGuiCol_WindowBg] = ImVec4(0.96f, 0.96f, 0.94f, 1.00f); 134 | colors[ImGuiCol_TitleBg] = ImVec4(1.00f, 0.40f, 0.00f, 1.00f); 135 | colors[ImGuiCol_TitleBgActive] = ImVec4(1.00f, 0.40f, 0.00f, 1.00f); 136 | colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.69f, 0.25f, 0.00f, 1.00f); 137 | colors[ImGuiCol_ChildBg] = ImVec4(0.96f, 0.96f, 0.94f, 1.00f); 138 | colors[ImGuiCol_PopupBg] = ImVec4(0.96f, 0.96f, 0.94f, 1.00f); 139 | colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); 140 | } 141 | break; 142 | case ColorScheme::Dark: 143 | { 144 | colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); 145 | colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); 146 | colors[ImGuiCol_WindowBg] = ImVec4(0.10f, 0.10f, 0.10f, 1.00f); 147 | colors[ImGuiCol_TitleBg] = ImVec4(1.00f, 0.40f, 0.00f, 0.50f); 148 | colors[ImGuiCol_TitleBgActive] = ImVec4(1.00f, 0.40f, 0.00f, 0.50f); 149 | colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.69f, 0.25f, 0.00f, 0.50f); 150 | colors[ImGuiCol_ChildBg] = ImVec4(0.10f, 0.10f, 0.10f, 1.00f); 151 | colors[ImGuiCol_PopupBg] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f); 152 | colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); 153 | } 154 | break; 155 | case ColorScheme::Green: 156 | { 157 | colors[ImGuiCol_Text] = ImVec4(0.00f, 1.00f, 0.00f, 1.00f); 158 | colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); 159 | colors[ImGuiCol_WindowBg] = ImVec4(0.10f, 0.10f, 0.10f, 1.00f); 160 | colors[ImGuiCol_TitleBg] = ImVec4(0.25f, 0.25f, 0.25f, 1.00f); 161 | colors[ImGuiCol_TitleBgActive] = ImVec4(0.25f, 0.25f, 0.25f, 1.00f); 162 | colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.50f, 1.00f, 0.50f, 1.00f); 163 | colors[ImGuiCol_ChildBg] = ImVec4(0.10f, 0.10f, 0.10f, 1.00f); 164 | colors[ImGuiCol_PopupBg] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); 165 | colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); 166 | } 167 | break; 168 | default: 169 | { 170 | } 171 | } 172 | } 173 | 174 | std::array windows { { 175 | { 176 | WindowContent::Top, 177 | false, 178 | 0, 0, 0, 10, 179 | }, 180 | { 181 | WindowContent::Show, 182 | false, 183 | 0, 0, 0, 10, 184 | }, 185 | { 186 | WindowContent::New, 187 | false, 188 | 0, 0, 0, 10, 189 | }, 190 | } }; 191 | }; 192 | 193 | } 194 | 195 | // HackerNews content 196 | HN::State stateHN; 197 | 198 | // UI state 199 | UI::State stateUI; 200 | 201 | extern "C" { 202 | EMSCRIPTEN_KEEPALIVE 203 | bool render_frame() { 204 | HN::ItemIds toUpdate; 205 | HN::ItemIds toRefresh; 206 | 207 | #ifdef __EMSCRIPTEN__ 208 | ImTui_ImplEmscripten_NewFrame(); 209 | #else 210 | bool isActive = g_updated; 211 | isActive |= ImTui_ImplNcurses_NewFrame(); 212 | #endif 213 | ImTui_ImplText_NewFrame(); 214 | 215 | ImGui::NewFrame(); 216 | 217 | if (ImGui::GetIO().DisplaySize.x < 50) { 218 | stateUI.nWindows = 1; 219 | } 220 | 221 | for (int windowId = 0; windowId < stateUI.nWindows; ++windowId) { 222 | const auto & items = stateHN.items; 223 | auto & window = stateUI.windows[windowId]; 224 | 225 | { 226 | auto wSize = ImGui::GetIO().DisplaySize; 227 | wSize.x /= stateUI.nWindows; 228 | if (stateUI.showStatusWindow) { 229 | wSize.y -= stateUI.statusWindowHeight; 230 | } 231 | wSize.x = int(wSize.x); 232 | ImGui::SetNextWindowPos(ImVec2(wSize.x*windowId, 0), ImGuiCond_Always); 233 | 234 | if (windowId < stateUI.nWindows - 1) { 235 | wSize.x -= 1.1; 236 | } 237 | ImGui::SetNextWindowSize(wSize, ImGuiCond_Always); 238 | } 239 | 240 | std::string title = "[Y] Hacker News (" + UI::kContentStr[window.content] + ")##" + std::to_string(windowId); 241 | ImGui::Begin(title.c_str(), nullptr, 242 | ImGuiWindowFlags_NoCollapse | 243 | ImGuiWindowFlags_NoResize | 244 | ImGuiWindowFlags_NoMove | 245 | ImGuiWindowFlags_NoScrollbar); 246 | 247 | ImGui::Text("%s", ""); 248 | if (window.showComments == false) { 249 | const auto & storyIds = 250 | (window.content == UI::WindowContent::Top) ? stateHN.idsTop : 251 | (window.content == UI::WindowContent::Show) ? stateHN.idsShow : 252 | (window.content == UI::WindowContent::New) ? stateHN.idsNew : 253 | stateHN.idsTop; 254 | 255 | int nShow = std::min(window.maxStories, (int) storyIds.size()); 256 | for (int i = 0; i < nShow; ++i) { 257 | const auto & id = storyIds[i]; 258 | 259 | toRefresh.push_back(id); 260 | if (items.find(id) == items.end() || ( 261 | std::holds_alternative(items.at(id).data) == false && 262 | std::holds_alternative(items.at(id).data) == false)) { 263 | continue; 264 | } 265 | 266 | const auto & item = items.at(id); 267 | 268 | bool isHovered = false; 269 | 270 | if (std::holds_alternative(item.data)) { 271 | const HN::Story & story = std::get(item.data); 272 | 273 | auto p0 = ImGui::GetCursorScreenPos(); 274 | 275 | // draw text to be able to calculate the final text size 276 | ImGui::PushTextWrapPos(ImGui::GetContentRegionAvailWidth()); 277 | ImGui::Text("%2d.", i + 1); 278 | ImGui::SameLine(); 279 | ImGui::Text("%s", story.title.c_str()); 280 | 281 | // draw hovered story highlight 282 | if (windowId == stateUI.hoveredWindowId && i == window.hoveredStoryId) { 283 | auto col0 = ImGui::GetStyleColorVec4(ImGuiCol_Text); 284 | auto col1 = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); 285 | ImGui::PushStyleColor(ImGuiCol_Text, col1); 286 | ImGui::PushStyleColor(ImGuiCol_TextDisabled, col0); 287 | 288 | auto p1 = ImGui::GetCursorScreenPos(); 289 | p1.y -= 1; 290 | if (p1.y > p0.y) { 291 | p1.x += ImGui::GetContentRegionAvailWidth() - 1; 292 | } else { 293 | p1.x += ImGui::CalcTextSize(story.title.c_str()).x + 5; 294 | } 295 | 296 | // highlight rectangle 297 | ImGui::GetWindowDrawList()->AddRectFilled(p0, p1, ImGui::GetColorU32(col0)); 298 | 299 | if (ImGui::IsKeyPressed('o', false) || ImGui::IsKeyPressed('O', false)) { 300 | openInBrowser(story.url); 301 | } 302 | } 303 | 304 | // go back to original position and redraw text over the highlight 305 | ImGui::SetCursorScreenPos(p0); 306 | 307 | ImGui::Text("%2d.", i + 1); 308 | isHovered |= ImGui::IsItemHovered(); 309 | ImGui::SameLine(); 310 | ImGui::Text("%s", story.title.c_str()); 311 | isHovered |= ImGui::IsItemHovered(); 312 | 313 | ImGui::PopTextWrapPos(); 314 | ImGui::SameLine(); 315 | 316 | if (windowId == stateUI.hoveredWindowId && i == window.hoveredStoryId) { 317 | ImGui::PopStyleColor(2); 318 | } 319 | 320 | ImGui::TextDisabled(" (%s)", story.domain.c_str()); 321 | 322 | if (stateUI.storyListMode != UI::StoryListMode::Micro) { 323 | ImGui::TextDisabled(" %d points by %s %s ago | %d comments", story.score, story.by.c_str(), stateHN.timeSince(story.time).c_str(), story.descendants); 324 | isHovered |= ImGui::IsItemHovered(); 325 | } 326 | } else if (std::holds_alternative(item.data)) { 327 | const HN::Job & job = std::get(item.data); 328 | 329 | if (windowId == stateUI.hoveredWindowId && i == window.hoveredStoryId) { 330 | auto col0 = ImGui::GetStyleColorVec4(ImGuiCol_Text); 331 | auto col1 = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); 332 | ImGui::PushStyleColor(ImGuiCol_Text, col1); 333 | ImGui::PushStyleColor(ImGuiCol_TextDisabled, col0); 334 | 335 | auto p0 = ImGui::GetCursorScreenPos(); 336 | p0.x += 1; 337 | auto p1 = p0; 338 | p1.x += ImGui::CalcTextSize(job.title.c_str()).x + 4; 339 | 340 | ImGui::GetWindowDrawList()->AddRectFilled(p0, p1, ImGui::GetColorU32(col0)); 341 | 342 | if (ImGui::IsKeyPressed('o', false) || ImGui::IsKeyPressed('O', false)) { 343 | openInBrowser(job.url); 344 | } 345 | } 346 | 347 | ImGui::Text("%2d.", i + 1); 348 | isHovered |= ImGui::IsItemHovered(); 349 | ImGui::SameLine(); 350 | ImGui::PushTextWrapPos(ImGui::GetContentRegionAvailWidth()); 351 | ImGui::Text("%s", job.title.c_str()); 352 | isHovered |= ImGui::IsItemHovered(); 353 | ImGui::PopTextWrapPos(); 354 | ImGui::SameLine(); 355 | 356 | if (windowId == stateUI.hoveredWindowId && i == window.hoveredStoryId) { 357 | ImGui::PopStyleColor(2); 358 | } 359 | 360 | ImGui::TextDisabled(" (%s)", job.domain.c_str()); 361 | 362 | if (stateUI.storyListMode != UI::StoryListMode::Micro) { 363 | ImGui::TextDisabled(" %d points by %s %s ago", job.score, job.by.c_str(), stateHN.timeSince(job.time).c_str()); 364 | isHovered |= ImGui::IsItemHovered(); 365 | } 366 | } 367 | 368 | if (isHovered) { 369 | stateUI.hoveredWindowId = windowId; 370 | window.hoveredStoryId = i; 371 | } 372 | 373 | if (stateUI.storyListMode == UI::StoryListMode::Spread) { 374 | ImGui::Text("%s", ""); 375 | } 376 | 377 | if (ImGui::GetCursorScreenPos().y + 3 > ImGui::GetWindowSize().y) { 378 | window.maxStories = i + 1; 379 | break; 380 | } else { 381 | if ((i == window.maxStories - 1) && (ImGui::GetCursorScreenPos().y + 2 < ImGui::GetWindowSize().y)) { 382 | ++window.maxStories; 383 | } 384 | } 385 | } 386 | 387 | if (windowId == stateUI.hoveredWindowId) { 388 | if (ImGui::IsMouseDoubleClicked(0) || 389 | ImGui::IsKeyPressed(ImGui::GetIO().KeyMap[ImGuiKey_Enter], false)) { 390 | if (stateUI.showHelpModal == false && window.hoveredStoryId < (int) storyIds.size()) { 391 | window.showComments = true; 392 | window.selectedStoryId = storyIds[window.hoveredStoryId]; 393 | } 394 | } 395 | 396 | if (ImGui::IsKeyPressed('r', false) && window.hoveredStoryId < (int) storyIds.size()) { 397 | toUpdate.push_back(storyIds[window.hoveredStoryId]); 398 | } 399 | 400 | if (ImGui::IsKeyPressed('k', true) || 401 | ImGui::IsKeyPressed(ImGui::GetIO().KeyMap[ImGuiKey_UpArrow], true)) { 402 | window.hoveredStoryId = std::max(0, window.hoveredStoryId - 1); 403 | } 404 | 405 | if (ImGui::IsKeyPressed('j', true) || 406 | ImGui::IsKeyPressed(ImGui::GetIO().KeyMap[ImGuiKey_DownArrow], true)) { 407 | window.hoveredStoryId = std::max(0, std::min(nShow - 1, window.hoveredStoryId + 1)); 408 | } 409 | 410 | if (ImGui::IsKeyPressed('g', true)) { 411 | window.hoveredStoryId = 0; 412 | } 413 | 414 | if (ImGui::IsKeyPressed('G', true)) { 415 | window.hoveredStoryId = nShow - 1; 416 | } 417 | 418 | if (ImGui::IsKeyPressed(ImGui::GetIO().KeyMap[ImGuiKey_Tab])) { 419 | stateUI.windows[stateUI.hoveredWindowId].content = 420 | (UI::WindowContent) ((int(stateUI.windows[stateUI.hoveredWindowId].content) + 1)%int(UI::WindowContent::Count)); 421 | } 422 | } 423 | } else { 424 | if (std::holds_alternative(items.at(window.selectedStoryId).data) == false) { 425 | window.showComments = false; 426 | } else { 427 | const auto & story = std::get(items.at(window.selectedStoryId).data); 428 | 429 | toRefresh.push_back(story.id); 430 | 431 | ImGui::Text("%s", story.title.c_str()); 432 | ImGui::TextDisabled("%d points by %s %s ago | %d comments", story.score, story.by.c_str(), stateHN.timeSince(story.time).c_str(), story.descendants); 433 | if (story.text.empty() == false) { 434 | ImGui::PushTextWrapPos(ImGui::GetContentRegionAvailWidth()); 435 | ImGui::Text("%s", story.text.c_str()); 436 | ImGui::PopTextWrapPos(); 437 | } 438 | 439 | ImGui::Text("%s", ""); 440 | 441 | int curCommentId = 0; 442 | bool forceUpdate = false; 443 | 444 | std::function renderComments; 445 | renderComments = [&](const HN::ItemIds & commentIds, int indent) { 446 | const int nComments = commentIds.size(); 447 | for (int i = 0; i < nComments; ++i) { 448 | const auto & id = commentIds[i]; 449 | 450 | if (forceUpdate) { 451 | toUpdate.push_back(id); 452 | } 453 | 454 | toRefresh.push_back(commentIds[i]); 455 | if (items.find(id) == items.end() || std::holds_alternative(items.at(id).data) == false) { 456 | continue; 457 | } 458 | 459 | const auto & item = items.at(id); 460 | const auto & comment = std::get(item.data); 461 | 462 | char header[128]; 463 | snprintf(header, 128, "%*s %s %s ago [%s]", indent, "", comment.by.c_str(), stateHN.timeSince(comment.time).c_str(), stateUI.collapsed[id] ? "+" : "-"); 464 | 465 | if (windowId == stateUI.hoveredWindowId && curCommentId == window.hoveredCommentId) { 466 | auto col0 = ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled); 467 | auto col1 = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); 468 | ImGui::PushStyleColor(ImGuiCol_TextDisabled, col1); 469 | 470 | auto p0 = ImGui::GetCursorScreenPos(); 471 | p0.x += 1 + indent; 472 | auto p1 = p0; 473 | p1.x += ImGui::CalcTextSize(header).x - indent; 474 | 475 | ImGui::GetWindowDrawList()->AddRectFilled(p0, p1, ImGui::GetColorU32(col0)); 476 | 477 | if (ImGui::GetCursorScreenPos().y < 4) { 478 | ImGui::SetScrollHereY(0.0f); 479 | } 480 | } 481 | 482 | bool isHovered = false; 483 | 484 | ImGui::TextDisabled("%s", header); 485 | isHovered |= ImGui::IsItemHovered(); 486 | 487 | if (windowId == stateUI.hoveredWindowId && curCommentId == window.hoveredCommentId) { 488 | ImGui::PopStyleColor(1); 489 | } 490 | 491 | if (stateUI.collapsed[id] == false) { 492 | ImGui::PushTextWrapPos(ImGui::GetContentRegionAvailWidth()); 493 | ImGui::Text("%*s", indent, ""); 494 | ImGui::SameLine(); 495 | ImGui::Text("%s", comment.text.c_str()); 496 | isHovered |= ImGui::IsItemHovered(); 497 | ImGui::PopTextWrapPos(); 498 | } 499 | 500 | if (windowId == stateUI.hoveredWindowId && curCommentId == window.hoveredCommentId) { 501 | if (ImGui::GetCursorScreenPos().y > ImGui::GetWindowSize().y + 4) { 502 | ImGui::SetScrollHereY(1.0f); 503 | } 504 | 505 | if (ImGui::IsMouseDoubleClicked(0) || 506 | ImGui::IsKeyPressed(ImGui::GetIO().KeyMap[ImGuiKey_Enter], false)) { 507 | stateUI.collapsed[id] = !stateUI.collapsed[id]; 508 | } 509 | } 510 | 511 | if (isHovered) { 512 | window.hoveredCommentId = curCommentId; 513 | } 514 | 515 | ImGui::Text("%s", ""); 516 | 517 | ++curCommentId; 518 | 519 | if (stateUI.collapsed[id] == false ) { 520 | if (comment.kids.size() > 0) { 521 | renderComments(comment.kids, indent + 1); 522 | } 523 | } else { 524 | //curCommentId += comment.kids.size(); 525 | } 526 | } 527 | }; 528 | 529 | if (windowId == stateUI.hoveredWindowId) { 530 | if (ImGui::IsKeyPressed('r', false)) { 531 | toUpdate.push_back(story.id); 532 | forceUpdate = true; 533 | } 534 | } 535 | 536 | ImGui::BeginChild("##comments", ImVec2(0, 0), false, ImGuiWindowFlags_NoScrollbar); 537 | renderComments(story.kids, 0); 538 | ImGui::EndChild(); 539 | 540 | if (windowId == stateUI.hoveredWindowId) { 541 | if (ImGui::IsKeyPressed('k', true) || 542 | ImGui::IsKeyPressed(ImGui::GetIO().KeyMap[ImGuiKey_UpArrow], true)) { 543 | window.hoveredCommentId = std::max(0, window.hoveredCommentId - 1); 544 | } 545 | 546 | if (ImGui::IsKeyPressed('j', true) || 547 | ImGui::IsKeyPressed(ImGui::GetIO().KeyMap[ImGuiKey_DownArrow], true)) { 548 | window.hoveredCommentId = std::min(curCommentId - 1, window.hoveredCommentId + 1); 549 | } 550 | 551 | if (ImGui::IsKeyPressed('g', true)) { 552 | window.hoveredCommentId = 0; 553 | } 554 | 555 | if (ImGui::IsKeyPressed('G', true)) { 556 | window.hoveredCommentId = curCommentId - 1; 557 | } 558 | 559 | if (ImGui::IsKeyPressed('o', false) || ImGui::IsKeyPressed('O', false)) { 560 | openInBrowser((std::string("https://news.ycombinator.com/item?id=") + std::to_string(story.id)).c_str()); 561 | } 562 | 563 | if (ImGui::IsMouseClicked(1) || 564 | ImGui::IsMouseClicked(2) || 565 | ImGui::IsKeyPressed('q', false) || 566 | ImGui::IsKeyPressed('b', false) || 567 | ImGui::IsKeyPressed(ImGui::GetIO().KeyMap[ImGuiKey_Backspace], false)) { 568 | window.showComments = false; 569 | } 570 | } 571 | 572 | window.hoveredCommentId = std::min(curCommentId - 1, window.hoveredCommentId); 573 | } 574 | } 575 | 576 | ImGui::End(); 577 | } 578 | 579 | if (ImGui::IsKeyPressed(ImGui::GetIO().KeyMap[ImGuiKey_LeftArrow], true)) { 580 | stateUI.hoveredWindowId = std::max(0, stateUI.hoveredWindowId - 1); 581 | } 582 | 583 | if (ImGui::IsKeyPressed(ImGui::GetIO().KeyMap[ImGuiKey_RightArrow], true)) { 584 | stateUI.hoveredWindowId = std::min(stateUI.nWindows - 1, stateUI.hoveredWindowId + 1); 585 | } 586 | 587 | if (stateUI.showStatusWindow) { 588 | { 589 | auto wSize = ImGui::GetIO().DisplaySize; 590 | ImGui::SetNextWindowPos(ImVec2(0, wSize.y - stateUI.statusWindowHeight), ImGuiCond_Always); 591 | ImGui::SetNextWindowSize(ImVec2(wSize.x, stateUI.statusWindowHeight), ImGuiCond_Always); 592 | } 593 | snprintf(stateUI.statusWindowHeader, 512, "Status | Story List Mode : %d |", (int) (stateUI.storyListMode)); 594 | ImGui::Begin(stateUI.statusWindowHeader, nullptr, 595 | ImGuiWindowFlags_NoCollapse | 596 | ImGuiWindowFlags_NoResize | 597 | ImGuiWindowFlags_NoMove); 598 | ImGui::Text(" API requests : %d / %d B (next update in %d s)", stateHN.nFetches, (int) stateHN.totalBytesDownloaded, stateHN.nextUpdate); 599 | ImGui::Text(" Last API request : %s", stateHN.curURI); 600 | ImGui::Text(" Source code : https://github.com/ggerganov/hnterm"); 601 | ImGui::End(); 602 | } 603 | 604 | if (ImGui::IsKeyPressed('s', false)) { 605 | stateUI.showStatusWindow = !stateUI.showStatusWindow; 606 | } 607 | 608 | if (ImGui::IsKeyPressed('1', false)) { 609 | stateUI.nWindows = 1; 610 | } 611 | 612 | if (ImGui::IsKeyPressed('2', false)) { 613 | stateUI.nWindows = 2; 614 | } 615 | 616 | if (ImGui::IsKeyPressed('3', false)) { 617 | stateUI.nWindows = 3; 618 | } 619 | 620 | if (ImGui::IsKeyPressed('v', false)) { 621 | stateUI.storyListMode = (UI::StoryListMode)(((int)(stateUI.storyListMode) + 1)%((int)(UI::StoryListMode::COUNT))); 622 | } 623 | 624 | if (ImGui::IsKeyPressed('c', false)) { 625 | stateUI.changeColorScheme(); 626 | } 627 | 628 | if (ImGui::IsKeyPressed('Q', false)) { 629 | return false; 630 | } 631 | 632 | stateUI.hoveredWindowId = std::min(stateUI.nWindows - 1, stateUI.hoveredWindowId); 633 | 634 | if (stateUI.showHelpWelcome || (stateUI.showHelpModal == false && (ImGui::IsKeyReleased('h') || ImGui::IsKeyReleased('H')))) { 635 | stateUI.showHelpWelcome = false; 636 | ImGui::OpenPopup("###Help"); 637 | auto col = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); 638 | col.w *= 0.9; 639 | ImGui::GetStyle().Colors[ImGuiCol_WindowBg] = col; 640 | } 641 | 642 | if (ImGui::BeginPopupModal("HNTerm v0.3###Help", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { 643 | #ifdef __EMSCRIPTEN__ 644 | ImGui::Text(" "); 645 | ImGui::Text("---------------------------------------------"); 646 | ImGui::Text("Emscripten port of HNTerm"); 647 | ImGui::Text("This demo is not suitable for mobile devices!"); 648 | ImGui::Text("---------------------------------------------"); 649 | #endif 650 | ImGui::Text(" "); 651 | ImGui::Text("Interactive browsing of https://news.ycombinator.com/"); 652 | ImGui::Text("Content is automatically updated - no need to refresh "); 653 | ImGui::Text(" "); 654 | ImGui::Text(" h/H - toggle Help window "); 655 | ImGui::Text(" s - toggle Status window "); 656 | ImGui::Text(" g - go to top "); 657 | ImGui::Text(" G - go to end "); 658 | ImGui::Text(" o/O - open in browser "); 659 | ImGui::Text(" up/down/j/k - navigate items "); 660 | ImGui::Text(" left/right - navigate windows "); 661 | ImGui::Text(" Tab - change current window content "); 662 | ImGui::Text(" 1/2/3 - change number of windows "); 663 | ImGui::Text(" q/b/bkspc - close comments "); 664 | ImGui::Text(" v - toggle story view mode "); 665 | ImGui::Text(" r - force refresh story "); 666 | ImGui::Text(" c - change colors "); 667 | ImGui::Text(" Q - quit "); 668 | ImGui::Text(" "); 669 | 670 | if (stateUI.showHelpModal) { 671 | for (int i = 0; i < 512; ++i) { 672 | if (ImGui::IsKeyReleased(i) || ImGui::IsMouseDown(0)) { 673 | ImGui::CloseCurrentPopup(); 674 | auto col = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); 675 | col.w = 1.0; 676 | ImGui::GetStyle().Colors[ImGuiCol_WindowBg] = col; 677 | stateUI.showHelpModal = false; 678 | break; 679 | } 680 | } 681 | } else { 682 | stateUI.showHelpModal = true; 683 | } 684 | 685 | ImGui::EndPopup(); 686 | } 687 | 688 | ImGui::Render(); 689 | 690 | ImTui_ImplText_RenderDrawData(ImGui::GetDrawData(), g_screen); 691 | 692 | #ifndef __EMSCRIPTEN__ 693 | ImTui_ImplNcurses_DrawScreen(isActive); 694 | #endif 695 | 696 | stateHN.forceUpdate(toUpdate); 697 | g_updated = stateHN.update(toRefresh); 698 | 699 | return true; 700 | } 701 | } 702 | 703 | int main([[maybe_unused]] int argc, [[maybe_unused]] char ** argv) { 704 | #ifndef __EMSCRIPTEN__ 705 | auto argm = parseCmdArguments(argc, argv); 706 | int mouseSupport = argm.find("--mouse") != argm.end() || argm.find("m") != argm.end(); 707 | if (argm.find("--help") != argm.end() || argm.find("-h") != argm.end()) { 708 | printf("Usage: hnterm [-m] [-h]\n"); 709 | printf(" -m, --mouse : ncurses mouse support\n"); 710 | printf(" -h, --help : print this help\n"); 711 | return -1; 712 | } 713 | #endif 714 | 715 | if (hnInit() == false) { 716 | fprintf(stderr, "Failed to initialize. Aborting\n"); 717 | return -1; 718 | } 719 | 720 | IMGUI_CHECKVERSION(); 721 | ImGui::CreateContext(); 722 | 723 | #ifdef __EMSCRIPTEN__ 724 | g_screen = ImTui_ImplEmscripten_Init(true); 725 | #else 726 | // when no changes occured - limit frame rate to 3.0 fps to save CPU 727 | g_screen = ImTui_ImplNcurses_Init(mouseSupport != 0, 60.0, 3.0); 728 | #endif 729 | ImTui_ImplText_Init(); 730 | 731 | stateUI.changeColorScheme(false); 732 | 733 | #ifndef __EMSCRIPTEN__ 734 | while (true) { 735 | if (render_frame() == false) break; 736 | } 737 | 738 | ImTui_ImplText_Shutdown(); 739 | ImTui_ImplNcurses_Shutdown(); 740 | hnFree(); 741 | #endif 742 | 743 | return 0; 744 | } 745 | 746 | -------------------------------------------------------------------------------- /examples/hnterm/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; background-color: black; 3 | -webkit-font-smoothing: subpixel-antialiased; 4 | font-smoothing: subpixel-antialiased; 5 | } 6 | #screen { 7 | margin: 0; 8 | padding: 0; 9 | font-size: 13px; 10 | height: 100%; 11 | font: sans-serif; 12 | } 13 | .no-sel { 14 | outline: none; 15 | -moz-user-select: none; 16 | -webkit-user-select: none; 17 | -webkit-touch-callout: none; 18 | -ms-user-select:none; 19 | user-select:none; 20 | -o-user-select:none; 21 | } 22 | .cell { 23 | pointer-events: none; 24 | } 25 | .cell-version { 26 | padding-left: 4px; 27 | padding-top: 0.5em; 28 | text-align: left; 29 | display: inline-block; 30 | float: left; 31 | color: rgba(255, 255, 255, 0.75); 32 | } 33 | .cell-about { 34 | padding-right: 24px; 35 | padding-top: 0.5em; 36 | text-align: right; 37 | display: inline-block; 38 | float: right; 39 | } 40 | .nav-link { 41 | text-decoration: none; 42 | color: rgba(255, 255, 255, 1.0); 43 | } 44 | svg { 45 | -webkit-filter: invert(100%); /* safari 6.0 - 9.0 */ 46 | filter: invert(100%); 47 | } 48 | 49 | .f0 {color:#000000} 50 | .f1 {color:#800000} 51 | .f2 {color:#008000} 52 | .f3 {color:#808000} 53 | .f4 {color:#000080} 54 | .f5 {color:#800080} 55 | .f6 {color:#008080} 56 | .f7 {color:#c0c0c0} 57 | .f8 {color:#808080} 58 | .f9 {color:#ff0000} 59 | .f10 {color:#00ff00} 60 | .f11 {color:#ffff00} 61 | .f12 {color:#0000ff} 62 | .f13 {color:#ff00ff} 63 | .f14 {color:#00ffff} 64 | .f15 {color:#ffffff} 65 | .f16 {color:#000000} 66 | .f17 {color:#00005f} 67 | .f18 {color:#000087} 68 | .f19 {color:#0000af} 69 | .f20 {color:#0000d7} 70 | .f21 {color:#0000ff} 71 | .f22 {color:#005f00} 72 | .f23 {color:#005f5f} 73 | .f24 {color:#005f87} 74 | .f25 {color:#005faf} 75 | .f26 {color:#005fd7} 76 | .f27 {color:#005fff} 77 | .f28 {color:#008700} 78 | .f29 {color:#00875f} 79 | .f30 {color:#008787} 80 | .f31 {color:#0087af} 81 | .f32 {color:#0087d7} 82 | .f33 {color:#0087ff} 83 | .f34 {color:#00af00} 84 | .f35 {color:#00af5f} 85 | .f36 {color:#00af87} 86 | .f37 {color:#00afaf} 87 | .f38 {color:#00afd7} 88 | .f39 {color:#00afff} 89 | .f40 {color:#00d700} 90 | .f41 {color:#00d75f} 91 | .f42 {color:#00d787} 92 | .f43 {color:#00d7af} 93 | .f44 {color:#00d7d7} 94 | .f45 {color:#00d7ff} 95 | .f46 {color:#00ff00} 96 | .f47 {color:#00ff5f} 97 | .f48 {color:#00ff87} 98 | .f49 {color:#00ffaf} 99 | .f50 {color:#00ffd7} 100 | .f51 {color:#00ffff} 101 | .f52 {color:#5f0000} 102 | .f53 {color:#5f005f} 103 | .f54 {color:#5f0087} 104 | .f55 {color:#5f00af} 105 | .f56 {color:#5f00d7} 106 | .f57 {color:#5f00ff} 107 | .f58 {color:#5f5f00} 108 | .f59 {color:#5f5f5f} 109 | .f60 {color:#5f5f87} 110 | .f61 {color:#5f5faf} 111 | .f62 {color:#5f5fd7} 112 | .f63 {color:#5f5fff} 113 | .f64 {color:#5f8700} 114 | .f65 {color:#5f875f} 115 | .f66 {color:#5f8787} 116 | .f67 {color:#5f87af} 117 | .f68 {color:#5f87d7} 118 | .f69 {color:#5f87ff} 119 | .f70 {color:#5faf00} 120 | .f71 {color:#5faf5f} 121 | .f72 {color:#5faf87} 122 | .f73 {color:#5fafaf} 123 | .f74 {color:#5fafd7} 124 | .f75 {color:#5fafff} 125 | .f76 {color:#5fd700} 126 | .f77 {color:#5fd75f} 127 | .f78 {color:#5fd787} 128 | .f79 {color:#5fd7af} 129 | .f80 {color:#5fd7d7} 130 | .f81 {color:#5fd7ff} 131 | .f82 {color:#5fff00} 132 | .f83 {color:#5fff5f} 133 | .f84 {color:#5fff87} 134 | .f85 {color:#5fffaf} 135 | .f86 {color:#5fffd7} 136 | .f87 {color:#5fffff} 137 | .f88 {color:#870000} 138 | .f89 {color:#87005f} 139 | .f90 {color:#870087} 140 | .f91 {color:#8700af} 141 | .f92 {color:#8700d7} 142 | .f93 {color:#8700ff} 143 | .f94 {color:#875f00} 144 | .f95 {color:#875f5f} 145 | .f96 {color:#875f87} 146 | .f97 {color:#875faf} 147 | .f98 {color:#875fd7} 148 | .f99 {color:#875fff} 149 | .f100 {color:#878700} 150 | .f101 {color:#87875f} 151 | .f102 {color:#878787} 152 | .f103 {color:#8787af} 153 | .f104 {color:#8787d7} 154 | .f105 {color:#8787ff} 155 | .f106 {color:#87af00} 156 | .f107 {color:#87af5f} 157 | .f108 {color:#87af87} 158 | .f109 {color:#87afaf} 159 | .f110 {color:#87afd7} 160 | .f111 {color:#87afff} 161 | .f112 {color:#87d700} 162 | .f113 {color:#87d75f} 163 | .f114 {color:#87d787} 164 | .f115 {color:#87d7af} 165 | .f116 {color:#87d7d7} 166 | .f117 {color:#87d7ff} 167 | .f118 {color:#87ff00} 168 | .f119 {color:#87ff5f} 169 | .f120 {color:#87ff87} 170 | .f121 {color:#87ffaf} 171 | .f122 {color:#87ffd7} 172 | .f123 {color:#87ffff} 173 | .f124 {color:#af0000} 174 | .f125 {color:#af005f} 175 | .f126 {color:#af0087} 176 | .f127 {color:#af00af} 177 | .f128 {color:#af00d7} 178 | .f129 {color:#af00ff} 179 | .f130 {color:#af5f00} 180 | .f131 {color:#af5f5f} 181 | .f132 {color:#af5f87} 182 | .f133 {color:#af5faf} 183 | .f134 {color:#af5fd7} 184 | .f135 {color:#af5fff} 185 | .f136 {color:#af8700} 186 | .f137 {color:#af875f} 187 | .f138 {color:#af8787} 188 | .f139 {color:#af87af} 189 | .f140 {color:#af87d7} 190 | .f141 {color:#af87ff} 191 | .f142 {color:#afaf00} 192 | .f143 {color:#afaf5f} 193 | .f144 {color:#afaf87} 194 | .f145 {color:#afafaf} 195 | .f146 {color:#afafd7} 196 | .f147 {color:#afafff} 197 | .f148 {color:#afd700} 198 | .f149 {color:#afd75f} 199 | .f150 {color:#afd787} 200 | .f151 {color:#afd7af} 201 | .f152 {color:#afd7d7} 202 | .f153 {color:#afd7ff} 203 | .f154 {color:#afff00} 204 | .f155 {color:#afff5f} 205 | .f156 {color:#afff87} 206 | .f157 {color:#afffaf} 207 | .f158 {color:#afffd7} 208 | .f159 {color:#afffff} 209 | .f160 {color:#d70000} 210 | .f161 {color:#d7005f} 211 | .f162 {color:#d70087} 212 | .f163 {color:#d700af} 213 | .f164 {color:#d700d7} 214 | .f165 {color:#d700ff} 215 | .f166 {color:#d75f00} 216 | .f167 {color:#d75f5f} 217 | .f168 {color:#d75f87} 218 | .f169 {color:#d75faf} 219 | .f170 {color:#d75fd7} 220 | .f171 {color:#d75fff} 221 | .f172 {color:#d78700} 222 | .f173 {color:#d7875f} 223 | .f174 {color:#d78787} 224 | .f175 {color:#d787af} 225 | .f176 {color:#d787d7} 226 | .f177 {color:#d787ff} 227 | .f178 {color:#d7af00} 228 | .f179 {color:#d7af5f} 229 | .f180 {color:#d7af87} 230 | .f181 {color:#d7afaf} 231 | .f182 {color:#d7afd7} 232 | .f183 {color:#d7afff} 233 | .f184 {color:#d7d700} 234 | .f185 {color:#d7d75f} 235 | .f186 {color:#d7d787} 236 | .f187 {color:#d7d7af} 237 | .f188 {color:#d7d7d7} 238 | .f189 {color:#d7d7ff} 239 | .f190 {color:#d7ff00} 240 | .f191 {color:#d7ff5f} 241 | .f192 {color:#d7ff87} 242 | .f193 {color:#d7ffaf} 243 | .f194 {color:#d7ffd7} 244 | .f195 {color:#d7ffff} 245 | .f196 {color:#ff0000} 246 | .f197 {color:#ff005f} 247 | .f198 {color:#ff0087} 248 | .f199 {color:#ff00af} 249 | .f200 {color:#ff00d7} 250 | .f201 {color:#ff00ff} 251 | .f202 {color:#ff5f00} 252 | .f203 {color:#ff5f5f} 253 | .f204 {color:#ff5f87} 254 | .f205 {color:#ff5faf} 255 | .f206 {color:#ff5fd7} 256 | .f207 {color:#ff5fff} 257 | .f208 {color:#ff8700} 258 | .f209 {color:#ff875f} 259 | .f210 {color:#ff8787} 260 | .f211 {color:#ff87af} 261 | .f212 {color:#ff87d7} 262 | .f213 {color:#ff87ff} 263 | .f214 {color:#ffaf00} 264 | .f215 {color:#ffaf5f} 265 | .f216 {color:#ffaf87} 266 | .f217 {color:#ffafaf} 267 | .f218 {color:#ffafd7} 268 | .f219 {color:#ffafff} 269 | .f220 {color:#ffd700} 270 | .f221 {color:#ffd75f} 271 | .f222 {color:#ffd787} 272 | .f223 {color:#ffd7af} 273 | .f224 {color:#ffd7d7} 274 | .f225 {color:#ffd7ff} 275 | .f226 {color:#ffff00} 276 | .f227 {color:#ffff5f} 277 | .f228 {color:#ffff87} 278 | .f229 {color:#ffffaf} 279 | .f230 {color:#ffffd7} 280 | .f231 {color:#ffffff} 281 | .f232 {color:#080808} 282 | .f233 {color:#121212} 283 | .f234 {color:#1c1c1c} 284 | .f235 {color:#262626} 285 | .f236 {color:#303030} 286 | .f237 {color:#3a3a3a} 287 | .f238 {color:#444444} 288 | .f239 {color:#4e4e4e} 289 | .f240 {color:#585858} 290 | .f241 {color:#626262} 291 | .f242 {color:#6c6c6c} 292 | .f243 {color:#767676} 293 | .f244 {color:#808080} 294 | .f245 {color:#8a8a8a} 295 | .f246 {color:#949494} 296 | .f247 {color:#9e9e9e} 297 | .f248 {color:#a8a8a8} 298 | .f249 {color:#b2b2b2} 299 | .f250 {color:#bcbcbc} 300 | .f251 {color:#c6c6c6} 301 | .f252 {color:#d0d0d0} 302 | .f253 {color:#dadada} 303 | .f254 {color:#e4e4e4} 304 | .f255 {color:#eeeeee} 305 | .b1 {background-color:#800000} 306 | .b2 {background-color:#008000} 307 | .b3 {background-color:#808000} 308 | .b4 {background-color:#000080} 309 | .b5 {background-color:#800080} 310 | .b6 {background-color:#008080} 311 | .b7 {background-color:#c0c0c0} 312 | .b8 {background-color:#808080} 313 | .b9 {background-color:#ff0000} 314 | .b10 {background-color:#00ff00} 315 | .b11 {background-color:#ffff00} 316 | .b12 {background-color:#0000ff} 317 | .b13 {background-color:#ff00ff} 318 | .b14 {background-color:#00ffff} 319 | .b15 {background-color:#ffffff} 320 | .b16 {background-color:#000000} 321 | .b17 {background-color:#00005f} 322 | .b18 {background-color:#000087} 323 | .b19 {background-color:#0000af} 324 | .b20 {background-color:#0000d7} 325 | .b21 {background-color:#0000ff} 326 | .b22 {background-color:#005f00} 327 | .b23 {background-color:#005f5f} 328 | .b24 {background-color:#005f87} 329 | .b25 {background-color:#005faf} 330 | .b26 {background-color:#005fd7} 331 | .b27 {background-color:#005fff} 332 | .b28 {background-color:#008700} 333 | .b29 {background-color:#00875f} 334 | .b30 {background-color:#008787} 335 | .b31 {background-color:#0087af} 336 | .b32 {background-color:#0087d7} 337 | .b33 {background-color:#0087ff} 338 | .b34 {background-color:#00af00} 339 | .b35 {background-color:#00af5f} 340 | .b36 {background-color:#00af87} 341 | .b37 {background-color:#00afaf} 342 | .b38 {background-color:#00afd7} 343 | .b39 {background-color:#00afff} 344 | .b40 {background-color:#00d700} 345 | .b41 {background-color:#00d75f} 346 | .b42 {background-color:#00d787} 347 | .b43 {background-color:#00d7af} 348 | .b44 {background-color:#00d7d7} 349 | .b45 {background-color:#00d7ff} 350 | .b46 {background-color:#00ff00} 351 | .b47 {background-color:#00ff5f} 352 | .b48 {background-color:#00ff87} 353 | .b49 {background-color:#00ffaf} 354 | .b50 {background-color:#00ffd7} 355 | .b51 {background-color:#00ffff} 356 | .b52 {background-color:#5f0000} 357 | .b53 {background-color:#5f005f} 358 | .b54 {background-color:#5f0087} 359 | .b55 {background-color:#5f00af} 360 | .b56 {background-color:#5f00d7} 361 | .b57 {background-color:#5f00ff} 362 | .b58 {background-color:#5f5f00} 363 | .b59 {background-color:#5f5f5f} 364 | .b60 {background-color:#5f5f87} 365 | .b61 {background-color:#5f5faf} 366 | .b62 {background-color:#5f5fd7} 367 | .b63 {background-color:#5f5fff} 368 | .b64 {background-color:#5f8700} 369 | .b65 {background-color:#5f875f} 370 | .b66 {background-color:#5f8787} 371 | .b67 {background-color:#5f87af} 372 | .b68 {background-color:#5f87d7} 373 | .b69 {background-color:#5f87ff} 374 | .b70 {background-color:#5faf00} 375 | .b71 {background-color:#5faf5f} 376 | .b72 {background-color:#5faf87} 377 | .b73 {background-color:#5fafaf} 378 | .b74 {background-color:#5fafd7} 379 | .b75 {background-color:#5fafff} 380 | .b76 {background-color:#5fd700} 381 | .b77 {background-color:#5fd75f} 382 | .b78 {background-color:#5fd787} 383 | .b79 {background-color:#5fd7af} 384 | .b80 {background-color:#5fd7d7} 385 | .b81 {background-color:#5fd7ff} 386 | .b82 {background-color:#5fff00} 387 | .b83 {background-color:#5fff5f} 388 | .b84 {background-color:#5fff87} 389 | .b85 {background-color:#5fffaf} 390 | .b86 {background-color:#5fffd7} 391 | .b87 {background-color:#5fffff} 392 | .b88 {background-color:#870000} 393 | .b89 {background-color:#87005f} 394 | .b90 {background-color:#870087} 395 | .b91 {background-color:#8700af} 396 | .b92 {background-color:#8700d7} 397 | .b93 {background-color:#8700ff} 398 | .b94 {background-color:#875f00} 399 | .b95 {background-color:#875f5f} 400 | .b96 {background-color:#875f87} 401 | .b97 {background-color:#875faf} 402 | .b98 {background-color:#875fd7} 403 | .b99 {background-color:#875fff} 404 | .b100 {background-color:#878700} 405 | .b101 {background-color:#87875f} 406 | .b102 {background-color:#878787} 407 | .b103 {background-color:#8787af} 408 | .b104 {background-color:#8787d7} 409 | .b105 {background-color:#8787ff} 410 | .b106 {background-color:#87af00} 411 | .b107 {background-color:#87af5f} 412 | .b108 {background-color:#87af87} 413 | .b109 {background-color:#87afaf} 414 | .b110 {background-color:#87afd7} 415 | .b111 {background-color:#87afff} 416 | .b112 {background-color:#87d700} 417 | .b113 {background-color:#87d75f} 418 | .b114 {background-color:#87d787} 419 | .b115 {background-color:#87d7af} 420 | .b116 {background-color:#87d7d7} 421 | .b117 {background-color:#87d7ff} 422 | .b118 {background-color:#87ff00} 423 | .b119 {background-color:#87ff5f} 424 | .b120 {background-color:#87ff87} 425 | .b121 {background-color:#87ffaf} 426 | .b122 {background-color:#87ffd7} 427 | .b123 {background-color:#87ffff} 428 | .b124 {background-color:#af0000} 429 | .b125 {background-color:#af005f} 430 | .b126 {background-color:#af0087} 431 | .b127 {background-color:#af00af} 432 | .b128 {background-color:#af00d7} 433 | .b129 {background-color:#af00ff} 434 | .b130 {background-color:#af5f00} 435 | .b131 {background-color:#af5f5f} 436 | .b132 {background-color:#af5f87} 437 | .b133 {background-color:#af5faf} 438 | .b134 {background-color:#af5fd7} 439 | .b135 {background-color:#af5fff} 440 | .b136 {background-color:#af8700} 441 | .b137 {background-color:#af875f} 442 | .b138 {background-color:#af8787} 443 | .b139 {background-color:#af87af} 444 | .b140 {background-color:#af87d7} 445 | .b141 {background-color:#af87ff} 446 | .b142 {background-color:#afaf00} 447 | .b143 {background-color:#afaf5f} 448 | .b144 {background-color:#afaf87} 449 | .b145 {background-color:#afafaf} 450 | .b146 {background-color:#afafd7} 451 | .b147 {background-color:#afafff} 452 | .b148 {background-color:#afd700} 453 | .b149 {background-color:#afd75f} 454 | .b150 {background-color:#afd787} 455 | .b151 {background-color:#afd7af} 456 | .b152 {background-color:#afd7d7} 457 | .b153 {background-color:#afd7ff} 458 | .b154 {background-color:#afff00} 459 | .b155 {background-color:#afff5f} 460 | .b156 {background-color:#afff87} 461 | .b157 {background-color:#afffaf} 462 | .b158 {background-color:#afffd7} 463 | .b159 {background-color:#afffff} 464 | .b160 {background-color:#d70000} 465 | .b161 {background-color:#d7005f} 466 | .b162 {background-color:#d70087} 467 | .b163 {background-color:#d700af} 468 | .b164 {background-color:#d700d7} 469 | .b165 {background-color:#d700ff} 470 | .b166 {background-color:#d75f00} 471 | .b167 {background-color:#d75f5f} 472 | .b168 {background-color:#d75f87} 473 | .b169 {background-color:#d75faf} 474 | .b170 {background-color:#d75fd7} 475 | .b171 {background-color:#d75fff} 476 | .b172 {background-color:#d78700} 477 | .b173 {background-color:#d7875f} 478 | .b174 {background-color:#d78787} 479 | .b175 {background-color:#d787af} 480 | .b176 {background-color:#d787d7} 481 | .b177 {background-color:#d787ff} 482 | .b178 {background-color:#d7af00} 483 | .b179 {background-color:#d7af5f} 484 | .b180 {background-color:#d7af87} 485 | .b181 {background-color:#d7afaf} 486 | .b182 {background-color:#d7afd7} 487 | .b183 {background-color:#d7afff} 488 | .b184 {background-color:#d7d700} 489 | .b185 {background-color:#d7d75f} 490 | .b186 {background-color:#d7d787} 491 | .b187 {background-color:#d7d7af} 492 | .b188 {background-color:#d7d7d7} 493 | .b189 {background-color:#d7d7ff} 494 | .b190 {background-color:#d7ff00} 495 | .b191 {background-color:#d7ff5f} 496 | .b192 {background-color:#d7ff87} 497 | .b193 {background-color:#d7ffaf} 498 | .b194 {background-color:#d7ffd7} 499 | .b195 {background-color:#d7ffff} 500 | .b196 {background-color:#ff0000} 501 | .b197 {background-color:#ff005f} 502 | .b198 {background-color:#ff0087} 503 | .b199 {background-color:#ff00af} 504 | .b200 {background-color:#ff00d7} 505 | .b201 {background-color:#ff00ff} 506 | .b202 {background-color:#ff5f00} 507 | .b203 {background-color:#ff5f5f} 508 | .b204 {background-color:#ff5f87} 509 | .b205 {background-color:#ff5faf} 510 | .b206 {background-color:#ff5fd7} 511 | .b207 {background-color:#ff5fff} 512 | .b208 {background-color:#ff8700} 513 | .b209 {background-color:#ff875f} 514 | .b210 {background-color:#ff8787} 515 | .b211 {background-color:#ff87af} 516 | .b212 {background-color:#ff87d7} 517 | .b213 {background-color:#ff87ff} 518 | .b214 {background-color:#ffaf00} 519 | .b215 {background-color:#ffaf5f} 520 | .b216 {background-color:#ffaf87} 521 | .b217 {background-color:#ffafaf} 522 | .b218 {background-color:#ffafd7} 523 | .b219 {background-color:#ffafff} 524 | .b220 {background-color:#ffd700} 525 | .b221 {background-color:#ffd75f} 526 | .b222 {background-color:#ffd787} 527 | .b223 {background-color:#ffd7af} 528 | .b224 {background-color:#ffd7d7} 529 | .b225 {background-color:#ffd7ff} 530 | .b226 {background-color:#ffff00} 531 | .b227 {background-color:#ffff5f} 532 | .b228 {background-color:#ffff87} 533 | .b229 {background-color:#ffffaf} 534 | .b230 {background-color:#ffffd7} 535 | .b231 {background-color:#ffffff} 536 | .b232 {background-color:#080808} 537 | .b233 {background-color:#121212} 538 | .b234 {background-color:#1c1c1c} 539 | .b235 {background-color:#262626} 540 | .b236 {background-color:#303030} 541 | .b237 {background-color:#3a3a3a} 542 | .b238 {background-color:#444444} 543 | .b239 {background-color:#4e4e4e} 544 | .b240 {background-color:#585858} 545 | .b241 {background-color:#626262} 546 | .b242 {background-color:#6c6c6c} 547 | .b243 {background-color:#767676} 548 | .b244 {background-color:#808080} 549 | .b245 {background-color:#8a8a8a} 550 | .b246 {background-color:#949494} 551 | .b247 {background-color:#9e9e9e} 552 | .b248 {background-color:#a8a8a8} 553 | .b249 {background-color:#b2b2b2} 554 | .b250 {background-color:#bcbcbc} 555 | .b251 {background-color:#c6c6c6} 556 | .b252 {background-color:#d0d0d0} 557 | .b253 {background-color:#dadada} 558 | .b254 {background-color:#e4e4e4} 559 | .b255 {background-color:#eeeeee} 560 | 561 | -------------------------------------------------------------------------------- /examples/imtui-common.h: -------------------------------------------------------------------------------- 1 | /*! \file common.h 2 | * \brief Enter description here. 3 | */ 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | struct VSync { 11 | VSync(double rate_fps = 60.0) : tStep_us(1000000.0/rate_fps) {} 12 | 13 | uint64_t tStep_us; 14 | uint64_t tLast_us = t_us(); 15 | uint64_t tNext_us = tLast_us + tStep_us; 16 | 17 | inline uint64_t t_us() const { 18 | return std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count(); // duh .. 19 | } 20 | 21 | inline void wait() { 22 | uint64_t tNow_us = t_us(); 23 | while (tNow_us < tNext_us - 100) { 24 | std::this_thread::sleep_for(std::chrono::microseconds((uint64_t) (0.9*(tNext_us - tNow_us)))); 25 | tNow_us = t_us(); 26 | } 27 | 28 | tNext_us += tStep_us; 29 | } 30 | 31 | inline float delta_s() { 32 | uint64_t tNow_us = t_us(); 33 | uint64_t res = tNow_us - tLast_us; 34 | tLast_us = tNow_us; 35 | return float(res)/1e6f; 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /examples/imtui-demo.h: -------------------------------------------------------------------------------- 1 | /*! \file imtui-demo.h 2 | * \brief Enter description here. 3 | */ 4 | 5 | #pragma once 6 | 7 | #include "imtui/imtui.h" 8 | 9 | namespace ImTui { 10 | 11 | void ShowAboutWindow(bool*); 12 | void ShowDemoWindow(bool*); 13 | void ShowUserGuide(); 14 | void ShowStyleEditor(ImGuiStyle* style = nullptr); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /examples/ncurses0/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(imtui-example-ncurses0 main.cpp) 2 | target_include_directories(imtui-example-ncurses0 PRIVATE ..) 3 | target_link_libraries(imtui-example-ncurses0 PRIVATE imtui-ncurses imtui-examples-common) 4 | -------------------------------------------------------------------------------- /examples/ncurses0/main.cpp: -------------------------------------------------------------------------------- 1 | #include "imtui/imtui.h" 2 | 3 | #include "imtui/imtui-impl-ncurses.h" 4 | 5 | #include "imtui-demo.h" 6 | 7 | int main() { 8 | IMGUI_CHECKVERSION(); 9 | ImGui::CreateContext(); 10 | 11 | auto screen = ImTui_ImplNcurses_Init(true); 12 | ImTui_ImplText_Init(); 13 | 14 | bool demo = true; 15 | int nframes = 0; 16 | float fval = 1.23f; 17 | 18 | while (true) { 19 | ImTui_ImplNcurses_NewFrame(); 20 | ImTui_ImplText_NewFrame(); 21 | 22 | ImGui::NewFrame(); 23 | 24 | ImGui::SetNextWindowPos(ImVec2(4, 27), ImGuiCond_Once); 25 | ImGui::SetNextWindowSize(ImVec2(50.0, 10.0), ImGuiCond_Once); 26 | ImGui::Begin("Hello, world!"); 27 | ImGui::Text("NFrames = %d", nframes++); 28 | ImGui::Text("Mouse Pos : x = %g, y = %g", ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y); 29 | ImGui::Text("Time per frame %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); 30 | ImGui::Text("Float:"); 31 | ImGui::SameLine(); 32 | ImGui::SliderFloat("##float", &fval, 0.0f, 10.0f); 33 | 34 | #ifndef __EMSCRIPTEN__ 35 | ImGui::Text("%s", ""); 36 | if (ImGui::Button("Exit program", { ImGui::GetContentRegionAvail().x, 2 })) { 37 | break; 38 | } 39 | #endif 40 | 41 | ImGui::End(); 42 | 43 | ImTui::ShowDemoWindow(&demo); 44 | 45 | ImGui::Render(); 46 | 47 | ImTui_ImplText_RenderDrawData(ImGui::GetDrawData(), screen); 48 | ImTui_ImplNcurses_DrawScreen(); 49 | } 50 | 51 | ImTui_ImplText_Shutdown(); 52 | ImTui_ImplNcurses_Shutdown(); 53 | 54 | return 0; 55 | } 56 | -------------------------------------------------------------------------------- /examples/slack/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 20) 2 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 3 | 4 | set(TARGET slack) 5 | 6 | option(SLACK_ENABLE_API_CACHE "slack: enable API caching" OFF) 7 | 8 | if (EMSCRIPTEN) 9 | set (CMAKE_CXX_FLAGS "-s ALLOW_MEMORY_GROWTH=1 -s FETCH=1 -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0") 10 | 11 | add_executable(${TARGET} 12 | main.cpp 13 | ) 14 | 15 | target_include_directories(${TARGET} PRIVATE 16 | .. 17 | ) 18 | 19 | target_link_libraries(${TARGET} PRIVATE 20 | imtui 21 | imtui-examples-common 22 | imtui-emscripten 23 | ) 24 | 25 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/index-tmpl.html ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/index.html @ONLY) 26 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/style.css ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/style.css @ONLY) 27 | else() 28 | add_executable(${TARGET} 29 | main.cpp 30 | ) 31 | 32 | target_include_directories(${TARGET} PRIVATE 33 | .. 34 | ) 35 | 36 | target_link_libraries(${TARGET} PRIVATE 37 | imtui-ncurses 38 | imtui-examples-common 39 | ) 40 | endif() 41 | 42 | if (SLACK_ENABLE_API_CACHE) 43 | target_compile_definitions(${TARGET} PRIVATE ENABLE_API_CACHE) 44 | endif() 45 | -------------------------------------------------------------------------------- /examples/slack/README.md: -------------------------------------------------------------------------------- 1 | # slack 2 | 3 | Text-based UI (TUI) for a [Slack](https://slack.com) client 4 | 5 |

6 | 7 | Slack client 8 | 9 |
10 | Running the example in a terminal 11 |

12 | 13 | ![image](https://user-images.githubusercontent.com/1991296/181059726-0df8657a-d0c7-48c4-97d2-583d9f1aa530.png) 14 | 15 | High quality video on Youtube: https://youtu.be/RJRGfvKEgEw 16 | 17 | Coding timelapse: https://youtu.be/cEqNYesDCXg 18 | 19 | ## Live demo in the browser 20 | 21 | The following page runs an Emscripten port of the application for demonstration purposes: 22 | 23 | [slack.ggerganov.com](https://slack.ggerganov.com/) *(not suitable for mobile devices)* 24 | 25 | ## Details 26 | 27 | This is mock UI for a Slack client that runs in your terminal. The business logic of the client is completely missing. The purpose of this example is 28 | to demonstrate the capabilities for the ImTui library. 29 | 30 | ## Building 31 | 32 | ### Linux and Mac: 33 | 34 | ```bash 35 | git clone https://github.com/ggerganov/imtui --recursive 36 | cd imtui 37 | mkdir build && cd build 38 | cmake .. 39 | make 40 | 41 | ./bin/slack 42 | ``` 43 | -------------------------------------------------------------------------------- /examples/slack/index-tmpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Slack (TUI) - Text-based mock UI 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
 
21 |
22 | 23 |
24 | 25 | | 26 | Build time: @GIT_DATE@ | 27 | Commit hash: @GIT_SHA1@ | 28 | Commit subject: @GIT_COMMIT_SUBJECT@ | 29 | 30 |
31 | 36 | 37 | 295 | 296 | 297 | 298 | -------------------------------------------------------------------------------- /examples/slack/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; background-color: black; 3 | -webkit-font-smoothing: subpixel-antialiased; 4 | font-smoothing: subpixel-antialiased; 5 | } 6 | #screen { 7 | margin: 0; 8 | padding: 0; 9 | font-size: 13px; 10 | height: 100%; 11 | font: sans-serif; 12 | } 13 | .no-sel { 14 | outline: none; 15 | -moz-user-select: none; 16 | -webkit-user-select: none; 17 | -webkit-touch-callout: none; 18 | -ms-user-select:none; 19 | user-select:none; 20 | -o-user-select:none; 21 | } 22 | .cell { 23 | pointer-events: none; 24 | } 25 | .cell-version { 26 | padding-left: 4px; 27 | padding-top: 0.5em; 28 | text-align: left; 29 | display: inline-block; 30 | float: left; 31 | color: rgba(255, 255, 255, 0.75); 32 | } 33 | .cell-about { 34 | padding-right: 24px; 35 | padding-top: 0.5em; 36 | text-align: right; 37 | display: inline-block; 38 | float: right; 39 | } 40 | .nav-link { 41 | text-decoration: none; 42 | color: rgba(255, 255, 255, 1.0); 43 | } 44 | svg { 45 | -webkit-filter: invert(100%); /* safari 6.0 - 9.0 */ 46 | filter: invert(100%); 47 | } 48 | 49 | .f0 {color:#000000} 50 | .f1 {color:#800000} 51 | .f2 {color:#008000} 52 | .f3 {color:#808000} 53 | .f4 {color:#000080} 54 | .f5 {color:#800080} 55 | .f6 {color:#008080} 56 | .f7 {color:#c0c0c0} 57 | .f8 {color:#808080} 58 | .f9 {color:#ff0000} 59 | .f10 {color:#00ff00} 60 | .f11 {color:#ffff00} 61 | .f12 {color:#0000ff} 62 | .f13 {color:#ff00ff} 63 | .f14 {color:#00ffff} 64 | .f15 {color:#ffffff} 65 | .f16 {color:#000000} 66 | .f17 {color:#00005f} 67 | .f18 {color:#000087} 68 | .f19 {color:#0000af} 69 | .f20 {color:#0000d7} 70 | .f21 {color:#0000ff} 71 | .f22 {color:#005f00} 72 | .f23 {color:#005f5f} 73 | .f24 {color:#005f87} 74 | .f25 {color:#005faf} 75 | .f26 {color:#005fd7} 76 | .f27 {color:#005fff} 77 | .f28 {color:#008700} 78 | .f29 {color:#00875f} 79 | .f30 {color:#008787} 80 | .f31 {color:#0087af} 81 | .f32 {color:#0087d7} 82 | .f33 {color:#0087ff} 83 | .f34 {color:#00af00} 84 | .f35 {color:#00af5f} 85 | .f36 {color:#00af87} 86 | .f37 {color:#00afaf} 87 | .f38 {color:#00afd7} 88 | .f39 {color:#00afff} 89 | .f40 {color:#00d700} 90 | .f41 {color:#00d75f} 91 | .f42 {color:#00d787} 92 | .f43 {color:#00d7af} 93 | .f44 {color:#00d7d7} 94 | .f45 {color:#00d7ff} 95 | .f46 {color:#00ff00} 96 | .f47 {color:#00ff5f} 97 | .f48 {color:#00ff87} 98 | .f49 {color:#00ffaf} 99 | .f50 {color:#00ffd7} 100 | .f51 {color:#00ffff} 101 | .f52 {color:#5f0000} 102 | .f53 {color:#5f005f} 103 | .f54 {color:#5f0087} 104 | .f55 {color:#5f00af} 105 | .f56 {color:#5f00d7} 106 | .f57 {color:#5f00ff} 107 | .f58 {color:#5f5f00} 108 | .f59 {color:#5f5f5f} 109 | .f60 {color:#5f5f87} 110 | .f61 {color:#5f5faf} 111 | .f62 {color:#5f5fd7} 112 | .f63 {color:#5f5fff} 113 | .f64 {color:#5f8700} 114 | .f65 {color:#5f875f} 115 | .f66 {color:#5f8787} 116 | .f67 {color:#5f87af} 117 | .f68 {color:#5f87d7} 118 | .f69 {color:#5f87ff} 119 | .f70 {color:#5faf00} 120 | .f71 {color:#5faf5f} 121 | .f72 {color:#5faf87} 122 | .f73 {color:#5fafaf} 123 | .f74 {color:#5fafd7} 124 | .f75 {color:#5fafff} 125 | .f76 {color:#5fd700} 126 | .f77 {color:#5fd75f} 127 | .f78 {color:#5fd787} 128 | .f79 {color:#5fd7af} 129 | .f80 {color:#5fd7d7} 130 | .f81 {color:#5fd7ff} 131 | .f82 {color:#5fff00} 132 | .f83 {color:#5fff5f} 133 | .f84 {color:#5fff87} 134 | .f85 {color:#5fffaf} 135 | .f86 {color:#5fffd7} 136 | .f87 {color:#5fffff} 137 | .f88 {color:#870000} 138 | .f89 {color:#87005f} 139 | .f90 {color:#870087} 140 | .f91 {color:#8700af} 141 | .f92 {color:#8700d7} 142 | .f93 {color:#8700ff} 143 | .f94 {color:#875f00} 144 | .f95 {color:#875f5f} 145 | .f96 {color:#875f87} 146 | .f97 {color:#875faf} 147 | .f98 {color:#875fd7} 148 | .f99 {color:#875fff} 149 | .f100 {color:#878700} 150 | .f101 {color:#87875f} 151 | .f102 {color:#878787} 152 | .f103 {color:#8787af} 153 | .f104 {color:#8787d7} 154 | .f105 {color:#8787ff} 155 | .f106 {color:#87af00} 156 | .f107 {color:#87af5f} 157 | .f108 {color:#87af87} 158 | .f109 {color:#87afaf} 159 | .f110 {color:#87afd7} 160 | .f111 {color:#87afff} 161 | .f112 {color:#87d700} 162 | .f113 {color:#87d75f} 163 | .f114 {color:#87d787} 164 | .f115 {color:#87d7af} 165 | .f116 {color:#87d7d7} 166 | .f117 {color:#87d7ff} 167 | .f118 {color:#87ff00} 168 | .f119 {color:#87ff5f} 169 | .f120 {color:#87ff87} 170 | .f121 {color:#87ffaf} 171 | .f122 {color:#87ffd7} 172 | .f123 {color:#87ffff} 173 | .f124 {color:#af0000} 174 | .f125 {color:#af005f} 175 | .f126 {color:#af0087} 176 | .f127 {color:#af00af} 177 | .f128 {color:#af00d7} 178 | .f129 {color:#af00ff} 179 | .f130 {color:#af5f00} 180 | .f131 {color:#af5f5f} 181 | .f132 {color:#af5f87} 182 | .f133 {color:#af5faf} 183 | .f134 {color:#af5fd7} 184 | .f135 {color:#af5fff} 185 | .f136 {color:#af8700} 186 | .f137 {color:#af875f} 187 | .f138 {color:#af8787} 188 | .f139 {color:#af87af} 189 | .f140 {color:#af87d7} 190 | .f141 {color:#af87ff} 191 | .f142 {color:#afaf00} 192 | .f143 {color:#afaf5f} 193 | .f144 {color:#afaf87} 194 | .f145 {color:#afafaf} 195 | .f146 {color:#afafd7} 196 | .f147 {color:#afafff} 197 | .f148 {color:#afd700} 198 | .f149 {color:#afd75f} 199 | .f150 {color:#afd787} 200 | .f151 {color:#afd7af} 201 | .f152 {color:#afd7d7} 202 | .f153 {color:#afd7ff} 203 | .f154 {color:#afff00} 204 | .f155 {color:#afff5f} 205 | .f156 {color:#afff87} 206 | .f157 {color:#afffaf} 207 | .f158 {color:#afffd7} 208 | .f159 {color:#afffff} 209 | .f160 {color:#d70000} 210 | .f161 {color:#d7005f} 211 | .f162 {color:#d70087} 212 | .f163 {color:#d700af} 213 | .f164 {color:#d700d7} 214 | .f165 {color:#d700ff} 215 | .f166 {color:#d75f00} 216 | .f167 {color:#d75f5f} 217 | .f168 {color:#d75f87} 218 | .f169 {color:#d75faf} 219 | .f170 {color:#d75fd7} 220 | .f171 {color:#d75fff} 221 | .f172 {color:#d78700} 222 | .f173 {color:#d7875f} 223 | .f174 {color:#d78787} 224 | .f175 {color:#d787af} 225 | .f176 {color:#d787d7} 226 | .f177 {color:#d787ff} 227 | .f178 {color:#d7af00} 228 | .f179 {color:#d7af5f} 229 | .f180 {color:#d7af87} 230 | .f181 {color:#d7afaf} 231 | .f182 {color:#d7afd7} 232 | .f183 {color:#d7afff} 233 | .f184 {color:#d7d700} 234 | .f185 {color:#d7d75f} 235 | .f186 {color:#d7d787} 236 | .f187 {color:#d7d7af} 237 | .f188 {color:#d7d7d7} 238 | .f189 {color:#d7d7ff} 239 | .f190 {color:#d7ff00} 240 | .f191 {color:#d7ff5f} 241 | .f192 {color:#d7ff87} 242 | .f193 {color:#d7ffaf} 243 | .f194 {color:#d7ffd7} 244 | .f195 {color:#d7ffff} 245 | .f196 {color:#ff0000} 246 | .f197 {color:#ff005f} 247 | .f198 {color:#ff0087} 248 | .f199 {color:#ff00af} 249 | .f200 {color:#ff00d7} 250 | .f201 {color:#ff00ff} 251 | .f202 {color:#ff5f00} 252 | .f203 {color:#ff5f5f} 253 | .f204 {color:#ff5f87} 254 | .f205 {color:#ff5faf} 255 | .f206 {color:#ff5fd7} 256 | .f207 {color:#ff5fff} 257 | .f208 {color:#ff8700} 258 | .f209 {color:#ff875f} 259 | .f210 {color:#ff8787} 260 | .f211 {color:#ff87af} 261 | .f212 {color:#ff87d7} 262 | .f213 {color:#ff87ff} 263 | .f214 {color:#ffaf00} 264 | .f215 {color:#ffaf5f} 265 | .f216 {color:#ffaf87} 266 | .f217 {color:#ffafaf} 267 | .f218 {color:#ffafd7} 268 | .f219 {color:#ffafff} 269 | .f220 {color:#ffd700} 270 | .f221 {color:#ffd75f} 271 | .f222 {color:#ffd787} 272 | .f223 {color:#ffd7af} 273 | .f224 {color:#ffd7d7} 274 | .f225 {color:#ffd7ff} 275 | .f226 {color:#ffff00} 276 | .f227 {color:#ffff5f} 277 | .f228 {color:#ffff87} 278 | .f229 {color:#ffffaf} 279 | .f230 {color:#ffffd7} 280 | .f231 {color:#ffffff} 281 | .f232 {color:#080808} 282 | .f233 {color:#121212} 283 | .f234 {color:#1c1c1c} 284 | .f235 {color:#262626} 285 | .f236 {color:#303030} 286 | .f237 {color:#3a3a3a} 287 | .f238 {color:#444444} 288 | .f239 {color:#4e4e4e} 289 | .f240 {color:#585858} 290 | .f241 {color:#626262} 291 | .f242 {color:#6c6c6c} 292 | .f243 {color:#767676} 293 | .f244 {color:#808080} 294 | .f245 {color:#8a8a8a} 295 | .f246 {color:#949494} 296 | .f247 {color:#9e9e9e} 297 | .f248 {color:#a8a8a8} 298 | .f249 {color:#b2b2b2} 299 | .f250 {color:#bcbcbc} 300 | .f251 {color:#c6c6c6} 301 | .f252 {color:#d0d0d0} 302 | .f253 {color:#dadada} 303 | .f254 {color:#e4e4e4} 304 | .f255 {color:#eeeeee} 305 | .b1 {background-color:#800000} 306 | .b2 {background-color:#008000} 307 | .b3 {background-color:#808000} 308 | .b4 {background-color:#000080} 309 | .b5 {background-color:#800080} 310 | .b6 {background-color:#008080} 311 | .b7 {background-color:#c0c0c0} 312 | .b8 {background-color:#808080} 313 | .b9 {background-color:#ff0000} 314 | .b10 {background-color:#00ff00} 315 | .b11 {background-color:#ffff00} 316 | .b12 {background-color:#0000ff} 317 | .b13 {background-color:#ff00ff} 318 | .b14 {background-color:#00ffff} 319 | .b15 {background-color:#ffffff} 320 | .b16 {background-color:#000000} 321 | .b17 {background-color:#00005f} 322 | .b18 {background-color:#000087} 323 | .b19 {background-color:#0000af} 324 | .b20 {background-color:#0000d7} 325 | .b21 {background-color:#0000ff} 326 | .b22 {background-color:#005f00} 327 | .b23 {background-color:#005f5f} 328 | .b24 {background-color:#005f87} 329 | .b25 {background-color:#005faf} 330 | .b26 {background-color:#005fd7} 331 | .b27 {background-color:#005fff} 332 | .b28 {background-color:#008700} 333 | .b29 {background-color:#00875f} 334 | .b30 {background-color:#008787} 335 | .b31 {background-color:#0087af} 336 | .b32 {background-color:#0087d7} 337 | .b33 {background-color:#0087ff} 338 | .b34 {background-color:#00af00} 339 | .b35 {background-color:#00af5f} 340 | .b36 {background-color:#00af87} 341 | .b37 {background-color:#00afaf} 342 | .b38 {background-color:#00afd7} 343 | .b39 {background-color:#00afff} 344 | .b40 {background-color:#00d700} 345 | .b41 {background-color:#00d75f} 346 | .b42 {background-color:#00d787} 347 | .b43 {background-color:#00d7af} 348 | .b44 {background-color:#00d7d7} 349 | .b45 {background-color:#00d7ff} 350 | .b46 {background-color:#00ff00} 351 | .b47 {background-color:#00ff5f} 352 | .b48 {background-color:#00ff87} 353 | .b49 {background-color:#00ffaf} 354 | .b50 {background-color:#00ffd7} 355 | .b51 {background-color:#00ffff} 356 | .b52 {background-color:#5f0000} 357 | .b53 {background-color:#5f005f} 358 | .b54 {background-color:#5f0087} 359 | .b55 {background-color:#5f00af} 360 | .b56 {background-color:#5f00d7} 361 | .b57 {background-color:#5f00ff} 362 | .b58 {background-color:#5f5f00} 363 | .b59 {background-color:#5f5f5f} 364 | .b60 {background-color:#5f5f87} 365 | .b61 {background-color:#5f5faf} 366 | .b62 {background-color:#5f5fd7} 367 | .b63 {background-color:#5f5fff} 368 | .b64 {background-color:#5f8700} 369 | .b65 {background-color:#5f875f} 370 | .b66 {background-color:#5f8787} 371 | .b67 {background-color:#5f87af} 372 | .b68 {background-color:#5f87d7} 373 | .b69 {background-color:#5f87ff} 374 | .b70 {background-color:#5faf00} 375 | .b71 {background-color:#5faf5f} 376 | .b72 {background-color:#5faf87} 377 | .b73 {background-color:#5fafaf} 378 | .b74 {background-color:#5fafd7} 379 | .b75 {background-color:#5fafff} 380 | .b76 {background-color:#5fd700} 381 | .b77 {background-color:#5fd75f} 382 | .b78 {background-color:#5fd787} 383 | .b79 {background-color:#5fd7af} 384 | .b80 {background-color:#5fd7d7} 385 | .b81 {background-color:#5fd7ff} 386 | .b82 {background-color:#5fff00} 387 | .b83 {background-color:#5fff5f} 388 | .b84 {background-color:#5fff87} 389 | .b85 {background-color:#5fffaf} 390 | .b86 {background-color:#5fffd7} 391 | .b87 {background-color:#5fffff} 392 | .b88 {background-color:#870000} 393 | .b89 {background-color:#87005f} 394 | .b90 {background-color:#870087} 395 | .b91 {background-color:#8700af} 396 | .b92 {background-color:#8700d7} 397 | .b93 {background-color:#8700ff} 398 | .b94 {background-color:#875f00} 399 | .b95 {background-color:#875f5f} 400 | .b96 {background-color:#875f87} 401 | .b97 {background-color:#875faf} 402 | .b98 {background-color:#875fd7} 403 | .b99 {background-color:#875fff} 404 | .b100 {background-color:#878700} 405 | .b101 {background-color:#87875f} 406 | .b102 {background-color:#878787} 407 | .b103 {background-color:#8787af} 408 | .b104 {background-color:#8787d7} 409 | .b105 {background-color:#8787ff} 410 | .b106 {background-color:#87af00} 411 | .b107 {background-color:#87af5f} 412 | .b108 {background-color:#87af87} 413 | .b109 {background-color:#87afaf} 414 | .b110 {background-color:#87afd7} 415 | .b111 {background-color:#87afff} 416 | .b112 {background-color:#87d700} 417 | .b113 {background-color:#87d75f} 418 | .b114 {background-color:#87d787} 419 | .b115 {background-color:#87d7af} 420 | .b116 {background-color:#87d7d7} 421 | .b117 {background-color:#87d7ff} 422 | .b118 {background-color:#87ff00} 423 | .b119 {background-color:#87ff5f} 424 | .b120 {background-color:#87ff87} 425 | .b121 {background-color:#87ffaf} 426 | .b122 {background-color:#87ffd7} 427 | .b123 {background-color:#87ffff} 428 | .b124 {background-color:#af0000} 429 | .b125 {background-color:#af005f} 430 | .b126 {background-color:#af0087} 431 | .b127 {background-color:#af00af} 432 | .b128 {background-color:#af00d7} 433 | .b129 {background-color:#af00ff} 434 | .b130 {background-color:#af5f00} 435 | .b131 {background-color:#af5f5f} 436 | .b132 {background-color:#af5f87} 437 | .b133 {background-color:#af5faf} 438 | .b134 {background-color:#af5fd7} 439 | .b135 {background-color:#af5fff} 440 | .b136 {background-color:#af8700} 441 | .b137 {background-color:#af875f} 442 | .b138 {background-color:#af8787} 443 | .b139 {background-color:#af87af} 444 | .b140 {background-color:#af87d7} 445 | .b141 {background-color:#af87ff} 446 | .b142 {background-color:#afaf00} 447 | .b143 {background-color:#afaf5f} 448 | .b144 {background-color:#afaf87} 449 | .b145 {background-color:#afafaf} 450 | .b146 {background-color:#afafd7} 451 | .b147 {background-color:#afafff} 452 | .b148 {background-color:#afd700} 453 | .b149 {background-color:#afd75f} 454 | .b150 {background-color:#afd787} 455 | .b151 {background-color:#afd7af} 456 | .b152 {background-color:#afd7d7} 457 | .b153 {background-color:#afd7ff} 458 | .b154 {background-color:#afff00} 459 | .b155 {background-color:#afff5f} 460 | .b156 {background-color:#afff87} 461 | .b157 {background-color:#afffaf} 462 | .b158 {background-color:#afffd7} 463 | .b159 {background-color:#afffff} 464 | .b160 {background-color:#d70000} 465 | .b161 {background-color:#d7005f} 466 | .b162 {background-color:#d70087} 467 | .b163 {background-color:#d700af} 468 | .b164 {background-color:#d700d7} 469 | .b165 {background-color:#d700ff} 470 | .b166 {background-color:#d75f00} 471 | .b167 {background-color:#d75f5f} 472 | .b168 {background-color:#d75f87} 473 | .b169 {background-color:#d75faf} 474 | .b170 {background-color:#d75fd7} 475 | .b171 {background-color:#d75fff} 476 | .b172 {background-color:#d78700} 477 | .b173 {background-color:#d7875f} 478 | .b174 {background-color:#d78787} 479 | .b175 {background-color:#d787af} 480 | .b176 {background-color:#d787d7} 481 | .b177 {background-color:#d787ff} 482 | .b178 {background-color:#d7af00} 483 | .b179 {background-color:#d7af5f} 484 | .b180 {background-color:#d7af87} 485 | .b181 {background-color:#d7afaf} 486 | .b182 {background-color:#d7afd7} 487 | .b183 {background-color:#d7afff} 488 | .b184 {background-color:#d7d700} 489 | .b185 {background-color:#d7d75f} 490 | .b186 {background-color:#d7d787} 491 | .b187 {background-color:#d7d7af} 492 | .b188 {background-color:#d7d7d7} 493 | .b189 {background-color:#d7d7ff} 494 | .b190 {background-color:#d7ff00} 495 | .b191 {background-color:#d7ff5f} 496 | .b192 {background-color:#d7ff87} 497 | .b193 {background-color:#d7ffaf} 498 | .b194 {background-color:#d7ffd7} 499 | .b195 {background-color:#d7ffff} 500 | .b196 {background-color:#ff0000} 501 | .b197 {background-color:#ff005f} 502 | .b198 {background-color:#ff0087} 503 | .b199 {background-color:#ff00af} 504 | .b200 {background-color:#ff00d7} 505 | .b201 {background-color:#ff00ff} 506 | .b202 {background-color:#ff5f00} 507 | .b203 {background-color:#ff5f5f} 508 | .b204 {background-color:#ff5f87} 509 | .b205 {background-color:#ff5faf} 510 | .b206 {background-color:#ff5fd7} 511 | .b207 {background-color:#ff5fff} 512 | .b208 {background-color:#ff8700} 513 | .b209 {background-color:#ff875f} 514 | .b210 {background-color:#ff8787} 515 | .b211 {background-color:#ff87af} 516 | .b212 {background-color:#ff87d7} 517 | .b213 {background-color:#ff87ff} 518 | .b214 {background-color:#ffaf00} 519 | .b215 {background-color:#ffaf5f} 520 | .b216 {background-color:#ffaf87} 521 | .b217 {background-color:#ffafaf} 522 | .b218 {background-color:#ffafd7} 523 | .b219 {background-color:#ffafff} 524 | .b220 {background-color:#ffd700} 525 | .b221 {background-color:#ffd75f} 526 | .b222 {background-color:#ffd787} 527 | .b223 {background-color:#ffd7af} 528 | .b224 {background-color:#ffd7d7} 529 | .b225 {background-color:#ffd7ff} 530 | .b226 {background-color:#ffff00} 531 | .b227 {background-color:#ffff5f} 532 | .b228 {background-color:#ffff87} 533 | .b229 {background-color:#ffffaf} 534 | .b230 {background-color:#ffffd7} 535 | .b231 {background-color:#ffffff} 536 | .b232 {background-color:#080808} 537 | .b233 {background-color:#121212} 538 | .b234 {background-color:#1c1c1c} 539 | .b235 {background-color:#262626} 540 | .b236 {background-color:#303030} 541 | .b237 {background-color:#3a3a3a} 542 | .b238 {background-color:#444444} 543 | .b239 {background-color:#4e4e4e} 544 | .b240 {background-color:#585858} 545 | .b241 {background-color:#626262} 546 | .b242 {background-color:#6c6c6c} 547 | .b243 {background-color:#767676} 548 | .b244 {background-color:#808080} 549 | .b245 {background-color:#8a8a8a} 550 | .b246 {background-color:#949494} 551 | .b247 {background-color:#9e9e9e} 552 | .b248 {background-color:#a8a8a8} 553 | .b249 {background-color:#b2b2b2} 554 | .b250 {background-color:#bcbcbc} 555 | .b251 {background-color:#c6c6c6} 556 | .b252 {background-color:#d0d0d0} 557 | .b253 {background-color:#dadada} 558 | .b254 {background-color:#e4e4e4} 559 | .b255 {background-color:#eeeeee} 560 | 561 | -------------------------------------------------------------------------------- /include/imtui/imtui-impl-emscripten.h: -------------------------------------------------------------------------------- 1 | /*! \file imtui-impl-emscripten.h 2 | * \brief Enter description here. 3 | */ 4 | 5 | #pragma once 6 | 7 | namespace ImTui { 8 | struct TScreen; 9 | } 10 | 11 | ImTui::TScreen * ImTui_ImplEmscripten_Init(bool mouseSupport); 12 | void ImTui_ImplEmscripten_Shutdown(); 13 | void ImTui_ImplEmscripten_NewFrame(); 14 | 15 | extern "C" { 16 | void set_mouse_pos(float x, float y); 17 | void set_mouse_down(int but, float x, float y); 18 | void set_mouse_up(int but, float x, float y); 19 | void set_mouse_wheel(float x, float y); 20 | void set_screen_size(int nx, int ny); 21 | 22 | void set_key_down(int key); 23 | void set_key_up(int key); 24 | void set_key_press(int key); 25 | 26 | void get_screen(char * buffer); 27 | } 28 | -------------------------------------------------------------------------------- /include/imtui/imtui-impl-ncurses.h: -------------------------------------------------------------------------------- 1 | /*! \file imtui-impl-ncurses.h 2 | * \brief Enter description here. 3 | */ 4 | 5 | #pragma once 6 | 7 | namespace ImTui { 8 | struct TScreen; 9 | } 10 | 11 | // the interface below allows the user to decide when the application is active or not 12 | // this can be used to reduce the redraw rate, and thus the CPU usage, when suitable 13 | // for example - there is no user input, or the displayed content hasn't changed significantly 14 | 15 | // fps_active - specify the redraw rate when the application is active 16 | // fps_idle - specify the redraw rate when the application is not active 17 | ImTui::TScreen * ImTui_ImplNcurses_Init(bool mouseSupport, float fps_active = 60.0, float fps_idle = -1.0); 18 | 19 | void ImTui_ImplNcurses_Shutdown(); 20 | 21 | // returns true if there is any user input from the keyboard/mouse 22 | bool ImTui_ImplNcurses_NewFrame(); 23 | 24 | // active - specify which redraw rate to use: fps_active or fps_idle 25 | void ImTui_ImplNcurses_DrawScreen(bool active = true); 26 | 27 | bool ImTui_ImplNcurses_ProcessEvent(); 28 | -------------------------------------------------------------------------------- /include/imtui/imtui-impl-text.h: -------------------------------------------------------------------------------- 1 | /*! \file imtui-impl-text.h 2 | * \brief Enter description here. 3 | */ 4 | 5 | #pragma once 6 | 7 | struct ImDrawData; 8 | 9 | namespace ImTui { 10 | struct TScreen; 11 | } 12 | 13 | bool ImTui_ImplText_Init(); 14 | void ImTui_ImplText_Shutdown(); 15 | void ImTui_ImplText_NewFrame(); 16 | void ImTui_ImplText_RenderDrawData(ImDrawData * drawData, ImTui::TScreen * screen); 17 | -------------------------------------------------------------------------------- /include/imtui/imtui.h: -------------------------------------------------------------------------------- 1 | /*! \file imtui.h 2 | * \brief Enter description here. 3 | */ 4 | 5 | #pragma once 6 | 7 | // simply expose the existing Dear ImGui API 8 | #include "imgui/imgui.h" 9 | 10 | #include "imtui/imtui-impl-text.h" 11 | 12 | #include 13 | #include 14 | 15 | namespace ImTui { 16 | 17 | using TChar = unsigned char; 18 | using TColor = unsigned char; 19 | 20 | // single screen cell 21 | // 0x0000FFFF - char 22 | // 0x00FF0000 - foreground color 23 | // 0xFF000000 - background color 24 | using TCell = uint32_t; 25 | 26 | struct TScreen { 27 | int nx = 0; 28 | int ny = 0; 29 | 30 | int nmax = 0; 31 | 32 | TCell * data = nullptr; 33 | 34 | ~TScreen() { 35 | if (data) delete [] data; 36 | } 37 | 38 | inline int size() const { return nx*ny; } 39 | 40 | inline void clear() { 41 | if (data) { 42 | memset(data, 0, nx*ny*sizeof(TCell)); 43 | } 44 | } 45 | 46 | inline void resize(int pnx, int pny) { 47 | nx = pnx; 48 | ny = pny; 49 | if (nx*ny <= nmax) return; 50 | 51 | if (data) delete [] data; 52 | 53 | nmax = nx*ny; 54 | data = new TCell[nmax]; 55 | } 56 | }; 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 11) 2 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 3 | 4 | # core 5 | 6 | add_library(imtui ${IMTUI_LIBRARY_TYPE} 7 | imtui-impl-text.cpp 8 | ) 9 | 10 | target_include_directories(imtui PUBLIC 11 | ../include 12 | ) 13 | 14 | target_link_libraries(imtui PUBLIC 15 | imgui-for-imtui 16 | ) 17 | 18 | target_link_libraries(imtui PRIVATE 19 | ${CMAKE_DL_LIBS} 20 | ) 21 | 22 | set_target_properties(imtui PROPERTIES PUBLIC_HEADER "../include/imtui/imtui.h;../include/imtui/imtui-impl-text.h") 23 | 24 | if (MINGW) 25 | target_link_libraries(imtui PUBLIC stdc++) 26 | endif() 27 | 28 | # ncurses 29 | 30 | if (IMTUI_SUPPORT_NCURSES) 31 | add_library(imtui-ncurses ${IMTUI_LIBRARY_TYPE} 32 | imtui-impl-ncurses.cpp 33 | ) 34 | 35 | target_include_directories(imtui-ncurses PRIVATE 36 | ${CURSES_INCLUDE_DIR} 37 | ) 38 | 39 | set_target_properties(imtui-ncurses PROPERTIES PUBLIC_HEADER "../include/imtui/imtui-impl-ncurses.h") 40 | 41 | target_link_libraries(imtui-ncurses PUBLIC 42 | imtui 43 | ${CURSES_LIBRARIES} 44 | ) 45 | endif() 46 | 47 | # emscripten 48 | 49 | if (EMSCRIPTEN) 50 | add_library(imtui-emscripten ${IMTUI_LIBRARY_TYPE} 51 | imtui-impl-emscripten.cpp 52 | ) 53 | 54 | target_link_libraries(imtui-emscripten PUBLIC 55 | imtui 56 | ) 57 | endif() 58 | 59 | if (IMTUI_STANDALONE AND NOT EMSCRIPTEN) 60 | install(TARGETS imtui 61 | EXPORT imtui 62 | PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/imtui 63 | INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/imtui 64 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 65 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 66 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 67 | ) 68 | 69 | install(TARGETS imtui-ncurses 70 | EXPORT imtui-ncurses 71 | PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/imtui 72 | INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/imtui 73 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 74 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 75 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 76 | ) 77 | endif() 78 | -------------------------------------------------------------------------------- /src/imtui-impl-emscripten.cpp: -------------------------------------------------------------------------------- 1 | /*! \file imtui-impl-emscripten.cpp 2 | * \brief Enter description here. 3 | */ 4 | 5 | #include "imtui/imtui.h" 6 | #include "imtui/imtui-impl-emscripten.h" 7 | #include "imtui/imtui-impl-text.h" 8 | 9 | #include 10 | 11 | // client input 12 | static bool ignoreMouse = false; 13 | static ImVec2 lastMousePos = { 0.0, 0.0 }; 14 | static bool lastMouseDown[5] = { false, false, false, false, false }; 15 | static float lastMouseWheel = 0.0; 16 | static float lastMouseWheelH = 0.0; 17 | 18 | static ImTui::TScreen * g_screen = nullptr; 19 | static char lastAddText[8]; 20 | static bool lastKeysDown[512]; 21 | 22 | ImTui::TScreen * ImTui_ImplEmscripten_Init(bool mouseSupport) { 23 | if (g_screen == nullptr) { 24 | g_screen = new ImTui::TScreen(); 25 | } 26 | 27 | ImGui::GetIO().KeyMap[ImGuiKey_Tab] = 9; 28 | ImGui::GetIO().KeyMap[ImGuiKey_LeftArrow] = 37; 29 | ImGui::GetIO().KeyMap[ImGuiKey_RightArrow] = 39; 30 | ImGui::GetIO().KeyMap[ImGuiKey_UpArrow] = 38; 31 | ImGui::GetIO().KeyMap[ImGuiKey_DownArrow] = 40; 32 | ImGui::GetIO().KeyMap[ImGuiKey_PageUp] = 33; 33 | ImGui::GetIO().KeyMap[ImGuiKey_PageDown] = 34; 34 | ImGui::GetIO().KeyMap[ImGuiKey_Home] = 36; 35 | ImGui::GetIO().KeyMap[ImGuiKey_End] = 35; 36 | ImGui::GetIO().KeyMap[ImGuiKey_Insert] = 45; 37 | ImGui::GetIO().KeyMap[ImGuiKey_Delete] = 46; 38 | ImGui::GetIO().KeyMap[ImGuiKey_Backspace] = 8; 39 | ImGui::GetIO().KeyMap[ImGuiKey_Space] = 32; 40 | ImGui::GetIO().KeyMap[ImGuiKey_Enter] = 13; 41 | ImGui::GetIO().KeyMap[ImGuiKey_Escape] = 27; 42 | ImGui::GetIO().KeyMap[ImGuiKey_KeyPadEnter] = 13; 43 | ImGui::GetIO().KeyMap[ImGuiKey_A] = 1; 44 | ImGui::GetIO().KeyMap[ImGuiKey_C] = 2; 45 | ImGui::GetIO().KeyMap[ImGuiKey_V] = 2; 46 | ImGui::GetIO().KeyMap[ImGuiKey_X] = 3; 47 | ImGui::GetIO().KeyMap[ImGuiKey_Y] = 4; 48 | ImGui::GetIO().KeyMap[ImGuiKey_Z] = 5; 49 | 50 | ignoreMouse = !mouseSupport; 51 | 52 | return g_screen; 53 | } 54 | 55 | void ImTui_ImplEmscripten_Shutdown() { 56 | if (g_screen) { 57 | delete g_screen; 58 | } 59 | 60 | g_screen = nullptr; 61 | } 62 | 63 | void ImTui_ImplEmscripten_NewFrame() { 64 | ImGui::GetIO().MousePos = lastMousePos; 65 | ImGui::GetIO().MouseWheelH = lastMouseWheelH; 66 | ImGui::GetIO().MouseWheel = lastMouseWheel; 67 | ImGui::GetIO().MouseDown[0] = lastMouseDown[0]; 68 | ImGui::GetIO().MouseDown[1] = lastMouseDown[2]; // right-click in browser is 2 69 | ImGui::GetIO().MouseDown[2] = lastMouseDown[1]; // scroll-click in browser is 1 70 | ImGui::GetIO().MouseDown[3] = lastMouseDown[3]; 71 | ImGui::GetIO().MouseDown[4] = lastMouseDown[4]; 72 | 73 | if (strlen(lastAddText) > 0 && (!(lastAddText[0] & 0x80)) ) { 74 | ImGui::GetIO().AddInputCharactersUTF8(lastAddText); 75 | } 76 | 77 | for (int i = 0; i < 512; ++i) { 78 | ImGui::GetIO().KeysDown[i] = lastKeysDown[i]; 79 | } 80 | 81 | lastMouseWheelH = 0.0; 82 | lastMouseWheel = 0.0; 83 | lastAddText[0] = 0; 84 | } 85 | 86 | EMSCRIPTEN_KEEPALIVE 87 | void set_mouse_pos(float x, float y) { 88 | if (ignoreMouse) return; 89 | 90 | lastMousePos.x = x; 91 | lastMousePos.y = y; 92 | } 93 | 94 | EMSCRIPTEN_KEEPALIVE 95 | void set_mouse_down(int but, float x, float y) { 96 | if (ignoreMouse) return; 97 | 98 | lastMouseDown[but] = true; 99 | lastMousePos.x = x; 100 | lastMousePos.y = y; 101 | } 102 | 103 | EMSCRIPTEN_KEEPALIVE 104 | void set_mouse_up(int but, float x, float y) { 105 | if (ignoreMouse) return; 106 | 107 | lastMouseDown[but] = false; 108 | lastMousePos.x = x; 109 | lastMousePos.y = y; 110 | } 111 | 112 | EMSCRIPTEN_KEEPALIVE 113 | void set_mouse_wheel(float x, float y) { 114 | if (ignoreMouse) return; 115 | 116 | lastMouseWheelH = x; 117 | lastMouseWheel = y; 118 | } 119 | 120 | EMSCRIPTEN_KEEPALIVE 121 | void get_screen(char * buffer) { 122 | int nx = g_screen->nx; 123 | int ny = g_screen->ny; 124 | 125 | int idx = 0; 126 | for (int y = 0; y < ny; ++y) { 127 | for (int x = 0; x < nx; ++x) { 128 | const auto & cell = g_screen->data[y*nx + x]; 129 | buffer[idx] = cell & 0x000000FF; ++idx; 130 | buffer[idx] = (cell & 0x00FF0000) >> 16; ++idx; 131 | buffer[idx] = (cell & 0xFF000000) >> 24; ++idx; 132 | } 133 | } 134 | } 135 | 136 | EMSCRIPTEN_KEEPALIVE 137 | void set_key_down(int key) { 138 | lastAddText[0] = 0; 139 | lastAddText[1] = 0; 140 | 141 | if (lastKeysDown[17]) { 142 | (key == 65) && (lastKeysDown[ImGui::GetIO().KeyMap[ImGuiKey_A]] = true); 143 | (key == 67) && (lastKeysDown[ImGui::GetIO().KeyMap[ImGuiKey_C]] = true); 144 | (key == 86) && (lastKeysDown[ImGui::GetIO().KeyMap[ImGuiKey_V]] = true); 145 | (key == 88) && (lastKeysDown[ImGui::GetIO().KeyMap[ImGuiKey_X]] = true); 146 | (key == 89) && (lastKeysDown[ImGui::GetIO().KeyMap[ImGuiKey_Y]] = true); 147 | (key == 90) && (lastKeysDown[ImGui::GetIO().KeyMap[ImGuiKey_Z]] = true); 148 | } else { 149 | bool isSpecial = false; 150 | for (int i = 0; i < ImGuiKey_COUNT; ++i) { 151 | if (key == ImGui::GetIO().KeyMap[i] && key != ImGui::GetIO().KeyMap[ImGuiKey_Space]) { 152 | isSpecial = true; 153 | break; 154 | } 155 | } 156 | 157 | if (key == 189) { // minus '-' sign 158 | key = 45; 159 | } 160 | 161 | if (isSpecial == false) { 162 | lastAddText[0] = key; 163 | } 164 | } 165 | 166 | lastKeysDown[key] = true; 167 | 168 | if (key == 16) { 169 | ImGui::GetIO().KeyShift = true; 170 | } 171 | 172 | if (key == 17) { 173 | ImGui::GetIO().KeyCtrl = true; 174 | } 175 | 176 | if (key == 18) { 177 | ImGui::GetIO().KeyAlt = true; 178 | } 179 | } 180 | 181 | EMSCRIPTEN_KEEPALIVE 182 | void set_key_up(int key) { 183 | lastKeysDown[key] = false; 184 | 185 | if (key == 16) { 186 | ImGui::GetIO().KeyShift = false; 187 | } 188 | 189 | if (key == 17) { 190 | ImGui::GetIO().KeyCtrl = false; 191 | lastKeysDown[ImGui::GetIO().KeyMap[ImGuiKey_A]] = false; 192 | lastKeysDown[ImGui::GetIO().KeyMap[ImGuiKey_C]] = false; 193 | lastKeysDown[ImGui::GetIO().KeyMap[ImGuiKey_V]] = false; 194 | lastKeysDown[ImGui::GetIO().KeyMap[ImGuiKey_X]] = false; 195 | lastKeysDown[ImGui::GetIO().KeyMap[ImGuiKey_Y]] = false; 196 | lastKeysDown[ImGui::GetIO().KeyMap[ImGuiKey_Z]] = false; 197 | } 198 | 199 | if (key == 18) { 200 | ImGui::GetIO().KeyAlt = false; 201 | } 202 | } 203 | 204 | EMSCRIPTEN_KEEPALIVE 205 | void set_key_press(int key) { 206 | if (key > 0) { 207 | lastAddText[0] = key; 208 | lastAddText[1] = 0; 209 | } 210 | } 211 | 212 | EMSCRIPTEN_KEEPALIVE 213 | void set_screen_size(int nx, int ny) { 214 | ImGui::GetIO().DisplaySize.x = nx; 215 | ImGui::GetIO().DisplaySize.y = ny; 216 | } 217 | -------------------------------------------------------------------------------- /src/imtui-impl-ncurses.cpp: -------------------------------------------------------------------------------- 1 | /*! \file imtui-impl-ncurses.cpp 2 | * \brief Enter description here. 3 | */ 4 | 5 | #include "imtui/imtui.h" 6 | #include "imtui/imtui-impl-ncurses.h" 7 | #include "imtui/imtui-impl-text.h" 8 | 9 | #ifdef _WIN32 10 | #define NCURSES_MOUSE_VERSION 11 | #include 12 | #define set_escdelay(X) 13 | 14 | #define KEY_OFFSET 0xec00 15 | 16 | #define KEY_CODE_YES (KEY_OFFSET + 0x00) /* If get_wch() gives a key code */ 17 | 18 | #define KEY_BREAK (KEY_OFFSET + 0x01) /* Not on PC KBD */ 19 | #define KEY_DOWN (KEY_OFFSET + 0x02) /* Down arrow key */ 20 | #define KEY_UP (KEY_OFFSET + 0x03) /* Up arrow key */ 21 | #define KEY_LEFT (KEY_OFFSET + 0x04) /* Left arrow key */ 22 | #define KEY_RIGHT (KEY_OFFSET + 0x05) /* Right arrow key */ 23 | #define KEY_HOME (KEY_OFFSET + 0x06) /* home key */ 24 | #define KEY_BACKSPACE (8) /* not on pc */ 25 | #define KEY_F0 (KEY_OFFSET + 0x08) /* function keys; 64 reserved */ 26 | #else 27 | #include 28 | #endif 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | namespace { 38 | struct VSync { 39 | VSync(double fps_active = 60.0, double fps_idle = 60.0) : 40 | tStepActive_us(1000000.0/fps_active), 41 | tStepIdle_us(1000000.0/fps_idle) {} 42 | 43 | uint64_t tStepActive_us; 44 | uint64_t tStepIdle_us; 45 | uint64_t tLast_us = t_us(); 46 | uint64_t tNext_us = tLast_us; 47 | 48 | inline uint64_t t_us() const { 49 | return std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count(); // duh .. 50 | } 51 | 52 | inline void wait(bool active) { 53 | uint64_t tNow_us = t_us(); 54 | 55 | auto tStep_us = active ? tStepActive_us : tStepIdle_us; 56 | auto tNextCur_us = tNext_us + tStep_us; 57 | 58 | while (tNow_us < tNextCur_us - 100) { 59 | if (tNow_us + 0.5*tStepActive_us < tNextCur_us) { 60 | int ch = wgetch(stdscr); 61 | 62 | if (ch != ERR) { 63 | ungetch(ch); 64 | tNextCur_us = tNow_us; 65 | 66 | return; 67 | } 68 | } 69 | 70 | std::this_thread::sleep_for(std::chrono::microseconds( 71 | std::min((uint64_t)(0.9*tStepActive_us), 72 | (uint64_t) (0.9*(tNextCur_us - tNow_us)) 73 | ))); 74 | 75 | tNow_us = t_us(); 76 | } 77 | 78 | tNext_us += tStep_us; 79 | } 80 | 81 | inline float delta_s() { 82 | uint64_t tNow_us = t_us(); 83 | uint64_t res = tNow_us - tLast_us; 84 | tLast_us = tNow_us; 85 | 86 | return float(res)/1e6f; 87 | } 88 | }; 89 | } 90 | 91 | static VSync g_vsync; 92 | static ImTui::TScreen * g_screen = nullptr; 93 | 94 | ImTui::TScreen * ImTui_ImplNcurses_Init(bool mouseSupport, float fps_active, float fps_idle) { 95 | if (g_screen == nullptr) { 96 | g_screen = new ImTui::TScreen(); 97 | } 98 | 99 | if (fps_idle < 0.0) { 100 | fps_idle = fps_active; 101 | } 102 | fps_idle = std::min(fps_active, fps_idle); 103 | g_vsync = VSync(fps_active, fps_idle); 104 | 105 | initscr(); 106 | use_default_colors(); 107 | start_color(); 108 | cbreak(); 109 | noecho(); 110 | curs_set(0); 111 | nodelay(stdscr, TRUE); 112 | wtimeout(stdscr, 0); 113 | set_escdelay(25); 114 | keypad(stdscr, true); 115 | 116 | if (mouseSupport) { 117 | mouseinterval(0); 118 | mousemask(ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION, NULL); 119 | printf("\033[?1003h\n"); 120 | } 121 | 122 | ImGui::GetIO().KeyMap[ImGuiKey_Tab] = 9; 123 | ImGui::GetIO().KeyMap[ImGuiKey_LeftArrow] = 260; 124 | ImGui::GetIO().KeyMap[ImGuiKey_RightArrow] = 261; 125 | ImGui::GetIO().KeyMap[ImGuiKey_UpArrow] = 259; 126 | ImGui::GetIO().KeyMap[ImGuiKey_DownArrow] = 258; 127 | ImGui::GetIO().KeyMap[ImGuiKey_PageUp] = 339; 128 | ImGui::GetIO().KeyMap[ImGuiKey_PageDown] = 338; 129 | ImGui::GetIO().KeyMap[ImGuiKey_Home] = 262; 130 | ImGui::GetIO().KeyMap[ImGuiKey_End] = 360; 131 | ImGui::GetIO().KeyMap[ImGuiKey_Insert] = 331; 132 | ImGui::GetIO().KeyMap[ImGuiKey_Delete] = 330; 133 | ImGui::GetIO().KeyMap[ImGuiKey_Backspace] = 263; 134 | ImGui::GetIO().KeyMap[ImGuiKey_Space] = 32; 135 | ImGui::GetIO().KeyMap[ImGuiKey_Enter] = 10; 136 | ImGui::GetIO().KeyMap[ImGuiKey_Escape] = 27; 137 | ImGui::GetIO().KeyMap[ImGuiKey_KeyPadEnter] = 343; 138 | ImGui::GetIO().KeyMap[ImGuiKey_A] = 1; 139 | ImGui::GetIO().KeyMap[ImGuiKey_C] = 3; 140 | ImGui::GetIO().KeyMap[ImGuiKey_V] = 22; 141 | ImGui::GetIO().KeyMap[ImGuiKey_X] = 24; 142 | ImGui::GetIO().KeyMap[ImGuiKey_Y] = 25; 143 | ImGui::GetIO().KeyMap[ImGuiKey_Z] = 26; 144 | 145 | ImGui::GetIO().KeyRepeatDelay = 0.050; 146 | ImGui::GetIO().KeyRepeatRate = 0.050; 147 | 148 | int screenSizeX = 0; 149 | int screenSizeY = 0; 150 | 151 | getmaxyx(stdscr, screenSizeY, screenSizeX); 152 | ImGui::GetIO().DisplaySize = ImVec2(screenSizeX, screenSizeY); 153 | 154 | return g_screen; 155 | } 156 | 157 | void ImTui_ImplNcurses_Shutdown() { 158 | // ref #11 : https://github.com/ggerganov/imtui/issues/11 159 | printf("\033[?1003l\n"); // Disable mouse movement events, as l = low 160 | 161 | endwin(); 162 | 163 | if (g_screen) { 164 | delete g_screen; 165 | } 166 | 167 | g_screen = nullptr; 168 | } 169 | 170 | bool ImTui_ImplNcurses_NewFrame() { 171 | bool hasInput = false; 172 | 173 | int screenSizeX = 0; 174 | int screenSizeY = 0; 175 | 176 | getmaxyx(stdscr, screenSizeY, screenSizeX); 177 | ImGui::GetIO().DisplaySize = ImVec2(screenSizeX, screenSizeY); 178 | 179 | static int mx = 0; 180 | static int my = 0; 181 | static int lbut = 0; 182 | static int rbut = 0; 183 | static unsigned long mstate = 0; 184 | static char input[3]; 185 | 186 | input[2] = 0; 187 | 188 | auto & keysDown = ImGui::GetIO().KeysDown; 189 | std::fill(keysDown, keysDown + 512, 0); 190 | 191 | ImGui::GetIO().KeyCtrl = false; 192 | ImGui::GetIO().KeyShift = false; 193 | 194 | while (true) { 195 | int c = wgetch(stdscr); 196 | 197 | if (c == ERR) { 198 | if ((mstate & 0xf) == 0x1) { 199 | lbut = 0; 200 | rbut = 0; 201 | } 202 | break; 203 | } else if (c == KEY_MOUSE) { 204 | MEVENT event; 205 | if (getmouse(&event) == OK) { 206 | mx = event.x; 207 | my = event.y; 208 | mstate = event.bstate; 209 | if ((mstate & 0x000f) == 0x0002) lbut = 1; 210 | if ((mstate & 0x000f) == 0x0001) lbut = 0; 211 | if ((mstate & 0xf000) == 0x2000) rbut = 1; 212 | if ((mstate & 0xf000) == 0x1000) rbut = 0; 213 | //printf("mstate = 0x%016lx\n", mstate); 214 | ImGui::GetIO().KeyCtrl |= ((mstate & 0x0F000000) == 0x01000000); 215 | } 216 | } else { 217 | input[0] = (c & 0x000000FF); 218 | input[1] = (c & 0x0000FF00) >> 8; 219 | //printf("c = %d, c0 = %d, c1 = %d xxx\n", c, input[0], input[1]); 220 | if (c < 127) { 221 | if (c != ImGui::GetIO().KeyMap[ImGuiKey_Enter]) { 222 | ImGui::GetIO().AddInputCharactersUTF8(input); 223 | } 224 | } 225 | if (c == 330) { 226 | ImGui::GetIO().KeysDown[ImGui::GetIO().KeyMap[ImGuiKey_Delete]] = true; 227 | } else if (c == KEY_BACKSPACE || c == KEY_DC || c == 127) { 228 | ImGui::GetIO().KeysDown[ImGui::GetIO().KeyMap[ImGuiKey_Backspace]] = true; 229 | // Shift + arrows (probably not portable :() 230 | } else if (c == 393) { 231 | ImGui::GetIO().KeysDown[ImGui::GetIO().KeyMap[ImGuiKey_LeftArrow]] = true; 232 | ImGui::GetIO().KeyShift = true; 233 | } else if (c == 402) { 234 | ImGui::GetIO().KeysDown[ImGui::GetIO().KeyMap[ImGuiKey_RightArrow]] = true; 235 | ImGui::GetIO().KeyShift = true; 236 | } else if (c == 337) { 237 | ImGui::GetIO().KeysDown[ImGui::GetIO().KeyMap[ImGuiKey_UpArrow]] = true; 238 | ImGui::GetIO().KeyShift = true; 239 | } else if (c == 336) { 240 | ImGui::GetIO().KeysDown[ImGui::GetIO().KeyMap[ImGuiKey_DownArrow]] = true; 241 | ImGui::GetIO().KeyShift = true; 242 | } else if (c == KEY_BACKSPACE) { 243 | ImGui::GetIO().KeysDown[ImGui::GetIO().KeyMap[ImGuiKey_Backspace]] = true; 244 | } else if (c == KEY_LEFT) { 245 | ImGui::GetIO().KeysDown[ImGui::GetIO().KeyMap[ImGuiKey_LeftArrow]] = true; 246 | } else if (c == KEY_RIGHT) { 247 | ImGui::GetIO().KeysDown[ImGui::GetIO().KeyMap[ImGuiKey_RightArrow]] = true; 248 | } else if (c == KEY_UP) { 249 | ImGui::GetIO().KeysDown[ImGui::GetIO().KeyMap[ImGuiKey_UpArrow]] = true; 250 | } else if (c == KEY_DOWN) { 251 | ImGui::GetIO().KeysDown[ImGui::GetIO().KeyMap[ImGuiKey_DownArrow]] = true; 252 | } else { 253 | keysDown[c] = true; 254 | } 255 | } 256 | 257 | hasInput = true; 258 | } 259 | 260 | if (ImGui::GetIO().KeysDown[ImGui::GetIO().KeyMap[ImGuiKey_A]]) ImGui::GetIO().KeyCtrl = true; 261 | if (ImGui::GetIO().KeysDown[ImGui::GetIO().KeyMap[ImGuiKey_C]]) ImGui::GetIO().KeyCtrl = true; 262 | if (ImGui::GetIO().KeysDown[ImGui::GetIO().KeyMap[ImGuiKey_V]]) ImGui::GetIO().KeyCtrl = true; 263 | if (ImGui::GetIO().KeysDown[ImGui::GetIO().KeyMap[ImGuiKey_X]]) ImGui::GetIO().KeyCtrl = true; 264 | if (ImGui::GetIO().KeysDown[ImGui::GetIO().KeyMap[ImGuiKey_Y]]) ImGui::GetIO().KeyCtrl = true; 265 | if (ImGui::GetIO().KeysDown[ImGui::GetIO().KeyMap[ImGuiKey_Z]]) ImGui::GetIO().KeyCtrl = true; 266 | 267 | ImGui::GetIO().MousePos.x = mx; 268 | ImGui::GetIO().MousePos.y = my; 269 | ImGui::GetIO().MouseDown[0] = lbut; 270 | ImGui::GetIO().MouseDown[1] = rbut; 271 | 272 | ImGui::GetIO().DeltaTime = g_vsync.delta_s(); 273 | 274 | return hasInput; 275 | } 276 | 277 | // state 278 | static int nColPairs = 1; 279 | static int nActiveFrames = 10; 280 | static ImTui::TScreen screenPrev; 281 | static std::vector curs; 282 | static std::array, 256*256> colPairs; 283 | 284 | void ImTui_ImplNcurses_DrawScreen(bool active) { 285 | if (active) nActiveFrames = 10; 286 | 287 | wrefresh(stdscr); 288 | 289 | int nx = g_screen->nx; 290 | int ny = g_screen->ny; 291 | 292 | bool compare = true; 293 | 294 | if (screenPrev.nx != nx || screenPrev.ny != ny) { 295 | screenPrev.resize(nx, ny); 296 | compare = false; 297 | } 298 | 299 | int ic = 0; 300 | curs.resize(nx + 1); 301 | 302 | for (int y = 0; y < ny; ++y) { 303 | bool isSame = compare; 304 | if (compare) { 305 | for (int x = 0; x < nx; ++x) { 306 | if (screenPrev.data[y*nx + x] != g_screen->data[y*nx + x]) { 307 | isSame = false; 308 | break; 309 | } 310 | } 311 | } 312 | if (isSame) continue; 313 | 314 | int lastp = 0xFFFFFFFF; 315 | move(y, 0); 316 | for (int x = 0; x < nx; ++x) { 317 | const auto cell = g_screen->data[y*nx + x]; 318 | const uint16_t f = (cell & 0x00FF0000) >> 16; 319 | const uint16_t b = (cell & 0xFF000000) >> 24; 320 | const uint16_t p = b*256 + f; 321 | 322 | if (colPairs[p].first == false) { 323 | init_pair(nColPairs, f, b); 324 | colPairs[p].first = true; 325 | colPairs[p].second = nColPairs; 326 | ++nColPairs; 327 | } 328 | 329 | if (lastp != (int) p) { 330 | if (curs.size() > 0) { 331 | curs[ic] = 0; 332 | addstr((char *) curs.data()); 333 | ic = 0; 334 | curs[0] = 0; 335 | } 336 | attron(COLOR_PAIR(colPairs[p].second)); 337 | lastp = p; 338 | } 339 | 340 | const uint16_t c = cell & 0x0000FFFF; 341 | curs[ic++] = c > 0 ? c : ' '; 342 | } 343 | 344 | if (curs.size() > 0) { 345 | curs[ic] = 0; 346 | addstr((char *) curs.data()); 347 | ic = 0; 348 | curs[0] = 0; 349 | } 350 | 351 | if (compare) { 352 | memcpy(screenPrev.data + y*nx, g_screen->data + y*nx, nx*sizeof(ImTui::TCell)); 353 | } 354 | } 355 | 356 | if (!compare) { 357 | memcpy(screenPrev.data, g_screen->data, nx*ny*sizeof(ImTui::TCell)); 358 | } 359 | 360 | g_vsync.wait(nActiveFrames --> 0); 361 | } 362 | 363 | bool ImTui_ImplNcurses_ProcessEvent() { 364 | return true; 365 | } 366 | -------------------------------------------------------------------------------- /src/imtui-impl-text.cpp: -------------------------------------------------------------------------------- 1 | /*! \file imtui-impl-text.cpp 2 | * \brief Enter description here. 3 | */ 4 | 5 | #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) 6 | #define _CRT_SECURE_NO_WARNINGS 7 | #endif 8 | 9 | #include "imtui/imtui.h" 10 | #include "imtui/imtui-impl-text.h" 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #define ABS(x) ((x >= 0) ? x : -x) 17 | 18 | void ScanLine(int x1, int y1, int x2, int y2, int ymax, std::vector & xrange) { 19 | int sx, sy, dx1, dy1, dx2, dy2, x, y, m, n, k, cnt; 20 | 21 | sx = x2 - x1; 22 | sy = y2 - y1; 23 | 24 | if (sx > 0) dx1 = 1; 25 | else if (sx < 0) dx1 = -1; 26 | else dx1 = 0; 27 | 28 | if (sy > 0) dy1 = 1; 29 | else if (sy < 0) dy1 = -1; 30 | else dy1 = 0; 31 | 32 | m = ABS(sx); 33 | n = ABS(sy); 34 | dx2 = dx1; 35 | dy2 = 0; 36 | 37 | if (m < n) 38 | { 39 | m = ABS(sy); 40 | n = ABS(sx); 41 | dx2 = 0; 42 | dy2 = dy1; 43 | } 44 | 45 | x = x1; y = y1; 46 | cnt = m + 1; 47 | k = n / 2; 48 | 49 | while (cnt--) { 50 | if ((y >= 0) && (y < ymax)) { 51 | if (x < xrange[2*y+0]) xrange[2*y+0] = x; 52 | if (x > xrange[2*y+1]) xrange[2*y+1] = x; 53 | } 54 | 55 | k += n; 56 | if (k < m) { 57 | x += dx2; 58 | y += dy2; 59 | } else { 60 | k -= m; 61 | x += dx1; 62 | y += dy1; 63 | } 64 | } 65 | } 66 | 67 | static std::vector g_xrange; 68 | 69 | void drawTriangle(ImVec2 p0, ImVec2 p1, ImVec2 p2, unsigned char col, ImTui::TScreen * screen) { 70 | int ymin = std::min(std::min(std::min((float) screen->size(), p0.y), p1.y), p2.y); 71 | int ymax = std::max(std::max(std::max(0.0f, p0.y), p1.y), p2.y); 72 | 73 | int ydelta = ymax - ymin + 1; 74 | 75 | if ((int) g_xrange.size() < 2*ydelta) { 76 | g_xrange.resize(2*ydelta); 77 | } 78 | 79 | for (int y = 0; y < ydelta; y++) { 80 | g_xrange[2*y+0] = 999999; 81 | g_xrange[2*y+1] = -999999; 82 | } 83 | 84 | ScanLine(p0.x, p0.y - ymin, p1.x, p1.y - ymin, ydelta, g_xrange); 85 | ScanLine(p1.x, p1.y - ymin, p2.x, p2.y - ymin, ydelta, g_xrange); 86 | ScanLine(p2.x, p2.y - ymin, p0.x, p0.y - ymin, ydelta, g_xrange); 87 | 88 | for (int y = 0; y < ydelta; y++) { 89 | if (g_xrange[2*y+1] >= g_xrange[2*y+0]) { 90 | int x = g_xrange[2*y+0]; 91 | int len = 1 + g_xrange[2*y+1] - g_xrange[2*y+0]; 92 | 93 | while (len--) { 94 | if (x >= 0 && x < screen->nx && y + ymin >= 0 && y + ymin < screen->ny) { 95 | auto & cell = screen->data[(y + ymin)*screen->nx + x]; 96 | cell &= 0x00FF0000; 97 | cell |= ' '; 98 | cell |= ((ImTui::TCell)(col) << 24); 99 | } 100 | ++x; 101 | } 102 | } 103 | } 104 | } 105 | 106 | inline ImTui::TColor rgbToAnsi256(ImU32 col, bool doAlpha) { 107 | ImTui::TColor r = col & 0x000000FF; 108 | ImTui::TColor g = (col & 0x0000FF00) >> 8; 109 | ImTui::TColor b = (col & 0x00FF0000) >> 16; 110 | 111 | if (r == g && g == b) { 112 | if (doAlpha) { 113 | ImTui::TColor a = (col & 0xFF000000) >> 24; 114 | r = (float(r)*a)/255.0f; 115 | } 116 | if (r < 8) { 117 | return 16; 118 | } 119 | 120 | if (r > 248) { 121 | return 231; 122 | } 123 | 124 | return std::round((float(r - 8) / 247) * 24) + 232; 125 | } 126 | 127 | if (doAlpha) { 128 | ImTui::TColor a = (col & 0xFF000000) >> 24; 129 | float scale = float(a)/255.0f; 130 | r = std::round(r*scale); 131 | g = std::round(g*scale); 132 | b = std::round(b*scale); 133 | } 134 | 135 | ImTui::TColor res = 16 136 | + (36 * std::round((float(r) / 255.0f) * 5.0f)) 137 | + (6 * std::round((float(g) / 255.0f) * 5.0f)) 138 | + std::round((float(b) / 255.0f) * 5.0f); 139 | 140 | return res; 141 | } 142 | 143 | void ImTui_ImplText_RenderDrawData(ImDrawData * drawData, ImTui::TScreen * screen) { 144 | // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) 145 | int fb_width = (int)(drawData->DisplaySize.x * drawData->FramebufferScale.x); 146 | int fb_height = (int)(drawData->DisplaySize.y * drawData->FramebufferScale.y); 147 | 148 | if (fb_width <= 0 || fb_height <= 0) { 149 | return; 150 | } 151 | 152 | screen->resize(ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y); 153 | screen->clear(); 154 | 155 | // Will project scissor/clipping rectangles into framebuffer space 156 | ImVec2 clip_off = drawData->DisplayPos; // (0,0) unless using multi-viewports 157 | ImVec2 clip_scale = drawData->FramebufferScale; // (1,1) unless using retina display which are often (2,2) 158 | 159 | // Render command lists 160 | for (int n = 0; n < drawData->CmdListsCount; n++) 161 | { 162 | const ImDrawList* cmd_list = drawData->CmdLists[n]; 163 | 164 | for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) 165 | { 166 | const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; 167 | { 168 | ImVec4 clip_rect; 169 | clip_rect.x = (pcmd->ClipRect.x - clip_off.x) * clip_scale.x; 170 | clip_rect.y = (pcmd->ClipRect.y - clip_off.y) * clip_scale.y; 171 | clip_rect.z = (pcmd->ClipRect.z - clip_off.x) * clip_scale.x; 172 | clip_rect.w = (pcmd->ClipRect.w - clip_off.y) * clip_scale.y; 173 | 174 | if (clip_rect.x < fb_width && clip_rect.y < fb_height && clip_rect.z >= 0.0f && clip_rect.w >= 0.0f) 175 | { 176 | float lastCharX = -10000.0f; 177 | float lastCharY = -10000.0f; 178 | 179 | for (unsigned int i = 0; i < pcmd->ElemCount; i += 3) { 180 | int vidx0 = cmd_list->IdxBuffer[pcmd->IdxOffset + i + 0]; 181 | int vidx1 = cmd_list->IdxBuffer[pcmd->IdxOffset + i + 1]; 182 | int vidx2 = cmd_list->IdxBuffer[pcmd->IdxOffset + i + 2]; 183 | 184 | auto pos0 = cmd_list->VtxBuffer[vidx0].pos; 185 | auto pos1 = cmd_list->VtxBuffer[vidx1].pos; 186 | auto pos2 = cmd_list->VtxBuffer[vidx2].pos; 187 | 188 | pos0.x = std::max(std::min(float(clip_rect.z - 1), pos0.x), clip_rect.x); 189 | pos1.x = std::max(std::min(float(clip_rect.z - 1), pos1.x), clip_rect.x); 190 | pos2.x = std::max(std::min(float(clip_rect.z - 1), pos2.x), clip_rect.x); 191 | pos0.y = std::max(std::min(float(clip_rect.w - 1), pos0.y), clip_rect.y); 192 | pos1.y = std::max(std::min(float(clip_rect.w - 1), pos1.y), clip_rect.y); 193 | pos2.y = std::max(std::min(float(clip_rect.w - 1), pos2.y), clip_rect.y); 194 | 195 | auto uv0 = cmd_list->VtxBuffer[vidx0].uv; 196 | auto uv1 = cmd_list->VtxBuffer[vidx1].uv; 197 | auto uv2 = cmd_list->VtxBuffer[vidx2].uv; 198 | 199 | auto col0 = cmd_list->VtxBuffer[vidx0].col; 200 | //auto col1 = cmd_list->VtxBuffer[vidx1].col; 201 | //auto col2 = cmd_list->VtxBuffer[vidx2].col; 202 | 203 | if (uv0.x != uv1.x || uv0.x != uv2.x || uv1.x != uv2.x || 204 | uv0.y != uv1.y || uv0.y != uv2.y || uv1.y != uv2.y) { 205 | int vvidx0 = cmd_list->IdxBuffer[pcmd->IdxOffset + i + 3]; 206 | int vvidx1 = cmd_list->IdxBuffer[pcmd->IdxOffset + i + 4]; 207 | int vvidx2 = cmd_list->IdxBuffer[pcmd->IdxOffset + i + 5]; 208 | 209 | auto ppos0 = cmd_list->VtxBuffer[vvidx0].pos; 210 | auto ppos1 = cmd_list->VtxBuffer[vvidx1].pos; 211 | auto ppos2 = cmd_list->VtxBuffer[vvidx2].pos; 212 | 213 | float x = ((pos0.x + pos1.x + pos2.x + ppos0.x + ppos1.x + ppos2.x)/6.0f); 214 | float y = ((pos0.y + pos1.y + pos2.y + ppos0.y + ppos1.y + ppos2.y)/6.0f) + 0.5f; 215 | 216 | if (std::fabs(y - lastCharY) < 0.5f && std::fabs(x - lastCharX) < 0.5f) { 217 | x = lastCharX + 1.0f; 218 | y = lastCharY; 219 | } 220 | 221 | lastCharX = x; 222 | lastCharY = y; 223 | 224 | int xx = (x) + 1; 225 | int yy = (y) + 0; 226 | if (xx < clip_rect.x || xx >= clip_rect.z || yy < clip_rect.y || yy >= clip_rect.w) { 227 | } else { 228 | auto & cell = screen->data[yy*screen->nx + xx]; 229 | cell &= 0xFF000000; 230 | cell |= (col0 & 0xff000000) >> 24; 231 | cell |= ((ImTui::TCell)(rgbToAnsi256(col0, false)) << 16); 232 | } 233 | i += 3; 234 | } else { 235 | drawTriangle(pos0, pos1, pos2, rgbToAnsi256(col0, true), screen); 236 | } 237 | } 238 | } 239 | } 240 | } 241 | } 242 | 243 | } 244 | 245 | bool ImTui_ImplText_Init() { 246 | ImGui::GetStyle().Alpha = 1.0f; 247 | ImGui::GetStyle().WindowPadding = ImVec2(0.5f, 0.0f); 248 | ImGui::GetStyle().WindowRounding = 0.0f; 249 | ImGui::GetStyle().WindowBorderSize = 0.0f; 250 | ImGui::GetStyle().WindowMinSize = ImVec2(4.0f, 2.0f); 251 | ImGui::GetStyle().WindowTitleAlign = ImVec2(0.0f, 0.0f); 252 | ImGui::GetStyle().WindowMenuButtonPosition= ImGuiDir_Left; 253 | ImGui::GetStyle().ChildRounding = 0.0f; 254 | ImGui::GetStyle().ChildBorderSize = 0.0f; 255 | ImGui::GetStyle().PopupRounding = 0.0f; 256 | ImGui::GetStyle().PopupBorderSize = 0.0f; 257 | ImGui::GetStyle().FramePadding = ImVec2(1.0f, 0.0f); 258 | ImGui::GetStyle().FrameRounding = 0.0f; 259 | ImGui::GetStyle().FrameBorderSize = 0.0f; 260 | ImGui::GetStyle().ItemSpacing = ImVec2(1.0f, 0.0f); 261 | ImGui::GetStyle().ItemInnerSpacing = ImVec2(1.0f, 0.0f); 262 | ImGui::GetStyle().TouchExtraPadding = ImVec2(0.5f, 0.0f); 263 | ImGui::GetStyle().IndentSpacing = 1.0f; 264 | ImGui::GetStyle().ColumnsMinSpacing = 1.0f; 265 | ImGui::GetStyle().ScrollbarSize = 0.5f; 266 | ImGui::GetStyle().ScrollbarRounding = 0.0f; 267 | ImGui::GetStyle().GrabMinSize = 0.1f; 268 | ImGui::GetStyle().GrabRounding = 0.0f; 269 | ImGui::GetStyle().TabRounding = 0.0f; 270 | ImGui::GetStyle().TabBorderSize = 0.0f; 271 | ImGui::GetStyle().ColorButtonPosition = ImGuiDir_Right; 272 | ImGui::GetStyle().ButtonTextAlign = ImVec2(0.5f,0.0f); 273 | ImGui::GetStyle().SelectableTextAlign = ImVec2(0.0f,0.0f); 274 | ImGui::GetStyle().DisplayWindowPadding = ImVec2(0.0f,0.0f); 275 | ImGui::GetStyle().DisplaySafeAreaPadding = ImVec2(0.0f,0.0f); 276 | ImGui::GetStyle().CellPadding = ImVec2(1.0f,0.0f); 277 | ImGui::GetStyle().MouseCursorScale = 1.0f; 278 | ImGui::GetStyle().AntiAliasedLines = false; 279 | ImGui::GetStyle().AntiAliasedFill = false; 280 | ImGui::GetStyle().CurveTessellationTol = 1.25f; 281 | 282 | ImGui::GetStyle().Colors[ImGuiCol_WindowBg] = ImVec4(0.15, 0.15, 0.15, 1.0f); 283 | ImGui::GetStyle().Colors[ImGuiCol_TitleBg] = ImVec4(0.35, 0.35, 0.35, 1.0f); 284 | ImGui::GetStyle().Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.15, 0.15, 0.15, 1.0f); 285 | ImGui::GetStyle().Colors[ImGuiCol_TextSelectedBg] = ImVec4(0.75, 0.75, 0.75, 0.5f); 286 | ImGui::GetStyle().Colors[ImGuiCol_NavHighlight] = ImVec4(0.00, 0.00, 0.00, 0.0f); 287 | 288 | ImFontConfig fontConfig; 289 | fontConfig.GlyphMinAdvanceX = 1.0f; 290 | fontConfig.SizePixels = 1.00; 291 | ImGui::GetIO().Fonts->AddFontDefault(&fontConfig); 292 | 293 | // Build atlas 294 | unsigned char* tex_pixels = NULL; 295 | int tex_w, tex_h; 296 | ImGui::GetIO().Fonts->GetTexDataAsRGBA32(&tex_pixels, &tex_w, &tex_h); 297 | 298 | return true; 299 | } 300 | 301 | void ImTui_ImplText_Shutdown() { 302 | } 303 | 304 | void ImTui_ImplText_NewFrame() { 305 | } 306 | -------------------------------------------------------------------------------- /third-party/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if (IMTUI_ALL_WARNINGS_3RD_PARTY) 2 | if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") 3 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic") 4 | else() 5 | # todo : windows 6 | endif() 7 | endif() 8 | 9 | if (APPLE) 10 | set(ADDITIONAL_LIBRARIES "-framework Cocoa") 11 | else (APPLE) 12 | unset(ADDITIONAL_LIBRARIES) 13 | endif (APPLE) 14 | 15 | add_library(imgui-for-imtui STATIC 16 | imgui/imgui/imgui.cpp 17 | imgui/imgui/imgui_draw.cpp 18 | imgui/imgui/imgui_demo.cpp 19 | imgui/imgui/imgui_widgets.cpp 20 | imgui/imgui/imgui_tables.cpp 21 | ) 22 | 23 | target_include_directories(imgui-for-imtui INTERFACE 24 | imgui 25 | ) 26 | 27 | target_include_directories(imgui-for-imtui PRIVATE 28 | imgui/imgui 29 | ) 30 | 31 | target_link_libraries(imgui-for-imtui PRIVATE 32 | ${ADDITIONAL_LIBRARIES} 33 | ) 34 | 35 | set_property(TARGET imgui-for-imtui PROPERTY POSITION_INDEPENDENT_CODE ON) 36 | 37 | set_target_properties(imgui-for-imtui PROPERTIES PUBLIC_HEADER "imgui/imgui/imgui.h;imgui/imgui/imconfig.h") 38 | 39 | if (MINGW) 40 | set_target_properties(imgui-for-imtui PROPERTIES COMPILE_FLAGS -fno-threadsafe-statics) 41 | endif() 42 | 43 | if (IMTUI_STANDALONE AND NOT EMSCRIPTEN) 44 | install(TARGETS imgui-for-imtui 45 | EXPORT imgui-for-imtui 46 | PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/imgui-for-imtui/imgui 47 | INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/imgui-for-imtui/imgui 48 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 49 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 50 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 51 | ) 52 | endif() 53 | --------------------------------------------------------------------------------