summaryrefslogtreecommitdiffstats
path: root/src/ui.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui.hpp')
-rw-r--r--src/ui.hpp563
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