foxygit / Torlinkc Log in
commits tags

/include/torlinkc/ui/app_state.hpp · 3.89 KB

raw
#pragma once

#include <optional>
#include <string>
#include <unordered_map>
#include <vector>

#include "torlinkc/engine/history.hpp"
#include "torlinkc/engine/types.hpp"
#include "torlinkc/sources/registry.hpp"
#include "torlinkc/sources/types.hpp"
#include "torlinkc/ui/sort.hpp"

namespace torlinkc::ui {

// Per-source status for the "N sources down" UI. Ported from
// useConcurrentSearch.ts's SourceState.
struct SourceState {
  bool loading = false;
  std::optional<std::string> error;
  std::optional<std::string> code;
  int count = 0;
};

// Ported from store.ts.
enum class View { Splash, Browser };

// Merges store.ts's Category ("all"/"games"/"movies"/"tv"/"anime") with its
// Section ("downloads"/"seeding") into one enum, since the C++ port has no
// separate synced category+section pair to keep in step.
enum class Section { All, Games, Movies, TV, Anime, Downloads, Seeding };

enum class Region { Sidebar, Content };

enum class DownloadFocus { Downloading, Paused, Failed, Recent };
enum class SeedFocus { Seeding, Paused, Missing, Idle };

// nullopt = the given category doesn't filter sources (Downloads/Seeding).
std::optional<Category> sectionToCategory(Section s);
std::string sectionLabel(Section s);

// The single source of truth for the TUI, mutated only on the UI thread --
// either directly inside an FTXUI event handler, or via a closure a
// background thread posts through ftxui::ScreenInteractive::Post(). Ported
// conceptually from ui/store.ts, but as a plain struct rather than a
// React-context-plus-useState bundle, since FTXUI has no equivalent
// reactivity to hang hooks off of (see the Phase 2 section of the plan).
// No internal locking: that invariant (UI-thread-only mutation) is what
// makes locking unnecessary, not something this struct enforces itself.
struct AppState {
  // Navigation (ported from store.ts's view/section/region/*Focus).
  View view = View::Splash;
  Section section = Section::All;
  Region region = Region::Content;
  std::optional<DownloadFocus> downloadFocus;
  std::optional<SeedFocus> seedFocus;

  // Index of the currently focused child in main.cpp's top-level
  // Container::Vertical -- see kFocus* constants in main.cpp. Kept in sync
  // with view/section/region by main.cpp's syncFocus(), which also runs
  // whenever SearchAggregator lands the first results of a search (so a
  // hotkey pressed right after Enter reaches the results list, not the
  // still-focused search box).
  int focusedIndex = 0;
  int sidebarCursor = 0;
  int downloadsCursor = 0;
  int seedingCursor = 0;
  std::vector<std::string> sidebarLabels;  // rebuilt each render; badges depend on live counts

  bool showHelp = false;
  bool editingFolder = false;
  bool editingTrackers = false;
  std::string folderPromptText;
  std::string trackersPromptText;

  // Search, across all sources (Phase 3's SearchAggregator).
  std::string query;
  std::vector<TorrentResult> results;  // deduped, default-ordered, unfiltered -- SearchAggregator's output
  std::unordered_map<std::string, SourceState> perSource;
  int doneSources = 0;
  int totalSources = 0;
  bool searching = false;

  // Display: section-as-category/hideDead/sort applied to `results`,
  // recomputed into `visibleResults` by main.cpp's refreshVisible() whenever
  // `results` or any of the three change. main.cpp's results table renders
  // this directly (one row per entry, in a fixed-width column layout), so
  // there's no separate label cache to keep in sync.
  bool hideDead = false;
  Sort sort;
  std::vector<TorrentResult> visibleResults;
  int selectedResult = 0;

  // Elapsed seconds while View::Browser is showing (see ui/clock.hpp) --
  // drives the downloads progress-bar sheen.
  float uiClock = 0.0f;

  // Downloads / seeds, replaced wholesale each engine-thread tick.
  std::vector<QueueItem> items;
  std::vector<HistoryItem> history;
  std::vector<SeedItem> seeds;

  std::string notice;
};

}  // namespace torlinkc::ui