Skip to content

Commit 04c923b

Browse files
committed
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: #13
1 parent 53d765b commit 04c923b

2 files changed

Lines changed: 184 additions & 112 deletions

File tree

main.cpp

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -863,21 +863,21 @@ static void hal_init(void)
863863
SDL_CreateThread(tick_thread, "tick", NULL);
864864

865865
// use pinetime_theme
866-
lv_theme_t* th = lv_pinetime_theme_init(
867-
LV_COLOR_WHITE, LV_COLOR_SILVER, 0, &jetbrains_mono_bold_20, &jetbrains_mono_bold_20, &jetbrains_mono_bold_20, &jetbrains_mono_bold_20);
868-
lv_theme_set_act(th);
869-
870-
/*Create a display buffer*/
871-
static lv_disp_buf_t disp_buf1;
872-
static lv_color_t buf1_1[LV_HOR_RES_MAX * 120];
873-
lv_disp_buf_init(&disp_buf1, buf1_1, NULL, LV_HOR_RES_MAX * 120);
874-
875-
/*Create a display*/
876-
lv_disp_drv_t disp_drv;
877-
lv_disp_drv_init(&disp_drv); /*Basic initialization*/
878-
disp_drv.buffer = &disp_buf1;
879-
disp_drv.flush_cb = monitor_flush;
880-
lv_disp_drv_register(&disp_drv);
866+
//lv_theme_t* th = lv_pinetime_theme_init(
867+
// LV_COLOR_WHITE, LV_COLOR_SILVER, 0, &jetbrains_mono_bold_20, &jetbrains_mono_bold_20, &jetbrains_mono_bold_20, &jetbrains_mono_bold_20);
868+
//lv_theme_set_act(th);
869+
870+
///*Create a display buffer*/
871+
//static lv_disp_buf_t disp_buf1;
872+
//static lv_color_t buf1_1[LV_HOR_RES_MAX * 120];
873+
//lv_disp_buf_init(&disp_buf1, buf1_1, NULL, LV_HOR_RES_MAX * 120);
874+
875+
///*Create a display*/
876+
//lv_disp_drv_t disp_drv;
877+
//lv_disp_drv_init(&disp_drv); /*Basic initialization*/
878+
//disp_drv.buffer = &disp_buf1;
879+
//disp_drv.flush_cb = monitor_flush;
880+
//lv_disp_drv_register(&disp_drv);
881881

882882
/* Add the mouse as input device
883883
* Use the 'mouse' driver which reads the PC's mouse*/

sim/displayapp/LittleVgl.cpp

Lines changed: 169 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,16 @@
33

44
#include <FreeRTOS.h>
55
#include <task.h>
6+
#include <timers.h>
67
////#include <projdefs.h>
78
#include "drivers/Cst816s.h"
89
#include "drivers/St7789.h"
910

11+
// lv-sim monitor display driver for monitor_flush() function
12+
#include "lv_drivers/display/monitor.h"
13+
14+
#include <array>
15+
1016
using namespace Pinetime::Components;
1117

