Completely redesign default backend widget drawing to use deferred drawing instead

Now instead of always having to remember to call LXYZ_Draw after LXYZ_Set, LXYZ_Set simply marks the widget as 'needing to be redrawn' - and the default backend will subsequently redraw it on next tick
The framebuffer was only redrawn on ticks anyways, so doesn't make any significant difference latency wise to defer drawing

This is still a WIP and causes the status widget in CheckResources menu to be redrawn with wrong background. However, this does fix some obscure issues such as e.g. typing many words into input widget causing it to get extended horizontally, then backspacing multiple characters, causing ghost parts of the input widget to get left behind still
This commit is contained in:
UnknownShadow200 2022-04-23 23:11:23 +10:00
parent f0583c32ca
commit ccfb87e54f
5 changed files with 91 additions and 91 deletions

View File

@ -89,6 +89,12 @@ void LBackend_CloseScreen(struct LScreen* s) { activeScreen = NULL; }
void LBackend_WidgetRepositioned(struct LWidget* w) {
}
void LBackend_MarkDirty(void* widget) {
struct LWidget* w = (struct LWidget*)widget;
pendingRedraw |= REDRAW_SOME;
w->dirty = true;
}
/* Marks the entire window as needing to be redrawn. */
static CC_NOINLINE void MarkAllDirty(void) {
dirty_rect.X = 0; dirty_rect.Width = Launcher_Framebuffer.width;
@ -114,13 +120,41 @@ static void DrawBoxBounds(BitmapCol color, int x, int y, int width, int height)
xBorder, height);
}
static CC_NOINLINE void DrawWidget(struct LWidget* w) {
w->last.X = w->x; w->last.Width = w->width;
w->last.Y = w->y; w->last.Height = w->height;
w->dirty = false;
w->VTABLE->Draw(w);
Launcher_MarkDirty(w->x, w->y, w->width, w->height);
}
static CC_NOINLINE void RedrawAll(void) {
struct LScreen* s = activeScreen;
int i;
s->DrawBackground(s, &Launcher_Framebuffer);
for (i = 0; i < s->numWidgets; i++) {
LWidget_Draw(s->widgets[i]);
DrawWidget(s->widgets[i]);
}
MarkAllDirty();
}
static CC_NOINLINE void RedrawDirty(void) {
struct LScreen* s = activeScreen;
struct LWidget* w;
int i;
for (i = 0; i < s->numWidgets; i++) {
w = s->widgets[i];
if (!w->dirty) continue;
/* check if widget might need redrawing of background behind */
if (!w->opaque || w->last.Width > w->width || w->last.Height > w->height) {
Launcher_ResetArea(w->last.X, w->last.Y, w->last.Width, w->last.Height);
Launcher_MarkDirty(w->last.X, w->last.Y, w->last.Width, w->last.Height);
}
DrawWidget(w);
}
MarkAllDirty();
}
@ -132,13 +166,16 @@ void LBackend_Redraw(void) {
void LBackend_Tick(void) {
activeScreen->Tick(activeScreen);
if (!dirty_rect.Width) return;
if (pendingRedraw & REDRAW_ALL) {
RedrawAll();
pendingRedraw = 0;
} else if (pendingRedraw & REDRAW_SOME) {
RedrawDirty();
pendingRedraw = 0;
}
if (!dirty_rect.Width) return;
Window_DrawFramebuffer(dirty_rect);
dirty_rect.X = 0; dirty_rect.Width = 0;
dirty_rect.Y = 0; dirty_rect.Height = 0;
@ -182,6 +219,7 @@ void LBackend_ButtonInit(struct LButton* w, int width, int height) {
void LBackend_ButtonUpdate(struct LButton* w) {
struct DrawTextArgs args;
DrawTextArgs_Make(&args, &w->text, &titleFont, true);
LBackend_MarkDirty(w);
w->_textWidth = Drawer2D_TextWidth(&args);
w->_textHeight = Drawer2D_TextHeight(&args);
@ -190,7 +228,6 @@ void LBackend_ButtonUpdate(struct LButton* w) {
void LBackend_ButtonDraw(struct LButton* w) {
struct DrawTextArgs args;
int xOffset, yOffset;
Launcher_MarkDirty(w->x, w->y, w->width, w->height);
LButton_DrawBackground(w, &Launcher_Framebuffer, w->x, w->y);
xOffset = w->width - w->_textWidth;
@ -213,9 +250,9 @@ void LBackend_ButtonDraw(struct LButton* w) {
static void LCheckbox_OnClick(void* w) {
struct LCheckbox* cb = (struct LCheckbox*)w;
cb->value = !cb->value;
LBackend_MarkDirty(cb);
LWidget_Redraw(cb);
cb->value = !cb->value;
if (cb->ValueChanged) cb->ValueChanged(cb);
}
@ -285,7 +322,6 @@ void LBackend_CheckboxDraw(struct LCheckbox* w) {
BitmapCol boxBottom = BitmapCol_Make(240, 240, 240, 255);
struct DrawTextArgs args;
int x, y, width, height;
Launcher_MarkDirty(w->x, w->y, w->width, w->height);
width = Display_ScaleX(CB_SIZE);
height = Display_ScaleY(CB_SIZE);
@ -387,24 +423,21 @@ static void LInput_DrawText(struct LInput* w, struct DrawTextArgs* args) {
}
}
static void LInput_UpdateDimensions(struct LInput* w, const cc_string* text) {
void LBackend_InputUpdate(struct LInput* w) {
cc_string text; char textBuffer[STRING_SIZE];
struct DrawTextArgs args;
int textWidth;
DrawTextArgs_Make(&args, text, &textFont, false);
String_InitArray(text, textBuffer);
LInput_UNSAFE_GetText(w, &text);
LBackend_MarkDirty(w);
DrawTextArgs_Make(&args, &text, &textFont, false);
textWidth = Drawer2D_TextWidth(&args);
w->width = max(w->minWidth, textWidth + inputExpand);
w->_textHeight = Drawer2D_TextHeight(&args);
}
void LBackend_InputUpdate(struct LInput* w) {
cc_string text; char textBuffer[STRING_SIZE];
String_InitArray(text, textBuffer);
LInput_UNSAFE_GetText(w, &text);
LInput_UpdateDimensions(w, &text);
}
void LBackend_InputDraw(struct LInput* w) {
cc_string text; char textBuffer[STRING_SIZE];
struct DrawTextArgs args;
@ -413,8 +446,6 @@ void LBackend_InputDraw(struct LInput* w) {
LInput_UNSAFE_GetText(w, &text);
DrawTextArgs_Make(&args, &text, &textFont, false);
/* TODO shouldn't be recalcing size in draw.... */
LInput_UpdateDimensions(w, &text);
LInput_DrawOuterBorder(w);
LInput_DrawInnerBorder(w);
Drawer2D_Clear(&Launcher_Framebuffer, BITMAPCOL_WHITE,
@ -523,18 +554,16 @@ void LBackend_InputSelect(struct LInput* w, int idx, cc_bool wasSelected) {
struct OpenKeyboardArgs args;
caretStart = DateTime_CurrentUTC_MS();
LInput_MoveCaretToCursor(w, idx);
/* TODO: Only draw outer border */
if (wasSelected) return;
LWidget_Draw(w);
LBackend_MarkDirty(w);
OpenKeyboardArgs_Init(&args, &w->text, w->inputType);
Window_OpenKeyboard(&args);
}
void LBackend_InputUnselect(struct LInput* w) {
caretStart = 0;
/* TODO: Only draw outer border */
LWidget_Draw(w);
LBackend_MarkDirty(w);
Window_CloseKeyboard();
}
@ -548,6 +577,7 @@ void LBackend_LabelInit(struct LLabel* w) { }
void LBackend_LabelUpdate(struct LLabel* w) {
struct DrawTextArgs args;
DrawTextArgs_Make(&args, &w->text, LLabel_GetFont(w), true);
LBackend_MarkDirty(w);
w->width = Drawer2D_TextWidth(&args);
w->height = Drawer2D_TextHeight(&args);
@ -583,7 +613,7 @@ void LBackend_SliderInit(struct LSlider* w, int width, int height) {
}
void LBackend_SliderUpdate(struct LSlider* w) {
LWidget_Draw(w);
LBackend_MarkDirty(w);
}
static void LSlider_DrawBoxBounds(struct LSlider* w) {
@ -653,7 +683,7 @@ void LBackend_TableReposition(struct LTable* w) {
void LBackend_TableFlagAdded(struct LTable* w) {
/* TODO: Only redraw flags */
LWidget_Draw(w);
LBackend_MarkDirty(w);
}
/* Draws background behind column headers */
@ -887,7 +917,7 @@ void LBackend_TableMouseDown(struct LTable* w, int idx) {
} else {
LTable_RowsClick(w, idx);
}
LWidget_Draw(w);
LBackend_MarkDirty(w);
}
void LBackend_TableMouseMove(struct LTable* w, int idx) {
@ -902,7 +932,7 @@ void LBackend_TableMouseMove(struct LTable* w, int idx) {
w->topRow = row;
LTable_ClampTopRow(w);
LWidget_Draw(w);
LBackend_MarkDirty(w);
} else if (w->draggingColumn >= 0) {
col = w->draggingColumn;
width = x - w->dragXStart;
@ -914,7 +944,7 @@ void LBackend_TableMouseMove(struct LTable* w, int idx) {
Math_Clamp(width, cellMinWidth, maxW - cellMinWidth);
if (width == w->columns[col].width) return;
w->columns[col].width = width;
LWidget_Draw(w);
LBackend_MarkDirty(w);
}
}

View File

@ -24,6 +24,7 @@ void LBackend_CloseScreen(struct LScreen* s);
void LBackend_Redraw(void);
void LBackend_Tick(void);
void LBackend_WidgetRepositioned(struct LWidget* w);
void LBackend_MarkDirty(void* widget);
void LBackend_ButtonInit(struct LButton* w, int width, int height);
void LBackend_ButtonUpdate(struct LButton* w);

View File

@ -545,12 +545,6 @@ static void DirectConnectScreen_UrlFilter(cc_string* str) {
str->length = 0;
}
CC_NOINLINE static void DirectConnectScreen_SetStatus(const char* text) {
struct LLabel* w = &DirectConnectScreen.lblStatus;
LLabel_SetConst(w, text);
LWidget_Redraw(w);
}
static void DirectConnectScreen_Load(struct DirectConnectScreen* s) {
cc_string addr; char addrBuffer[STRING_SIZE];
cc_string mppass; char mppassBuffer[STRING_SIZE];
@ -580,13 +574,14 @@ static void DirectConnectScreen_StartClient(void* w) {
const cc_string* user = &DirectConnectScreen.iptUsername.text;
const cc_string* addr = &DirectConnectScreen.iptAddress.text;
const cc_string* mppass = &DirectConnectScreen.iptMppass.text;
struct LLabel* status = &DirectConnectScreen.lblStatus;
cc_string ip, port;
cc_uint16 raw_port;
int index = String_LastIndexOf(addr, ':');
if (index == 0 || index == addr->length - 1) {
DirectConnectScreen_SetStatus("&eInvalid address"); return;
LLabel_SetConst(status, "&eInvalid address"); return;
}
/* support either "[IP]" or "[IP]:[PORT]" */
@ -599,13 +594,13 @@ static void DirectConnectScreen_StartClient(void* w) {
}
if (!user->length) {
DirectConnectScreen_SetStatus("&eUsername required"); return;
LLabel_SetConst(status, "&eUsername required"); return;
}
if (!Socket_ValidAddress(&ip)) {
DirectConnectScreen_SetStatus("&eInvalid ip"); return;
LLabel_SetConst(status, "&eInvalid ip"); return;
}
if (!Convert_ParseUInt16(&port, &raw_port)) {
DirectConnectScreen_SetStatus("&eInvalid port"); return;
LLabel_SetConst(status, "&eInvalid port"); return;
}
if (!mppass->length) mppass = &defMppass;
@ -614,7 +609,7 @@ static void DirectConnectScreen_StartClient(void* w) {
Options_Set("launcher-dc-port", &port);
Options_SetSecure("launcher-dc-mppass", mppass);
DirectConnectScreen_SetStatus("");
LLabel_SetConst(status, "");
Launcher_StartGame(user, mppass, &ip, &port, &String_Empty);
}
@ -787,7 +782,6 @@ CC_NOINLINE static void MainScreen_Error(struct LWebTask* task, const char* acti
LWebTask_DisplayError(task, action, &str);
LLabel_SetText(&s->lblStatus, &str);
LWidget_Redraw(&s->lblStatus);
s->signingIn = false;
}
@ -797,12 +791,10 @@ static void MainScreen_DoLogin(void) {
cc_string* pass = &s->iptPassword.text;
if (!user->length) {
LLabel_SetConst(&s->lblStatus, "&eUsername required");
LWidget_Redraw(&s->lblStatus); return;
LLabel_SetConst(&s->lblStatus, "&eUsername required"); return;
}
if (!pass->length) {
LLabel_SetConst(&s->lblStatus, "&ePassword required");
LWidget_Redraw(&s->lblStatus); return;
LLabel_SetConst(&s->lblStatus, "&ePassword required"); return;
}
if (GetTokenTask.Base.working) return;
@ -811,7 +803,6 @@ static void MainScreen_DoLogin(void) {
GetTokenTask_Run();
LLabel_SetConst(&s->lblStatus, "&eSigning in..");
LWidget_Redraw(&s->lblStatus);
s->signingIn = true;
}
static void MainScreen_Login(void* w) { MainScreen_DoLogin(); }
@ -855,9 +846,7 @@ static void MainScreen_ResumeHover(void* w) {
} else {
String_Format3(&str, "&eResume as %s to %s:%s", &info.user, &info.ip, &info.port);
}
LLabel_SetText(&s->lblStatus, &str);
LWidget_Redraw(&s->lblStatus);
}
static void MainScreen_ResumeUnhover(void* w) {
@ -865,7 +854,6 @@ static void MainScreen_ResumeUnhover(void* w) {
if (s->signingIn) return;
LLabel_SetConst(&s->lblStatus, "");
LWidget_Redraw(&s->lblStatus);
}
static void MainScreen_Init(struct LScreen* s_) {
@ -964,20 +952,17 @@ static void MainScreen_TickCheckUpdates(struct MainScreen* s) {
} else {
LLabel_SetConst(&s->lblUpdate, "&cCheck failed");
}
LWidget_Redraw(&s->lblUpdate);
}
static void MainScreen_LoginPhase2(struct MainScreen* s, const cc_string* user) {
/* website returns case correct username */
if (!String_Equals(&s->iptUsername.text, user)) {
LInput_SetText(&s->iptUsername, user);
LWidget_Redraw(&s->iptUsername);
}
String_Copy(&Launcher_Username, user);
FetchServersTask_Run();
LLabel_SetConst(&s->lblStatus, "&eRetrieving servers list..");
LWidget_Redraw(&s->lblStatus);
}
static void MainScreen_TickGetToken(struct MainScreen* s) {
@ -1008,7 +993,6 @@ static void MainScreen_TickSignIn(struct MainScreen* s) {
if (SignInTask.error) {
LLabel_SetConst(&s->lblStatus, SignInTask.error);
LWidget_Redraw(&s->lblStatus);
} else if (SignInTask.Base.success) {
MainScreen_LoginPhase2(s, &SignInTask.username);
} else {
@ -1191,10 +1175,10 @@ static void FetchResourcesScreen_Layout(struct LScreen* s_) {
static void FetchResourcesScreen_SetStatus(struct FetchResourcesScreen* s, const cc_string* str) {
struct LLabel* w = &s->lblStatus;
/* TODO FIXY FIXY FIXY */ w->opaque = true; /* need to rethink ResetArea for Launcher */
CheckResourcesScreen_ResetArea(w->last.X, w->last.Y,
w->last.Width, w->last.Height);
LLabel_SetText(w, str);
LWidget_Draw(w);
}
static void FetchResourcesScreen_UpdateStatus(struct FetchResourcesScreen* s, int reqID) {
@ -1293,7 +1277,6 @@ static void ServersScreen_Refresh(void* w) {
FetchServersTask_Run();
btn = &ServersScreen.btnRefresh;
LButton_SetConst(btn, "&eWorking..");
LWidget_Redraw(btn);
}
static void ServersScreen_HashFilter(cc_string* str) {
@ -1313,19 +1296,19 @@ static void ServersScreen_HashFilter(cc_string* str) {
static void ServersScreen_SearchChanged(struct LInput* w) {
struct ServersScreen* s = &ServersScreen;
LTable_ApplyFilter(&s->table);
LWidget_Draw(&s->table);
LBackend_MarkDirty(&s->table);
}
static void ServersScreen_HashChanged(struct LInput* w) {
struct ServersScreen* s = &ServersScreen;
LTable_ShowSelected(&s->table);
LWidget_Draw(&s->table);
LBackend_MarkDirty(&s->table);
}
static void ServersScreen_OnSelectedChanged(void) {
struct ServersScreen* s = &ServersScreen;
LWidget_Redraw(&s->iptHash);
LWidget_Draw(&s->table);
LBackend_MarkDirty(&s->iptHash);
LBackend_MarkDirty(&s->table);
}
static void ServersScreen_ReloadServers(struct ServersScreen* s) {
@ -1394,12 +1377,11 @@ static void ServersScreen_Tick(struct LScreen* s_) {
if (FetchServersTask.Base.success) {
ServersScreen_ReloadServers(s);
LWidget_Draw(&s->table);
LBackend_MarkDirty(&s->table);
}
LButton_SetConst(&s->btnRefresh,
FetchServersTask.Base.success ? "Refresh" : "&cFailed");
LWidget_Redraw(&s->btnRefresh);
}
static void ServersScreen_Free(struct LScreen* s_) {
@ -1718,7 +1700,6 @@ static void UpdatesScreen_Format(struct LLabel* lbl, const char* prefix, cc_uint
UpdatesScreen_FormatTime(&str, delta);
}
LLabel_SetText(lbl, &str);
LWidget_Redraw(lbl);
}
static void UpdatesScreen_FormatBoth(struct UpdatesScreen* s) {
@ -1749,7 +1730,6 @@ static void UpdatesScreen_DoFetch(struct UpdatesScreen* s) {
UpdatesScreen_UpdateHeader(s, &str);
String_AppendConst(&str, "..");
LLabel_SetText(&s->lblStatus, &str);
LWidget_Redraw(&s->lblStatus);
}
static void UpdatesScreen_Get(cc_bool release, int buildIndex) {
@ -1785,7 +1765,6 @@ static void UpdatesScreen_UpdateProgress(struct UpdatesScreen* s, struct LWebTas
UpdatesScreen_UpdateHeader(s, &str);
String_Format1(&str, " &a%i%%", &s->buildProgress);
LLabel_SetText(&s->lblStatus, &str);
LWidget_Redraw(&s->lblStatus);
}
static void UpdatesScreen_FetchTick(struct UpdatesScreen* s) {
@ -1800,7 +1779,6 @@ static void UpdatesScreen_FetchTick(struct UpdatesScreen* s) {
String_InitArray(str, strBuffer);
LWebTask_DisplayError(&FetchUpdateTask.Base, "fetching update", &str);
LLabel_SetText(&s->lblStatus, &str);
LWidget_Redraw(&s->lblStatus);
} else {
/* FetchUpdateTask handles saving the updated file for us */
Launcher_ShouldExit = true;

View File

@ -40,21 +40,6 @@ void LWidget_CalcPosition(void* widget) {
LBackend_WidgetRepositioned(w);
}
void LWidget_Draw(void* widget) {
struct LWidget* w = (struct LWidget*)widget;
w->last.X = w->x; w->last.Width = w->width;
w->last.Y = w->y; w->last.Height = w->height;
w->VTABLE->Draw(w);
Launcher_MarkDirty(w->x, w->y, w->width, w->height);
}
void LWidget_Redraw(void* widget) {
struct LWidget* w = (struct LWidget*)widget;
Launcher_ResetArea(w->last.X, w->last.Y, w->last.Width, w->last.Height);
LWidget_Draw(w);
}
/*########################################################################################################################*
*------------------------------------------------------ButtonWidget-------------------------------------------------------*
@ -132,14 +117,18 @@ static void LButton_Draw(void* widget) {
static void LButton_Hover(void* w, int idx, cc_bool wasOver) {
/* only need to redraw when changing from unhovered to hovered */
if (!wasOver) LWidget_Draw(w);
if (!wasOver) LBackend_MarkDirty(w);
}
static void LButton_Unhover(void* w) {
LBackend_MarkDirty(w);
}
static const struct LWidgetVTABLE lbutton_VTABLE = {
LButton_Draw, NULL,
NULL, NULL, /* Key */
LButton_Hover, LWidget_Draw, /* Hover */
NULL, NULL /* Select */
NULL, NULL, /* Key */
LButton_Hover, LButton_Unhover, /* Hover */
NULL, NULL /* Select */
};
void LButton_Init(struct LButton* w, int width, int height, const char* text) {
w->VTABLE = &lbutton_VTABLE;
@ -218,7 +207,7 @@ static void LInput_AdvanceCaretPos(struct LInput* w, cc_bool forwards) {
w->caretPos += (forwards ? 1 : -1);
if (w->caretPos < 0 || w->caretPos >= w->text.length) w->caretPos = -1;
LWidget_Redraw(w);
LBackend_InputUpdate(w);
}
static void LInput_CopyFromClipboard(struct LInput* w) {
@ -252,7 +241,7 @@ static void LInput_Backspace(struct LInput* w) {
if (w->TextChanged) w->TextChanged(w);
LInput_ClampCaret(w);
LWidget_Redraw(w);
LBackend_InputUpdate(w);
}
/* Removes the character at the caret in the currently entered text */
@ -264,7 +253,7 @@ static void LInput_Delete(struct LInput* w) {
if (w->TextChanged) w->TextChanged(w);
LInput_ClampCaret(w);
LWidget_Redraw(w);
LBackend_InputUpdate(w);
}
static void LInput_KeyDown(void* widget, int key, cc_bool was) {
@ -315,7 +304,7 @@ static void LInput_KeyChar(void* widget, char c) {
cc_bool appended = LInput_Append(w, c);
if (appended && w->TextChanged) w->TextChanged(w);
if (appended) LWidget_Redraw(w);
if (appended) LBackend_InputUpdate(w);
}
static void LInput_TextChanged(void* widget, const cc_string* str) {
@ -335,6 +324,7 @@ static const struct LWidgetVTABLE linput_VTABLE = {
void LInput_Init(struct LInput* w, int width, const char* hintText) {
w->VTABLE = &linput_VTABLE;
w->tabSelectable = true;
w->opaque = true;
String_InitArray(w->text, w->_textBuffer);
w->hintText = hintText;
@ -362,13 +352,12 @@ void LInput_AppendString(struct LInput* w, const cc_string* str) {
}
if (appended && w->TextChanged) w->TextChanged(w);
if (appended) LWidget_Redraw(w);
if (appended) LBackend_InputUpdate(w);
}
void LInput_SetString(struct LInput* w, const cc_string* str) {
LInput_SetText(w, str);
if (w->TextChanged) w->TextChanged(w);
LWidget_Redraw(w);
}
@ -448,6 +437,7 @@ static const struct LWidgetVTABLE lslider_VTABLE = {
void LSlider_Init(struct LSlider* w, int width, int height, BitmapCol color) {
w->VTABLE = &lslider_VTABLE;
w->color = color;
w->opaque = true;
LBackend_SliderInit(w, width, height);
}
@ -613,7 +603,7 @@ static void LTable_MouseWheel(void* widget, float delta) {
struct LTable* w = (struct LTable*)widget;
w->topRow -= Utils_AccumulateWheelDelta(&w->_wheelAcc, delta);
LTable_ClampTopRow(w);
LWidget_Draw(w);
LBackend_MarkDirty(w);
w->_lastRow = -1;
}
@ -641,6 +631,7 @@ void LTable_Init(struct LTable* w, struct FontDesc* rowFont) {
w->numColumns = Array_Elems(tableColumns);
w->rowFont = rowFont;
w->sortingCol = -1;
w->opaque = true;
for (i = 0; i < w->numColumns; i++) {
w->columns[i].width = Display_ScaleX(w->columns[i].width);

View File

@ -39,6 +39,8 @@ struct LWidgetVTABLE {
cc_bool selected; /* Whether this widget is last widget to be clicked on */ \
cc_bool tabSelectable; /* Whether this widget gets selected when pressing tab */ \
cc_uint8 horAnchor, verAnchor; /* Specifies the reference point for when this widget is resized */ \
cc_bool dirty; /* Whether this widget needs to be redrawn */ \
cc_bool opaque; /* Whethe this widget completely obscures background behind it */ \
int xOffset, yOffset; /* Offset from the reference point */ \
void (*OnClick)(void* widget); /* Called when widget is clicked */ \
void (*OnHover)(void* widget); /* Called when widget is hovered over */ \
@ -50,8 +52,6 @@ struct LWidgetVTABLE {
struct LWidget { LWidget_Layout };
void LWidget_SetLocation(void* widget, cc_uint8 horAnchor, cc_uint8 verAnchor, int xOffset, int yOffset);
void LWidget_CalcPosition(void* widget);
void LWidget_Draw(void* widget);
void LWidget_Redraw(void* widget);
void LWidget_CalcOffsets(void);
struct LButton {