Files
Sprinter-SDCC/examples/mdview2/mdview2_md.c
T
snark13 05916a3cc6 mdview2: вынести md_key() в mdview2_md.c (симметрия с raw_key)
MD-навигация после загрузки (стрелки/PgUp/PgDn/Home/End/←/→) вынесена из
switch в main() в md_key(scan) — peer к raw_key(): возвращает 1/0, сама
перерисовывает область+статус. main() теперь симметричен для обоих видов:
F-клавиши (F1/F8/F10, для RAW ещё F2/F3) разбираются в цикле, навигация
делегируется md_key()/raw_key().

HPAN_STEP вынесен в mdview2.h (был продублирован в ядре и raw). load_key()
(навигация во время прогрессивной загрузки, bounded по drawable_lines)
остаётся в ядре — у неё нет RAW-аналога.

Поведение сохранено (F1 теперь без лишнего render_updated_status — show_help
и так перерисовывает всё). Размер: exe 28115→28173 (+58 Б — стоимость
границы функции, как у raw_key). Сборка чистая.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 11:21:51 +03:00

274 lines
13 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* mdview2_md.c — представление MD-документа (peer к mdview2_raw.c): отрисовка
* области из рендер-кэша, статус-бар, нижнее меню и прокрутка (верт./гориз.).
*
* Работает поверх готового рендер-кэша (cache_dir_get → cache_rec_t) и
* состояния навигации (top_line/viewport_x/n_lines, см. mdview2.h). Никакого
* парсинга/обращения к исходному файлу: всё рисуется из (char,attr)-ячеек кэша
* через win_rest_remap (ремап 8-битных кодировок — в mdview2_enc.c).
*
* Пара видов: mdview2_md.c (MD, из кэша) ↔ mdview2_raw.c (RAW, из fb()).
*/
#include <stdint.h>
#include <stdio.h> /* dec8/dec16 */
#include <conio.h> /* wrchar/gotoxy/cputs/textattr/scroll/COLOR */
#include <bios/text.h> /* bios_set_place/bios_fillcharattr */
#include "mdview2.h"
/* Фаза 4-5 — cache-only draw path: рисует одну строку ИЗ КЭША, без
* единого обращения к fb()/исходному файлу. Горизонтальный скролл для
* nowrap-строк (Фаза 5) — это просто смещение начала среза на vx*2
* байт ВНУТРИ ТОГО ЖЕ кэш-буфера (cache_reserve гарантирует, что вся
* строка лежит в одной странице, так что off+vx*2 не пересекает
* границу страницы); не nowrap-строки игнорируют viewport_x, как и
* раньше в живом render_line() (там это называлось effective_vx). */
static void draw_line_from_cache(uint16_t line_idx, uint8_t row)
{
cache_rec_t rec;
cache_dir_get(line_idx, &rec);
bios_set_place(row, 0);
bios_fillcharattr(' ', ATTR_TEXT, SCREEN_W);
/* Горизонтальный сдвиг применяется ко ВСЕМ строкам hscroll-блока (код,
* таблицы) — по ТИПУ, а не по длине: блок двигается целиком, включая
* строки короче 80 (короткая строка, ушедшая за левый край, просто
* показывает маркер '<' на пустом месте). HR и границы fence не имеют
* IF_HSCROLL и не сдвигаются. */
uint8_t vx = (rec.flags & IF_HSCROLL) ? viewport_x : 0;
if (rec.len > vx) {
uint16_t remaining = (uint16_t)(rec.len - vx);
uint8_t w = (remaining > SCREEN_W) ? SCREEN_W : (uint8_t)remaining;
uint16_t off = (uint16_t)(rec.off + (uint16_t)vx * 2u);
win_rest_remap(row, w, rec.page, off);
if (remaining > SCREEN_W) {
wrchar(SCREEN_W - 1, row, '>', ATTR_TRUNC);
}
}
if (vx > 0) {
wrchar(0, row, '<', ATTR_TRUNC);
}
}
/* Перерисовывает все VIEW_H строк окна из кэша — основной путь
* перерисовки видимой области после загрузки файла (Фаза 3-4). */
void draw_viewport_from_cache(void)
{
for (uint8_t i = 0; i < VIEW_H; i++) {
draw_line_from_cache((uint16_t)(top_line + i), (uint8_t)(VIEW_TOP_ROW + i));
}
}
/* Число строк, готовых к показу. Во время загрузки последняя эмитированная
* строка ещё в g_cells (не зафлашена), поэтому доступны [0..n_lines-2]. */
uint16_t drawable_lines(void)
{
if (g_loading) return (n_lines >= 1) ? (uint16_t)(n_lines - 1) : 0;
return n_lines;
}
/* Возвращает процент прокрутки (0..100) относительно доступного диапазона. */
static uint8_t calc_pct(void)
{
uint16_t total = drawable_lines();
uint8_t pct;
if (total <= VIEW_H)
pct = 100;
else
pct = (uint8_t)((uint32_t)top_line * 100UL / (uint32_t)(total - VIEW_H));
return pct;
}
/* Рисует динамическую часть статус-бара без printf.
* Полностью перезаписывает колонки 45..79, чтобы не оставлять «хвосты». */
void render_status_numbers(void)
{
uint16_t total = drawable_lines();
uint16_t last = top_line + VIEW_H;
if (last > total) last = total;
uint8_t pct = calc_pct();
textattr(ATTR_BAR);
bios_set_place(0, 45);
bios_fillcharattr(' ', ATTR_BAR, (uint8_t)(SCREEN_W - 45));
wrchar(45, 0, 0xB3, ATTR_BAR); /* вертикальный разделитель */
gotoxy(47, 0);
cputs("L ");
dec16(top_line + 1);
cputs("-");
dec16(last);
cputs(" / ");
dec16(total);
if (g_loading) cputs("..."); /* ещё грузится */
wrchar(70, 0, 0xB3, ATTR_BAR); /* вертикальный разделитель */
gotoxy( 73, 0);
dec8(pct);
cputs("%");
}
/* Полностью перерисовывает статус-бар (заголовок + числа). */
void render_full_status(void)
{
fill_row(0, ATTR_BAR);
/* Размещение в шапке: "MDVIEW", затем слот спиннера и имя файла. */
put_str_attr(1, 0, "MDVIEW", ATTR_BAR);
put_str_attr(10, 0, filename, ATTR_BAR);
put_str_attr(37, 0, enc_name(g_encoding), ATTR_BAR); /* текущая кодировка */
render_status_numbers();
}
/* Обновляет только изменяемую числовую часть статус-бара. */
void render_updated_status(void)
{
render_status_numbers();
}
/* Отрисовывает нижнюю строку меню. */
void render_menu(void)
{
/* Блок i = колонка i*8 (8 шириной): 2 поз. НОМЕР клавиши без 'F' (стиль
* ATTR_MENU_K, ведущий «чёрный» пробел кроме F10) + 6 поз. ТЕКСТ-функция
* (ATTR_MENU_T) сразу за номером. Номера рисуем для ВСЕХ 10 клавиш; текст —
* только у задействованных и доступных сейчас. */
fill_row(MENU_ROW, ATTR_MENU_T);
char num[3]; num[2] = 0;
for (uint8_t i = 0; i < 9; i++) { /* F1..F9: ' 1'..' 9' */
num[0] = ' '; num[1] = (char)('1' + i);
put_str_attr((uint8_t)(i * 8), MENU_ROW, num, ATTR_MENU_K);
}
put_str_attr(9 * 8, MENU_ROW, "10", ATTR_MENU_K); /* F10 */
put_str_attr(0 * 8 + 2, MENU_ROW, "Help", ATTR_MENU_T);
#if WITH_RAW
if (g_ready) /* RAW доступен после постройки документа */
put_str_attr(1 * 8 + 2, MENU_ROW, (g_view == VIEW_MD) ? "RAW" : "MD", ATTR_MENU_T);
if (g_view != VIEW_MD) /* Wrap/Unwrap — только в RAW */
put_str_attr(2 * 8 + 2, MENU_ROW,
(g_view == VIEW_RAW_WRAP) ? "UnWrap" : "Wrap", ATTR_MENU_T);
#endif
if (g_f8_enabled) /* смена кодировки сейчас возможна */
put_str_attr(7 * 8 + 2, MENU_ROW, "CodePg", ATTR_MENU_T);
put_str_attr(9 * 8 + 2, MENU_ROW, "Exit", ATTR_MENU_T);
}
/* ==================================================================
* Прокрутка
* ================================================================== */
/* Ограничивает top_line допустимым диапазоном с учётом высоты окна. */
void clamp_top(void)
{
if (n_lines <= VIEW_H) {
top_line = 0;
} else if (top_line > n_lines - VIEW_H) {
top_line = (uint16_t)(n_lines - VIEW_H);
}
}
/* Прокрутка вверх на n строк с частичной перерисовкой при n == 1. */
void scroll_up(uint16_t n)
{
uint16_t new_top_line = (top_line >= n) ? (uint16_t)(top_line - n) : 0;
if( new_top_line != top_line) {
top_line = new_top_line;
if (n == 1) {
scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 2, 0);
draw_line_from_cache((uint16_t)(top_line), (uint8_t)(VIEW_TOP_ROW));
} else {
draw_viewport_from_cache();
}
}
}
/* Прокрутка вниз на n строк с частичной перерисовкой при n == 1. */
void scroll_down(uint16_t n)
{
uint16_t new_top_line = (top_line + n < n_lines - VIEW_H) ?
(uint16_t)(top_line + n) : n_lines - VIEW_H;
if( new_top_line != top_line) {
top_line = new_top_line;
if (n == 1) {
scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 1, 0);
draw_line_from_cache((uint16_t)(top_line + VIEW_H - 1), (uint8_t)(VIEW_TOP_ROW + VIEW_H - 1));
} else {
clamp_top();
draw_viewport_from_cache();
}
}
}
/* Горизонтальный сдвиг (только если в окне есть nowrap-строки).
* Максимум сдвига ограничен самой широкой nowrap-строкой на экране.
* Фаза 5: ширина берётся прямо из директории кэша (rec.len — уже
* посчитанная реальная длина в ячейках после рендера), без единого
* обращения к fb()/seg_off()/исходному файлу. */
void scroll_h(int8_t delta)
{
uint16_t maxw = 0;
for (uint8_t i = 0; i < VIEW_H; i++) {
uint16_t li = (uint16_t)(top_line + i);
if (li >= n_lines) break;
cache_rec_t rec;
cache_dir_get(li, &rec);
if (!(rec.flags & IF_HSCROLL)) continue; /* только код/таблицы */
if (rec.len > maxw) maxw = rec.len; /* самая широкая строка блока */
}
if (maxw == 0) return; /* нет строк шире экрана */
/* Максимальный сдвиг = ширина за пределами экрана, в границах uint8. */
uint16_t over = (maxw > SCREEN_W) ? (uint16_t)(maxw - SCREEN_W) : 0;
if (over > 248u) over = 248u;
uint8_t max_vx = (uint8_t)over;
int16_t nx = (int16_t)viewport_x + delta;
if (nx < 0) nx = 0;
if (nx > (int16_t)max_vx) nx = max_vx;
/* ПОРЯДОК ВАЖЕН: записываем viewport_x ДО сравнения, а сравниваем уже
* сохранённую копию old_vx с new_vx. Иначе SDCC генерирует для
* `if (new_vx != viewport_x) viewport_x = new_vx;` такой код:
* ld a, new_vx ; sub a,(viewport_x) ; jr Z,.. ; ld (viewport_x),a
* — т.е. для записи переиспользует регистр A, испорченный вычитанием в
* сравнении, и кладёт в viewport_x не new_vx, а (new_vx - old_vx).
* (asm-дамп 2026-06-24 подтвердил; даёт 10-8=2, 10-2=8 — ровно
* наблюдавшийся "прыжок" скролла.) Записав viewport_x первой, мы убираем
* портящий sub с пути записи; сравнение ниже только решает, перерисовывать
* ли экран. */
uint8_t new_vx = (uint8_t)nx;
uint8_t old_vx = viewport_x;
viewport_x = new_vx;
if (new_vx != old_vx)
draw_viewport_from_cache();
}
/* ---- навигация MD после загрузки (peer к raw_key) ---------------- */
/* Обрабатывает навигационную клавишу в MD-режиме (вызывается из главного
* цикла). Сама перерисовывает область/статус. Возврат: 1 — обработано,
* 0 — не наша клавиша (F1/F8/F10 разбирает main). */
uint8_t md_key(uint8_t scan)
{
switch (scan) {
case KEY_UP: scroll_up(1); break;
case KEY_DOWN: scroll_down(1); break;
case KEY_LEFT: scroll_h(-(int8_t)HPAN_STEP); break;
case KEY_RIGHT: scroll_h(+(int8_t)HPAN_STEP); break;
case KEY_PGUP: scroll_up(VIEW_H); break;
case KEY_PGDN: scroll_down(VIEW_H); break;
case KEY_HOME: top_line = 0; viewport_x = 0;
draw_viewport_from_cache(); break;
case KEY_END: top_line = (n_lines > VIEW_H) ? (uint16_t)(n_lines - VIEW_H) : 0;
viewport_x = 0;
draw_viewport_from_cache(); break;
default: return 0; /* не наша клавиша */
}
render_updated_status();
return 1;
}