1218
lv_style_t* LabelBigStyle = nullptr;
@@ -37,8 +43,8 @@ LittleVgl::LittleVgl(Pinetime::Drivers::St7789& lcd, Pinetime::Drivers::Cst816S&
3743

3844
void LittleVgl::Init() {
3945
// lv_init();
40-
// InitTheme();
41-
// InitDisplay();
46+
InitTheme();
47+
InitDisplay();
4248
InitTouchpad();
4349
}
4450

@@ -91,105 +97,171 @@ void LittleVgl::SetFullRefresh(FullRefreshDirections direction) {
9197
fullRefresh = true;
9298
}
9399

100+
// glue the lvgl code to the lv-sim monitor driver
101+
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) {
102+
lv_area_t area;
103+
area.x1 = x;
104+
area.x2 = x+width-1;
105+
area.y1 = y;
106+
area.y2 = y+height-1;
107+
lv_color_t* color_p = reinterpret_cast<lv_color_t*>(data);
108+
monitor_flush(disp_drv, &area, color_p);
109+
}
110+
111+
// copied from lv_drivers/display/monitor.c to get the SDL_Window for the InfiniTime screen
112+
extern "C"
113+
{
114+
typedef struct {
115+
SDL_Window * window;
116+
SDL_Renderer * renderer;
117+
SDL_Texture * texture;
118+
volatile bool sdl_refr_qry;
119+
#if MONITOR_DOUBLE_BUFFERED
120+
uint32_t * tft_fb_act;
121+
#else
122+
uint32_t tft_fb[LV_HOR_RES_MAX * LV_VER_RES_MAX];
123+
#endif
124+
}monitor_t;
125+
extern monitor_t monitor;
126+
}
127+
128+
// positive height moves screen down (draw y=0 to y=height)
129+
// negative height moves screen up (draw y=height to y=0)
130+
void MoveScreen(lv_disp_drv_t *disp_drv, int16_t height) {
131+
if (height == 0)
132+
return; // nothing to do
133+
134+
const int sdl_width = 240;
135+
const int sdl_height = 240;
136+
auto renderer = monitor.renderer;
137+
138+
const Uint32 format = SDL_PIXELFORMAT_RGBA8888;
139+
SDL_Surface *surface = SDL_CreateRGBSurfaceWithFormat(0, sdl_width, sdl_height, 32, format);
140+
SDL_RenderReadPixels(renderer, NULL, format, surface->pixels, surface->pitch);
141+
uint8_t *pixels = (uint8_t*) surface->pixels;
142+
143+
std::array<lv_color16_t, 240*240> color_p;
144+
for (int hi = 0; hi < sdl_height; hi++) {
145+
for (int wi = 0; wi < sdl_width; wi++) {
146+
auto red = pixels[hi*surface->pitch + wi*4 + 3]; // red
147+
auto green = pixels[hi*surface->pitch + wi*4 + 2]; // greeen
148+
auto blue = pixels[hi*surface->pitch + wi*4 + 1]; // blue
149+
color_p.at(hi * sdl_width + wi) = LV_COLOR_MAKE(red, green, blue);
150+
}
151+
}
152+
int16_t buffer_height = sdl_height - abs(height);
153+
if (height >= 0) {
154+
DrawBuffer(disp_drv, 0, height, sdl_width, sdl_height, (uint8_t*)color_p.data(), sdl_width*buffer_height *2);
155+
} else {
156+
DrawBuffer(disp_drv, 0, 0, sdl_width, sdl_height, (uint8_t*)(&color_p.at(sdl_width*abs(height))), sdl_width*buffer_height *2);
157+
}
158+
}
159+
94160
void LittleVgl::FlushDisplay(const lv_area_t* area, lv_color_t* color_p) {
95-
// uint16_t y1, y2, width, height = 0;
96-
//
97-
// ulTaskNotifyTake(pdTRUE, 200);
98-
// // Notification is still needed (even if there is a mutex on SPI) because of the DataCommand pin
99-
// // which cannot be set/clear during a transfer.
100-
//
101-
// if ((scrollDirection == LittleVgl::FullRefreshDirections::Down) && (area->y2 == visibleNbLines - 1)) {
102-
// writeOffset = ((writeOffset + totalNbLines) - visibleNbLines) % totalNbLines;
103-
// } else if ((scrollDirection == FullRefreshDirections::Up) && (area->y1 == 0)) {
104-
// writeOffset = (writeOffset + visibleNbLines) % totalNbLines;
105-
// }
106-
//
107-
// y1 = (area->y1 + writeOffset) % totalNbLines;
108-
// y2 = (area->y2 + writeOffset) % totalNbLines;
109-
//
110-
// width = (area->x2 - area->x1) + 1;
111-
// height = (area->y2 - area->y1) + 1;
112-
//
113-
// if (scrollDirection == LittleVgl::FullRefreshDirections::Down) {
114-
//
115-
// if (area->y2 < visibleNbLines - 1) {
116-
// uint16_t toScroll = 0;
117-
// if (area->y1 == 0) {
118-
// toScroll = height * 2;
119-
// scrollDirection = FullRefreshDirections::None;
120-
// lv_disp_set_direction(lv_disp_get_default(), 0);
121-
// } else {
122-
// toScroll = height;
123-
// }
124-
//
125-
// if (scrollOffset >= toScroll)
126-
// scrollOffset -= toScroll;
127-
// else {
128-
// toScroll -= scrollOffset;
129-
// scrollOffset = (totalNbLines) -toScroll;
130-
// }
131-
// lcd.VerticalScrollStartAddress(scrollOffset);
132-
// }
133-
//
134-
// } else if (scrollDirection == FullRefreshDirections::Up) {
135-
//
136-
// if (area->y1 > 0) {
137-
// if (area->y2 == visibleNbLines - 1) {
138-
// scrollOffset += (height * 2);
139-
// scrollDirection = FullRefreshDirections::None;
140-
// lv_disp_set_direction(lv_disp_get_default(), 0);
141-
// } else {
142-
// scrollOffset += height;
143-
// }
144-
// scrollOffset = scrollOffset % totalNbLines;
145-
// lcd.VerticalScrollStartAddress(scrollOffset);
146-
// }
147-
// } else if (scrollDirection == FullRefreshDirections::Left or scrollDirection == FullRefreshDirections::LeftAnim) {
148-
// if (area->x2 == visibleNbLines - 1) {
149-
// scrollDirection = FullRefreshDirections::None;
150-
// lv_disp_set_direction(lv_disp_get_default(), 0);
151-
// }
152-
// } else if (scrollDirection == FullRefreshDirections::Right or scrollDirection == FullRefreshDirections::RightAnim) {
153-
// if (area->x1 == 0) {
154-
// scrollDirection = FullRefreshDirections::None;
155-
// lv_disp_set_direction(lv_disp_get_default(), 0);
156-
// }
157-
// }
158-
//
159-
// if (y2 < y1) {
160-
// height = totalNbLines - y1;
161-
//
162-
// if (height > 0) {
163-
// lcd.DrawBuffer(area->x1, y1, width, height, reinterpret_cast<const uint8_t*>(color_p), width * height * 2);
164-
// ulTaskNotifyTake(pdTRUE, 100);
165-
// }
166-
//
167-
// uint16_t pixOffset = width * height;
168-
// height = y2 + 1;
169-
// lcd.DrawBuffer(area->x1, 0, width, height, reinterpret_cast<const uint8_t*>(color_p + pixOffset), width * height * 2);
170-
//
171-
// } else {
172-
// lcd.DrawBuffer(area->x1, y1, width, height, reinterpret_cast<const uint8_t*>(color_p), width * height * 2);
173-
// }
174-
//
175-
// // IMPORTANT!!!
176-
// // Inform the graphics library that you are ready with the flushing
177-
// lv_disp_flush_ready(&disp_drv);
161+
uint16_t y1, y2, width, height = 0;
162+
163+
//ulTaskNotifyTake(pdTRUE, 200);
164+
// Notification is still needed (even if there is a mutex on SPI) because of the DataCommand pin
165+
// which cannot be set/clear during a transfer.
166+
167+
//if ((scrollDirection == LittleVgl::FullRefreshDirections::Down) && (area->y2 == visibleNbLines - 1)) {
168+
// writeOffset = ((writeOffset + totalNbLines) - visibleNbLines) % totalNbLines;
169+
//} else if ((scrollDirection == FullRefreshDirections::Up) && (area->y1 == 0)) {
170+
// writeOffset = (writeOffset + visibleNbLines) % totalNbLines;
171+
//}
172+
173+
y1 = (area->y1 + writeOffset) % totalNbLines;
174+
y2 = (area->y2 + writeOffset) % totalNbLines;
175+
176+
width = (area->x2 - area->x1) + 1;
177+
height = (area->y2 - area->y1) + 1;
178+
179+
if (scrollDirection == LittleVgl::FullRefreshDirections::Down) {
180+
181+
if (area->y2 < visibleNbLines - 1) {
182+
uint16_t toScroll = 0;
183+
if (area->y1 == 0) {
184+
toScroll = height * 2;
185+
scrollDirection = FullRefreshDirections::None;
186+
lv_disp_set_direction(lv_disp_get_default(), 0);
187+
} else {
188+
toScroll = height;
189+
}
190+
191+
if (scrollOffset >= toScroll)
192+
scrollOffset -= toScroll;
193+
else {
194+
toScroll -= scrollOffset;
195+
scrollOffset = (totalNbLines) -toScroll;
196+
}
197+
lcd.VerticalScrollStartAddress(scrollOffset);
198+
199+
}
200+
// move the whole screen down and draw the new screen at the top of the display
201+
MoveScreen(&disp_drv, static_cast<int16_t>(height));
202+
y1 = 0;
203+
y2 = height;
204+
205+
} else if (scrollDirection == FullRefreshDirections::Up) {
206+
207+
if (area->y1 > 0) {
208+
if (area->y2 == visibleNbLines - 1) {
209+
scrollOffset += (height * 2);
210+
scrollDirection = FullRefreshDirections::None;
211+
lv_disp_set_direction(lv_disp_get_default(), 0);
212+
} else {
213+
scrollOffset += height;
214+
}
215+
scrollOffset = scrollOffset % totalNbLines;
216+
lcd.VerticalScrollStartAddress(scrollOffset);
217+
}
218+
// move the whole screen up and draw the new screen at the bottom the display
219+
MoveScreen(&disp_drv, -static_cast<int16_t>(height));
220+
y1 = LV_VER_RES - height;
221+
y2 = LV_VER_RES;
222+
} else if (scrollDirection == FullRefreshDirections::Left or scrollDirection == FullRefreshDirections::LeftAnim) {
223+
if (area->x2 == visibleNbLines - 1) {
224+
scrollDirection = FullRefreshDirections::None;
225+
lv_disp_set_direction(lv_disp_get_default(), 0);
226+
}
227+
} else if (scrollDirection == FullRefreshDirections::Right or scrollDirection == FullRefreshDirections::RightAnim) {
228+
if (area->x1 == 0) {
229+
scrollDirection = FullRefreshDirections::None;
230+
lv_disp_set_direction(lv_disp_get_default(), 0);
231+
}
232+
}
233+
234+
if (y2 < y1) {
235+
height = totalNbLines - y1;
236+
237+
if (height > 0) {
238+
//lcd.DrawBuffer(area->x1, y1, width, height, reinterpret_cast<const uint8_t*>(color_p), width * height * 2);
239+
DrawBuffer(&disp_drv, area->x1, y1, width, height, reinterpret_cast<uint8_t*>(color_p), width * height * 2);
240+
//ulTaskNotifyTake(pdTRUE, 100);
241+
}
242+
243+
uint16_t pixOffset = width * height;
244+
height = y2 + 1;
245+
//lcd.DrawBuffer(area->x1, 0, width, height, reinterpret_cast<const uint8_t*>(color_p + pixOffset), width * height * 2);
246+
DrawBuffer(&disp_drv, area->x1, 0, width, height, reinterpret_cast<uint8_t*>(color_p + pixOffset), width * height * 2);
247+
248+
} else {
249+
//lcd.DrawBuffer(area->x1, y1, width, height, reinterpret_cast<const uint8_t*>(color_p), width * height * 2);
250+
DrawBuffer(&disp_drv, area->x1, y1, width, height, reinterpret_cast<uint8_t*>(color_p), width * height * 2);
251+
}
252+
253+
// IMPORTANT!!!
254+
// Inform the graphics library that you are ready with the flushing
255+
//lv_disp_flush_ready(&disp_drv);
178256

257+
// call flush with flushing_last set
258+
// workaround because lv_disp_flush_ready() doesn't seem to trigger monitor_flush
179259
lv_disp_t *disp = lv_disp_get_default();
180-
lv_disp_drv_t *disp_drv = &disp->driver;
181-
lv_area_t area_trimmed = *area;
182-
if (area->x1 < 0)
183-
area_trimmed.x1 = 0;
184-
if (area->x2 >= LV_HOR_RES)
185-
area_trimmed.x2 = LV_HOR_RES-1;
186-
if (area->y1 < 0)
187-
area_trimmed.y1 = 0;
188-
if (area->y2 >= LV_VER_RES)
189-
area_trimmed.y2 = LV_VER_RES-1;
190-
// tell flush_cb this is the last thing to flush to get the monitor refreshed
191260
lv_disp_get_buf(disp)->flushing_last = true;
192-
disp_drv->flush_cb(disp_drv, &area_trimmed, color_p);
261+
lv_area_t area_zero {0,0,0,0};
262+
monitor_flush(&disp_drv, &area_zero, color_p);
263+
// delay drawing to mimic PineTime display rendering speed
264+
vTaskDelay(pdMS_TO_TICKS(3));
193265
}
194266

195267
void LittleVgl::SetNewTouchPoint(uint16_t x, uint16_t y, bool contact) {

0 commit comments

Comments
 (0)