diff --git a/src/ui/editor.h b/src/ui/editor.h index 2b85fdf..15510f2 100644 --- a/src/ui/editor.h +++ b/src/ui/editor.h @@ -56,6 +56,9 @@ class Editor { bool ime_font_acquired; uint32_t ime_cursor_anchor_pos; bool ime_cursor_anchor_valid; + bool scroll_to_start_pending; + bool scroll_to_end_pending; + bool programmatic_scroll_in_progress; String current_file; std::function on_exit_cb; std::function on_save_cb; @@ -68,6 +71,7 @@ class Editor { save_popup(nullptr), save_popup_timer(nullptr), ime_cand_syncing(false), ime_is_k9_mode(false), ime_cand_count(0), ime_cand_page(0), ime_visible(false), large_doc_mode(false), read_chunk_mode(false), read_chunk_has_prev(false), read_chunk_has_next(false), ime_font_acquired(false), ime_cursor_anchor_pos(0), ime_cursor_anchor_valid(false), + scroll_to_start_pending(false), scroll_to_end_pending(false), programmatic_scroll_in_progress(false), current_file(""), on_exit_cb(nullptr), on_save_cb(nullptr), on_read_prev_chunk_cb(nullptr), on_read_next_chunk_cb(nullptr) { memset(ime_compose, 0, sizeof(ime_compose)); memset(ime_cands, 0, sizeof(ime_cands)); @@ -206,6 +210,7 @@ class Editor { lv_obj_set_style_height(textarea, editor_cursor_h, LV_PART_CURSOR); lv_obj_add_event_cb(textarea, textarea_cursor_anchor_event_cb, LV_EVENT_CLICKED, this); lv_obj_add_event_cb(textarea, textarea_cursor_anchor_event_cb, LV_EVENT_VALUE_CHANGED, this); + lv_obj_add_event_cb(textarea, textarea_scroll_event_cb, LV_EVENT_SCROLL_BEGIN, this); lv_obj_add_event_cb(textarea, textarea_scroll_event_cb, LV_EVENT_SCROLL, this); lv_obj_add_event_cb(textarea, textarea_scroll_event_cb, LV_EVENT_SCROLL_END, this); ime_cursor_anchor_pos = lv_textarea_get_cursor_pos(textarea); @@ -611,6 +616,10 @@ class Editor { static void textarea_scroll_event_cb(lv_event_t* e) { Editor* ed = (Editor*)lv_event_get_user_data(e); if (!ed) return; + if (lv_event_get_code(e) == LV_EVENT_SCROLL_BEGIN && !ed->programmatic_scroll_in_progress) { + ed->scroll_to_start_pending = false; + ed->scroll_to_end_pending = false; + } ed->refreshReadChunkButtons(); } @@ -650,24 +659,34 @@ class Editor { static void async_scroll_top_cb(void* user_data) { Editor* ed = (Editor*)user_data; - if (!ed || !ed->textarea) return; + if (!ed || !ed->textarea || !ed->scroll_to_start_pending) return; + ed->scroll_to_start_pending = false; + ed->programmatic_scroll_in_progress = true; lv_obj_scroll_to_y(ed->textarea, 0, LV_ANIM_OFF); + ed->programmatic_scroll_in_progress = false; ed->refreshReadChunkButtons(); } static void async_scroll_bottom_cb(void* user_data) { Editor* ed = (Editor*)user_data; - if (!ed || !ed->textarea) return; + if (!ed || !ed->textarea || !ed->scroll_to_end_pending) return; + ed->scroll_to_end_pending = false; + ed->programmatic_scroll_in_progress = true; lv_obj_update_layout(ed->textarea); int32_t bottom_offset = lv_obj_get_scroll_bottom(ed->textarea); lv_obj_scroll_by(ed->textarea, 0, bottom_offset, LV_ANIM_OFF); + ed->programmatic_scroll_in_progress = false; ed->refreshReadChunkButtons(); } void moveCursorAndViewToStart() { if (!textarea) return; lv_textarea_set_cursor_pos(textarea, 0); + scroll_to_start_pending = true; + scroll_to_end_pending = false; + programmatic_scroll_in_progress = true; lv_obj_scroll_to_y(textarea, 0, LV_ANIM_OFF); + programmatic_scroll_in_progress = false; ime_cursor_anchor_pos = 0; ime_cursor_anchor_valid = true; lv_async_call(async_scroll_top_cb, this); @@ -678,6 +697,8 @@ class Editor { const char* text = lv_textarea_get_text(textarea); uint32_t len = text ? (uint32_t)strlen(text) : 0; lv_textarea_set_cursor_pos(textarea, (int32_t)len); + scroll_to_end_pending = true; + scroll_to_start_pending = false; ime_cursor_anchor_pos = len; ime_cursor_anchor_valid = true; lv_async_call(async_scroll_bottom_cb, this); diff --git a/webflash/index.html b/webflash/index.html index 240813f..1141c34 100644 --- a/webflash/index.html +++ b/webflash/index.html @@ -650,6 +650,16 @@

Log

return `${Math.round(size / 1024)} KB`; } + function getFlashFileSize(file) { + const sizeBytes = file?.sizeBytes ?? file?.data?.length; + const size = Number(sizeBytes || 0); + return Number.isFinite(size) && size > 0 ? size : 0; + } + + function getTotalFlashSize(files) { + return files.reduce((total, file) => total + getFlashFileSize(file), 0); + } + function formatTimestamp(value) { const date = value ? new Date(value) : null; if (!date || Number.isNaN(date.getTime())) { @@ -982,12 +992,27 @@

Log

} function updateFlashProgress(files, fileIndex, writtenBytes, totalBytes) { - const percentPerFile = 100 / files.length; - const percent = Math.min(100, Math.round((fileIndex * percentPerFile) + ((writtenBytes / totalBytes) * percentPerFile))); const fileName = files[fileIndex]?.name || `part ${fileIndex + 1}`; + const totalFlashBytes = getTotalFlashSize(files); + const completedFlashBytes = files.slice(0, fileIndex).reduce((total, file) => total + getFlashFileSize(file), 0); + const currentFileSize = getFlashFileSize(files[fileIndex]); + let currentWrittenBytes = 0; + if (currentFileSize > 0 && totalBytes > 0) { + const transferProgressRatio = writtenBytes / totalBytes; + const currentRatio = Math.min(1, Math.max(0, transferProgressRatio)); + currentWrittenBytes = Math.round(currentFileSize * currentRatio); + } else if (currentFileSize > 0) { + currentWrittenBytes = Math.min(currentFileSize, Math.max(0, writtenBytes)); + } + const overallWrittenBytes = completedFlashBytes + currentWrittenBytes; + // Prefer the known total firmware bytes, then the current file size, then the loader-reported total. + const displayTotalBytes = totalFlashBytes || currentFileSize || Math.max(0, totalBytes); + const percent = totalFlashBytes > 0 + ? Math.min(100, Math.round((overallWrittenBytes / totalFlashBytes) * 100)) + : 0; flashProgress.hidden = false; flashProgress.value = percent; - flashStatus.textContent = `Writing ${fileName}... ${percent}% (${Math.round(writtenBytes / 1024)} KB / ${Math.round(totalBytes / 1024)} KB transferred)`; + flashStatus.textContent = `Writing ${fileName}... ${percent}% (${formatSize(overallWrittenBytes)} / ${formatSize(displayTotalBytes)})`; } function updateRestoreProgress(target, writtenBytes, totalBytes) { @@ -1136,7 +1161,7 @@

Log

setFlashStatus(`Downloading ${partFileName} (${index + 1}/${parts.length})...`, false, "wait"); data = await downloadPartData(part.path, partFileName); } - files.push({ name: partFileName, data, address: part.offset }); + files.push({ name: partFileName, data, address: part.offset, sizeBytes: data.length }); if (!localFile) { logLine(`Downloaded ${partFileName} (${formatSize(data.length)}).`, "info", "flash"); }