Files
roro9stack/src/apps/gemini_app.h
T
twislaandClaude Opus 5.5 9ca9a16de8 Gemini: read big pages from the card as you scroll (Q88)
A page larger than memory allows is now windowed instead of cut: one
pass over its file (cache or Saved Page) counts lines, indexes every
64th (offset, and the preformatted state there in bit 31: about 1 KB
for a 1 MB page) and loads the first window. Scrolling near either end
reads the next or previous window on the Gemini task; the line on top
of the screen stays put, the scrollbar follows the whole page, and Tab
at a window's edge pages on instead of wrapping. Window budgets count
the memory the old window gives back.

Display pages alternate between two cache files so the one on screen
is never overwritten by the next fetch; jobs use a third.

On the device, Cosmos with IRC connected: 226 of 419 lines at first,
then lines 192-419, back to 64 and 0 while scrolling. `key space` added
to the console's key command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-05 01:44:01 +02:00

82 lines
3.2 KiB
C++

#pragma once
#include <memory>
#include <string>
#include <vector>
#include "app.h"
#include "dialog_model.h"
#include "gemtext.h"
#include "line_editor.h"
#include "services/gemini_service.h"
#include "ui/canvas.h"
namespace roro {
// Geminispace on the Cardputer (docs/milestones/G1.md). Pages come from the GeminiService as
// lines; the App wraps them once (keeping only each line's first row), then wraps just the lines
// on screen when it draws, so a long page costs 4 bytes a line on top of its text.
class GeminiApp : public App {
public:
explicit GeminiApp(GeminiService& gemini) : gemini_(gemini) {}
void onEnter() override;
bool onKey(const KeyEvent& e) override;
bool textEntryActive() const override { return addressOpen_ || inputOpen_; }
void update(uint32_t nowMs) override;
void draw(Canvas& c) override;
private:
struct Visit {
std::string url;
int topRow;
};
void navigate(const std::string& url, bool remember = true, bool preferSaved = false);
void follow(const std::string& link);
std::string titleOf() const;
bool isSaved() const { return !page_.savedFrom.empty(); }
std::string savedPathOf() const { return page_.url.substr(7); } // file://…
void show(GeminiPage&& page);
void analyse(); // types and links of the lines in memory
void applyWindow(GeminiPage&& window);
void requestWindow(size_t firstLine);
size_t topLine() const; // the source line (in the whole page) at the top of the screen
bool moreBelow() const { return page_.windowed && page_.windowStart + page_.text.lineCount() < page_.totalLines; }
bool moreAbove() const { return page_.windowed && page_.windowStart > 0; }
void showMessage(const std::string& url, const std::string& gemtext);
void layout(Canvas& c);
void drawLine(Canvas& c, size_t index, int firstRow, int y, int rows);
std::vector<std::string> rowsOf(Canvas& c, size_t index) const;
gemini::GemLine lineAt(size_t index) const;
int rowsOnScreen() const;
void selectLink(int direction);
void scrollTo(int row);
GeminiService& gemini_;
GeminiPage page_;
std::vector<uint8_t> types_; // per source line: gemini::LineType
std::vector<uint32_t> firstRow_; // per source line, plus the total at the end
std::vector<uint32_t> links_; // source lines that are links
bool layoutDirty_ = true;
int topRow_ = 0, hscroll_ = 0;
int selected_ = -1; // into links_
int restoreRow_ = -1; // going back: where that page was scrolled to, applied after layout
bool windowLoading_ = false;
long keepLine_ = -1; // a new window: the source line to keep at the top, applied after layout
std::vector<Visit> history_;
bool loading_ = false;
std::string loadingUrl_, status_;
uint32_t statusMs_ = 0;
bool addressOpen_ = false;
LineEditor address_{1024};
std::unique_ptr<DialogModel> certDialog_;
GeminiPage pendingCert_;
std::unique_ptr<DialogModel> deleteDialog_;
bool inputOpen_ = false, inputSensitive_ = false;
std::string inputPrompt_, inputUrl_;
LineEditor input_{1024};
std::string reopen_; // after a refresh: the Saved Page to show again
};
} // namespace roro