diff options
Diffstat (limited to 'src/ui.hpp')
-rw-r--r-- | src/ui.hpp | 563 |
1 files changed, 0 insertions, 563 deletions
diff --git a/src/ui.hpp b/src/ui.hpp deleted file mode 100644 index de77614..0000000 --- a/src/ui.hpp +++ /dev/null @@ -1,563 +0,0 @@ -#pragma once - -#include <list> -#include <string> -#include <unordered_set> -#include <vector> - -#include "logger.hpp" -#include "rttt.hpp" -#include "view.hpp" - -#include "imgui/imgui.h" - -namespace rttt { -namespace ui { - -enum class ColorScheme : int { - Default, - Dark, - Green, - COUNT, -}; - -struct scroll_window_state { - ItemVariant highlighted_item; - - // The line at the top of the screen - float scroll_top = 0; - // TODO we can probably do away when we use lazy views everywhere - int no_shown_items = 0; - - // To deal with large items - bool large_item_mode = false; - float large_item_offset = 0; - float highlighted_item_size = 0; - - view::mode ui_view_mode = view::mode::normal; -}; - -struct WindowData { - rttt::Path path; - rttt::ui::scroll_window_state scroll_state; -}; - -using namespace rttt; -struct state { - int hoveredWindowId = 0; - int statusWindowHeight = 5; - - ColorScheme colorScheme = ColorScheme::Dark; - bool display_popup = false; - bool showStatusWindow = true; - - bool logSkipOnce = true; - - bool textInput = false; - char inputWindowHeader[512]; - int inputWindowHeight = 2; - char inputBuffer[128]; - - int nWindows = 1; - - char statusWindowHeader[512]; - - // Keeping this around for a long time. Should check how that is for - // performance. Note should only add it when read/or collapsed. Ideally remove - // if/when neither again - rttt::active_storage<std::string, view::item_state> item_view_states; - - void changeColorScheme(bool inc = true) { - if (inc) { - colorScheme = - (ColorScheme)(((int)colorScheme + 1) % ((int)ColorScheme::COUNT)); - } - - ImVec4 *colors = ImGui::GetStyle().Colors; - switch (colorScheme) { - case ColorScheme::Default: { - colors[ImGuiCol_Text] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); - colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); - colors[ImGuiCol_WindowBg] = ImVec4(0.96f, 0.96f, 0.94f, 1.00f); - colors[ImGuiCol_FrameBg] = colors[ImGuiCol_WindowBg]; // InputText - colors[ImGuiCol_TitleBg] = ImVec4(1.00f, 0.40f, 0.00f, 1.00f); - colors[ImGuiCol_TitleBgActive] = ImVec4(1.00f, 0.40f, 0.00f, 1.00f); - colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.69f, 0.25f, 0.00f, 1.00f); - colors[ImGuiCol_ChildBg] = ImVec4(0.96f, 0.96f, 0.94f, 1.00f); - colors[ImGuiCol_PopupBg] = ImVec4(0.96f, 0.96f, 0.94f, 1.00f); - colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); - } break; - case ColorScheme::Dark: { - colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); - colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); - colors[ImGuiCol_WindowBg] = ImVec4(0.10f, 0.10f, 0.10f, 1.00f); - colors[ImGuiCol_FrameBg] = colors[ImGuiCol_WindowBg]; // InputText - colors[ImGuiCol_TitleBg] = ImVec4(1.00f, 0.40f, 0.00f, 0.50f); - colors[ImGuiCol_TitleBgActive] = ImVec4(1.00f, 0.40f, 0.00f, 0.50f); - colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.69f, 0.25f, 0.00f, 0.50f); - colors[ImGuiCol_ChildBg] = ImVec4(0.10f, 0.10f, 0.10f, 1.00f); - colors[ImGuiCol_PopupBg] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f); - colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); - } break; - case ColorScheme::Green: { - colors[ImGuiCol_Text] = ImVec4(0.00f, 1.00f, 0.00f, 1.00f); - colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); - colors[ImGuiCol_WindowBg] = ImVec4(0.10f, 0.10f, 0.10f, 1.00f); - colors[ImGuiCol_FrameBg] = colors[ImGuiCol_WindowBg]; // InputText - colors[ImGuiCol_TitleBg] = ImVec4(0.25f, 0.25f, 0.25f, 1.00f); - colors[ImGuiCol_TitleBgActive] = ImVec4(0.25f, 0.25f, 0.25f, 1.00f); - colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.50f, 1.00f, 0.50f, 1.00f); - colors[ImGuiCol_ChildBg] = ImVec4(0.10f, 0.10f, 0.10f, 1.00f); - colors[ImGuiCol_PopupBg] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); - colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); - } break; - default: { - } - } - } - - std::array<WindowData, 3> windows{{ - {rttt::parse_path("/r/front"), rttt::ui::scroll_window_state()}, - {rttt::parse_path("/hn/top"), rttt::ui::scroll_window_state()}, - {rttt::parse_path("/hn/show"), rttt::ui::scroll_window_state()}, - }}; -}; - -inline bool draw_url_popup(bool open, std::string path_url, - const ItemVariant &item) { - if (ImGui::BeginPopupModal("Pick an url (Escape to close).###pickurl", NULL, - ImGuiWindowFlags_AlwaysAutoResize)) { - std::vector<std::string> urls = {path_url}; - auto maybe_url = try_url(item); - if (maybe_url.has_value()) - urls.push_back(maybe_url.value()); - auto maybe_text = try_text(item); - assert(maybe_text.has_value()); - - ImGui::NewLine(); - - // We always first show the first URL - auto comment_urls = rttt::extractURL(maybe_text.value()); - urls.insert(urls.end(), comment_urls.begin(), comment_urls.end()); - std::string chars = "0123456789"; - for (size_t i = 0; i < std::min(10UL, urls.size()); ++i) { - ImGui::Text("[%zu] %s ", i, urls[i].c_str()); - if (ImGui::IsKeyPressed(chars[i], false)) { - rttt::openInBrowser(urls[i].c_str()); - ImGui::CloseCurrentPopup(); - open = false; - } - } - - if (ImGui::IsKeyPressed(ImGui::GetIO().KeyMap[ImGuiKey_Escape], false)) { - ImGui::CloseCurrentPopup(); - open = false; - } - ImGui::NewLine(); - ImGui::EndPopup(); - } - return open; -} - -inline bool draw_help_popup(bool open) { - if (ImGui::BeginPopupModal("rttt v0.4###Help", NULL, - ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::Text(" "); - ImGui::Text("Browsing of the things"); - ImGui::Text("Content is automatically updated - no need to refresh |"); - ImGui::Text(" "); - ImGui::Text(" ?/H - toggle Help window "); - ImGui::Text(" / - Open a new path by typing it (e.g. " - "/r/front, /hn/top) "); - ImGui::Text(" s - toggle Messages window "); - ImGui::Text(" g - go to top "); - ImGui::Text(" G - go to bottom (of the loaded items)"); - ImGui::Text(" o/O - open in browser "); - ImGui::Text(" j/k down/up - navigate items "); - ImGui::Text( - " J/K - mark items read/unread while navigating "); - ImGui::Text(" left/right - navigate windows "); - ImGui::Text(" Tab - change current window content "); - ImGui::Text(" 1/2/3 - change number of windows "); - ImGui::Text(" Space - collapse comments "); - ImGui::Text(" h - close comments "); - ImGui::Text(" v - toggle view mode "); - ImGui::Text(" r - force refresh story "); - ImGui::Text(" c - change colors "); - ImGui::Text(" a/z - up/down vote a comment (only when logged in) "); - ImGui::Text(" u - login to reddit "); - ImGui::Text(" q/Q - quit "); - ImGui::Text(" "); - - if (open) { - if (ImGui::IsKeyPressed(ImGui::GetIO().KeyMap[ImGuiKey_Escape], false)) { - ImGui::CloseCurrentPopup(); - auto col = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); - col.w = 1.0; - ImGui::GetStyle().Colors[ImGuiCol_WindowBg] = col; - open = false; - } - } else { - open = true; - } - - ImGui::EndPopup(); - } - return open; -} - -inline auto toggleCollapsed(rttt::active_storage<std::string, int> &&collapsed, - const std::string &id) { - if (collapsed.contains(id)) - collapsed.erase(id); - else - collapsed.insert({id, 0}); - return std::move(collapsed); -} - -inline void drawHighlightBar(float top) { - // Should we explore using ImGui::GetWindowDrawList()->AddRectFilled(p0, p1, - // ImGui::GetColorU32(col0)); instead? - auto col0 = ImGui::GetStyleColorVec4(ImGuiCol_TitleBg); - // auto col1 = ImGui::GetStyleColorVec4(ImGuiCol_Text); - ImGui::PushStyleColor(ImGuiCol_Text, col0); - // ImGui::PushStyleColor(ImGuiCol_TitleBg, col1); - - auto bottom = ImGui::GetCursorPosY(); - ImGui::SetCursorPosY(top); - std::string mark = "|"; - for (size_t i = 0; i < bottom - top; ++i) { - ImGui::Text("%s", mark.c_str()); - } - // ImGui::PopStyleColor(2); - ImGui::PopStyleColor(); -} - -template <typename T> -void drawStoryData(const T &data, size_t id, bool isHighlighted, view::mode mode, - bool read) { - auto top = ImGui::GetCursorPosY(); - ImGui::Indent(1); - ImGui::Text("%3zu.", id + 1); - ImGui::SameLine(); - ImGui::PushTextWrapPos(ImGui::GetContentRegionAvailWidth() - 2 - - data.domain.size()); - if (read) - ImGui::TextDisabled("%s", data.title.c_str()); - else - ImGui::Text("%s", data.title.c_str()); - ImGui::PopTextWrapPos(); - ImGui::SameLine(); - ImGui::TextDisabled(" (%s)", data.domain.c_str()); - ImGui::Indent(5); - // TODO: unify with header line for comments - if constexpr (std::is_same<rss::Story, T>()) { - if (!data.by.empty()) { - ImGui::TextDisabled("by %s", data.by.c_str()); - ImGui::SameLine(); - } - ImGui::TextDisabled("%s ago", rttt::timeSince(data.time).c_str()); - } else if constexpr (std::is_same<reddit::Story, T>() || - std::is_same<reddit::Comment, T>()) { - ImGui::TextDisabled("%d(%d) points by %s %s ago | %d comments", data.score, - data.vote, data.by.c_str(), - rttt::timeSince(data.time).c_str(), data.descendants); - } else { - ImGui::TextDisabled("%d points by %s %s ago | %d comments", data.score, - data.by.c_str(), rttt::timeSince(data.time).c_str(), - data.descendants); - } - - if (mode == view::mode::text && !data.text.empty()) { - ImGui::NewLine(); - ImGui::PushTextWrapPos(ImGui::GetContentRegionAvailWidth() - 2); - ImGui::TextDisabled("%s", data.text.c_str()); - ImGui::PopTextWrapPos(); - ImGui::NewLine(); - } - - ImGui::Unindent(5); - ImGui::Unindent(1); - if (isHighlighted) { - drawHighlightBar(top); - } -} - -template <typename T> -void commentModeDrawComment(const T &comment, int indent, bool isHighlighted, - bool is_collapsed, bool is_read) { - if constexpr (std::is_same<rss::Story, T>()) { - assert(false); - return; - } - - auto top = ImGui::GetCursorPosY(); - // TODO: use ImGui::Indent(indent); instead of the formatted indent - if (!is_collapsed) { - ImGui::PushTextWrapPos(ImGui::GetContentRegionAvailWidth() - 2); - auto maybe_title = try_title(comment); - if (maybe_title.has_value()) { - if (is_read) - ImGui::TextDisabled("%*s %s", 2 * indent, "", maybe_title.value().c_str()); - else - ImGui::Text("%*s %s", 2 * indent, "", maybe_title.value().c_str()); - ImGui::NewLine(); - } - - // TODO: use different color than disabled? - if (is_read) - ImGui::TextDisabled("%*s", 2 * indent, ""); - else - ImGui::Text("%*s", 2 * indent, ""); - ImGui::SameLine(); - if (is_read) - ImGui::TextDisabled("%s", comment.text.c_str()); - else - ImGui::Text("%s", comment.text.c_str()); - ImGui::PopTextWrapPos(); - } - - char header[128]; - if constexpr (std::is_same<rss::Story, T>() || - std::is_same<hackernews::Comment, T>()) { - snprintf(header, 128, "%*s %s %s ago [%s]", 2 * indent, "", - comment.by.c_str(), rttt::timeSince(comment.time).c_str(), - is_collapsed ? "+" : "-"); - // TODO: create a has_like template function (which only return true if - // reddit and score != 0) - } else if constexpr (std::is_same<reddit::Story, T>() || - std::is_same<reddit::Comment, T>()) { - snprintf(header, 128, "%*s %d(%d) points by %s %s ago [%s]", 2 * indent, "", - comment.score, comment.vote, comment.by.c_str(), - rttt::timeSince(comment.time).c_str(), is_collapsed ? "+" : "-"); - // TODO: create a has_score template function - } else { - snprintf(header, 128, "%*s %d points by %s %s ago [%s]", 2 * indent, "", - comment.score, comment.by.c_str(), - rttt::timeSince(comment.time).c_str(), is_collapsed ? "+" : "-"); - } - - ImGui::TextDisabled("%s", header); - - if (isHighlighted) { - drawHighlightBar(top); - } - ImGui::NewLine(); -} - -/** - * This function contains the logic to call the correct draw function depending - * on data type and uimode - */ -inline void drawItem(const ItemVariant &item, size_t id, size_t indent, - bool isHighlighted, rttt::list_mode mode, - view::mode view_mode, - const view::item_state item_view_state) { - if (std::holds_alternative<unknown_type>(item)) { - logger::push_back("Unknown item"); - return; - } - if (mode != rttt::list_mode::comment) { - std::visit( - [&id, &isHighlighted, &view_mode, &item_view_state](const auto &data) { - // TODO: implement an is_story<decltype(data)> (or similar) - if constexpr (std::is_same<const rttt::reddit::Story &, - decltype(data)>::value || - std::is_same<const rttt::hackernews::Story &, - decltype(data)>::value || - std::is_same<const rttt::rss::Story &, - decltype(data)>::value) { - drawStoryData(data, id, isHighlighted, view_mode, - item_view_state.read); - } else { - assert(false); - } - }, - item); - } else { - std::visit( - [&isHighlighted, &indent, &item_view_state](const auto &data) { - // TODO: implement an has_collapsed<decltype(data)> (or similar) - if constexpr (rttt::has_text<decltype(data)>::value) { - commentModeDrawComment(data, indent, isHighlighted, - item_view_state.collapsed, - item_view_state.read); - } else { - assert(false); - } - }, - item); - } -} - -// Draw a scrollable window of items -template <typename T> -scroll_window_state drawItems( - scroll_window_state &&state, T &items, const rttt::list_mode &list_mode, - rttt::active_storage<std::string, view::item_state> &item_view_states) { - size_t counter = 0; - ImGui::SetScrollY(state.scroll_top); - - auto maybe_hid = try_id_string(state.highlighted_item); - - auto it = items.begin(); - auto s_it = it; - - bool passed_highlighted_item = false; - // Number of stories to draw below the scrollable area. - // Important because hackernews only loads the "viewable" items, so we need to - // cache some extra for smooth scroling - size_t draw_extra = 10; - - for (; it != items.end(); ++it) { - const auto &indent = (*it).first; - const auto &item = (*it).second; - - bool isHighlighted = false; - - if (!maybe_hid.has_value()) { - // Invalid value, so select the top one - if (counter == 0) - isHighlighted = true; - } else { - auto maybe_id = try_id_string(item); - if (maybe_id.has_value() && maybe_id.value() == maybe_hid.value()) { - isHighlighted = true; - } - } - - // Check whether the current item is larger than our window - if (isHighlighted) { - state.highlighted_item = item; - s_it = it; - state.highlighted_item_size = ImGui::GetCursorPosY(); - - if (ImGui::GetCursorPosY() <= state.scroll_top) { - ImGui::SetScrollHereY(0.0); - state.scroll_top = ImGui::GetScrollY(); - } - if (state.large_item_mode) { - // This catches some corner cases when scrolling back up - while (state.large_item_offset == 0 && - ImGui::GetCursorPosY() < - state.scroll_top + state.large_item_offset + 10) { - state.scroll_top -= 10; - } - ImGui::SetScrollY(state.scroll_top + state.large_item_offset + 10); - } - } - - auto maybe_id = try_id_string(item); - if (maybe_id && item_view_states.contains(maybe_id.value())) { - drawItem(item, counter, indent, isHighlighted, list_mode, - state.ui_view_mode, item_view_states.at(maybe_id.value())); - item_view_states.mark_active(maybe_id.value()); - } else { - drawItem(item, counter, indent, isHighlighted, list_mode, - state.ui_view_mode, view::item_state()); - } - - if (isHighlighted) { - passed_highlighted_item = true; - if (ImGui::GetCursorPosY() > - state.scroll_top + ImGui::GetWindowHeight()) { - // Tried using IsItemVisible, but that does not seem to work with - // scrolling - state.highlighted_item_size = - ImGui::GetCursorPosY() - state.highlighted_item_size; - state.large_item_mode = - (state.highlighted_item_size - state.large_item_offset) > - ImGui::GetWindowHeight(); - if (!state.large_item_mode) { - ImGui::SetScrollHereY(1.0); - state.scroll_top = ImGui::GetScrollY(); - } - } - } - - ++counter; - - if (passed_highlighted_item && - ImGui::GetCursorPosY() > state.scroll_top + ImGui::GetWindowHeight()) { - --draw_extra; - // Stop drawing if we have shown the highlighted_item and are outside the - // visible area - if (draw_extra == 0) - break; - } - } - - state.no_shown_items = counter; - - if (ImGui::IsWindowFocused()) { - if (ImGui::IsKeyPressed('J', true) || ImGui::IsKeyPressed('K', true)) { - auto maybe_id = try_id_string(state.highlighted_item); - if (maybe_id) - item_view_states = - view::toggle_read(std::move(item_view_states), maybe_id.value()); - } - - // Bind keys - if (ImGui::IsKeyPressed('k', true) || ImGui::IsKeyPressed('K', true) || - ImGui::IsKeyPressed(ImGui::GetIO().KeyMap[ImGuiKey_UpArrow], true)) { - if (state.large_item_mode) { - --state.large_item_offset; - if (state.large_item_offset < 0) { - state.large_item_mode = false; - state.large_item_offset = 0; - } - } - if (!state.large_item_mode) { - if (s_it != items.begin()) { - --s_it; - state.highlighted_item = (*s_it).second; - } - } - } - - if (ImGui::IsKeyPressed('j', true) || ImGui::IsKeyPressed('J', true) || - ImGui::IsKeyPressed(ImGui::GetIO().KeyMap[ImGuiKey_DownArrow], true)) { - if (state.large_item_mode) { - ++state.large_item_offset; - if ((state.highlighted_item_size - state.large_item_offset) <= - ImGui::GetWindowHeight()) { - state.large_item_mode = false; - state.large_item_offset = 0; - } - } - if (!state.large_item_mode) { - if (!items.empty() && s_it != items.end()) { - ++s_it; - if (s_it != items.end()) - state.highlighted_item = (*s_it).second; - } - } - } - - if (ImGui::IsKeyReleased('g')) { - state.large_item_mode = false; - state.large_item_offset = 0; - state.highlighted_item = (*items.begin()).second; - } - - if (ImGui::IsKeyReleased('G')) { - state.large_item_mode = false; - state.large_item_offset = 0; - state.highlighted_item = (*(--it)).second; - } - - // TODO: Should this really be here? This function is really about the scrolling etc... - // While the key is more about window status - if (ImGui::IsKeyReleased('v')) { - state.ui_view_mode = (view::mode)(((int)(state.ui_view_mode) + 1) % - ((int)(view::mode::COUNT))); - state.large_item_mode = false; - state.large_item_offset = 0; - } - } - - return std::move(state); -} - -} // namespace ui -} // namespace rttt |