Add a read-only 42-day occurrence agenda with keyboard navigation, six-week paging, exact return-to-grid focus, search and calendar overlays, and bounded recurrence expansion. Keep agenda state outside persistence, document month-only mutations, and cover empty windows, filtering, overlays, navigation, rendering, and terminal restoration.
145 lines
4.5 KiB
C++
145 lines
4.5 KiB
C++
#pragma once
|
|
|
|
#include <chrono>
|
|
#include <optional>
|
|
#include <span>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <vector>
|
|
|
|
#include "nocal/domain/event.hpp"
|
|
#include "nocal/domain/event_query.hpp"
|
|
|
|
namespace nocal::tui {
|
|
|
|
enum class ItemSegment {
|
|
single,
|
|
start,
|
|
continuation,
|
|
end,
|
|
};
|
|
|
|
// A deliberately small view-model keeps the renderer independent of storage and
|
|
// makes it straightforward to unit test. TuiApp adapts domain Events to this.
|
|
struct CalendarItem {
|
|
std::string id;
|
|
std::string title;
|
|
std::chrono::year_month_day day;
|
|
std::optional<std::chrono::minutes> time_of_day;
|
|
bool all_day{false};
|
|
std::optional<std::chrono::minutes> end_time_of_day;
|
|
std::chrono::year_month_day start_day{};
|
|
std::chrono::year_month_day end_day{};
|
|
std::string location;
|
|
std::string description;
|
|
std::string detail_title;
|
|
bool detail_all_day{false};
|
|
std::optional<std::chrono::minutes> detail_start_time_of_day;
|
|
ItemSegment segment{ItemSegment::single};
|
|
bool recurring{false};
|
|
std::string recurrence_summary;
|
|
std::string source_time_zone;
|
|
};
|
|
|
|
struct SearchResultItem {
|
|
std::string focus_id;
|
|
std::string title;
|
|
std::chrono::year_month_day day;
|
|
std::optional<std::chrono::minutes> time_of_day;
|
|
bool all_day{false};
|
|
std::string location;
|
|
bool recurring{false};
|
|
};
|
|
|
|
struct AgendaItem {
|
|
std::string focus_id;
|
|
std::string title;
|
|
std::chrono::year_month_day day;
|
|
std::optional<std::chrono::minutes> time_of_day;
|
|
bool all_day{false};
|
|
std::string location;
|
|
std::string calendar_id;
|
|
bool recurring{false};
|
|
};
|
|
|
|
struct ScreenState {
|
|
std::chrono::year_month visible_month{std::chrono::year{1970}, std::chrono::month{1}};
|
|
std::chrono::year_month_day selected_day{std::chrono::year{1970}, std::chrono::month{1},
|
|
std::chrono::day{1}};
|
|
std::chrono::year_month_day today{std::chrono::year{1970}, std::chrono::month{1},
|
|
std::chrono::day{1}};
|
|
int width{80};
|
|
int height{24};
|
|
bool show_help{false};
|
|
bool ansi{true};
|
|
std::optional<std::string> focused_event_id;
|
|
bool show_event_details{false};
|
|
bool confirm_delete{false};
|
|
bool search_prompt{false};
|
|
bool show_search_results{false};
|
|
std::string search_query;
|
|
std::vector<SearchResultItem> search_results;
|
|
std::size_t search_result_index{0};
|
|
std::string notification;
|
|
bool notification_is_error{false};
|
|
bool show_calendar_picker{false};
|
|
std::size_t calendar_index{0};
|
|
bool show_agenda{false};
|
|
std::chrono::year_month_day agenda_start_day{
|
|
std::chrono::year{1970}, std::chrono::month{1}, std::chrono::day{1}};
|
|
std::vector<AgendaItem> agenda_items;
|
|
std::size_t agenda_index{0};
|
|
};
|
|
|
|
// Render a complete frame. The result contains no cursor positioning, which
|
|
// makes it useful both for snapshots and for non-interactive output.
|
|
[[nodiscard]] std::string render_month(std::span<const CalendarItem> items,
|
|
const ScreenState& state);
|
|
|
|
// Convenience adapter for the domain model. Occurrence instants are projected
|
|
// into the user's process-local zone; the reader retains an explicit source TZID.
|
|
[[nodiscard]] std::string render_month(std::span<const Event> events,
|
|
const ScreenState& state);
|
|
|
|
// Calendar visibility filters presentation only. Focus identities are still
|
|
// derived from the complete event collection.
|
|
[[nodiscard]] std::string render_month(std::span<const Event> events,
|
|
std::span<const Calendar> calendars,
|
|
const ScreenState& state);
|
|
|
|
// Stable focus identity shared by the renderer and controller. Non-recurring
|
|
// events retain their historical UID/synthetic identity; recurring instances
|
|
// additionally include their concrete start instant.
|
|
[[nodiscard]] std::string occurrence_focus_id(std::span<const Event> events,
|
|
const EventOccurrence& occurrence);
|
|
|
|
enum class Action {
|
|
none,
|
|
left,
|
|
right,
|
|
up,
|
|
down,
|
|
previous_month,
|
|
next_month,
|
|
today,
|
|
toggle_help,
|
|
previous_event,
|
|
next_event,
|
|
select,
|
|
back,
|
|
add_event,
|
|
edit_event,
|
|
delete_event,
|
|
undo,
|
|
redo,
|
|
search,
|
|
agenda,
|
|
calendars,
|
|
quit,
|
|
};
|
|
|
|
// Decodes a complete key sequence, including common terminal arrow/Page keys.
|
|
[[nodiscard]] Action decode_key(std::string_view sequence) noexcept;
|
|
|
|
} // namespace nocal::tui
|