From 04c923bd82aa13108cef8217562a4dca33c85617 Mon Sep 17 00:00:00 2001 From: Reinhold Gschweicher Date: Mon, 13 Jun 2022 20:46:03 +0200 Subject: [PATCH] LittleVgl: implement screen transitions like on PineTime Move lvgl display init from main.cpp into LittleVgl.cpp to be closer to InfiniTime, where display initialization is also done in LitteVgl.cpp. Enable the original FlushDisplay code to get the screen transition animations like on the real PineTime. Also slow down the rendering, to actually be able to see the screen flushing. For the Up and Down screen transitions implement the screen movement. When moving Down, move the the whole screen content down, and then draw the new part of the new screen at the top of the display. Repeat until screen transition is finished. Fixes: https://github.com/InfiniTimeOrg/InfiniSim/issues/13 --- main.cpp | 26 ++-- sim/displayapp/LittleVgl.cpp | 268 ++++++++++++++++++++++------------- 2 files changed, 183 insertions(+), 111 deletions(-) diff --git a/main.cpp b/main.cpp index b39b0bf..b776560 100644 --- a/main.cpp +++ b/main.cpp @@ -863,21 +863,21 @@ static void hal_init(void) SDL_CreateThread(tick_thread, "tick", NULL); // use pinetime_theme - lv_theme_t* th = lv_pinetime_theme_init( - LV_COLOR_WHITE, LV_COLOR_SILVER, 0, &jetbrains_mono_bold_20, &jetbrains_mono_bold_20, &jetbrains_mono_bold_20, &jetbrains_mono_bold_20); - lv_theme_set_act(th); + //lv_theme_t* th = lv_pinetime_theme_init( + // LV_COLOR_WHITE, LV_COLOR_SILVER, 0, &jetbrains_mono_bold_20, &jetbrains_mono_bold_20, &jetbrains_mono_bold_20, &jetbrains_mono_bold_20); + //lv_theme_set_act(th); - /*Create a display buffer*/ - static lv_disp_buf_t disp_buf1; - static lv_color_t buf1_1[LV_HOR_RES_MAX * 120]; - lv_disp_buf_init(&disp_buf1, buf1_1, NULL, LV_HOR_RES_MAX * 120); + ///*Create a display buffer*/ + //static lv_disp_buf_t disp_buf1; + //static lv_color_t buf1_1[LV_HOR_RES_MAX * 120]; + //lv_disp_buf_init(&disp_buf1, buf1_1, NULL, LV_HOR_RES_MAX * 120); - /*Create a display*/ - lv_disp_drv_t disp_drv; - lv_disp_drv_init(&disp_drv); /*Basic initialization*/ - disp_drv.buffer = &disp_buf1; - disp_drv.flush_cb = monitor_flush; - lv_disp_drv_register(&disp_drv); + ///*Create a display*/ + //lv_disp_drv_t disp_drv; + //lv_disp_drv_init(&disp_drv); /*Basic initialization*/ + //disp_drv.buffer = &disp_buf1; + //disp_drv.flush_cb = monitor_flush; + //lv_disp_drv_register(&disp_drv); /* Add the mouse as input device * Use the 'mouse' driver which reads the PC's mouse*/ diff --git a/sim/displayapp/LittleVgl.cpp b/sim/displayapp/LittleVgl.cpp index a244f84..5aa161b 100644 --- a/sim/displayapp/LittleVgl.cpp +++ b/sim/displayapp/LittleVgl.cpp @@ -3,10 +3,16 @@ #include #include +#include ////#include #include "drivers/Cst816s.h" #include "drivers/St7789.h" +// lv-sim monitor display driver for monitor_flush() function +#include "lv_drivers/display/monitor.h" + +#include + using namespace Pinetime::Components; lv_style_t* LabelBigStyle = nullptr; @@ -37,8 +43,8 @@ LittleVgl::LittleVgl(Pinetime::Drivers::St7789& lcd, Pinetime::Drivers::Cst816S& void LittleVgl::Init() { // lv_init(); -// InitTheme(); -// InitDisplay(); + InitTheme(); + InitDisplay(); InitTouchpad(); } @@ -91,105 +97,171 @@ void LittleVgl::SetFullRefresh(FullRefreshDirections direction) { fullRefresh = true; } -void LittleVgl::FlushDisplay(const lv_area_t* area, lv_color_t* color_p) { -// uint16_t y1, y2, width, height = 0; -// -// ulTaskNotifyTake(pdTRUE, 200); -// // Notification is still needed (even if there is a mutex on SPI) because of the DataCommand pin -// // which cannot be set/clear during a transfer. -// -// if ((scrollDirection == LittleVgl::FullRefreshDirections::Down) && (area->y2 == visibleNbLines - 1)) { -// writeOffset = ((writeOffset + totalNbLines) - visibleNbLines) % totalNbLines; -// } else if ((scrollDirection == FullRefreshDirections::Up) && (area->y1 == 0)) { -// writeOffset = (writeOffset + visibleNbLines) % totalNbLines; -// } -// -// y1 = (area->y1 + writeOffset) % totalNbLines; -// y2 = (area->y2 + writeOffset) % totalNbLines; -// -// width = (area->x2 - area->x1) + 1; -// height = (area->y2 - area->y1) + 1; -// -// if (scrollDirection == LittleVgl::FullRefreshDirections::Down) { -// -// if (area->y2 < visibleNbLines - 1) { -// uint16_t toScroll = 0; -// if (area->y1 == 0) { -// toScroll = height * 2; -// scrollDirection = FullRefreshDirections::None; -// lv_disp_set_direction(lv_disp_get_default(), 0); -// } else { -// toScroll = height; -// } -// -// if (scrollOffset >= toScroll) -// scrollOffset -= toScroll; -// else { -// toScroll -= scrollOffset; -// scrollOffset = (totalNbLines) -toScroll; -// } -// lcd.VerticalScrollStartAddress(scrollOffset); -// } -// -// } else if (scrollDirection == FullRefreshDirections::Up) { -// -// if (area->y1 > 0) { -// if (area->y2 == visibleNbLines - 1) { -// scrollOffset += (height * 2); -// scrollDirection = FullRefreshDirections::None; -// lv_disp_set_direction(lv_disp_get_default(), 0); -// } else { -// scrollOffset += height; -// } -// scrollOffset = scrollOffset % totalNbLines; -// lcd.VerticalScrollStartAddress(scrollOffset); -// } -// } else if (scrollDirection == FullRefreshDirections::Left or scrollDirection == FullRefreshDirections::LeftAnim) { -// if (area->x2 == visibleNbLines - 1) { -// scrollDirection = FullRefreshDirections::None; -// lv_disp_set_direction(lv_disp_get_default(), 0); -// } -// } else if (scrollDirection == FullRefreshDirections::Right or scrollDirection == FullRefreshDirections::RightAnim) { -// if (area->x1 == 0) { -// scrollDirection = FullRefreshDirections::None; -// lv_disp_set_direction(lv_disp_get_default(), 0); -// } -// } -// -// if (y2 < y1) { -// height = totalNbLines - y1; -// -// if (height > 0) { -// lcd.DrawBuffer(area->x1, y1, width, height, reinterpret_cast(color_p), width * height * 2); -// ulTaskNotifyTake(pdTRUE, 100); -// } -// -// uint16_t pixOffset = width * height; -// height = y2 + 1; -// lcd.DrawBuffer(area->x1, 0, width, height, reinterpret_cast(color_p + pixOffset), width * height * 2); -// -// } else { -// lcd.DrawBuffer(area->x1, y1, width, height, reinterpret_cast(color_p), width * height * 2); -// } -// -// // IMPORTANT!!! -// // Inform the graphics library that you are ready with the flushing -// lv_disp_flush_ready(&disp_drv); +// glue the lvgl code to the lv-sim monitor driver +void DrawBuffer(lv_disp_drv_t *disp_drv, uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t* data, size_t size) { + lv_area_t area; + area.x1 = x; + area.x2 = x+width-1; + area.y1 = y; + area.y2 = y+height-1; + lv_color_t* color_p = reinterpret_cast(data); + monitor_flush(disp_drv, &area, color_p); +} +// copied from lv_drivers/display/monitor.c to get the SDL_Window for the InfiniTime screen +extern "C" +{ +typedef struct { + SDL_Window * window; + SDL_Renderer * renderer; + SDL_Texture * texture; + volatile bool sdl_refr_qry; +#if MONITOR_DOUBLE_BUFFERED + uint32_t * tft_fb_act; +#else + uint32_t tft_fb[LV_HOR_RES_MAX * LV_VER_RES_MAX]; +#endif +}monitor_t; +extern monitor_t monitor; +} + +// positive height moves screen down (draw y=0 to y=height) +// negative height moves screen up (draw y=height to y=0) +void MoveScreen(lv_disp_drv_t *disp_drv, int16_t height) { + if (height == 0) + return; // nothing to do + + const int sdl_width = 240; + const int sdl_height = 240; + auto renderer = monitor.renderer; + + const Uint32 format = SDL_PIXELFORMAT_RGBA8888; + SDL_Surface *surface = SDL_CreateRGBSurfaceWithFormat(0, sdl_width, sdl_height, 32, format); + SDL_RenderReadPixels(renderer, NULL, format, surface->pixels, surface->pitch); + uint8_t *pixels = (uint8_t*) surface->pixels; + + std::array color_p; + for (int hi = 0; hi < sdl_height; hi++) { + for (int wi = 0; wi < sdl_width; wi++) { + auto red = pixels[hi*surface->pitch + wi*4 + 3]; // red + auto green = pixels[hi*surface->pitch + wi*4 + 2]; // greeen + auto blue = pixels[hi*surface->pitch + wi*4 + 1]; // blue + color_p.at(hi * sdl_width + wi) = LV_COLOR_MAKE(red, green, blue); + } + } + int16_t buffer_height = sdl_height - abs(height); + if (height >= 0) { + DrawBuffer(disp_drv, 0, height, sdl_width, sdl_height, (uint8_t*)color_p.data(), sdl_width*buffer_height *2); + } else { + DrawBuffer(disp_drv, 0, 0, sdl_width, sdl_height, (uint8_t*)(&color_p.at(sdl_width*abs(height))), sdl_width*buffer_height *2); + } +} + +void LittleVgl::FlushDisplay(const lv_area_t* area, lv_color_t* color_p) { + uint16_t y1, y2, width, height = 0; + + //ulTaskNotifyTake(pdTRUE, 200); + // Notification is still needed (even if there is a mutex on SPI) because of the DataCommand pin + // which cannot be set/clear during a transfer. + + //if ((scrollDirection == LittleVgl::FullRefreshDirections::Down) && (area->y2 == visibleNbLines - 1)) { + // writeOffset = ((writeOffset + totalNbLines) - visibleNbLines) % totalNbLines; + //} else if ((scrollDirection == FullRefreshDirections::Up) && (area->y1 == 0)) { + // writeOffset = (writeOffset + visibleNbLines) % totalNbLines; + //} + + y1 = (area->y1 + writeOffset) % totalNbLines; + y2 = (area->y2 + writeOffset) % totalNbLines; + + width = (area->x2 - area->x1) + 1; + height = (area->y2 - area->y1) + 1; + + if (scrollDirection == LittleVgl::FullRefreshDirections::Down) { + + if (area->y2 < visibleNbLines - 1) { + uint16_t toScroll = 0; + if (area->y1 == 0) { + toScroll = height * 2; + scrollDirection = FullRefreshDirections::None; + lv_disp_set_direction(lv_disp_get_default(), 0); + } else { + toScroll = height; + } + + if (scrollOffset >= toScroll) + scrollOffset -= toScroll; + else { + toScroll -= scrollOffset; + scrollOffset = (totalNbLines) -toScroll; + } + lcd.VerticalScrollStartAddress(scrollOffset); + + } + // move the whole screen down and draw the new screen at the top of the display + MoveScreen(&disp_drv, static_cast(height)); + y1 = 0; + y2 = height; + + } else if (scrollDirection == FullRefreshDirections::Up) { + + if (area->y1 > 0) { + if (area->y2 == visibleNbLines - 1) { + scrollOffset += (height * 2); + scrollDirection = FullRefreshDirections::None; + lv_disp_set_direction(lv_disp_get_default(), 0); + } else { + scrollOffset += height; + } + scrollOffset = scrollOffset % totalNbLines; + lcd.VerticalScrollStartAddress(scrollOffset); + } + // move the whole screen up and draw the new screen at the bottom the display + MoveScreen(&disp_drv, -static_cast(height)); + y1 = LV_VER_RES - height; + y2 = LV_VER_RES; + } else if (scrollDirection == FullRefreshDirections::Left or scrollDirection == FullRefreshDirections::LeftAnim) { + if (area->x2 == visibleNbLines - 1) { + scrollDirection = FullRefreshDirections::None; + lv_disp_set_direction(lv_disp_get_default(), 0); + } + } else if (scrollDirection == FullRefreshDirections::Right or scrollDirection == FullRefreshDirections::RightAnim) { + if (area->x1 == 0) { + scrollDirection = FullRefreshDirections::None; + lv_disp_set_direction(lv_disp_get_default(), 0); + } + } + + if (y2 < y1) { + height = totalNbLines - y1; + + if (height > 0) { + //lcd.DrawBuffer(area->x1, y1, width, height, reinterpret_cast(color_p), width * height * 2); + DrawBuffer(&disp_drv, area->x1, y1, width, height, reinterpret_cast(color_p), width * height * 2); + //ulTaskNotifyTake(pdTRUE, 100); + } + + uint16_t pixOffset = width * height; + height = y2 + 1; + //lcd.DrawBuffer(area->x1, 0, width, height, reinterpret_cast(color_p + pixOffset), width * height * 2); + DrawBuffer(&disp_drv, area->x1, 0, width, height, reinterpret_cast(color_p + pixOffset), width * height * 2); + + } else { + //lcd.DrawBuffer(area->x1, y1, width, height, reinterpret_cast(color_p), width * height * 2); + DrawBuffer(&disp_drv, area->x1, y1, width, height, reinterpret_cast(color_p), width * height * 2); + } + + // IMPORTANT!!! + // Inform the graphics library that you are ready with the flushing + //lv_disp_flush_ready(&disp_drv); + + // call flush with flushing_last set + // workaround because lv_disp_flush_ready() doesn't seem to trigger monitor_flush lv_disp_t *disp = lv_disp_get_default(); - lv_disp_drv_t *disp_drv = &disp->driver; - lv_area_t area_trimmed = *area; - if (area->x1 < 0) - area_trimmed.x1 = 0; - if (area->x2 >= LV_HOR_RES) - area_trimmed.x2 = LV_HOR_RES-1; - if (area->y1 < 0) - area_trimmed.y1 = 0; - if (area->y2 >= LV_VER_RES) - area_trimmed.y2 = LV_VER_RES-1; - // tell flush_cb this is the last thing to flush to get the monitor refreshed lv_disp_get_buf(disp)->flushing_last = true; - disp_drv->flush_cb(disp_drv, &area_trimmed, color_p); + lv_area_t area_zero {0,0,0,0}; + monitor_flush(&disp_drv, &area_zero, color_p); + // delay drawing to mimic PineTime display rendering speed + vTaskDelay(pdMS_TO_TICKS(3)); } void LittleVgl::SetNewTouchPoint(uint16_t x, uint16_t y, bool contact) {