feat: MTK scrollbar widget, Wikipedia app improvements
This commit is contained in:
@@ -774,6 +774,122 @@ inline void draw_separator(Canvas& c, int x, int y, int w, const Theme& theme) {
|
|||||||
c.hline(x, y, w, theme.border);
|
c.hline(x, y, w, theme.border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static constexpr int SCROLLBAR_W = 12;
|
||||||
|
static constexpr int SCROLLBAR_OUTER_PAD = 4;
|
||||||
|
static constexpr int SCROLLBAR_TRACK_PAD = 8;
|
||||||
|
static constexpr int SCROLLBAR_THUMB_INSET = 2;
|
||||||
|
static constexpr int SCROLLBAR_MIN_THUMB = 24;
|
||||||
|
|
||||||
|
inline bool scrollbar_needed(int content_extent, int view_extent) {
|
||||||
|
return content_extent > view_extent && view_extent > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int scrollbar_max_offset(int content_extent, int view_extent) {
|
||||||
|
int max_offset = content_extent - view_extent;
|
||||||
|
return max_offset > 0 ? max_offset : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Rect scrollbar_track_rect(const Rect& viewport,
|
||||||
|
int width = SCROLLBAR_W,
|
||||||
|
int outer_pad = SCROLLBAR_OUTER_PAD,
|
||||||
|
int track_pad = SCROLLBAR_TRACK_PAD) {
|
||||||
|
int h = gui_max(viewport.h - track_pad * 2, 0);
|
||||||
|
int usable_w = viewport.w - outer_pad;
|
||||||
|
if (usable_w <= 0 || h <= 0) return {0, 0, 0, 0};
|
||||||
|
int actual_w = gui_clamp(width, 1, usable_w);
|
||||||
|
return {
|
||||||
|
viewport.x + viewport.w - actual_w - outer_pad,
|
||||||
|
viewport.y + track_pad,
|
||||||
|
actual_w,
|
||||||
|
h
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int scrollbar_thumb_extent(int content_extent,
|
||||||
|
int view_extent,
|
||||||
|
int track_extent,
|
||||||
|
int min_thumb = SCROLLBAR_MIN_THUMB) {
|
||||||
|
if (!scrollbar_needed(content_extent, view_extent) || track_extent <= 0)
|
||||||
|
return track_extent;
|
||||||
|
|
||||||
|
int thumb = (int)(((int64_t)view_extent * track_extent) / content_extent);
|
||||||
|
return gui_clamp(thumb, gui_min(min_thumb, track_extent), track_extent);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Rect scrollbar_thumb_rect(const Rect& track,
|
||||||
|
int content_extent,
|
||||||
|
int view_extent,
|
||||||
|
int offset,
|
||||||
|
int min_thumb = SCROLLBAR_MIN_THUMB,
|
||||||
|
int thumb_inset = SCROLLBAR_THUMB_INSET) {
|
||||||
|
if (track.empty() || !scrollbar_needed(content_extent, view_extent))
|
||||||
|
return {0, 0, 0, 0};
|
||||||
|
|
||||||
|
int max_offset = scrollbar_max_offset(content_extent, view_extent);
|
||||||
|
int thumb_h = scrollbar_thumb_extent(content_extent, view_extent,
|
||||||
|
track.h, min_thumb);
|
||||||
|
int travel = gui_max(track.h - thumb_h, 0);
|
||||||
|
int clamped_offset = gui_clamp(offset, 0, max_offset);
|
||||||
|
int thumb_y = track.y + (max_offset > 0
|
||||||
|
? (int)(((int64_t)clamped_offset * travel) / max_offset)
|
||||||
|
: 0);
|
||||||
|
int thumb_w = gui_max(track.w - thumb_inset * 2, 1);
|
||||||
|
return {track.x + thumb_inset, thumb_y, thumb_w, thumb_h};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int scrollbar_offset_from_thumb_top(const Rect& track,
|
||||||
|
int content_extent,
|
||||||
|
int view_extent,
|
||||||
|
int thumb_extent,
|
||||||
|
int thumb_top) {
|
||||||
|
int max_offset = scrollbar_max_offset(content_extent, view_extent);
|
||||||
|
int travel = gui_max(track.h - thumb_extent, 0);
|
||||||
|
if (track.empty() || max_offset <= 0 || travel <= 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
int rel = gui_clamp(thumb_top - track.y, 0, travel);
|
||||||
|
return (int)(((int64_t)rel * max_offset + travel / 2) / travel);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Color scrollbar_thumb_color(const Theme& theme,
|
||||||
|
bool hovered,
|
||||||
|
bool dragging) {
|
||||||
|
Color base = mix(theme.border, theme.text_muted, 54);
|
||||||
|
if (dragging) return mix(base, theme.accent, 118);
|
||||||
|
if (hovered) return mix(base, theme.accent, 42);
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void draw_scrollbar(Canvas& c,
|
||||||
|
const Rect& track,
|
||||||
|
const Rect& thumb,
|
||||||
|
bool hovered,
|
||||||
|
bool dragging,
|
||||||
|
const Theme& theme) {
|
||||||
|
if (track.empty() || thumb.empty()) return;
|
||||||
|
|
||||||
|
if (hovered || dragging) {
|
||||||
|
int rail_w = track.w > 6 ? track.w - 6 : track.w;
|
||||||
|
if (rail_w < 1) rail_w = 1;
|
||||||
|
Rect rail = {
|
||||||
|
track.x + (track.w - rail_w) / 2,
|
||||||
|
track.y,
|
||||||
|
rail_w,
|
||||||
|
track.h
|
||||||
|
};
|
||||||
|
Color rail_fill = dragging
|
||||||
|
? mix(theme.window_bg, theme.accent, 34)
|
||||||
|
: mix(theme.window_bg, theme.text_muted, 24);
|
||||||
|
c.fill_rounded_rect(rail.x, rail.y, rail.w, rail.h,
|
||||||
|
gui_max(theme.radius_sm, rail.w / 2),
|
||||||
|
rail_fill);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color thumb_fill = scrollbar_thumb_color(theme, hovered, dragging);
|
||||||
|
int radius = gui_max(theme.radius_sm, thumb.w / 2);
|
||||||
|
c.fill_rounded_rect(thumb.x, thumb.y, thumb.w, thumb.h, radius, thumb_fill);
|
||||||
|
}
|
||||||
|
|
||||||
inline Rect tab_rect(const Rect& bar, int count, int index) {
|
inline Rect tab_rect(const Rect& bar, int count, int index) {
|
||||||
if (count <= 0) return {bar.x, bar.y, 0, bar.h};
|
if (count <= 0) return {bar.x, bar.y, 0, bar.h};
|
||||||
int slot_w = bar.w / count;
|
int slot_w = bar.w / count;
|
||||||
|
|||||||
+355
-85
@@ -9,6 +9,7 @@
|
|||||||
#include <montauk/string.h>
|
#include <montauk/string.h>
|
||||||
#include <montauk/heap.h>
|
#include <montauk/heap.h>
|
||||||
#include <gui/mtk.hpp>
|
#include <gui/mtk.hpp>
|
||||||
|
#include <gui/mtk/settings.hpp>
|
||||||
#include <gui/standalone.hpp>
|
#include <gui/standalone.hpp>
|
||||||
#include <gui/svg.hpp>
|
#include <gui/svg.hpp>
|
||||||
#include <gui/truetype.hpp>
|
#include <gui/truetype.hpp>
|
||||||
@@ -30,7 +31,7 @@ using namespace gui::colors;
|
|||||||
static constexpr int INIT_W = 820;
|
static constexpr int INIT_W = 820;
|
||||||
static constexpr int INIT_H = 580;
|
static constexpr int INIT_H = 580;
|
||||||
static int TOOLBAR_H = 42;
|
static int TOOLBAR_H = 42;
|
||||||
static constexpr int SCROLLBAR_W = 14;
|
static constexpr int SCROLLBAR_W = mtk::SCROLLBAR_W;
|
||||||
static int FONT_SIZE = 18;
|
static int FONT_SIZE = 18;
|
||||||
static int TITLE_SIZE = 32;
|
static int TITLE_SIZE = 32;
|
||||||
static int SECTION_SIZE = 24;
|
static int SECTION_SIZE = 24;
|
||||||
@@ -39,7 +40,7 @@ static constexpr int RESP_MAX = 131072;
|
|||||||
static constexpr int MAX_LINES = 2000;
|
static constexpr int MAX_LINES = 2000;
|
||||||
|
|
||||||
static const char WIKI_HOST[] = "en.wikipedia.org";
|
static const char WIKI_HOST[] = "en.wikipedia.org";
|
||||||
static const char DISPLAY_ICON_PATH[] = "0:/icons/dialog-text-and-font.svg";
|
static const char DISPLAY_ICON_PATH[] = "0:/icons/text_line_spacing.svg";
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Display line
|
// Display line
|
||||||
@@ -57,7 +58,7 @@ struct WikiLine {
|
|||||||
// App state
|
// App state
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
enum class AppPhase { IDLE, LOADING, DONE, ERR };
|
enum class AppPhase { WELCOME, IDLE, LOADING, DONE, ERR };
|
||||||
enum class ReaderTextSize : uint8_t { SMALL = 0, MEDIUM, LARGE, COUNT };
|
enum class ReaderTextSize : uint8_t { SMALL = 0, MEDIUM, LARGE, COUNT };
|
||||||
enum class ReaderFontFace : uint8_t { SANS = 0, SERIF, BOOK, COUNT };
|
enum class ReaderFontFace : uint8_t { SANS = 0, SERIF, BOOK, COUNT };
|
||||||
|
|
||||||
@@ -81,7 +82,7 @@ struct ReaderPalette {
|
|||||||
static constexpr int READER_TEXT_SIZE_COUNT = (int)ReaderTextSize::COUNT;
|
static constexpr int READER_TEXT_SIZE_COUNT = (int)ReaderTextSize::COUNT;
|
||||||
static constexpr int READER_FONT_FACE_COUNT = (int)ReaderFontFace::COUNT;
|
static constexpr int READER_FONT_FACE_COUNT = (int)ReaderFontFace::COUNT;
|
||||||
|
|
||||||
static AppPhase g_phase = AppPhase::IDLE;
|
static AppPhase g_phase = AppPhase::WELCOME;
|
||||||
static char g_query[256] = {};
|
static char g_query[256] = {};
|
||||||
static mtk::TextInputState g_query_input = {};
|
static mtk::TextInputState g_query_input = {};
|
||||||
static char g_status[256] = {};
|
static char g_status[256] = {};
|
||||||
@@ -97,12 +98,18 @@ static bool g_scroll_dragging = false;
|
|||||||
static int g_scroll_drag_offset = 0;
|
static int g_scroll_drag_offset = 0;
|
||||||
static bool g_display_controls_open = false;
|
static bool g_display_controls_open = false;
|
||||||
static ReaderTextSize g_reader_text_size = ReaderTextSize::MEDIUM;
|
static ReaderTextSize g_reader_text_size = ReaderTextSize::MEDIUM;
|
||||||
static ReaderFontFace g_reader_font_face = ReaderFontFace::SANS;
|
static ReaderFontFace g_reader_font_face = ReaderFontFace::BOOK;
|
||||||
static char g_title[512] = {};
|
static char g_title[512] = {};
|
||||||
static int g_extract_len = 0;
|
static int g_extract_len = 0;
|
||||||
static int g_base_font_size = 18;
|
static int g_base_font_size = 18;
|
||||||
static int g_base_title_size = 32;
|
static int g_base_title_size = 32;
|
||||||
static int g_base_section_size = 24;
|
static int g_base_section_size = 24;
|
||||||
|
static Color g_accent = colors::ACCENT;
|
||||||
|
static bool g_welcome_feature_loaded = false;
|
||||||
|
static char g_welcome_feature_title[256] = {};
|
||||||
|
static char g_welcome_feature_extract[1024] = {};
|
||||||
|
static char g_welcome_status[192] = "Loading today's featured article...";
|
||||||
|
static char g_welcome_date[16] = {};
|
||||||
|
|
||||||
// Large buffers — heap allocated in _start
|
// Large buffers — heap allocated in _start
|
||||||
static WikiLine* g_lines = nullptr;
|
static WikiLine* g_lines = nullptr;
|
||||||
@@ -140,7 +147,10 @@ struct ReaderToolbarLayout {
|
|||||||
Rect search_button;
|
Rect search_button;
|
||||||
Rect text_size_buttons[READER_TEXT_SIZE_COUNT];
|
Rect text_size_buttons[READER_TEXT_SIZE_COUNT];
|
||||||
Rect font_buttons[READER_FONT_FACE_COUNT];
|
Rect font_buttons[READER_FONT_FACE_COUNT];
|
||||||
|
Rect text_size_label;
|
||||||
|
Rect font_face_label;
|
||||||
int sep_x;
|
int sep_x;
|
||||||
|
bool show_labels;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void apply_reader_preferences();
|
static void apply_reader_preferences();
|
||||||
@@ -178,18 +188,18 @@ static ReaderPalette reader_palette() {
|
|||||||
return {
|
return {
|
||||||
WHITE,
|
WHITE,
|
||||||
WHITE,
|
WHITE,
|
||||||
Color::from_rgb(0xF6, 0xF7, 0xF9),
|
Color::from_rgb(0xF5, 0xF5, 0xF5),
|
||||||
Color::from_rgb(0xD5, 0xDB, 0xE3),
|
colors::BORDER,
|
||||||
Color::from_rgb(0x20, 0x23, 0x29),
|
colors::TEXT_COLOR,
|
||||||
Color::from_rgb(0x11, 0x14, 0x18),
|
Color::from_rgb(0x22, 0x22, 0x22),
|
||||||
Color::from_rgb(0x7A, 0x84, 0x92),
|
Color::from_rgb(0x77, 0x77, 0x77),
|
||||||
Color::from_rgb(0x33, 0x66, 0xCC),
|
g_accent,
|
||||||
Color::from_rgb(0x24, 0x52, 0xAF),
|
mtk::darken(g_accent, 32),
|
||||||
Color::from_rgb(0xE5, 0xEE, 0xFF),
|
mtk::lighten(g_accent, 214),
|
||||||
Color::from_rgb(0xC8, 0x32, 0x32),
|
Color::from_rgb(0xD0, 0x3E, 0x3E),
|
||||||
Color::from_rgb(0xA9, 0x24, 0x24),
|
Color::from_rgb(0xDD, 0x44, 0x44),
|
||||||
Color::from_rgb(0xE7, 0xEB, 0xF0),
|
Color::from_rgb(0xCC, 0xCC, 0xCC),
|
||||||
Color::from_rgb(0x97, 0x9E, 0xA8),
|
WHITE,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,17 +268,17 @@ static int display_toggle_button_w() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int search_row_y() {
|
static int search_row_y() {
|
||||||
return 8;
|
return 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int reader_row_y() {
|
static int reader_row_y() {
|
||||||
return search_row_y() + search_control_h() + 8;
|
return search_row_y() + search_control_h() + 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void update_toolbar_height() {
|
static void update_toolbar_height() {
|
||||||
int h = search_row_y() + search_control_h() + 8;
|
int h = search_row_y() + search_control_h() + 6;
|
||||||
if (g_display_controls_open)
|
if (g_display_controls_open)
|
||||||
h += reader_button_h() + 8;
|
h += reader_button_h() + 6;
|
||||||
TOOLBAR_H = h;
|
TOOLBAR_H = h;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,7 +293,7 @@ static void reload_display_icons() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int segmented_button_w(const char* label, int min_w) {
|
static int segmented_button_w(const char* label, int min_w) {
|
||||||
return gui_max(text_width(label) + 18, min_w);
|
return gui_max(text_width(label) + 16, min_w);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int button_group_width(const char* const* labels, int count, int min_w, int gap) {
|
static int button_group_width(const char* const* labels, int count, int min_w, int gap) {
|
||||||
@@ -313,8 +323,9 @@ static ReaderToolbarLayout toolbar_layout() {
|
|||||||
int search_h = search_control_h();
|
int search_h = search_control_h();
|
||||||
int reader_h = reader_button_h();
|
int reader_h = reader_button_h();
|
||||||
int display_w = display_toggle_button_w();
|
int display_w = display_toggle_button_w();
|
||||||
|
int search_btn_w = 104;
|
||||||
|
|
||||||
lo.search_button = {g_win_w - pad - 108, search_row_y(), 108, search_h};
|
lo.search_button = {g_win_w - pad - search_btn_w, search_row_y(), search_btn_w, search_h};
|
||||||
lo.display_button = {lo.search_button.x - 8 - display_w, search_row_y(), display_w, search_h};
|
lo.display_button = {lo.search_button.x - 8 - display_w, search_row_y(), display_w, search_h};
|
||||||
int field_w = lo.display_button.x - pad - 8;
|
int field_w = lo.display_button.x - pad - 8;
|
||||||
if (field_w < 80) field_w = 80;
|
if (field_w < 80) field_w = 80;
|
||||||
@@ -323,19 +334,42 @@ static ReaderToolbarLayout toolbar_layout() {
|
|||||||
if (!g_display_controls_open)
|
if (!g_display_controls_open)
|
||||||
return lo;
|
return lo;
|
||||||
|
|
||||||
int gap = 6;
|
int gap = 4;
|
||||||
int group_gap = 20;
|
int group_gap = 18;
|
||||||
int size_w = button_group_width(g_text_size_labels, READER_TEXT_SIZE_COUNT, 34, gap);
|
int label_gap = 8;
|
||||||
int font_w = button_group_width(g_font_face_labels, READER_FONT_FACE_COUNT, 50, gap);
|
int size_w = button_group_width(g_text_size_labels, READER_TEXT_SIZE_COUNT, 36, gap);
|
||||||
int total_w = size_w + font_w + group_gap;
|
int font_w = button_group_width(g_font_face_labels, READER_FONT_FACE_COUNT, 54, gap);
|
||||||
int x = gui_max((g_win_w - total_w) / 2, pad);
|
int text_label_w = text_width("Text");
|
||||||
int y = reader_row_y();
|
int font_label_w = text_width("Font");
|
||||||
|
int available_w = g_win_w - pad * 2;
|
||||||
|
int total_w = text_label_w + label_gap + size_w + group_gap +
|
||||||
|
font_label_w + label_gap + font_w;
|
||||||
|
lo.show_labels = total_w <= available_w;
|
||||||
|
if (!lo.show_labels) {
|
||||||
|
total_w = size_w + group_gap + font_w;
|
||||||
|
text_label_w = 0;
|
||||||
|
font_label_w = 0;
|
||||||
|
}
|
||||||
|
|
||||||
layout_button_group(lo.text_size_buttons, READER_TEXT_SIZE_COUNT, g_text_size_labels, 34, gap, &x, y, reader_h);
|
int x = total_w < available_w ? (g_win_w - total_w) / 2 : pad;
|
||||||
|
int y = reader_row_y();
|
||||||
|
int label_y = y + (reader_h - system_font_height()) / 2;
|
||||||
|
|
||||||
|
if (lo.show_labels) {
|
||||||
|
lo.text_size_label = {x, label_y, text_label_w, system_font_height()};
|
||||||
|
x += text_label_w + label_gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
layout_button_group(lo.text_size_buttons, READER_TEXT_SIZE_COUNT, g_text_size_labels, 36, gap, &x, y, reader_h);
|
||||||
x += group_gap;
|
x += group_gap;
|
||||||
lo.sep_x = x - group_gap / 2;
|
lo.sep_x = x - group_gap / 2;
|
||||||
|
|
||||||
layout_button_group(lo.font_buttons, READER_FONT_FACE_COUNT, g_font_face_labels, 50, gap, &x, y, reader_h);
|
if (lo.show_labels) {
|
||||||
|
lo.font_face_label = {x, label_y, font_label_w, system_font_height()};
|
||||||
|
x += font_label_w + label_gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
layout_button_group(lo.font_buttons, READER_FONT_FACE_COUNT, g_font_face_labels, 54, gap, &x, y, reader_h);
|
||||||
return lo;
|
return lo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,10 +411,14 @@ static void draw_display_toggle_button(Canvas& canvas,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* fallback = "Aa";
|
int glyph_w = gui_min(bounds.w - 10, 16);
|
||||||
int tw = text_width(fallback);
|
int glyph_h = 12;
|
||||||
int ty = bounds.y + (bounds.h - system_font_height()) / 2;
|
int gx = bounds.x + (bounds.w - glyph_w) / 2;
|
||||||
canvas.text(bounds.x + (bounds.w - tw) / 2, ty, fallback, colors.fg);
|
int gy = bounds.y + (bounds.h - glyph_h) / 2;
|
||||||
|
canvas.fill_rect(gx, gy, glyph_w - 4, 2, colors.fg);
|
||||||
|
canvas.fill_rect(gx, gy + 5, glyph_w, 2, colors.fg);
|
||||||
|
canvas.fill_rect(gx, gy + 10, glyph_w - 6, 2, colors.fg);
|
||||||
|
canvas.fill_rect(gx + glyph_w - 3, gy - 1, 3, 4, colors.fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int visible_line_count() {
|
static int visible_line_count() {
|
||||||
@@ -390,8 +428,11 @@ static int visible_line_count() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int max_scroll_lines() {
|
static int max_scroll_lines() {
|
||||||
int max_scroll = g_line_count - visible_line_count();
|
return mtk::scrollbar_max_offset(g_line_count, visible_line_count());
|
||||||
return max_scroll > 0 ? max_scroll : 0;
|
}
|
||||||
|
|
||||||
|
static bool document_scrollable() {
|
||||||
|
return g_phase == AppPhase::WELCOME || g_phase == AppPhase::DONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void clamp_scroll() {
|
static void clamp_scroll() {
|
||||||
@@ -407,12 +448,7 @@ static Rect scrollbar_track_rect() {
|
|||||||
int visible = visible_line_count();
|
int visible = visible_line_count();
|
||||||
if (visible <= 0 || g_line_count <= visible) return {0, 0, 0, 0};
|
if (visible <= 0 || g_line_count <= visible) return {0, 0, 0, 0};
|
||||||
|
|
||||||
return {
|
return mtk::scrollbar_track_rect(content);
|
||||||
g_win_w - SCROLLBAR_W - 4,
|
|
||||||
content.y + 8,
|
|
||||||
SCROLLBAR_W,
|
|
||||||
gui_max(content.h - 16, 0)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Rect scrollbar_thumb_rect() {
|
static Rect scrollbar_thumb_rect() {
|
||||||
@@ -422,12 +458,7 @@ static Rect scrollbar_thumb_rect() {
|
|||||||
int visible = visible_line_count();
|
int visible = visible_line_count();
|
||||||
if (visible <= 0 || g_line_count <= visible) return {0, 0, 0, 0};
|
if (visible <= 0 || g_line_count <= visible) return {0, 0, 0, 0};
|
||||||
|
|
||||||
int max_sc = max_scroll_lines();
|
return mtk::scrollbar_thumb_rect(track, g_line_count, visible, g_scroll_y);
|
||||||
int thumb_h = (visible * track.h) / g_line_count;
|
|
||||||
thumb_h = gui_clamp(thumb_h, 24, track.h);
|
|
||||||
int travel = gui_max(track.h - thumb_h, 0);
|
|
||||||
int thumb_y = track.y + (max_sc > 0 ? (g_scroll_y * travel) / max_sc : 0);
|
|
||||||
return {track.x + 2, thumb_y, track.w - 4, thumb_h};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void set_scroll_from_thumb_top(int thumb_top) {
|
static void set_scroll_from_thumb_top(int thumb_top) {
|
||||||
@@ -435,15 +466,11 @@ static void set_scroll_from_thumb_top(int thumb_top) {
|
|||||||
Rect thumb = scrollbar_thumb_rect();
|
Rect thumb = scrollbar_thumb_rect();
|
||||||
if (track.empty() || thumb.empty()) return;
|
if (track.empty() || thumb.empty()) return;
|
||||||
|
|
||||||
int max_sc = max_scroll_lines();
|
g_scroll_y = mtk::scrollbar_offset_from_thumb_top(track,
|
||||||
int travel = gui_max(track.h - thumb.h, 0);
|
g_line_count,
|
||||||
if (max_sc <= 0 || travel <= 0) {
|
visible_line_count(),
|
||||||
g_scroll_y = 0;
|
thumb.h,
|
||||||
return;
|
thumb_top);
|
||||||
}
|
|
||||||
|
|
||||||
int rel = gui_clamp(thumb_top - track.y, 0, travel);
|
|
||||||
g_scroll_y = (rel * max_sc + travel / 2) / travel;
|
|
||||||
clamp_scroll();
|
clamp_scroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,6 +557,28 @@ static void append_codepoint(char* out, int* j, int maxOut, unsigned codepoint)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void underscores_to_spaces(char* text) {
|
||||||
|
if (!text) return;
|
||||||
|
for (int i = 0; text[i]; i++) {
|
||||||
|
if (text[i] == '_') text[i] = ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_main_page_query(const char* query) {
|
||||||
|
if (!query) return false;
|
||||||
|
|
||||||
|
char normalized[16];
|
||||||
|
int out = 0;
|
||||||
|
for (int i = 0; query[i] && out < (int)sizeof(normalized) - 1; i++) {
|
||||||
|
char ch = query[i];
|
||||||
|
if (ch == ' ' || ch == '_' || ch == '-') continue;
|
||||||
|
if (ch >= 'A' && ch <= 'Z') ch = (char)(ch - 'A' + 'a');
|
||||||
|
normalized[out++] = ch;
|
||||||
|
}
|
||||||
|
normalized[out] = '\0';
|
||||||
|
return strcmp(normalized, "mainpage") == 0;
|
||||||
|
}
|
||||||
|
|
||||||
static unsigned decode_utf8_codepoint(const char* buf, int len, int* consumed) {
|
static unsigned decode_utf8_codepoint(const char* buf, int len, int* consumed) {
|
||||||
if (!buf || len <= 0) {
|
if (!buf || len <= 0) {
|
||||||
if (consumed) *consumed = 0;
|
if (consumed) *consumed = 0;
|
||||||
@@ -598,6 +647,15 @@ static int parse_status_code(const char* buf, int len) {
|
|||||||
return (buf[i]-'0')*100 + (buf[i+1]-'0')*10 + (buf[i+2]-'0');
|
return (buf[i]-'0')*100 + (buf[i+1]-'0')*10 + (buf[i+2]-'0');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int find_substr(const char* buf, int len, const char* needle) {
|
||||||
|
int nlen = (int)strlen(needle);
|
||||||
|
if (!buf || !needle || nlen <= 0 || nlen > len) return -1;
|
||||||
|
for (int i = 0; i <= len - nlen; i++) {
|
||||||
|
if (memcmp(buf + i, needle, nlen) == 0) return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// URL encoding
|
// URL encoding
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -834,16 +892,106 @@ static void build_display_lines(const char* title, const char* extract, int extr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rebuild_article(bool preserve_scroll = true) {
|
static int document_top_pad() {
|
||||||
|
return g_phase == AppPhase::WELCOME ? 24 : 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Rect document_column_rect() {
|
||||||
|
Rect column = article_column_rect();
|
||||||
|
if (g_phase == AppPhase::WELCOME) {
|
||||||
|
int inset = gui_min(24, column.w / 12);
|
||||||
|
if (column.w - inset * 2 >= 320) {
|
||||||
|
column.x += inset;
|
||||||
|
column.w -= inset * 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return column;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void build_welcome_lines(bool preserve_scroll = false) {
|
||||||
|
ReaderPalette palette = reader_palette();
|
||||||
|
TrueTypeFont* body_font = current_body_font();
|
||||||
|
TrueTypeFont* heading_font = current_heading_font();
|
||||||
|
int old_scroll = g_scroll_y;
|
||||||
|
|
||||||
|
if (!body_font) return;
|
||||||
|
|
||||||
|
g_line_count = 0;
|
||||||
|
g_scroll_y = 0;
|
||||||
|
|
||||||
|
Rect column = document_column_rect();
|
||||||
|
int max_px = column.w;
|
||||||
|
TrueTypeFont* tf = heading_font ? heading_font : body_font;
|
||||||
|
|
||||||
|
const char* title = "Welcome to the Montauk Wikipedia reader";
|
||||||
|
wrap_text(tf, TITLE_SIZE, title, (int)strlen(title), max_px, palette.heading);
|
||||||
|
add_spacer(gui_max(FONT_SIZE - 4, 10));
|
||||||
|
|
||||||
|
const char* intro =
|
||||||
|
"Search for an article above, or use this page as a calmer starting point than the Wikipedia Main Page.";
|
||||||
|
wrap_text(body_font, FONT_SIZE, intro, (int)strlen(intro), max_px, palette.text);
|
||||||
|
add_spacer(gui_max(FONT_SIZE + 2, 16));
|
||||||
|
|
||||||
|
if (g_welcome_feature_loaded && g_welcome_feature_title[0]) {
|
||||||
|
wrap_text(tf, SECTION_SIZE, "Featured article today",
|
||||||
|
(int)strlen("Featured article today"), max_px, palette.heading);
|
||||||
|
add_spacer(gui_max(FONT_SIZE - 6, 10));
|
||||||
|
|
||||||
|
wrap_text(tf, gui_max(FONT_SIZE + 2, 16),
|
||||||
|
g_welcome_feature_title,
|
||||||
|
(int)strlen(g_welcome_feature_title),
|
||||||
|
max_px, palette.accent);
|
||||||
|
add_spacer(gui_max(FONT_SIZE - 4, 10));
|
||||||
|
|
||||||
|
if (g_welcome_feature_extract[0]) {
|
||||||
|
wrap_text(body_font, FONT_SIZE,
|
||||||
|
g_welcome_feature_extract,
|
||||||
|
(int)strlen(g_welcome_feature_extract),
|
||||||
|
max_px, palette.text);
|
||||||
|
add_spacer(gui_max(FONT_SIZE - 6, 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
char hint[360];
|
||||||
|
snprintf(hint, sizeof(hint),
|
||||||
|
"To open the full article, search for \"%s\".",
|
||||||
|
g_welcome_feature_title);
|
||||||
|
add_spacer(gui_max(FONT_SIZE - 8, 8));
|
||||||
|
wrap_text(body_font, gui_max(FONT_SIZE - 2, 12),
|
||||||
|
hint, (int)strlen(hint), max_px, palette.muted);
|
||||||
|
} else {
|
||||||
|
const char* section = "Start reading";
|
||||||
|
wrap_text(tf, SECTION_SIZE, section, (int)strlen(section), max_px, palette.heading);
|
||||||
|
add_spacer(gui_max(FONT_SIZE - 8, 8));
|
||||||
|
|
||||||
|
const char* suggestions =
|
||||||
|
"Try topics like Linux, Montauk, operating system, Norway, mathematics, music, or any article title that comes to mind.";
|
||||||
|
wrap_text(body_font, FONT_SIZE, suggestions, (int)strlen(suggestions), max_px, palette.text);
|
||||||
|
|
||||||
|
if (g_welcome_status[0]) {
|
||||||
|
add_spacer(gui_max(FONT_SIZE - 6, 8));
|
||||||
|
wrap_text(body_font, gui_max(FONT_SIZE - 2, 12),
|
||||||
|
g_welcome_status,
|
||||||
|
(int)strlen(g_welcome_status),
|
||||||
|
max_px, palette.muted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preserve_scroll)
|
||||||
|
g_scroll_y = gui_clamp(old_scroll, 0, max_scroll_lines());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rebuild_current_document(bool preserve_scroll = true) {
|
||||||
if (g_phase == AppPhase::DONE && g_extract_len > 0)
|
if (g_phase == AppPhase::DONE && g_extract_len > 0)
|
||||||
build_display_lines(g_title, g_extract_buf, g_extract_len, preserve_scroll);
|
build_display_lines(g_title, g_extract_buf, g_extract_len, preserve_scroll);
|
||||||
|
else if (g_phase == AppPhase::WELCOME)
|
||||||
|
build_welcome_lines(preserve_scroll);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void apply_reader_setting_change() {
|
static void apply_reader_setting_change() {
|
||||||
g_scroll_dragging = false;
|
g_scroll_dragging = false;
|
||||||
apply_reader_preferences();
|
apply_reader_preferences();
|
||||||
update_toolbar_height();
|
update_toolbar_height();
|
||||||
rebuild_article(true);
|
rebuild_current_document(true);
|
||||||
clamp_scroll();
|
clamp_scroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -878,22 +1026,124 @@ static bool handle_reader_option_click(int mx, int my) {
|
|||||||
// Network search (blocking)
|
// Network search (blocking)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
static void do_search(const char* query) {
|
static bool ensure_wiki_tls_ready(char* err, int err_len) {
|
||||||
// Lazy TLS/DNS init
|
|
||||||
if (!g_tls_ready) {
|
if (!g_tls_ready) {
|
||||||
g_server_ip = montauk::resolve(WIKI_HOST);
|
g_server_ip = montauk::resolve(WIKI_HOST);
|
||||||
if (g_server_ip == 0) {
|
if (g_server_ip == 0) {
|
||||||
snprintf(g_status, sizeof(g_status),
|
if (err && err_len > 0)
|
||||||
"Error: could not resolve en.wikipedia.org");
|
snprintf(err, err_len, "Error: could not resolve en.wikipedia.org");
|
||||||
g_phase = AppPhase::ERR; return;
|
return false;
|
||||||
}
|
}
|
||||||
g_tas = tls::load_trust_anchors();
|
g_tas = tls::load_trust_anchors();
|
||||||
if (g_tas.count == 0) {
|
if (g_tas.count == 0) {
|
||||||
snprintf(g_status, sizeof(g_status), "Error: no CA certificates loaded");
|
if (err && err_len > 0)
|
||||||
g_phase = AppPhase::ERR; return;
|
snprintf(err, err_len, "Error: no CA certificates loaded");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
g_tls_ready = true;
|
g_tls_ready = true;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool welcome_feed_path(char* path, int path_len) {
|
||||||
|
Montauk::DateTime dt {};
|
||||||
|
montauk::gettime(&dt);
|
||||||
|
|
||||||
|
if (dt.Year < 2001 || dt.Month < 1 || dt.Month > 12 ||
|
||||||
|
dt.Day < 1 || dt.Day > 31) {
|
||||||
|
snprintf(g_welcome_status, sizeof(g_welcome_status),
|
||||||
|
"Daily featured article unavailable until the system date is set.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(g_welcome_date, sizeof(g_welcome_date),
|
||||||
|
"%04u-%02u-%02u",
|
||||||
|
(unsigned)dt.Year, (unsigned)dt.Month, (unsigned)dt.Day);
|
||||||
|
snprintf(path, path_len,
|
||||||
|
"/api/rest_v1/feed/featured/%04u/%02u/%02u",
|
||||||
|
(unsigned)dt.Year, (unsigned)dt.Month, (unsigned)dt.Day);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void do_welcome_fetch() {
|
||||||
|
char err[192] = {};
|
||||||
|
if (!ensure_wiki_tls_ready(err, sizeof(err))) {
|
||||||
|
snprintf(g_welcome_status, sizeof(g_welcome_status), "%s", err);
|
||||||
|
build_welcome_lines(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char path[128];
|
||||||
|
if (!welcome_feed_path(path, sizeof(path))) {
|
||||||
|
build_welcome_lines(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int respLen = wiki_fetch(path, g_resp_buf, RESP_MAX);
|
||||||
|
if (respLen <= 0) {
|
||||||
|
snprintf(g_welcome_status, sizeof(g_welcome_status),
|
||||||
|
"Daily featured article unavailable; search is ready.");
|
||||||
|
build_welcome_lines(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
g_resp_buf[respLen] = '\0';
|
||||||
|
|
||||||
|
int headerEnd = find_header_end(g_resp_buf, respLen);
|
||||||
|
if (headerEnd < 0) {
|
||||||
|
snprintf(g_welcome_status, sizeof(g_welcome_status),
|
||||||
|
"Daily featured article unavailable; search is ready.");
|
||||||
|
build_welcome_lines(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int status = parse_status_code(g_resp_buf, headerEnd);
|
||||||
|
const char* body = g_resp_buf + headerEnd;
|
||||||
|
int bodyLen = respLen - headerEnd;
|
||||||
|
if (status < 200 || status >= 300) {
|
||||||
|
snprintf(g_welcome_status, sizeof(g_welcome_status),
|
||||||
|
"Daily featured article unavailable; search is ready.");
|
||||||
|
build_welcome_lines(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int tfa = find_substr(body, bodyLen, "\"tfa\"");
|
||||||
|
if (tfa < 0) {
|
||||||
|
snprintf(g_welcome_status, sizeof(g_welcome_status),
|
||||||
|
"Daily featured article unavailable; search is ready.");
|
||||||
|
build_welcome_lines(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int tfaLen = bodyLen - tfa;
|
||||||
|
extract_json_string(body + tfa, tfaLen, "title",
|
||||||
|
g_welcome_feature_title,
|
||||||
|
sizeof(g_welcome_feature_title));
|
||||||
|
extract_json_string(body + tfa, tfaLen, "extract",
|
||||||
|
g_welcome_feature_extract,
|
||||||
|
sizeof(g_welcome_feature_extract));
|
||||||
|
|
||||||
|
underscores_to_spaces(g_welcome_feature_title);
|
||||||
|
g_welcome_feature_loaded = g_welcome_feature_title[0] != '\0';
|
||||||
|
if (g_welcome_feature_loaded)
|
||||||
|
g_welcome_status[0] = '\0';
|
||||||
|
else
|
||||||
|
snprintf(g_welcome_status, sizeof(g_welcome_status),
|
||||||
|
"Daily featured article unavailable; search is ready.");
|
||||||
|
|
||||||
|
build_welcome_lines(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void do_search(const char* query) {
|
||||||
|
if (is_main_page_query(query)) {
|
||||||
|
g_phase = AppPhase::WELCOME;
|
||||||
|
build_welcome_lines(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ensure_wiki_tls_ready(g_status, sizeof(g_status))) {
|
||||||
|
g_phase = AppPhase::ERR;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
static char encoded[1024];
|
static char encoded[1024];
|
||||||
url_encode_title(query, encoded, sizeof(encoded));
|
url_encode_title(query, encoded, sizeof(encoded));
|
||||||
@@ -952,7 +1202,7 @@ static void render(Canvas& canvas) {
|
|||||||
ReaderToolbarLayout lo = toolbar_layout();
|
ReaderToolbarLayout lo = toolbar_layout();
|
||||||
Rect toolbar = {0, 0, g_win_w, TOOLBAR_H};
|
Rect toolbar = {0, 0, g_win_w, TOOLBAR_H};
|
||||||
Rect content = content_rect();
|
Rect content = content_rect();
|
||||||
Rect column = article_column_rect();
|
Rect column = document_column_rect();
|
||||||
bool searching = (g_phase == AppPhase::LOADING);
|
bool searching = (g_phase == AppPhase::LOADING);
|
||||||
|
|
||||||
// Background
|
// Background
|
||||||
@@ -974,6 +1224,14 @@ static void render(Canvas& canvas) {
|
|||||||
theme);
|
theme);
|
||||||
|
|
||||||
if (g_display_controls_open) {
|
if (g_display_controls_open) {
|
||||||
|
int row_top = reader_row_y() - 4;
|
||||||
|
canvas.hline(8, row_top, gui_max(g_win_w - 16, 0), theme.border);
|
||||||
|
|
||||||
|
if (lo.show_labels) {
|
||||||
|
canvas.text(lo.text_size_label.x, lo.text_size_label.y, "Text", theme.text_muted);
|
||||||
|
canvas.text(lo.font_face_label.x, lo.font_face_label.y, "Font", theme.text_muted);
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < READER_TEXT_SIZE_COUNT; i++) {
|
for (int i = 0; i < READER_TEXT_SIZE_COUNT; i++) {
|
||||||
mtk::draw_button(canvas, lo.text_size_buttons[i], g_text_size_labels[i], mtk::BUTTON_SECONDARY,
|
mtk::draw_button(canvas, lo.text_size_buttons[i], g_text_size_labels[i], mtk::BUTTON_SECONDARY,
|
||||||
mtk::widget_state((int)g_reader_text_size == i,
|
mtk::widget_state((int)g_reader_text_size == i,
|
||||||
@@ -1002,8 +1260,8 @@ static void render(Canvas& canvas) {
|
|||||||
render_message(canvas, content, "Searching Wikipedia...", theme.text_muted);
|
render_message(canvas, content, "Searching Wikipedia...", theme.text_muted);
|
||||||
} else if (g_phase == AppPhase::ERR) {
|
} else if (g_phase == AppPhase::ERR) {
|
||||||
render_message(canvas, content, g_status, theme.danger);
|
render_message(canvas, content, g_status, theme.danger);
|
||||||
} else if (g_phase == AppPhase::DONE && g_line_count > 0) {
|
} else if (document_scrollable() && g_line_count > 0) {
|
||||||
int y = content.y + 8;
|
int y = content.y + document_top_pad();
|
||||||
|
|
||||||
for (int i = g_scroll_y; i < g_line_count && y < g_win_h; i++) {
|
for (int i = g_scroll_y; i < g_line_count && y < g_win_h; i++) {
|
||||||
WikiLine& l = g_lines[i];
|
WikiLine& l = g_lines[i];
|
||||||
@@ -1021,15 +1279,10 @@ static void render(Canvas& canvas) {
|
|||||||
Rect track = scrollbar_track_rect();
|
Rect track = scrollbar_track_rect();
|
||||||
if (!track.empty()) {
|
if (!track.empty()) {
|
||||||
Rect thumb = scrollbar_thumb_rect();
|
Rect thumb = scrollbar_thumb_rect();
|
||||||
Color thumb_color = (g_scroll_dragging || mouse_in_rect(thumb))
|
mtk::draw_scrollbar(canvas, track, thumb,
|
||||||
? theme.accent_hover
|
mouse_in_rect(track),
|
||||||
: theme.selection;
|
g_scroll_dragging,
|
||||||
|
theme);
|
||||||
canvas.fill_rounded_rect(track.x, track.y, track.w, track.h,
|
|
||||||
theme.radius_sm, theme.surface_alt);
|
|
||||||
canvas.rect(track.x, track.y, track.w, track.h, theme.border);
|
|
||||||
canvas.fill_rounded_rect(thumb.x, thumb.y, thumb.w, thumb.h,
|
|
||||||
theme.radius_sm, thumb_color);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1080,7 +1333,9 @@ extern "C" void _start() {
|
|||||||
if (!g_heading_fonts[i]) g_heading_fonts[i] = g_body_fonts[i];
|
if (!g_heading_fonts[i]) g_heading_fonts[i] = g_body_fonts[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g_accent = mtk::load_system_accent();
|
||||||
apply_scale(montauk::win_getscale());
|
apply_scale(montauk::win_getscale());
|
||||||
|
build_welcome_lines(false);
|
||||||
|
|
||||||
WsWindow win;
|
WsWindow win;
|
||||||
if (!win.create("Wikipedia", INIT_W, INIT_H))
|
if (!win.create("Wikipedia", INIT_W, INIT_H))
|
||||||
@@ -1092,8 +1347,18 @@ extern "C" void _start() {
|
|||||||
host.present();
|
host.present();
|
||||||
|
|
||||||
bool search_pending = false;
|
bool search_pending = false;
|
||||||
|
bool welcome_fetch_pending = true;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
if (welcome_fetch_pending) {
|
||||||
|
welcome_fetch_pending = false;
|
||||||
|
do_welcome_fetch();
|
||||||
|
canvas = host.canvas();
|
||||||
|
render(canvas);
|
||||||
|
host.present();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
Montauk::WinEvent ev;
|
Montauk::WinEvent ev;
|
||||||
int r = win.poll(&ev);
|
int r = win.poll(&ev);
|
||||||
|
|
||||||
@@ -1114,7 +1379,7 @@ extern "C" void _start() {
|
|||||||
if (ev.type == 4) {
|
if (ev.type == 4) {
|
||||||
apply_scale(win.scale_factor);
|
apply_scale(win.scale_factor);
|
||||||
g_scroll_dragging = false;
|
g_scroll_dragging = false;
|
||||||
rebuild_article(true);
|
rebuild_current_document(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ev.type == 2) {
|
if (ev.type == 2) {
|
||||||
@@ -1122,7 +1387,7 @@ extern "C" void _start() {
|
|||||||
g_win_w = win.width;
|
g_win_w = win.width;
|
||||||
g_win_h = win.height;
|
g_win_h = win.height;
|
||||||
g_scroll_dragging = false;
|
g_scroll_dragging = false;
|
||||||
rebuild_article(true);
|
rebuild_current_document(true);
|
||||||
clamp_scroll();
|
clamp_scroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1137,7 +1402,7 @@ extern "C" void _start() {
|
|||||||
mtk::text_input_key(g_query_input, g_query,
|
mtk::text_input_key(g_query_input, g_query,
|
||||||
(int)sizeof(g_query), ev.key);
|
(int)sizeof(g_query), ev.key);
|
||||||
}
|
}
|
||||||
} else if (g_phase == AppPhase::DONE) {
|
} else if (document_scrollable()) {
|
||||||
// Navigation keys
|
// Navigation keys
|
||||||
int visible = visible_line_count();
|
int visible = visible_line_count();
|
||||||
int max_sc = max_scroll_lines();
|
int max_sc = max_scroll_lines();
|
||||||
@@ -1204,7 +1469,7 @@ extern "C" void _start() {
|
|||||||
g_search_focused = false;
|
g_search_focused = false;
|
||||||
g_display_controls_open = !g_display_controls_open;
|
g_display_controls_open = !g_display_controls_open;
|
||||||
update_toolbar_height();
|
update_toolbar_height();
|
||||||
rebuild_article(true);
|
rebuild_current_document(true);
|
||||||
clamp_scroll();
|
clamp_scroll();
|
||||||
} else if (handle_reader_option_click(mx, my)) {
|
} else if (handle_reader_option_click(mx, my)) {
|
||||||
g_search_focused = false;
|
g_search_focused = false;
|
||||||
@@ -1221,7 +1486,7 @@ extern "C" void _start() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Scroll wheel
|
// Scroll wheel
|
||||||
if (ev.mouse.scroll != 0 && g_phase == AppPhase::DONE) {
|
if (ev.mouse.scroll != 0 && document_scrollable()) {
|
||||||
g_scroll_y += ev.mouse.scroll * 3;
|
g_scroll_y += ev.mouse.scroll * 3;
|
||||||
clamp_scroll();
|
clamp_scroll();
|
||||||
}
|
}
|
||||||
@@ -1230,11 +1495,16 @@ extern "C" void _start() {
|
|||||||
// Trigger search if requested and query is non-empty
|
// Trigger search if requested and query is non-empty
|
||||||
if (search_pending && g_query[0] != '\0') {
|
if (search_pending && g_query[0] != '\0') {
|
||||||
search_pending = false;
|
search_pending = false;
|
||||||
|
if (is_main_page_query(g_query)) {
|
||||||
|
g_phase = AppPhase::WELCOME;
|
||||||
|
build_welcome_lines(false);
|
||||||
|
} else {
|
||||||
g_phase = AppPhase::LOADING;
|
g_phase = AppPhase::LOADING;
|
||||||
canvas = host.canvas();
|
canvas = host.canvas();
|
||||||
render(canvas);
|
render(canvas);
|
||||||
host.present();
|
host.present();
|
||||||
do_search(g_query); // blocking
|
do_search(g_query); // blocking
|
||||||
|
}
|
||||||
clamp_scroll();
|
clamp_scroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ ICONS=(
|
|||||||
"actions/symbolic/document-export-symbolic.svg"
|
"actions/symbolic/document-export-symbolic.svg"
|
||||||
"actions/symbolic/edit-undo-symbolic.svg"
|
"actions/symbolic/edit-undo-symbolic.svg"
|
||||||
"actions/symbolic/edit-redo-symbolic.svg"
|
"actions/symbolic/edit-redo-symbolic.svg"
|
||||||
"actions/16/dialog-text-and-font.svg"
|
"actions/16/text_line_spacing.svg"
|
||||||
"actions/symbolic/format-justify-left-symbolic.svg"
|
"actions/symbolic/format-justify-left-symbolic.svg"
|
||||||
"actions/symbolic/format-justify-center-symbolic.svg"
|
"actions/symbolic/format-justify-center-symbolic.svg"
|
||||||
"actions/symbolic/format-justify-right-symbolic.svg"
|
"actions/symbolic/format-justify-right-symbolic.svg"
|
||||||
|
|||||||
Reference in New Issue
Block a user