├── .github └── images │ ├── dim_cmder.png │ ├── dim_command_prompt.png │ ├── widgets_alacritty.png │ └── widgets_hyper.png ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── demo.bat ├── demos ├── CMakeLists.txt ├── dim │ ├── CMakeLists.txt │ └── main.cpp └── widgets │ ├── CMakeLists.txt │ └── main.cpp ├── dim.bat ├── include ├── CMakeLists.txt ├── Console.hpp ├── styles │ ├── Colours.hpp │ ├── StateColours.hpp │ ├── Style.hpp │ ├── StyleButton.hpp │ ├── StyleScaleHorizontal.hpp │ ├── StyleScaleVertical.hpp │ ├── StyleText.hpp │ ├── StyleWindow.hpp │ └── Symbols.hpp ├── util │ ├── Area.hpp │ ├── Direction.hpp │ ├── EscapeCodes.hpp │ ├── Orientation.hpp │ ├── State.hpp │ └── StateWindow.hpp └── widgets │ ├── Button.hpp │ ├── List.hpp │ ├── Listener.hpp │ ├── Scale.hpp │ ├── Scroll.hpp │ ├── Text.hpp │ ├── Widget.hpp │ ├── Window.hpp │ └── managers │ ├── Manager.hpp │ └── Pack.hpp └── src ├── CMakeLists.txt └── main └── cpp ├── Console.cpp └── widgets ├── Button.cpp ├── List.cpp ├── Scale.cpp ├── Scroll.cpp ├── Text.cpp ├── Widget.cpp └── Window.cpp /.github/images/dim_cmder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeflatedPickle/blame/1dee118f7e4c8fc133bef4378749cfdd635e74c1/.github/images/dim_cmder.png -------------------------------------------------------------------------------- /.github/images/dim_command_prompt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeflatedPickle/blame/1dee118f7e4c8fc133bef4378749cfdd635e74c1/.github/images/dim_command_prompt.png -------------------------------------------------------------------------------- /.github/images/widgets_alacritty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeflatedPickle/blame/1dee118f7e4c8fc133bef4378749cfdd635e74c1/.github/images/widgets_alacritty.png -------------------------------------------------------------------------------- /.github/images/widgets_hyper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeflatedPickle/blame/1dee118f7e4c8fc133bef4378749cfdd635e74c1/.github/images/widgets_hyper.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # CMake files 35 | cmake-build-debug 36 | 37 | # IntelliJ project files 38 | .idea 39 | *.iml 40 | out 41 | gen 42 | 43 | # Other files 44 | *.stackdump 45 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | project(blame) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | 6 | add_subdirectory(src) 7 | add_subdirectory(include) 8 | 9 | target_include_directories(blame_src PUBLIC 10 | src/main/cpp 11 | include) 12 | 13 | target_link_libraries(blame_src stdc++fs) 14 | 15 | add_subdirectory(demos) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, DeflatedPickle 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # blame 2 | A curses-like library for C++. 3 | 4 | ## Compatibility 5 | Your terminal will need to implement at least the ANSI escape code for moving the caret for Blame to look readable, and the font used will need to support the [block elements](https://en.wikipedia.org/wiki/Block_Elements) and [box drawing](https://en.wikipedia.org/wiki/Box_Drawing) unicode blocks. 6 | 7 | ## Demos 8 | ### Widgets 9 | The `/demos/widgets` folder contains a demonstration of different widgets offered by Blame. 10 | 11 | The Widget showcase demo as of commit [fd6bd1d](https://github.com/DeflatedPickle/blame/commit/fd6bd1d6d46f9194eff2150310479505b4d70d36): 12 | 13 | ![Widgets Alacritty](.github/images/widgets_alacritty.png) 14 | 15 | ![Widgets Hyper](.github/images/widgets_hyper.png) 16 | 17 | ### Dim 18 | The `/demos/dim` folder contains a Vim-like demonstration. 19 | 20 | The Dim demo as of commit [4eb183f](https://github.com/DeflatedPickle/blame/commit/4eb183ff8300f6145f8b0ce93247f7468c3f9197): 21 | 22 | ![Dim Command Prompt](.github/images/dim_command_prompt.png) 23 | 24 | ![Dim Cmder](.github/images/dim_cmder.png) 25 | ### Bi.Sh 26 | Coming soon - A shell. 27 | 28 | ### Pitty 29 | Coming soon - A terminal emulator (inside a terminal emulator, potentially, inside a terminal emulator). 30 | 31 | ## Building 32 | ### Windows 33 | To build the library on Windows, you will need [CMake](https://cmake.org/) 3.13 or higher and [Cygwin](https://www.cygwin.com/) 2.11.2 or higher (Cygwin is required as it is the only, to my knowledge, GNU tool distribution on Windows to support libraries such as `termios` and `ioctl`). 34 | 35 | ## Alternatives 36 | - [ncurses](https://www.gnu.org/software/ncurses/ncurses.html) 37 | - [dialog](https://invisible-island.net/dialog/) 38 | - [Newt](https://pagure.io/newt) 39 | - [Turbo Vision](http://tvision.sourceforge.net/) -------------------------------------------------------------------------------- /demo.bat: -------------------------------------------------------------------------------- 1 | start "" cmake-build-debug/demos/widgets/blame_demo_widgets.exe -------------------------------------------------------------------------------- /demos/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | project(blame_demos) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | 6 | add_subdirectory(widgets) 7 | add_subdirectory(dim) -------------------------------------------------------------------------------- /demos/dim/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | project(blame_demo_dim) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | 6 | add_executable(blame_demo_dim main.cpp) 7 | target_link_libraries(blame_demo_dim LINK_PUBLIC blame_src) -------------------------------------------------------------------------------- /demos/dim/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // TODO: Horizontal scrolling 12 | // TODO: Scrolling file list 13 | // TODO: Scrolling line numbers (multiple widgets) 14 | // TODO: Change the list to a tree with expanding folders 15 | // TODO: Syntax highlighting 16 | int main() { 17 | std::shared_ptr console(new Blame::Console()); 18 | console->setTitle("Dim"); 19 | 20 | auto new_colours = Blame::Styles::Colours(); 21 | new_colours.border.normal = Blame::Util::EscapeCodes::foregroundWhite(); 22 | new_colours.border.focused = Blame::Util::EscapeCodes::foregroundBlue(); 23 | new_colours.text.focused = Blame::Util::EscapeCodes::foregroundWhite(); 24 | 25 | std::unique_ptr window(new Blame::Widgets::Window(console.get(), "Dim")); 26 | window->style.symbols.middle_fill = " "; 27 | window->place(0, 0, 10, 10); 28 | window->fullscreen(); 29 | window->state = Blame::Util::State::DISABLED; 30 | console->focus_order.pop_back(); 31 | 32 | // Load files 33 | 34 | std::vector file_paths; 35 | std::vector file_names; 36 | 37 | for (const auto &file : std::experimental::filesystem::directory_iterator(std::experimental::filesystem::current_path())) { 38 | file_paths.push_back(file.path().string()); 39 | file_names.push_back(file.path().string().substr(file.path().string().find_last_of("/\\") + 1)); 40 | } 41 | 42 | // List 43 | 44 | std::shared_ptr list(new Blame::Widgets::List(console.get(), window.get(), file_names, nullptr)); 45 | list->style.symbols.middle_fill = " "; 46 | list->style.colours = new_colours; 47 | list->place(1, 1, 18, console->height - 6); 48 | list->view_area_width = list->width; 49 | list->view_area_height = list->height; 50 | list->updateClientArea(); 51 | list->updateViewArea(); 52 | 53 | std::vector list_vector; 54 | list_vector.emplace_back(list.get()); 55 | 56 | std::shared_ptr list_scroll_x(new Blame::Widgets::Scroll(console.get(), window.get(), Blame::Util::Orientation::HORIZONTAL, list_vector)); 57 | list_scroll_x->style.colours = new_colours; 58 | list_scroll_x->place(list->column - 1, list->row + list->height, list->width, 3); 59 | list_scroll_x->handle_max = list_scroll_x->width; 60 | 61 | std::shared_ptr list_scroll_y(new Blame::Widgets::Scroll(console.get(), window.get(), Blame::Util::Orientation::VERTICAL, list_vector)); 62 | list_scroll_y->style.colours = new_colours; 63 | list_scroll_y->place(list->column + list->width + 1, 1, 1, console->height - 6); 64 | list_scroll_y->handle_max = list_scroll_y->height; 65 | 66 | // Line Numbers 67 | 68 | std::shared_ptr line_numbers(new Blame::Widgets::Text(console.get(), window.get())); 69 | line_numbers->style.symbols.middle_fill = " "; 70 | line_numbers->style.colours = new_colours; 71 | line_numbers->place(list_scroll_y->column + list_scroll_y->width + 1, 1, 3, console->height - 6); 72 | line_numbers->state = Blame::Util::State::DISABLED; 73 | line_numbers->view_area_width = line_numbers->width; 74 | line_numbers->view_area_height = line_numbers->height; 75 | line_numbers->updateClientArea(); 76 | line_numbers->updateViewArea(); 77 | 78 | // Text 79 | 80 | std::shared_ptr text(new Blame::Widgets::Text(console.get(), window.get())); 81 | text->style.symbols.middle_fill = " "; 82 | text->style.colours = new_colours; 83 | text->place(line_numbers->column + line_numbers->width + 1, 1, console->width - 4 - 12 - list->width - 2, console->height - 6); 84 | text->view_area_width = text->width; 85 | text->view_area_height = text->height; 86 | text->focus(); 87 | text->updateClientArea(); 88 | text->updateViewArea(); 89 | 90 | std::vector content_vector {line_numbers.get(), text.get()}; 91 | 92 | std::shared_ptr text_scroll_x(new Blame::Widgets::Scroll(console.get(), window.get(), Blame::Util::Orientation::HORIZONTAL, content_vector)); 93 | text_scroll_x->style.colours = new_colours; 94 | text_scroll_x->place(text->column - 1, text->row + text->height, text->width, 3); 95 | text_scroll_x->handle_max = text_scroll_x->width; 96 | 97 | std::shared_ptr text_scroll_y(new Blame::Widgets::Scroll(console.get(), window.get(), Blame::Util::Orientation::VERTICAL, content_vector)); 98 | text_scroll_y->style.colours = new_colours; 99 | text_scroll_y->place(text->column + text->width + 1, 1, 1, console->height - 6); 100 | text_scroll_y->handle_max = text_scroll_y->height; 101 | 102 | // Open file in text widget 103 | 104 | list->command = [=]() { 105 | std::ifstream file(file_paths[list->selection]); 106 | std::string content((std::istreambuf_iterator(file)), 107 | (std::istreambuf_iterator())); 108 | 109 | std::vector new_content; 110 | 111 | int place = 0; 112 | for (auto c : content) { 113 | if (c == '\n') { 114 | place++; 115 | } 116 | else { 117 | if (new_content.size() <= place) { 118 | new_content.emplace_back(""); 119 | } 120 | 121 | new_content[place] = new_content[place] + c; 122 | } 123 | } 124 | 125 | std::vector numbers; 126 | for (auto i = 1; i < new_content.size(); i++) { 127 | if (i < 10) { 128 | numbers.emplace_back(" " + std::to_string(i)); 129 | } 130 | else if (i >= 10 && i < 100) { 131 | numbers.emplace_back(" " + std::to_string(i)); 132 | } 133 | else { 134 | numbers.emplace_back(std::to_string(i)); 135 | } 136 | } 137 | line_numbers->content = numbers; 138 | 139 | text->content = new_content; 140 | 141 | text_scroll_y->max = (int) new_content.size(); 142 | text_scroll_y->handle_size = std::abs(text->view_area_height - 2 - (int) new_content.size()) % text_scroll_y->max; 143 | }; 144 | 145 | console->mainLoop(); 146 | 147 | return 0; 148 | } -------------------------------------------------------------------------------- /demos/widgets/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | project(blame_demo_widgets) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | 6 | add_executable(blame_demo_widgets main.cpp) 7 | target_link_libraries(blame_demo_widgets LINK_PUBLIC blame_src) -------------------------------------------------------------------------------- /demos/widgets/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | int main() { 12 | std::unique_ptr console(new Blame::Console()); 13 | console->setTitle("Hello, World!"); 14 | 15 | std::unique_ptr window(new Blame::Widgets::Window(console.get(), "Hello World")); 16 | window->place(5, 5, 22, 18); 17 | 18 | std::unique_ptr text(new Blame::Widgets::Text(console.get(), window.get())); 19 | text->pack(Blame::Util::Direction::DOWN); 20 | 21 | std::shared_ptr button(new Blame::Widgets::Button(console.get(), window.get(), "Click Me", nullptr)); 22 | button->command = [=]() { button->text = "Clicked!"; }; 23 | button->pack(Blame::Util::Direction::DOWN); 24 | 25 | std::shared_ptr scale(new Blame::Widgets::Scale(console.get(), window.get(), Blame::Util::Orientation::HORIZONTAL, nullptr)); 26 | scale->pack(Blame::Util::Direction::DOWN); 27 | scale->min = -1; 28 | scale->max = 1; 29 | 30 | std::shared_ptr scale_text(new Blame::Widgets::Text(console.get(), window.get())); 31 | scale_text->pack(Blame::Util::Direction::DOWN); 32 | scale_text->width = 8; 33 | scale_text->height = 3; 34 | scale_text->state = Blame::Util::State::DISABLED; 35 | 36 | scale->command = [=]() { scale_text->content[0] = std::to_string(scale->current); }; 37 | 38 | console->mainLoop(); 39 | 40 | return 0; 41 | } 42 | -------------------------------------------------------------------------------- /dim.bat: -------------------------------------------------------------------------------- 1 | start "" cmake-build-debug/demos/dim/blame_demo_dim.exe -------------------------------------------------------------------------------- /include/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | project(blame_include) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | 6 | add_library(blame_include 7 | Console.hpp 8 | 9 | util/EscapeCodes.hpp 10 | util/State.hpp 11 | util/StateWindow.hpp 12 | 13 | widgets/Widget.hpp 14 | 15 | widgets/Window.hpp 16 | 17 | util/Direction.hpp 18 | 19 | widgets/Listener.hpp 20 | 21 | util/Area.hpp 22 | 23 | styles/Style.hpp 24 | styles/Symbols.hpp 25 | styles/Colours.hpp 26 | styles/StateColours.hpp 27 | 28 | styles/StyleWindow.hpp 29 | styles/StyleButton.hpp 30 | styles/StyleScaleHorizontal.hpp 31 | styles/StyleScaleVertical.hpp 32 | styles/StyleText.hpp 33 | 34 | widgets/Button.hpp 35 | 36 | widgets/managers/Manager.hpp 37 | widgets/managers/Pack.hpp 38 | 39 | widgets/Text.hpp 40 | 41 | widgets/Scale.hpp 42 | 43 | widgets/List.hpp 44 | 45 | widgets/Scroll.hpp) 46 | 47 | set_target_properties(blame_include PROPERTIES LINKER_LANGUAGE CXX) -------------------------------------------------------------------------------- /include/Console.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | namespace Blame { 17 | class Console { 18 | public: 19 | Console(); 20 | ~Console(); 21 | 22 | void mainLoop(); 23 | void drawLoop(); 24 | 25 | void clear(); 26 | 27 | void redraw(); 28 | void drawBackground() { 29 | // this->moveCaret(std::cout, 0, 0); 30 | // for (int y = 0; y < this->height - 1; y++) { 31 | // for (int x = 0; x < this->width; x++) { 32 | // *this->buffer_list[this->current_buffer] << "░"; 33 | // } 34 | // } 35 | // this->flipBuffers(); 36 | } 37 | void setTitle(std::string str); 38 | 39 | void moveCaret(std::ostream& stream, int column, int row); 40 | 41 | void quit() { 42 | this->exit.store(true); 43 | std::cout << std::endl; 44 | 45 | // Perform clean up on all widgets 46 | for (auto widget : this->widget_list) { 47 | widget->quit(); 48 | } 49 | widget_list.clear(); 50 | clear(); 51 | } 52 | 53 | void incrementFocus(long pos) { 54 | this->focused_widget->unfocus(); 55 | if (pos + 1 >= this->focus_order.size()) { 56 | if (this->focus_order[0]->state != Blame::Util::State::DISABLED) { 57 | this->focus_order[0]->focus(0); 58 | } 59 | } 60 | else { 61 | if (this->focus_order[0]->state != Blame::Util::State::DISABLED) { 62 | this->focus_order[pos + 1]->focus(pos); 63 | } 64 | } 65 | } 66 | 67 | void incrementFocus() { 68 | auto pos = std::distance(focus_order.begin(), 69 | std::find(focus_order.begin(), focus_order.end(), this->focused_widget)); 70 | 71 | this->incrementFocus(pos); 72 | } 73 | 74 | void flipBuffers() { 75 | this->has_flipped.exchange(true); 76 | 77 | switch (this->current_buffer) { 78 | case 0: 79 | std::cout << back_buffer.str(); 80 | // Clear the buffer 81 | back_buffer.str(std::string()); 82 | this->current_buffer = 1; 83 | break; 84 | 85 | case 1: 86 | std::cout << front_buffer.str(); 87 | // Clear the buffer 88 | front_buffer.str(std::string()); 89 | this->current_buffer = 0; 90 | break; 91 | 92 | default: 93 | break; 94 | } 95 | 96 | // this->setTitle(std::to_string(this->current_buffer)); 97 | } 98 | 99 | int width; 100 | int height; 101 | 102 | Blame::Util::Area client_area; 103 | 104 | std::vector widget_list; 105 | std::vector focus_order; 106 | Blame::Widgets::Listener *focused_widget; 107 | 108 | std::ostringstream front_buffer; 109 | std::ostringstream back_buffer; 110 | std::vector buffer_list; 111 | int current_buffer = 0; 112 | 113 | std::atomic_bool has_flipped; 114 | 115 | // The grid drawn into by widgets, updates the screen_grid 116 | std::vector> raw_grid; 117 | // A duplicate of the raw_grid, used for the screen 118 | std::vector> screen_grid; 119 | 120 | protected: 121 | // std::string colour_background; 122 | std::atomic_bool exit; 123 | }; 124 | } 125 | -------------------------------------------------------------------------------- /include/styles/Colours.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace Blame::Styles { 8 | struct Colours { 9 | Blame::Styles::StateColours border; 10 | Blame::Styles::StateColours background_border; 11 | Blame::Styles::StateColours background_content; 12 | Blame::Styles::StateColours text; 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /include/styles/StateColours.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Blame::Styles { 6 | struct StateColours { 7 | std::string normal; 8 | std::string focused; 9 | std::string active; 10 | std::string disabled; 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /include/styles/Style.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace Blame::Styles { 9 | struct Style { 10 | Blame::Styles::Symbols symbols = Symbols(); 11 | Blame::Styles::Colours colours = Colours(); 12 | }; 13 | } -------------------------------------------------------------------------------- /include/styles/StyleButton.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace Blame::Styles { 7 | struct StyleButton : Blame::Styles::Style { 8 | StyleButton() { 9 | // Colours 10 | this->colours.border.normal = Blame::Util::EscapeCodes::foregroundBlack(); 11 | this->colours.background_content.normal = Blame::Util::EscapeCodes::backgroundWhite(); 12 | this->colours.background_border.normal = Blame::Util::EscapeCodes::backgroundWhite(); 13 | this->colours.text.normal = Blame::Util::EscapeCodes::foregroundBlack(); 14 | 15 | this->colours.border.focused = Blame::Util::EscapeCodes::foregroundBlue(); 16 | this->colours.background_content.focused = Blame::Util::EscapeCodes::backgroundCyan(); 17 | this->colours.background_border.focused = Blame::Util::EscapeCodes::backgroundWhite(); 18 | this->colours.text.focused = Blame::Util::EscapeCodes::foregroundBlack(); 19 | 20 | this->colours.border.active = Blame::Util::EscapeCodes::foregroundGreen(); 21 | this->colours.background_content.active = Blame::Util::EscapeCodes::backgroundWhite(); 22 | this->colours.background_border.active = Blame::Util::EscapeCodes::backgroundWhite(); 23 | this->colours.text.active = Blame::Util::EscapeCodes::foregroundBlack(); 24 | 25 | // Symbols 26 | this->symbols.top_left = "┌"; 27 | this->symbols.top_middle = "─"; 28 | this->symbols.top_right = "┐"; 29 | this->symbols.middle_left = "│"; 30 | // this->symbols->middle_fill = "░"; 31 | this->symbols.middle_fill = "█"; 32 | this->symbols.middle_right = "│"; 33 | this->symbols.bottom_left = "└"; 34 | this->symbols.bottom_middle = "─"; 35 | this->symbols.bottom_right = "┘"; 36 | } 37 | }; 38 | } -------------------------------------------------------------------------------- /include/styles/StyleScaleHorizontal.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace Blame::Styles { 7 | struct StyleScaleHorizontal : Blame::Styles::Style { 8 | StyleScaleHorizontal() { 9 | // Colours 10 | this->colours.border.normal = Blame::Util::EscapeCodes::foregroundBlack(); 11 | this->colours.background_content.normal = Blame::Util::EscapeCodes::foregroundBlack() + Blame::Util::EscapeCodes::backgroundWhite(); 12 | this->colours.background_border.normal = Blame::Util::EscapeCodes::backgroundWhite(); 13 | this->colours.text.normal = Blame::Util::EscapeCodes::foregroundBlack(); 14 | 15 | this->colours.border.focused = Blame::Util::EscapeCodes::foregroundBlue(); 16 | this->colours.background_content.focused = Blame::Util::EscapeCodes::foregroundBlack() + Blame::Util::EscapeCodes::backgroundWhite(); 17 | this->colours.background_border.focused = Blame::Util::EscapeCodes::backgroundWhite(); 18 | 19 | // Symbols 20 | this->symbols.top_left = " "; 21 | this->symbols.top_middle = " "; 22 | this->symbols.top_right = " "; 23 | this->symbols.middle_left = "["; 24 | this->symbols.middle_fill = "─"; 25 | this->symbols.middle_right = "]"; 26 | this->symbols.bottom_left = " "; 27 | this->symbols.bottom_middle = " "; 28 | this->symbols.bottom_right = " "; 29 | } 30 | }; 31 | } -------------------------------------------------------------------------------- /include/styles/StyleScaleVertical.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Blame::Styles { 6 | struct StyleScaleVertical : Blame::Styles::Style { 7 | StyleScaleVertical() { 8 | // Colours 9 | this->colours.border.normal = Blame::Util::EscapeCodes::foregroundBlack(); 10 | this->colours.background_content.normal = Blame::Util::EscapeCodes::foregroundBlack() + Blame::Util::EscapeCodes::backgroundWhite(); 11 | this->colours.background_border.normal = Blame::Util::EscapeCodes::backgroundWhite(); 12 | this->colours.text.normal = Blame::Util::EscapeCodes::foregroundBlack(); 13 | 14 | this->colours.border.focused = Blame::Util::EscapeCodes::foregroundBlue(); 15 | this->colours.background_content.focused = Blame::Util::EscapeCodes::foregroundBlack() + Blame::Util::EscapeCodes::backgroundWhite(); 16 | this->colours.background_border.focused = Blame::Util::EscapeCodes::backgroundWhite(); 17 | 18 | // Symbols 19 | this->symbols.top_left = " "; 20 | this->symbols.top_middle = "⎴"; 21 | this->symbols.top_right = " "; 22 | this->symbols.middle_left = " "; 23 | this->symbols.middle_fill = "│"; 24 | this->symbols.middle_right = " "; 25 | this->symbols.bottom_left = " "; 26 | this->symbols.bottom_middle = "⎵"; 27 | this->symbols.bottom_right = " "; 28 | } 29 | }; 30 | } -------------------------------------------------------------------------------- /include/styles/StyleText.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace Blame::Styles { 7 | struct StyleText : Blame::Styles::Style { 8 | StyleText() { 9 | // Colours 10 | this->colours.border.normal = Blame::Util::EscapeCodes::foregroundBlack(); 11 | this->colours.background_content.normal = Blame::Util::EscapeCodes::backgroundWhite(); 12 | this->colours.background_border.normal = Blame::Util::EscapeCodes::backgroundWhite(); 13 | this->colours.text.normal = Blame::Util::EscapeCodes::foregroundBlack(); 14 | 15 | this->colours.border.focused = Blame::Util::EscapeCodes::foregroundBlue(); 16 | this->colours.background_content.focused = Blame::Util::EscapeCodes::backgroundWhite(); 17 | this->colours.background_border.focused = Blame::Util::EscapeCodes::backgroundWhite(); 18 | this->colours.text.focused = Blame::Util::EscapeCodes::foregroundBlack(); 19 | 20 | this->colours.border.disabled = Blame::Util::EscapeCodes::foregroundRed(); 21 | this->colours.background_content.disabled = Blame::Util::EscapeCodes::backgroundBlack(); 22 | this->colours.background_border.disabled = Blame::Util::EscapeCodes::backgroundWhite(); 23 | this->colours.text.disabled = Blame::Util::EscapeCodes::foregroundWhite(); 24 | 25 | // Symbols 26 | this->symbols.top_left = "┌"; 27 | this->symbols.top_middle = "─"; 28 | this->symbols.top_right = "┐"; 29 | this->symbols.middle_left = "│"; 30 | // this->symbols->middle_fill = "░"; 31 | this->symbols.middle_fill = "█"; 32 | this->symbols.middle_right = "│"; 33 | this->symbols.bottom_left = "└"; 34 | this->symbols.bottom_middle = "─"; 35 | this->symbols.bottom_right = "┘"; 36 | } 37 | }; 38 | } -------------------------------------------------------------------------------- /include/styles/StyleWindow.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Blame::Styles { 6 | struct StyleWindow : Blame::Styles::Style { 7 | StyleWindow() { 8 | // Colours 9 | this->colours.border.normal = Blame::Util::EscapeCodes::foregroundCyan(); 10 | this->colours.background_content.normal = Blame::Util::EscapeCodes::backgroundWhite(); 11 | this->colours.text.normal = Blame::Util::EscapeCodes::foregroundBlack(); 12 | 13 | this->colours.border.focused = Blame::Util::EscapeCodes::foregroundGreen(); 14 | this->colours.background_content.focused = Blame::Util::EscapeCodes::backgroundWhite(); 15 | this->colours.text.focused = Blame::Util::EscapeCodes::foregroundBlack(); 16 | 17 | // Symbols 18 | this->symbols.top_left = "╔"; 19 | this->symbols.top_middle = "═"; 20 | this->symbols.top_right = "╗"; 21 | this->symbols.middle_left = "║"; 22 | // this->symbols.middle_fill = "░"; 23 | this->symbols.middle_fill = "█"; 24 | this->symbols.middle_right = "║"; 25 | this->symbols.bottom_left = "╚"; 26 | this->symbols.bottom_middle = "═"; 27 | this->symbols.bottom_right = "╝"; 28 | } 29 | }; 30 | } -------------------------------------------------------------------------------- /include/styles/Symbols.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Blame::Styles { 6 | struct Symbols { 7 | std::string top_left; 8 | std::string top_middle; 9 | std::string top_right; 10 | std::string middle_left; 11 | std::string middle_fill; 12 | std::string middle_right; 13 | std::string bottom_left; 14 | std::string bottom_middle; 15 | std::string bottom_right; 16 | }; 17 | } -------------------------------------------------------------------------------- /include/util/Area.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Blame::Util { 4 | struct Area { 5 | int left; 6 | int right; // Width 7 | int top; 8 | int bottom; // Height 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /include/util/Direction.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Blame::Util { 4 | enum Direction { 5 | UP, 6 | DOWN, 7 | LEFT, 8 | RIGHT 9 | }; 10 | } -------------------------------------------------------------------------------- /include/util/EscapeCodes.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Blame::Util { 6 | class EscapeCodes { 7 | public: 8 | static std::string cursorUp() { 9 | return "\u001b[{n}A"; 10 | } 11 | 12 | static std::string cursorDown() { 13 | return "\u001b[{n}B"; 14 | } 15 | 16 | static std::string cursorRight() { 17 | return "\u001b[{n}C"; 18 | } 19 | 20 | static std::string cursorLeft() { 21 | return "\u001b[{n}D"; 22 | } 23 | 24 | static std::string caretOff() { 25 | return "\u001b[?25l"; 26 | } 27 | 28 | static std::string caretOn() { 29 | return "\u001b[?25h"; 30 | } 31 | 32 | static std::string reset() { 33 | return "\u001b[0m"; 34 | } 35 | 36 | static std::string bold() { 37 | return "\u001b[1m"; 38 | } 39 | 40 | static std::string underline() { 41 | return "\u001b[4m"; 42 | } 43 | 44 | static std::string reversed() { 45 | return "\u001b[7m"; 46 | } 47 | 48 | static std::string foregroundBlack() { 49 | return "\u001b[30m"; 50 | } 51 | 52 | static std::string backgroundBlack() { 53 | return "\u001b[40m"; 54 | } 55 | 56 | static std::string brightBlack() { 57 | return "\u001b[30;1m"; 58 | } 59 | 60 | static std::string backgroundBrightBlack() { 61 | return "\u001b[40;1m"; 62 | } 63 | 64 | static std::string foregroundRed() { 65 | return "\u001b[31m"; 66 | } 67 | 68 | static std::string backgroundRed() { 69 | return "\u001b[41m"; 70 | } 71 | 72 | static std::string foregroundBrightRed() { 73 | return "\u001b[31;1m"; 74 | } 75 | 76 | static std::string backgroundBrightRed() { 77 | return "\u001b[41;1m"; 78 | } 79 | 80 | static std::string foregroundGreen() { 81 | return "\u001b[32m"; 82 | } 83 | 84 | static std::string backgroundGreen() { 85 | return "\u001b[42m"; 86 | } 87 | 88 | static std::string foregroundBrightGreen() { 89 | return "\u001b[32;1m"; 90 | } 91 | 92 | static std::string backgroundBrightGreen() { 93 | return "\u001b[42;1m"; 94 | } 95 | 96 | static std::string foregroundYellow() { 97 | return "\u001b[33m"; 98 | } 99 | 100 | static std::string backgroundYellow() { 101 | return "\u001b[43m"; 102 | } 103 | 104 | static std::string foregroundBrightYellow() { 105 | return "\u001b[33;1m"; 106 | } 107 | 108 | static std::string backgroundBrightYellow() { 109 | return "\u001b[43;1m"; 110 | } 111 | 112 | static std::string foregroundBlue() { 113 | return "\u001b[34m"; 114 | } 115 | 116 | static std::string backgroundBlue() { 117 | return "\u001b[44m"; 118 | } 119 | 120 | static std::string foregroundBrightBlue() { 121 | return "\u001b[34;1m"; 122 | } 123 | 124 | static std::string backgroundBrightBlue() { 125 | return "\u001b[44;1m"; 126 | } 127 | 128 | static std::string foregroundMagenta() { 129 | return "\u001b[35m"; 130 | } 131 | 132 | static std::string backgroundMagenta() { 133 | return "\u001b[45m"; 134 | } 135 | 136 | static std::string foregroundBrightMagenta() { 137 | return "\u001b[35;1m"; 138 | } 139 | 140 | static std::string backgroundBrightMagenta() { 141 | return "\u001b[45;1m"; 142 | } 143 | 144 | static std::string foregroundCyan() { 145 | return "\u001b[36m"; 146 | } 147 | 148 | static std::string backgroundCyan() { 149 | return "\u001b[46m"; 150 | } 151 | 152 | static std::string foregroundBrightCyan() { 153 | return "\u001b[36;1m"; 154 | } 155 | 156 | static std::string backgroundBrightCyan() { 157 | return "\u001b[46;1m"; 158 | } 159 | 160 | static std::string foregroundWhite() { 161 | return "\u001b[37m"; 162 | } 163 | 164 | static std::string backgroundWhite() { 165 | return "\u001b[47m"; 166 | } 167 | 168 | static std::string foregroundBrightWhite() { 169 | return "\u001b[37;1m"; 170 | } 171 | 172 | static std::string backgroundBrightWhite() { 173 | return "\u001b[47;1m"; 174 | } 175 | }; 176 | } -------------------------------------------------------------------------------- /include/util/Orientation.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Blame::Util { 4 | enum Orientation { 5 | HORIZONTAL, 6 | VERTICAL 7 | }; 8 | } -------------------------------------------------------------------------------- /include/util/State.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Blame::Util { 4 | enum State { 5 | NORMAL, 6 | FOCUSED, 7 | ACTIVE, 8 | DISABLED 9 | }; 10 | } -------------------------------------------------------------------------------- /include/util/StateWindow.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Blame::Util { 4 | enum StateWindow { 5 | RESTORED, 6 | MINIMIZED, 7 | MAXIMIZED, 8 | FULLSCREEN, 9 | CLOSED 10 | }; 11 | } -------------------------------------------------------------------------------- /include/widgets/Button.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace Blame::Widgets { 8 | class Button : public Blame::Widgets::Widget { 9 | public: 10 | Button(Blame::Console *console, Blame::Widgets::Widget *parent, std::string text, std::function command); 11 | 12 | void redraw() override; 13 | 14 | void activate() override { 15 | this->command(); 16 | 17 | Widget::activate(); 18 | } 19 | 20 | std::string text; 21 | std::function command; 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /include/widgets/List.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace Blame::Widgets { 9 | class List : public Blame::Widgets::Widget { 10 | public: 11 | List(Blame::Console *console, Blame::Widgets::Widget *parent, std::vector items, std::function command); 12 | 13 | void redraw() override; 14 | 15 | void move(Blame::Util::Direction direction) override { 16 | if (direction == Blame::Util::Direction::UP) { 17 | if (this->selection - 1 >= 0) { 18 | this->selection--; 19 | } 20 | } 21 | else if (direction == Blame::Util::Direction::DOWN) { 22 | if (this->selection + 1 <= this->items.size() - 1) { 23 | this->selection++; 24 | } 25 | } 26 | 27 | Widget::move(direction); 28 | } 29 | 30 | void activate() override { 31 | this->command(); 32 | 33 | Widget::activate(); 34 | } 35 | 36 | std::vector items; 37 | std::function command; 38 | 39 | int selection; 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /include/widgets/Listener.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace Blame::Widgets { 7 | class Listener { 8 | public: 9 | virtual void quit() = 0; 10 | 11 | virtual void focus() = 0; 12 | virtual void focus(long pos) = 0; 13 | virtual void unfocus() = 0; 14 | 15 | virtual void move(Blame::Util::Direction direction) = 0; 16 | virtual void activate() = 0; 17 | virtual void text(std::string text) = 0; 18 | 19 | Blame::Util::State state = Blame::Util::State::NORMAL; 20 | }; 21 | } -------------------------------------------------------------------------------- /include/widgets/Scale.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace Blame::Widgets { 9 | class Scale : public Blame::Widgets::Widget { 10 | public: 11 | Scale(Blame::Console *console, Blame::Widgets::Widget *parent, Blame::Util::Orientation orientation, std::function command); 12 | 13 | void redraw() override; 14 | 15 | void move(Blame::Util::Direction direction) override; 16 | 17 | void activate() override {} 18 | 19 | Blame::Util::Orientation orientation; 20 | std::function command; 21 | 22 | float min; 23 | float current; 24 | float max; 25 | 26 | protected: 27 | int handle_min; 28 | int handle_current; 29 | int handle_max; 30 | 31 | std::string symbol_handle; 32 | 33 | std::string colour_handle; 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /include/widgets/Scroll.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace Blame::Widgets { 9 | class Scroll : public Blame::Widgets::Widget { 10 | public: 11 | Scroll(Blame::Console *console, Blame::Widgets::Widget *parent, Blame::Util::Orientation orientation, std::vector widgets); 12 | 13 | void redraw() override; 14 | 15 | void move(Blame::Util::Direction direction) override; 16 | 17 | void activate() override {} 18 | 19 | Blame::Util::Orientation orientation; 20 | std::vector widgets; 21 | 22 | int min = 0; 23 | int current = 0; 24 | int max = 0; 25 | 26 | int handle_min; 27 | int handle_current; 28 | int handle_max; 29 | 30 | int handle_size = 1; 31 | 32 | protected: 33 | std::string symbol_handle; 34 | 35 | std::string colour_handle; 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /include/widgets/Text.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Blame::Widgets { 6 | class Text : public Blame::Widgets::Widget { 7 | public: 8 | Text(Blame::Console *console, Blame::Widgets::Widget *parent); 9 | 10 | void redraw() override; 11 | 12 | void move(Blame::Util::Direction direction) override; 13 | void activate() override {} 14 | void text(std::string text) override; 15 | 16 | int caret_x; 17 | int caret_y; 18 | 19 | std::vector content; 20 | 21 | protected: 22 | std::string symbol_caret; 23 | 24 | std::string colour_caret; 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /include/widgets/Widget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | // TODO: Add proper resize methods that change the client area 15 | // TODO: Add the ability to scroll a viewable area of the widgets client area 16 | namespace Blame::Widgets { 17 | class Widget : public Blame::Widgets::Listener { 18 | public: 19 | Widget(Blame::Console *console, Blame::Widgets::Widget *parent); 20 | 21 | ~Widget() { 22 | children.clear(); 23 | } 24 | 25 | virtual void redraw(); 26 | 27 | void quit() override {} 28 | 29 | void focus() override { 30 | this->console->focused_widget = this; 31 | this->state = Blame::Util::State::FOCUSED; 32 | 33 | this->console->redraw(); 34 | } 35 | 36 | void focus(long pos) override { 37 | if (this->state != Blame::Util::State::DISABLED) { 38 | this->console->focused_widget = this; 39 | this->state = Blame::Util::State::FOCUSED; 40 | } 41 | else { 42 | this->console->incrementFocus(pos + 1); 43 | } 44 | } 45 | 46 | void unfocus() override { 47 | this->state = Blame::Util::State::NORMAL; 48 | this->console->redraw(); 49 | 50 | } 51 | 52 | void move(Blame::Util::Direction direction) override { 53 | this->console->redraw(); 54 | } 55 | 56 | void activate() override { 57 | this->state = Blame::Util::State::ACTIVE; 58 | this->console->redraw(); 59 | } 60 | 61 | void text(std::string text) override { 62 | this->console->redraw(); 63 | } 64 | 65 | std::string getCurrentColour(Blame::Styles::StateColours stateColours) { 66 | switch (this->state) { 67 | case Blame::Util::State::NORMAL: 68 | return stateColours.normal; 69 | 70 | case Blame::Util::State::FOCUSED: 71 | return stateColours.focused; 72 | 73 | case Blame::Util::State::ACTIVE: 74 | return stateColours.active; 75 | 76 | case Blame::Util::State::DISABLED: 77 | return stateColours.disabled; 78 | } 79 | 80 | return stateColours.normal; 81 | } 82 | 83 | void place(int x, int y, int width, int height) { 84 | this->column = x + 1; 85 | this->row = y; 86 | this->width = width; 87 | this->height = height; 88 | 89 | this->updateClientArea(); 90 | } 91 | // void placeBeside(); 92 | // void placeInside(); 93 | // void placeRemove(); 94 | 95 | void pack(Blame::Util::Direction direction) { 96 | if (this->parent->manager == nullptr) { 97 | this->updateClientArea(); 98 | this->parent->manager = new Blame::Widgets::Managers::Pack(); 99 | auto manager = static_cast(this->parent->manager); 100 | manager->direction = direction; 101 | manager->next_x = this->parent->client_area.left; 102 | manager->next_y = this->parent->client_area.top - 1; 103 | this->parent->manager = static_cast(manager); 104 | } 105 | 106 | auto manager = static_cast(this->parent->manager); 107 | manager->widgets.push_back(this); 108 | 109 | this->column = manager->next_x; 110 | this->row = manager->next_y; 111 | 112 | switch (direction) { 113 | case Blame::Util::Direction::RIGHT: 114 | manager->next_x += this->width + 2; 115 | break; 116 | 117 | case Blame::Util::Direction::DOWN: 118 | manager->next_y += this->height; 119 | break; 120 | 121 | case Blame::Util::Direction::UP: 122 | break; 123 | 124 | case Blame::Util::Direction::LEFT: 125 | break; 126 | } 127 | 128 | manager->direction = direction; 129 | 130 | this->parent->manager = manager; 131 | } 132 | // void packRemove(); 133 | 134 | // void grid(int row, int column); 135 | // void gridRemove(); 136 | 137 | virtual void updateClientArea() { 138 | this->client_area.left = this->column + 1; 139 | this->client_area.right = this->column - 1 + this->width; 140 | this->client_area.top = this->row + 2; 141 | this->client_area.bottom = this->row - 1 + this->height; 142 | } 143 | 144 | virtual void updateViewArea() { 145 | this->view_area.left = this->client_area.left + this->view_area_offset_x; 146 | this->view_area.right = this->view_area.left + this->view_area_width; 147 | this->view_area.top = this->client_area.top + this->view_area_offset_y; 148 | this->view_area.bottom = this->view_area.top + this->view_area_height; 149 | } 150 | 151 | int column; 152 | int row; 153 | int width; 154 | int height; 155 | 156 | // The full area the widget takes up 157 | Blame::Util::Area client_area; 158 | // A portion of the client area that can be seen, usually inside the borders 159 | Blame::Util::Area view_area; 160 | int view_area_offset_x = 0; 161 | int view_area_offset_y = 0; 162 | int view_area_width = 0; 163 | int view_area_height = 0; 164 | 165 | bool resizable; 166 | 167 | std::ostringstream widget_stream; 168 | 169 | std::atomic_bool is_redrawn; 170 | 171 | Blame::Widgets::Managers::Manager *manager = nullptr; 172 | std::vector children; 173 | 174 | Blame::Styles::Style style; 175 | 176 | protected: 177 | Blame::Console *console = nullptr; 178 | Blame::Widgets::Widget *parent = nullptr; 179 | 180 | // int padding_top; 181 | // int padding_left; 182 | // int padding_right; 183 | // int padding_bottom; 184 | 185 | // int internal_padding_top; 186 | // int internal_padding_left; 187 | // int internal_padding_right; 188 | // int internal_padding_bottom; 189 | }; 190 | } -------------------------------------------------------------------------------- /include/widgets/Window.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace Blame::Widgets { 9 | class Window : public Blame::Widgets::Widget { 10 | public: 11 | Window(Blame::Console *console, std::string title_text); 12 | 13 | void redraw() override; 14 | 15 | void move(Blame::Util::Direction direction) override; 16 | 17 | void activate() override {} 18 | 19 | // void restore(); 20 | // void minimize(); 21 | // void maximize(); 22 | void fullscreen() { 23 | this->old_width = this->width; 24 | this->old_height = this->height; 25 | 26 | this->state_window = Blame::Util::StateWindow::FULLSCREEN; 27 | 28 | this->width = this->console->width - 2; 29 | this->height = this->console->height - 1; 30 | } 31 | // void close(); 32 | 33 | std::string title_text; 34 | 35 | Blame::Util::StateWindow state_window = Blame::Util::StateWindow::RESTORED; 36 | 37 | protected: 38 | std::string symbol_title_intersect_left; 39 | std::string symbol_title_intersect_right; 40 | 41 | std::string colour_background_title; 42 | 43 | std::string symbol_minimize; 44 | std::string symbol_maximize; 45 | std::string symbol_restore; 46 | std::string symbol_close; 47 | 48 | private: 49 | int old_width; 50 | int old_height; 51 | }; 52 | } -------------------------------------------------------------------------------- /include/widgets/managers/Manager.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Blame::Widgets::Managers { 4 | class Manager { 5 | 6 | }; 7 | } -------------------------------------------------------------------------------- /include/widgets/managers/Pack.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace Blame::Widgets { 7 | class Widget; 8 | } 9 | 10 | namespace Blame::Widgets::Managers { 11 | class Pack : public Blame::Widgets::Managers::Manager { 12 | public: 13 | Blame::Util::Direction direction; 14 | 15 | int next_x; 16 | int next_y; 17 | 18 | std::vector widgets; 19 | }; 20 | } -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | project(blame_src) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | 6 | add_library(blame_src 7 | main/cpp/Console.cpp 8 | 9 | main/cpp/widgets/Widget.cpp 10 | 11 | main/cpp/widgets/Window.cpp 12 | 13 | main/cpp/widgets/Button.cpp 14 | 15 | main/cpp/widgets/Text.cpp 16 | 17 | main/cpp/widgets/Scale.cpp 18 | 19 | main/cpp/widgets/List.cpp 20 | 21 | main/cpp/widgets/Scroll.cpp) -------------------------------------------------------------------------------- /src/main/cpp/Console.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | // TODO: Add signals for console events 15 | Blame::Console::Console() { 16 | this->clear(); 17 | this->moveCaret(std::cout, 0, 0); 18 | std::cout << Blame::Util::EscapeCodes::caretOff(); 19 | 20 | this->buffer_list.push_back(&front_buffer); 21 | this->buffer_list.push_back(&back_buffer); 22 | 23 | this->exit.store(false); 24 | this->has_flipped.store(true); 25 | 26 | struct winsize size{}; 27 | ioctl(STDOUT_FILENO, TIOCGWINSZ, &size); 28 | this->width = size.ws_col; 29 | this->height = size.ws_row; 30 | 31 | // Creates a row of empty cells for the height and width of the terminal 32 | for (auto i = 0; i < this->height; i++) { 33 | std::vector vec; 34 | 35 | for (auto j = 0; j < this->width + 1; j++) { 36 | vec.emplace_back(""); 37 | } 38 | 39 | this->raw_grid.push_back(vec); 40 | this->screen_grid.push_back(vec); 41 | } 42 | 43 | this->client_area.left = 0; 44 | this->client_area.top = 0; 45 | this->client_area.right = this->width; 46 | this->client_area.bottom = this->height; 47 | 48 | struct termios term{}; 49 | tcgetattr(STDIN_FILENO, &term); 50 | term.c_lflag &= ~ICANON; 51 | term.c_lflag &= ~ECHO; 52 | tcsetattr(STDIN_FILENO, TCSANOW, &term); 53 | } 54 | 55 | Blame::Console::~Console() { 56 | struct termios term{}; 57 | tcgetattr(STDIN_FILENO, &term); 58 | term.c_lflag |= ECHO; 59 | term.c_lflag |= ICANON; 60 | tcsetattr(STDIN_FILENO, TCSANOW, &term); 61 | } 62 | 63 | void Blame::Console::mainLoop() { 64 | char first; 65 | char second; 66 | char third; 67 | 68 | bool find_second_third; 69 | 70 | this->focused_widget = this->focus_order[0]; 71 | this->focused_widget->focus(); 72 | 73 | // std::thread draw_thread(&Blame::Console::drawLoop, this); 74 | 75 | // this->drawBackground(); 76 | 77 | while (!this->exit.load()) { 78 | find_second_third = true; 79 | std::cout.flush(); 80 | 81 | // std::cin >> first; 82 | 83 | first = (char) std::cin.get(); 84 | 85 | // TODO: Change to the Escape key 86 | if (first == 'q') { 87 | this->clear(); 88 | this->quit(); 89 | } 90 | 91 | // Tab 92 | if (first == 9) { 93 | this->incrementFocus(); 94 | find_second_third = false; 95 | } 96 | 97 | // TODO: Change to the Enter key 98 | if (first == 'e') { 99 | auto focused_widget = dynamic_cast(this->focused_widget); 100 | focused_widget->activate(); 101 | find_second_third = false; 102 | } 103 | 104 | if (first != 27) { 105 | auto focused_widget = dynamic_cast(this->focused_widget); 106 | focused_widget->text(std::string(1, first)); 107 | find_second_third = false; 108 | } 109 | 110 | if (find_second_third) { 111 | std::cin >> second; 112 | std::cin >> third; 113 | } 114 | 115 | if (first == 27 && second == 91) { 116 | if (third == 65) { 117 | // std::cout << "UP"; 118 | auto focused_widget = dynamic_cast(this->focused_widget); 119 | focused_widget->move(Blame::Util::Direction::UP); 120 | } 121 | else if (third == 66) { 122 | // std::cout << "DOWN"; 123 | auto focused_widget = dynamic_cast(this->focused_widget); 124 | focused_widget->move(Blame::Util::Direction::DOWN); 125 | } 126 | else if (third == 67) { 127 | // std::cout << "RIGHT"; 128 | auto focused_widget = dynamic_cast(this->focused_widget); 129 | focused_widget->move(Blame::Util::Direction::RIGHT); 130 | } 131 | else if (third == 68) { 132 | // std::cout << "LEFT"; 133 | auto focused_widget = dynamic_cast(this->focused_widget); 134 | focused_widget->move(Blame::Util::Direction::LEFT); 135 | } 136 | } 137 | } 138 | } 139 | 140 | void Blame::Console::drawLoop() { 141 | std::chrono::milliseconds current_time = std::chrono::duration_cast( 142 | std::chrono::system_clock::now().time_since_epoch()); 143 | std::chrono::milliseconds start_time = current_time; 144 | int frame_count = 0; 145 | 146 | while (!this->exit.load()) { 147 | frame_count++; 148 | current_time = std::chrono::duration_cast( 149 | std::chrono::system_clock::now().time_since_epoch()); 150 | 151 | // this->setTitle("Current Time: " + std::to_string(current_time.count()) + " Start Time:" + std::to_string(start_time.count()) + " Final: " + std::to_string(current_time.count() - start_time.count())); 152 | if (current_time.count() - start_time.count() > 0.25f) { 153 | frame_count = 0; 154 | 155 | start_time = current_time; 156 | 157 | // this->setTitle("--------------------------------"); 158 | if (this->has_flipped.load()) { 159 | this->has_flipped.exchange(false); 160 | 161 | if (this->buffer_list[this->current_buffer]->rdbuf() != std::cout.rdbuf()) { 162 | this->redraw(); 163 | } 164 | } 165 | } 166 | } 167 | } 168 | 169 | void Blame::Console::clear() { 170 | std::cout << Blame::Util::EscapeCodes::reset(); 171 | std::cout << "\033c"; 172 | } 173 | 174 | void Blame::Console::redraw() { 175 | // TODO: Only redraw the cells that have been changed, don't clear the whole thing 176 | // this->clear(); 177 | 178 | // this->drawBackground(); 179 | 180 | // TODO: Maybe add a check for if they need to be redrawn? 181 | for (auto widget : this->widget_list) { 182 | // The widgets are added as listeners, since the Widget class is incomplete at the time of headers 183 | // meaning they can't be used for the typing of the list, so we have to cast them to widgets... 184 | // which they might not be 185 | auto new_widget = dynamic_cast(widget); 186 | 187 | if (new_widget->is_redrawn.load()) { 188 | new_widget->redraw(); 189 | 190 | if (!new_widget->children.empty()) { 191 | for (auto child : new_widget->children) { 192 | child->redraw(); 193 | } 194 | } 195 | } 196 | } 197 | 198 | // TODO: Group cells into packets 199 | // auto redraw_count = 0; 200 | for (auto y = 0; y < this->raw_grid.size(); y++) { 201 | for (auto x = 0; x < this->raw_grid[y].size(); x++) { 202 | if (x == this->raw_grid[y].size()) { 203 | if (y != this->raw_grid.size()) { 204 | *this->buffer_list[!this->current_buffer] << std::endl; 205 | } 206 | } 207 | 208 | // TODO: Remove leftward caret movements 209 | this->moveCaret(*this->buffer_list[!this->current_buffer], x, y); 210 | 211 | if (x > 0 && y > 0 && this->raw_grid[y][x].empty()) { 212 | *this->buffer_list[!this->current_buffer] << "░" << Blame::Util::EscapeCodes::reset(); 213 | } 214 | 215 | if (this->raw_grid[y][x] != this->screen_grid[y][x]) { 216 | this->screen_grid[y][x] = this->raw_grid[y][x]; 217 | 218 | // redraw_count++; 219 | // this->setTitle("Redrawn: " + std::to_string(redraw_count) + " Cells, Out Of: " + std::to_string(this->raw_grid.size() * this->raw_grid[0].size()) + " Cells"); 220 | } 221 | else { 222 | // Shows which cells were redrawn 223 | // *this->buffer_list[!this->current_buffer] << "@" << Blame::Util::EscapeCodes::reset(); 224 | continue; 225 | } 226 | 227 | *this->buffer_list[!this->current_buffer] << Blame::Util::EscapeCodes::reset() << this->screen_grid[y][x] << Blame::Util::EscapeCodes::reset(); 228 | } 229 | } 230 | 231 | this->flipBuffers(); 232 | } 233 | 234 | void Blame::Console::setTitle(std::string str) { 235 | std::cout << "\033]2;" << str.c_str() << "\007"; 236 | } 237 | 238 | void Blame::Console::moveCaret(std::ostream &stream, int column, int row) { 239 | stream << "\033[" << row << ";" << column << "H"; 240 | } 241 | -------------------------------------------------------------------------------- /src/main/cpp/widgets/Button.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | Blame::Widgets::Button::Button(Blame::Console *console, Blame::Widgets::Widget *parent, std::string text, std::function command) : Widget(console, parent) { 8 | this->text = text; 9 | this->command = command; 10 | 11 | this->width = 8; 12 | this->height = 3; 13 | 14 | this->style = Blame::Styles::StyleButton(); 15 | } 16 | 17 | void Blame::Widgets::Button::redraw() { 18 | Widget::redraw(); 19 | 20 | for (auto i = 0; i < this->text.length(); i++) { 21 | this->console->raw_grid[this->client_area.top][this->client_area.left + i] = 22 | this->getCurrentColour(this->style.colours.border) 23 | + this->getCurrentColour(this->style.colours.background_border) 24 | + this->text[i]; 25 | } 26 | 27 | this->is_redrawn.exchange(true); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/cpp/widgets/List.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | Blame::Widgets::List::List(Blame::Console *console, Blame::Widgets::Widget *parent, std::vector items, 8 | std::function command) : Widget(console, parent) { 9 | this->items = items; 10 | this->command = command; 11 | this->selection = 0; 12 | 13 | this->width = 8; 14 | this->height = (int) items.size() + 2; 15 | 16 | this->style = Blame::Styles::StyleText(); 17 | } 18 | 19 | void Blame::Widgets::List::redraw() { 20 | Widget::redraw(); 21 | 22 | int iteration = 0; 23 | for (auto item = 0; item < this->items.size(); item++) { 24 | for (auto i = 0; i < this->items[item].length(); i++) { 25 | if (this->client_area.top + iteration < this->view_area.bottom - 2 26 | && this->client_area.left + i < this->view_area.right) { 27 | this->console->raw_grid[this->client_area.top + iteration][this->client_area.left + i] = 28 | this->getCurrentColour(this->style.colours.background_content) 29 | + (iteration == this->selection ? Blame::Util::EscapeCodes::backgroundBlue() 30 | + Blame::Util::EscapeCodes::foregroundYellow() 31 | : this->getCurrentColour(this->style.colours.text)) 32 | + this->items[item + this->view_area_offset_y][i + this->view_area_offset_x]; 33 | } 34 | } 35 | 36 | iteration++; 37 | } 38 | 39 | this->is_redrawn.exchange(true); 40 | } 41 | -------------------------------------------------------------------------------- /src/main/cpp/widgets/Scale.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | Blame::Widgets::Scale::Scale(Blame::Console *console, Blame::Widgets::Widget *parent, Blame::Util::Orientation orientation, std::function command) : Widget(console, parent) { 10 | this->orientation = orientation; 11 | this->command = command; 12 | 13 | // TODO: Smoothly transition through the handle parts to represent each segment of a float value 14 | switch (this->orientation) { 15 | case Blame::Util::Orientation::HORIZONTAL: 16 | this->width = 8; 17 | this->height = 3; 18 | 19 | this->symbol_handle = "█"; 20 | 21 | this->style = Blame::Styles::StyleScaleHorizontal(); 22 | break; 23 | 24 | case Blame::Util::Orientation::VERTICAL: 25 | this->width = 1; 26 | this->height = 8; 27 | 28 | this->symbol_handle = "█"; 29 | 30 | this->style = Blame::Styles::StyleScaleVertical(); 31 | break; 32 | } 33 | 34 | this->handle_current = 0; 35 | 36 | this->handle_min = 0; 37 | this->handle_max = 8; 38 | 39 | this->colour_handle = Blame::Util::EscapeCodes::foregroundMagenta(); 40 | } 41 | 42 | void Blame::Widgets::Scale::redraw() { 43 | Widget::redraw(); 44 | 45 | switch (this->orientation) { 46 | case Blame::Util::Orientation::HORIZONTAL: 47 | this->console->raw_grid[this->client_area.top][this->client_area.left + this->handle_current] = 48 | this->getCurrentColour(this->style.colours.background_content) 49 | + this->colour_handle 50 | + this->symbol_handle; 51 | break; 52 | 53 | case Blame::Util::Orientation::VERTICAL: 54 | this->console->raw_grid[this->client_area.top + this->handle_current][this->client_area.left] = 55 | this->getCurrentColour(this->style.colours.background_content) 56 | + this->colour_handle 57 | + this->symbol_handle; 58 | break; 59 | } 60 | 61 | this->is_redrawn.exchange(true); 62 | } 63 | 64 | void Blame::Widgets::Scale::move(Blame::Util::Direction direction) { 65 | if (this != this->console->focused_widget) 66 | return; 67 | 68 | switch (direction) { 69 | case Blame::Util::Direction::UP: 70 | if (this->handle_current - 1 > this->handle_min - 1 && this->orientation == Blame::Util::Orientation::VERTICAL) { 71 | this->handle_current--; 72 | } 73 | break; 74 | 75 | case Blame::Util::Direction::DOWN: 76 | if (this->handle_current + 1 < this->handle_max - 2 && this->orientation == Blame::Util::Orientation::VERTICAL) { 77 | this->handle_current++; 78 | } 79 | break; 80 | 81 | case Blame::Util::Direction::LEFT: 82 | if (this->handle_current - 1 > this->handle_min - 1 && this->orientation == Blame::Util::Orientation::HORIZONTAL) { 83 | this->handle_current--; 84 | } 85 | break; 86 | 87 | case Blame::Util::Direction::RIGHT: 88 | if (this->handle_current + 1 < this->handle_max && this->orientation == Blame::Util::Orientation::HORIZONTAL) { 89 | this->handle_current++; 90 | } 91 | break; 92 | } 93 | 94 | this->current = ((((float) this->handle_current / (this->handle_max - 1)) * this->max) - (std::fabs(this->min) / 2)) * (this->max - this->min); 95 | this->command(); 96 | 97 | Widget::move(direction); 98 | } 99 | -------------------------------------------------------------------------------- /src/main/cpp/widgets/Scroll.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | Blame::Widgets::Scroll::Scroll(Blame::Console *console, Blame::Widgets::Widget *parent, Blame::Util::Orientation orientation, std::vector widgets) : Widget(console, parent) { 10 | this->orientation = orientation; 11 | this->widgets = widgets; 12 | 13 | switch (this->orientation) { 14 | case Blame::Util::Orientation::HORIZONTAL: 15 | this->width = 8; 16 | this->height = 3; 17 | 18 | this->symbol_handle = "█"; 19 | 20 | this->style = Blame::Styles::StyleScaleHorizontal(); 21 | break; 22 | 23 | case Blame::Util::Orientation::VERTICAL: 24 | this->width = 1; 25 | this->height = 8; 26 | 27 | this->symbol_handle = "█"; 28 | 29 | this->style = Blame::Styles::StyleScaleVertical(); 30 | break; 31 | } 32 | 33 | this->handle_current = 0; 34 | 35 | this->handle_min = 0; 36 | this->handle_max = 8; 37 | 38 | this->colour_handle = Blame::Util::EscapeCodes::foregroundBlue(); 39 | } 40 | 41 | void Blame::Widgets::Scroll::redraw() { 42 | Widget::redraw(); 43 | 44 | switch (this->orientation) { 45 | case Blame::Util::Orientation::HORIZONTAL: 46 | for (auto i = 0; i < this->handle_size; i++) { 47 | if (this->client_area.left + this->current + i < this->console->width) { 48 | this->console->raw_grid[this->client_area.top][this->client_area.left + this->current + i] = 49 | this->getCurrentColour(this->style.colours.background_content) 50 | + this->colour_handle 51 | + this->symbol_handle; 52 | } 53 | } 54 | break; 55 | 56 | case Blame::Util::Orientation::VERTICAL: 57 | for (auto i = 0; i < this->handle_size; i++) { 58 | if (this->client_area.top + this->current + i < this->console->height) { 59 | this->console->raw_grid[this->client_area.top + this->current + i][this->client_area.left] = 60 | this->getCurrentColour(this->style.colours.background_content) 61 | + this->colour_handle 62 | + this->symbol_handle; 63 | } 64 | } 65 | break; 66 | } 67 | 68 | this->is_redrawn.exchange(true); 69 | } 70 | 71 | void Blame::Widgets::Scroll::move(Blame::Util::Direction direction) { 72 | if (this != this->console->focused_widget) 73 | return; 74 | 75 | switch (direction) { 76 | case Blame::Util::Direction::UP: 77 | if (this->handle_current - 1 > this->handle_min - 1 && this->orientation == Blame::Util::Orientation::VERTICAL) { 78 | this->handle_current--; 79 | 80 | for (auto i : this->widgets) { 81 | i->view_area_offset_y--; 82 | } 83 | } 84 | break; 85 | 86 | case Blame::Util::Direction::DOWN: 87 | if (this->handle_current + 1 < this->handle_max - 2 && this->orientation == Blame::Util::Orientation::VERTICAL) { 88 | this->handle_current++; 89 | 90 | for (auto i : this->widgets) { 91 | i->view_area_offset_y++; 92 | } 93 | } 94 | break; 95 | 96 | case Blame::Util::Direction::LEFT: 97 | if (this->handle_current - 1 > this->handle_min - 1 && this->orientation == Blame::Util::Orientation::HORIZONTAL) { 98 | this->handle_current--; 99 | 100 | for (auto i : this->widgets) { 101 | i->view_area_offset_x--; 102 | } 103 | } 104 | break; 105 | 106 | case Blame::Util::Direction::RIGHT: 107 | if (this->handle_current + 1 < this->handle_max && this->orientation == Blame::Util::Orientation::HORIZONTAL) { 108 | this->handle_current++; 109 | 110 | for (auto i : this->widgets) { 111 | i->view_area_offset_x++; 112 | } 113 | } 114 | break; 115 | } 116 | 117 | this->current = (this->handle_max / this->handle_size) * this->handle_current; 118 | 119 | Widget::move(direction); 120 | } 121 | -------------------------------------------------------------------------------- /src/main/cpp/widgets/Text.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | // TODO: Add support for multiple text caret's 6 | // TODO: Add support for selecting text 7 | Blame::Widgets::Text::Text(Blame::Console *console, Blame::Widgets::Widget *parent) : Widget(console, parent) { 8 | this->height = 6; 9 | this->width = 12; 10 | 11 | this->view_area_width = this->width; 12 | this->view_area_height = this->height; 13 | this->updateViewArea(); 14 | 15 | this->symbol_caret = "_"; 16 | this->colour_caret = Blame::Util::EscapeCodes::foregroundMagenta(); 17 | 18 | this->style = Blame::Styles::StyleText(); 19 | 20 | this->caret_x = 0; 21 | this->caret_y = 0; 22 | 23 | this->content.emplace_back(""); 24 | } 25 | 26 | void Blame::Widgets::Text::redraw() { 27 | Widget::redraw(); 28 | 29 | int iteration = 0; 30 | for (auto line = 0; line < this->content.size(); line++) { 31 | for (auto i = 0; i < this->content[line].length(); i++) { 32 | if (this->client_area.top + iteration < this->view_area.bottom - 2 33 | && this->client_area.left + i < this->view_area.right) { 34 | this->console->raw_grid[this->client_area.top + iteration][this->client_area.left + i] = 35 | this->getCurrentColour(this->style.colours.background_content) 36 | + this->getCurrentColour(this->style.colours.text) 37 | + this->content[line + this->view_area_offset_y][i + this->view_area_offset_x]; 38 | } 39 | } 40 | iteration++; 41 | } 42 | 43 | if (this->state != Blame::Util::State::DISABLED) { 44 | this->console->raw_grid[this->client_area.top + this->caret_y][this->client_area.left + this->caret_x] = 45 | this->colour_caret 46 | + this->symbol_caret; 47 | } 48 | 49 | this->is_redrawn.exchange(true); 50 | } 51 | 52 | void Blame::Widgets::Text::move(Blame::Util::Direction direction) { 53 | if (this != this->console->focused_widget || this->state == Blame::Util::State::DISABLED) 54 | return; 55 | 56 | switch (direction) { 57 | case Blame::Util::Direction::UP: 58 | if (this->caret_y - 1 > -1) { 59 | this->caret_x = 0; 60 | this->caret_y--; 61 | } 62 | break; 63 | 64 | case Blame::Util::Direction::DOWN: 65 | if (this->caret_y + 1 < this->height - 2 && this->caret_y + 1 < this->content.size()) { 66 | this->caret_y++; 67 | 68 | if (this->content[this->caret_y].size() <= 1) { 69 | this->caret_x = 0; 70 | } 71 | } 72 | break; 73 | 74 | case Blame::Util::Direction::LEFT: 75 | if (this->caret_x - 1 > -1) { 76 | this->caret_x--; 77 | } 78 | else { 79 | this->caret_y--; 80 | this->caret_x = (int) this->content[this->caret_y].size(); 81 | } 82 | break; 83 | 84 | case Blame::Util::Direction::RIGHT: 85 | if (this->caret_x + 1 < this->width) { 86 | this->caret_x++; 87 | 88 | if (this->caret_x + 1 > this->content[this->caret_y].size() + 1) { 89 | this->caret_x = 0; 90 | this->move(Blame::Util::Direction::DOWN); 91 | } 92 | } 93 | break; 94 | } 95 | 96 | Widget::move(direction); 97 | } 98 | 99 | void Blame::Widgets::Text::text(std::string text) { 100 | if (this->state == Blame::Util::State::DISABLED) 101 | return; 102 | 103 | switch (text.c_str()[0]) { 104 | // Enter 105 | case '\n': 106 | this->caret_x = 0; 107 | this->caret_y++; 108 | 109 | if (this->caret_y >= this->content.size()) { 110 | this->content.emplace_back(""); 111 | } 112 | break; 113 | 114 | // Space 115 | case ' ': 116 | this->content[this->caret_y].insert((unsigned long)this->caret_x, " "); 117 | this->caret_x++; 118 | break; 119 | 120 | // Backspace 121 | case 127: 122 | // FIXME: Doesn't work with multiline text 123 | if (this->caret_x - 1 > -1) { 124 | this->caret_x--; 125 | } 126 | 127 | this->content[this->caret_y].erase((unsigned long)this->caret_x, 1); 128 | break; 129 | 130 | // Everything else 131 | default: 132 | // this->console->setTitle(std::to_string(text.c_str()[0])); 133 | this->content[this->caret_y].insert((unsigned long)this->caret_x, text); 134 | this->caret_x++; 135 | break; 136 | } 137 | 138 | Widget::text(text); 139 | } 140 | -------------------------------------------------------------------------------- /src/main/cpp/widgets/Widget.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | Blame::Widgets::Widget::Widget(Blame::Console *console, Blame::Widgets::Widget *parent) { 6 | this->console = console; 7 | this->parent = parent; 8 | 9 | // Should Be Handled by Parent or Console: 10 | // Valgrind warning 11 | if (this->parent != nullptr) { 12 | this->parent->children.push_back(this); 13 | } 14 | else { 15 | this->console->widget_list.push_back(this); 16 | } 17 | 18 | this->console->focus_order.push_back(this); 19 | this->is_redrawn.store(true); 20 | 21 | this->updateClientArea(); 22 | } 23 | 24 | void Blame::Widgets::Widget::redraw() { 25 | this->updateClientArea(); 26 | this->is_redrawn.exchange(false); 27 | 28 | for (int y = 0; y < this->height; y++) { 29 | for (int x = 0; x < this->width + 1; x++) { 30 | // Top Left 31 | if (x == 0 && y == 0) { 32 | this->console->raw_grid[this->row + y + 1][this->column + x] = 33 | this->getCurrentColour(this->style.colours.border) 34 | + this->getCurrentColour(this->style.colours.background_border) 35 | + this->style.symbols.top_left; 36 | } 37 | // Middle Left 38 | else if (x == 0 && y > 0 && y < this->height - 1) { 39 | this->console->raw_grid[this->row + y + 1][this->column + x] = 40 | this->getCurrentColour(this->style.colours.border) 41 | + this->getCurrentColour(this->style.colours.background_border) 42 | + this->style.symbols.middle_left; 43 | } 44 | // Bottom Left 45 | else if (x == 0 && y == this->height - 1) { 46 | this->console->raw_grid[this->row + y + 1][this->column + x] = 47 | this->getCurrentColour(this->style.colours.border) 48 | + this->getCurrentColour(this->style.colours.background_border) 49 | + this->style.symbols.bottom_left; 50 | } 51 | 52 | // Top Middle 53 | if (y == 0) { 54 | this->console->raw_grid[this->row + y + 1][this->column + x + 1] = 55 | this->getCurrentColour(this->style.colours.border) 56 | + this->getCurrentColour(this->style.colours.background_border) 57 | + this->style.symbols.top_middle; 58 | } 59 | // Bottom Middle 60 | else if (y == this->height - 1) { 61 | this->console->raw_grid[this->row + y + 1][this->column + x + 1] = 62 | this->getCurrentColour(this->style.colours.border) 63 | + this->getCurrentColour(this->style.colours.background_border) 64 | + this->style.symbols.bottom_middle; 65 | } 66 | // Middle Fill 67 | else { 68 | this->console->raw_grid[this->row + y + 1][this->column + x + 1] = 69 | this->getCurrentColour(this->style.colours.background_content) 70 | + this->style.symbols.middle_fill; 71 | } 72 | 73 | // Top Right 74 | if (x == this->width && y == 0) { 75 | this->console->raw_grid[this->row + y + 1][this->column + x + 1] = 76 | this->getCurrentColour(this->style.colours.border) 77 | + this->getCurrentColour(this->style.colours.background_border) 78 | + this->style.symbols.top_right; 79 | } 80 | // Middle Right 81 | else if (x == this->width && y > 0 && y < this->height - 1) { 82 | this->console->raw_grid[this->row + y + 1][this->column + x + 1] = 83 | this->getCurrentColour(this->style.colours.border) 84 | + this->getCurrentColour(this->style.colours.background_border) 85 | + this->style.symbols.middle_right; 86 | } 87 | // Bottom Right 88 | else if (x == this->width && y == this->height - 1) { 89 | this->console->raw_grid[this->row + y + 1][this->column + x + 1] = 90 | this->getCurrentColour(this->style.colours.border) 91 | + this->getCurrentColour(this->style.colours.background_border) 92 | + this->style.symbols.bottom_right; 93 | } 94 | } 95 | } 96 | 97 | this->is_redrawn.exchange(true); 98 | } 99 | -------------------------------------------------------------------------------- /src/main/cpp/widgets/Window.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | Blame::Widgets::Window::Window(Blame::Console *console, std::string title_text) : Widget(console, nullptr) { 8 | this->title_text = title_text; 9 | 10 | this->symbol_title_intersect_left = "╠"; 11 | this->symbol_title_intersect_right = "╣"; 12 | 13 | this->style = Blame::Styles::StyleWindow(); 14 | } 15 | 16 | // FIXME: Draws the line under the title twice 17 | void Blame::Widgets::Window::redraw() { 18 | if (this->state_window == Blame::Util::StateWindow::RESTORED || this->state_window == Blame::Util::StateWindow::MAXIMIZED) { 19 | this->is_redrawn.exchange(false); 20 | 21 | // Account for the title height 22 | this->row -= 2; 23 | 24 | for (int y = 0; y < 3; y++) { 25 | for (int x = 0; x < this->width + 1; x++) { 26 | // Top Left 27 | if (x == 0 && y == 0) { 28 | this->console->raw_grid[this->row + y + 1][this->column + x] = 29 | this->getCurrentColour(this->style.colours.border) 30 | + this->getCurrentColour(this->style.colours.background_border) 31 | + this->style.symbols.top_left; 32 | } 33 | // Middle Left 34 | else if (x == 0 && y > 0 && y < this->height - 1) { 35 | this->console->raw_grid[this->row + y + 1][this->column + x] = 36 | this->getCurrentColour(this->style.colours.border) 37 | + this->getCurrentColour(this->style.colours.background_border) 38 | + this->style.symbols.middle_left; 39 | } 40 | 41 | // Top Middle 42 | if (x > 0 && y == 0) { 43 | this->console->raw_grid[this->row + y + 1][this->column + x] = 44 | this->getCurrentColour(this->style.colours.border) 45 | + this->getCurrentColour(this->style.colours.background_border) 46 | + this->style.symbols.top_middle; 47 | } 48 | // Middle Fill 49 | else { 50 | if (x >= 1 && x <= this->title_text.length()) { 51 | this->console->raw_grid[this->row + y + 1][this->column + x + 1] = 52 | this->getCurrentColour(this->style.colours.border) 53 | + this->getCurrentColour(this->style.colours.background_content) 54 | + this->title_text[x - 1]; 55 | } 56 | // Text 57 | else if (x == 0 || x > this->title_text.length()) { 58 | this->console->raw_grid[this->row + y + 1][this->column + x + 1] = 59 | this->getCurrentColour(this->style.colours.background_content) 60 | + this->style.symbols.middle_fill; 61 | } 62 | } 63 | 64 | // Top Right 65 | if (x == this->width && y == 0) { 66 | this->console->raw_grid[this->row + y + 1][this->column + x + 1] = 67 | this->getCurrentColour(this->style.colours.border) 68 | + this->getCurrentColour(this->style.colours.background_border) 69 | + this->style.symbols.top_right; 70 | } 71 | // Middle Right 72 | else if (x == this->width && y > 0 && y < this->height - 1) { 73 | this->console->raw_grid[this->row + y + 1][this->column + x + 1] = 74 | this->getCurrentColour(this->style.colours.border) 75 | + this->getCurrentColour(this->style.colours.background_border) 76 | + this->style.symbols.middle_right; 77 | } 78 | } 79 | } 80 | 81 | this->style.symbols.top_left = this->symbol_title_intersect_left; 82 | this->style.symbols.top_right = this->symbol_title_intersect_right; 83 | 84 | this->row += 2; 85 | } 86 | 87 | Widget::redraw(); 88 | 89 | this->style.symbols.top_left = "╔"; 90 | this->style.symbols.top_right = "╗"; 91 | 92 | this->is_redrawn.exchange(true); 93 | } 94 | 95 | void Blame::Widgets::Window::move(Blame::Util::Direction direction) { 96 | switch (direction) { 97 | case Blame::Util::Direction::UP: 98 | if (this->row - 2 > console->client_area.top) { 99 | for (auto i = this->column; i < this->column + this->width + 2; i++) { 100 | this->console->raw_grid[this->row + this->height][i] = ""; 101 | } 102 | 103 | this->row--; 104 | 105 | for (auto child : this->children) { 106 | child->row--; 107 | } 108 | } 109 | break; 110 | 111 | case Blame::Util::Direction::DOWN: 112 | if (this->row + 1 + this->height < console->client_area.bottom) { 113 | for (auto i = this->column; i < this->column + this->width + 2; i++) { 114 | this->console->raw_grid[this->row - 1][i] = ""; 115 | } 116 | 117 | this->row++; 118 | 119 | for (auto child : this->children) { 120 | child->row++; 121 | } 122 | } 123 | break; 124 | 125 | case Blame::Util::Direction::LEFT: 126 | if (this->column - 1 > console->client_area.left) { 127 | for (auto i = this->row - 1; i < this->row + this->height + 1; i++) { 128 | this->console->raw_grid[i][this->column + this->width + 1] = ""; 129 | } 130 | 131 | this->column--; 132 | 133 | for (auto child : this->children) { 134 | child->column--; 135 | } 136 | } 137 | break; 138 | 139 | case Blame::Util::Direction::RIGHT: 140 | if (this->column + 1 + this->width < console->client_area.right) { 141 | for (auto i = this->row - 1; i < this->row + this->height + 1; i++) { 142 | this->console->raw_grid[i][this->column] = ""; 143 | } 144 | 145 | this->column++; 146 | 147 | for (auto child : this->children) { 148 | child->column++; 149 | } 150 | } 151 | break; 152 | } 153 | 154 | Widget::move(direction); 155 | } 156 | --------------------------------------------------------------------------------