From 254bf104b3de2d3092b2ed6861d5c38d5c8741b9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Mar 2026 10:40:58 +0000 Subject: [PATCH 1/5] Initial plan From 53b87e984926011f5dabdf68b004ef5363654ff5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Mar 2026 10:54:24 +0000 Subject: [PATCH 2/5] refactor: fix editor chunk scroll jump by suppressing anim during text swap - Extract TEXTAREA_SCROLL_ANIM_MS constant (was magic 260) - Refactor setReadChunkText to accept nav state (has_prev, has_next) and perform text swap + scroll positioning atomically - Temporarily set anim_duration=0 before lv_textarea_set_text() to prevent LVGL internal auto-scroll animation from conflicting with the explicit scroll target - Simplify loadReadChunkAtIndex: compute nav state locally and pass to editor in a single call, removing separate setReadChunkNavigationState Co-authored-by: shiftz300 <130174875+shiftz300@users.noreply.github.com> --- src/app.h | 6 ++++-- src/ui/editor.h | 47 +++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/app.h b/src/app.h index fbd2a35..515a07f 100644 --- a/src/app.h +++ b/src/app.h @@ -283,10 +283,12 @@ class AppManager { if (bytes_read == 0 && read_chunk_file_size > 0) return false; read_chunk_bytes = bytes_read; + bool has_prev = read_chunk_index > 0; + bool has_next = ((uint64_t)offset + (uint64_t)read_chunk_bytes) < read_chunk_file_size; + editor.setTitle(current_filename); editor.setReadChunkMode(true); - editor.setReadChunkNavigationState(read_chunk_index > 0, ((uint64_t)offset + (uint64_t)read_chunk_bytes) < read_chunk_file_size); - editor.setReadChunkText(content, anchor_end); + editor.setReadChunkText(content, has_prev, has_next, anchor_end); return true; } diff --git a/src/ui/editor.h b/src/ui/editor.h index 34b0faa..26448ca 100644 --- a/src/ui/editor.h +++ b/src/ui/editor.h @@ -20,6 +20,7 @@ class Editor { static constexpr uint8_t IME_CAND_PER_PAGE = 8; static constexpr uint16_t IME_CAND_MAX = 160; static constexpr size_t LARGE_DOC_PERF_THRESHOLD = 2 * 1024; + static constexpr int32_t TEXTAREA_SCROLL_ANIM_MS = 260; static constexpr int32_t CHUNK_NAV_BTN_SIZE = 24; static constexpr lv_opa_t CHUNK_NAV_BTN_BG_OPA = static_cast((LV_OPA_COVER * 75) / 100); lv_obj_t* screen; @@ -199,7 +200,7 @@ class Editor { lv_obj_set_style_width(textarea, 1, LV_PART_CURSOR); lv_obj_add_flag(textarea, LV_OBJ_FLAG_SCROLL_ELASTIC); lv_obj_add_flag(textarea, LV_OBJ_FLAG_SCROLL_MOMENTUM); - lv_obj_set_style_anim_duration(textarea, 260, 0); + lv_obj_set_style_anim_duration(textarea, TEXTAREA_SCROLL_ANIM_MS, 0); int32_t editor_cursor_h = EDITOR_TEXT_SIZE_PX; if (editor_cursor_h < 2) editor_cursor_h = 2; lv_obj_set_style_height(textarea, editor_cursor_h, LV_PART_CURSOR); @@ -365,12 +366,50 @@ class Editor { refreshReadChunkButtons(); } - void setReadChunkText(const String& content, bool anchor_end) { + void setReadChunkText(const String& content, bool has_prev, bool has_next, bool anchor_end) { if (!textarea) return; + + // Suppress scroll animations during content swap so that the + // internal auto-scroll triggered by lv_textarea_set_text() does + // not conflict with the explicit scroll below. This is the root + // cause of the visible "scroll-up jump" on every chunk switch. + lv_obj_set_style_anim_duration(textarea, 0, 0); + applyLargeDocPerfMode(content.length()); + + // Batch-update navigation state before the final button refresh. + read_chunk_has_prev = has_prev; + read_chunk_has_next = has_next; + + // Replace content. LVGL internally moves the cursor to the end + // of the new text and auto-scrolls, but with anim_duration == 0 + // the scroll is instantaneous and invisible. lv_textarea_set_text(textarea, content.c_str()); - if (anchor_end) moveCursorAndViewToEnd(); - else moveCursorAndViewToStart(); + + // Set cursor and record anchor for IME. + if (anchor_end) { + uint32_t len = (uint32_t)content.length(); + lv_textarea_set_cursor_pos(textarea, (int32_t)len); + ime_cursor_anchor_pos = len; + } else { + lv_textarea_set_cursor_pos(textarea, 0); + ime_cursor_anchor_pos = 0; + } + ime_cursor_anchor_valid = true; + + // Force layout so scroll metrics are accurate, then set the + // definitive scroll position in one shot. + lv_obj_update_layout(textarea); + if (anchor_end) { + int32_t bottom = lv_obj_get_scroll_bottom(textarea); + if (bottom > 0) lv_obj_scroll_by(textarea, 0, bottom, LV_ANIM_OFF); + } else { + lv_obj_scroll_to_y(textarea, 0, LV_ANIM_OFF); + } + + // Restore normal scroll animation for user interaction. + lv_obj_set_style_anim_duration(textarea, TEXTAREA_SCROLL_ANIM_MS, 0); + clearReadChunkFocus(); refreshReadChunkButtons(); } From aaa96289642465a84699ee1814dcc4cf98d4178a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Mar 2026 11:08:34 +0000 Subject: [PATCH 3/5] perf: optimize LVGL config for ESP32-2432S028R hardware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Disable unused SW color format renderers (RGB565A8, XRGB8888, L8, AL88, I1) saving ~28KB flash; keep RGB565/RGB888/ARGB8888/A8 - Enable LV_OBJ_STYLE_CACHE for faster style property lookups (+8B/widget) - Increase LV_DRAW_SW_CIRCLE_CACHE_SIZE 4→8 for rounded-corner perf - Enable LV_CACHE_DEF_SIZE=32KB for JPEG image decode caching - Enable LV_IMAGE_HEADER_CACHE_DEF_CNT=8 for image header caching - Disable LV_THEME_DEFAULT_GROW to reduce press animation redraws Co-authored-by: shiftz300 <130174875+shiftz300@users.noreply.github.com> --- src/lv_conf.h | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/lv_conf.h b/src/lv_conf.h index 53ba09a..6f7ecd4 100644 --- a/src/lv_conf.h +++ b/src/lv_conf.h @@ -162,14 +162,14 @@ */ #define LV_DRAW_SW_SUPPORT_RGB565 1 - #define LV_DRAW_SW_SUPPORT_RGB565A8 1 - #define LV_DRAW_SW_SUPPORT_RGB888 1 - #define LV_DRAW_SW_SUPPORT_XRGB8888 1 - #define LV_DRAW_SW_SUPPORT_ARGB8888 1 - #define LV_DRAW_SW_SUPPORT_L8 1 - #define LV_DRAW_SW_SUPPORT_AL88 1 - #define LV_DRAW_SW_SUPPORT_A8 1 - #define LV_DRAW_SW_SUPPORT_I1 1 + #define LV_DRAW_SW_SUPPORT_RGB565A8 0 /* not used on this display */ + #define LV_DRAW_SW_SUPPORT_RGB888 1 /* needed for gradients */ + #define LV_DRAW_SW_SUPPORT_XRGB8888 0 /* not used on this display */ + #define LV_DRAW_SW_SUPPORT_ARGB8888 1 /* needed for transparency compositing */ + #define LV_DRAW_SW_SUPPORT_L8 0 /* not used on this display */ + #define LV_DRAW_SW_SUPPORT_AL88 0 /* not used on this display */ + #define LV_DRAW_SW_SUPPORT_A8 1 /* needed for font/glyph alpha masks */ + #define LV_DRAW_SW_SUPPORT_I1 0 /* not used on this display */ /* Set the number of draw unit. * > 1 requires an operating system enabled in `LV_USE_OS` @@ -196,7 +196,7 @@ * The circumference of 1/4 circle are saved for anti-aliasing * radius * 4 bytes are used per circle (the most often used radiuses are saved) * 0: to disable caching */ - #define LV_DRAW_SW_CIRCLE_CACHE_SIZE 4 + #define LV_DRAW_SW_CIRCLE_CACHE_SIZE 8 #endif #define LV_USE_DRAW_SW_ASM LV_DRAW_SW_ASM_NONE @@ -381,11 +381,11 @@ *Used by image decoders such as `lv_lodepng` to keep the decoded image in the memory. *If size is not set to 0, the decoder will fail to decode when the cache is full. *If size is 0, the cache function is not enabled and the decoded mem will be released immediately after use.*/ -#define LV_CACHE_DEF_SIZE 0 +#define LV_CACHE_DEF_SIZE (32 * 1024) /*Default number of image header cache entries. The cache is used to store the headers of images *The main logic is like `LV_CACHE_DEF_SIZE` but for image headers.*/ -#define LV_IMAGE_HEADER_CACHE_DEF_CNT 0 +#define LV_IMAGE_HEADER_CACHE_DEF_CNT 8 /*Number of stops allowed per gradient. Increase this to allow more stops. *This adds (sizeof(lv_color_t) + 1) bytes per additional stop*/ @@ -396,7 +396,7 @@ #define LV_COLOR_MIX_ROUND_OFS 0 /* Add 2 x 32 bit variables to each lv_obj_t to speed up getting style properties */ -#define LV_OBJ_STYLE_CACHE 0 +#define LV_OBJ_STYLE_CACHE 1 /* Add `id` field to `lv_obj_t` */ #define LV_USE_OBJ_ID 0 @@ -703,8 +703,8 @@ /*0: Light mode; 1: Dark mode*/ #define LV_THEME_DEFAULT_DARK 0 - /*1: Enable grow on press*/ - #define LV_THEME_DEFAULT_GROW 1 + /*0: Disable grow on press to reduce redraw overhead on resource-constrained targets*/ + #define LV_THEME_DEFAULT_GROW 0 /*Default transition time in [ms]*/ #define LV_THEME_DEFAULT_TRANSITION_TIME 80 From f201cb0becd85bfe43759eb99a9b562189968d0b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Mar 2026 13:35:45 +0000 Subject: [PATCH 4/5] plan: disable DMA + add image format support Co-authored-by: shiftz300 <130174875+shiftz300@users.noreply.github.com> --- platformio.ini | 4 ++-- src/Setup_ESP32_2432S028R_ILI9341.h | 2 +- src/lv_conf.h | 26 +++++++++++++------------- src/main.cpp | 2 +- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/platformio.ini b/platformio.ini index 3abc8ed..104336e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -32,7 +32,7 @@ build_flags = -DLV_USE_FS_STDIO=0 -DLV_USE_FS_POSIX=0 -DLV_USE_IME_PINYIN=1 - -DLVGL_DRAW_BUF_DIV=4 + -DLVGL_DRAW_BUF_DIV=2 -DLVGL_DOUBLE_BUF=1 -DLV_MEM_POOL_SIZE_KB=384 -DCORE_DEBUG_LEVEL=0 @@ -42,7 +42,7 @@ build_flags = -DARDUINO_LOOP_STACK_SIZE=16384 -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue - -Os + -O2 lib_deps = bodmer/TFT_eSPI@^2.5.43 https://github.com/PaulStoffregen/XPT2046_Touchscreen.git#v1.4 diff --git a/src/Setup_ESP32_2432S028R_ILI9341.h b/src/Setup_ESP32_2432S028R_ILI9341.h index de492cd..4660ae6 100644 --- a/src/Setup_ESP32_2432S028R_ILI9341.h +++ b/src/Setup_ESP32_2432S028R_ILI9341.h @@ -365,7 +365,7 @@ // #define SPI_FREQUENCY 10000000 // #define SPI_FREQUENCY 20000000 // #define SPI_FREQUENCY 27000000 -#define SPI_FREQUENCY 55000000 +#define SPI_FREQUENCY 80000000 // #define SPI_FREQUENCY 55000000 // #define SPI_FREQUENCY 80000000 diff --git a/src/lv_conf.h b/src/lv_conf.h index 6f7ecd4..44a3ca6 100644 --- a/src/lv_conf.h +++ b/src/lv_conf.h @@ -144,7 +144,7 @@ * and can't be drawn in chunks. */ /*The target buffer size for simple layer chunks.*/ -#define LV_DRAW_LAYER_SIMPLE_BUF_SIZE (12 * 1024) /*[bytes]*/ +#define LV_DRAW_LAYER_SIMPLE_BUF_SIZE (24 * 1024) /*[bytes]*/ /* The stack size of the drawing thread. * NOTE: If FreeType or ThorVG is enabled, it is recommended to set it to 32KB or more. @@ -162,14 +162,14 @@ */ #define LV_DRAW_SW_SUPPORT_RGB565 1 - #define LV_DRAW_SW_SUPPORT_RGB565A8 0 /* not used on this display */ - #define LV_DRAW_SW_SUPPORT_RGB888 1 /* needed for gradients */ - #define LV_DRAW_SW_SUPPORT_XRGB8888 0 /* not used on this display */ - #define LV_DRAW_SW_SUPPORT_ARGB8888 1 /* needed for transparency compositing */ - #define LV_DRAW_SW_SUPPORT_L8 0 /* not used on this display */ - #define LV_DRAW_SW_SUPPORT_AL88 0 /* not used on this display */ - #define LV_DRAW_SW_SUPPORT_A8 1 /* needed for font/glyph alpha masks */ - #define LV_DRAW_SW_SUPPORT_I1 0 /* not used on this display */ + #define LV_DRAW_SW_SUPPORT_RGB565A8 1 + #define LV_DRAW_SW_SUPPORT_RGB888 1 + #define LV_DRAW_SW_SUPPORT_XRGB8888 1 + #define LV_DRAW_SW_SUPPORT_ARGB8888 1 + #define LV_DRAW_SW_SUPPORT_L8 1 + #define LV_DRAW_SW_SUPPORT_AL88 1 + #define LV_DRAW_SW_SUPPORT_A8 1 + #define LV_DRAW_SW_SUPPORT_I1 1 /* Set the number of draw unit. * > 1 requires an operating system enabled in `LV_USE_OS` @@ -190,13 +190,13 @@ /*Allow buffering some shadow calculation. *LV_DRAW_SW_SHADOW_CACHE_SIZE is the max. shadow size to buffer, where shadow size is `shadow_width + radius` *Caching has LV_DRAW_SW_SHADOW_CACHE_SIZE^2 RAM cost*/ - #define LV_DRAW_SW_SHADOW_CACHE_SIZE 0 + #define LV_DRAW_SW_SHADOW_CACHE_SIZE 128 /* Set number of maximally cached circle data. * The circumference of 1/4 circle are saved for anti-aliasing * radius * 4 bytes are used per circle (the most often used radiuses are saved) * 0: to disable caching */ - #define LV_DRAW_SW_CIRCLE_CACHE_SIZE 8 + #define LV_DRAW_SW_CIRCLE_CACHE_SIZE 32 #endif #define LV_USE_DRAW_SW_ASM LV_DRAW_SW_ASM_NONE @@ -381,7 +381,7 @@ *Used by image decoders such as `lv_lodepng` to keep the decoded image in the memory. *If size is not set to 0, the decoder will fail to decode when the cache is full. *If size is 0, the cache function is not enabled and the decoded mem will be released immediately after use.*/ -#define LV_CACHE_DEF_SIZE (32 * 1024) +#define LV_CACHE_DEF_SIZE (64 * 1024) /*Default number of image header cache entries. The cache is used to store the headers of images *The main logic is like `LV_CACHE_DEF_SIZE` but for image headers.*/ @@ -703,7 +703,7 @@ /*0: Light mode; 1: Dark mode*/ #define LV_THEME_DEFAULT_DARK 0 - /*0: Disable grow on press to reduce redraw overhead on resource-constrained targets*/ + /*0: Disable grow on press to eliminate extra redraw area during press animations*/ #define LV_THEME_DEFAULT_GROW 0 /*Default transition time in [ms]*/ diff --git a/src/main.cpp b/src/main.cpp index ae82691..acd5120 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -415,7 +415,7 @@ static void tft_flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * tft.startWrite(); tft.setAddrWindow(area->x1, area->y1, w, h); - tft.pushColors((uint16_t *)px_map, len, true); + tft.pushPixelsDMA((uint16_t *)px_map, len); tft.endWrite(); lv_display_flush_ready(disp); From 8dc4ddf00f0e06a4621c823a7572968982d13005 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Mar 2026 13:38:01 +0000 Subject: [PATCH 5/5] fix: disable DMA (screen corruption) + enable PNG/BMP/GIF image decoders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Comment out ESP32_DMA define — causes screen corruption on this CYD - Revert pushPixelsDMA() to pushColors() in flush callback - Enable LV_USE_LODEPNG=1 for PNG image support - Enable LV_USE_BMP=1 for BMP image support - Enable LV_USE_GIF=1 for GIF image support - Update isImageFile() to recognize .png, .bmp, .gif extensions Co-authored-by: shiftz300 <130174875+shiftz300@users.noreply.github.com> --- src/Setup_ESP32_2432S028R_ILI9341.h | 4 ++-- src/app.h | 2 +- src/lv_conf.h | 6 +++--- src/main.cpp | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Setup_ESP32_2432S028R_ILI9341.h b/src/Setup_ESP32_2432S028R_ILI9341.h index 4660ae6..bd3b666 100644 --- a/src/Setup_ESP32_2432S028R_ILI9341.h +++ b/src/Setup_ESP32_2432S028R_ILI9341.h @@ -380,8 +380,8 @@ // then uncomment the following line: #define USE_HSPI_PORT -// Enable TFT_eSPI DMA path on classic ESP32 to improve flush throughput. -#define ESP32_DMA +// DMA disabled: causes screen corruption on this hardware. +// #define ESP32_DMA // Comment out the following #define if "SPI Transactions" do not need to be // supported. When commented out the code size will be smaller and sketches will diff --git a/src/app.h b/src/app.h index 515a07f..ef1389e 100644 --- a/src/app.h +++ b/src/app.h @@ -514,7 +514,7 @@ class AppManager { if (dot < 0) return false; String ext = path.substring(dot + 1); ext.toLowerCase(); - return (ext == "jpg" || ext == "jpeg"); + return (ext == "jpg" || ext == "jpeg" || ext == "png" || ext == "bmp" || ext == "gif"); } void showPrevImage() { diff --git a/src/lv_conf.h b/src/lv_conf.h index 44a3ca6..5ba0da3 100644 --- a/src/lv_conf.h +++ b/src/lv_conf.h @@ -791,13 +791,13 @@ #endif /*LODEPNG decoder library*/ -#define LV_USE_LODEPNG 0 +#define LV_USE_LODEPNG 1 /*PNG decoder(libpng) library*/ #define LV_USE_LIBPNG 0 /*BMP decoder library*/ -#define LV_USE_BMP 0 +#define LV_USE_BMP 1 /* JPG + split JPG decoder library. * Split JPG is a custom format optimized for embedded systems. */ @@ -808,7 +808,7 @@ #define LV_USE_LIBJPEG_TURBO 0 /*GIF decoder library*/ -#define LV_USE_GIF 0 +#define LV_USE_GIF 1 #if LV_USE_GIF /*GIF decoder accelerate*/ #define LV_GIF_CACHE_DECODE_DATA 0 diff --git a/src/main.cpp b/src/main.cpp index acd5120..ae82691 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -415,7 +415,7 @@ static void tft_flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * tft.startWrite(); tft.setAddrWindow(area->x1, area->y1, w, h); - tft.pushPixelsDMA((uint16_t *)px_map, len); + tft.pushColors((uint16_t *)px_map, len, true); tft.endWrite(); lv_display_flush_ready(disp);