#include "LBackend.h" #if defined CC_BUILD_WEB /* Web backend doesn't use the launcher */ #elif defined CC_BUILD_WIN_TEST /* Testing windows UI backend */ #include "LBackend_Win.c" #elif defined CC_BUILD_IOS /* iOS uses custom UI backend */ #else #include "Launcher.h" #include "Drawer2D.h" #include "Window.h" #include "LWidgets.h" #include "String.h" #include "Gui.h" #include "Drawer2D.h" #include "Launcher.h" #include "ExtMath.h" #include "Window.h" #include "Funcs.h" #include "LWeb.h" #include "Platform.h" #include "LScreens.h" #include "Input.h" #include "Utils.h" #include "Event.h" #include "Stream.h" #include "Logger.h" #include "Errors.h" struct FontDesc titleFont, textFont, hintFont, logoFont, rowFont; /* Contains the pixels that are drawn to the window */ static struct Context2D framebuffer; /* The area/region of the window that needs to be redrawn and presented to the screen. */ /* If width is 0, means no area needs to be redrawn. */ Rect2D dirty_rect; static cc_uint8 pendingRedraw; #define REDRAW_ALL 0x02 #define REDRAW_SOME 0x01 static int xBorder, xBorder2, xBorder3, xBorder4; static int yBorder, yBorder2, yBorder3, yBorder4; static int xInputOffset, yInputOffset, inputExpand; static int caretOffset, caretWidth, caretHeight; static int scrollbarWidth, dragPad, gridlineWidth, gridlineHeight; static int hdrYOffset, hdrYPadding, rowYOffset, rowYPadding; static int cellXOffset, cellXPadding, cellMinWidth; static int flagXOffset, flagYOffset; static void HookEvents(void); void LBackend_Init(void) { xBorder = Display_ScaleX(1); yBorder = Display_ScaleY(1); if (xBorder < 1) { xBorder = 1; } if (yBorder < 1) { yBorder = 1; } xBorder2 = xBorder * 2; xBorder3 = xBorder * 3; xBorder4 = xBorder * 4; yBorder2 = yBorder * 2; yBorder3 = yBorder * 3; yBorder4 = yBorder * 4; xInputOffset = Display_ScaleX(5); yInputOffset = Display_ScaleY(2); inputExpand = Display_ScaleX(20); caretOffset = Display_ScaleY(5); caretWidth = Display_ScaleX(10); caretHeight = Display_ScaleY(2); scrollbarWidth = Display_ScaleX(10); dragPad = Display_ScaleX(8); gridlineWidth = Display_ScaleX(2); gridlineHeight = Display_ScaleY(2); hdrYOffset = Display_ScaleY(3); hdrYPadding = Display_ScaleY(5); rowYOffset = Display_ScaleY(3); rowYPadding = Display_ScaleY(1); cellXOffset = Display_ScaleX(6); cellXPadding = Display_ScaleX(5); cellMinWidth = Display_ScaleX(20); flagXOffset = Display_ScaleX(2); flagYOffset = Display_ScaleY(6); Font_Make(&titleFont, 16, FONT_FLAGS_BOLD); Font_Make(&textFont, 14, FONT_FLAGS_NONE); Font_Make(&hintFont, 12, FONT_FLAGS_NONE); HookEvents(); } void LBackend_Free(void) { Font_Free(&titleFont); Font_Free(&textFont); Font_Free(&hintFont); Font_Free(&logoFont); Font_Free(&rowFont); } void LBackend_UpdateTitleFont(void) { Font_Free(&logoFont); Launcher_MakeTitleFont(&logoFont); } void LBackend_DrawTitle(struct Context2D* ctx, const char* title) { Launcher_DrawTitle(&logoFont, title, ctx); } /* Scales up flag bitmap if necessary */ static void LBackend_ScaleFlag(struct Bitmap* bmp) { struct Bitmap scaled; int width = Display_ScaleX(bmp->width); int height = Display_ScaleY(bmp->height); /* at default DPI don't need to rescale it */ if (width == bmp->width && height == bmp->height) return; Bitmap_TryAllocate(&scaled, width, height); if (!scaled.scan0) { Logger_SysWarn(ERR_OUT_OF_MEMORY, "resizing flags bitmap"); return; } Bitmap_Scale(&scaled, bmp, 0, 0, bmp->width, bmp->height); Mem_Free(bmp->scan0); *bmp = scaled; } void LBackend_DecodeFlag(struct Flag* flag, cc_uint8* data, cc_uint32 len) { struct Stream s; cc_result res; Stream_ReadonlyMemory(&s, data, len); res = Png_Decode(&flag->bmp, &s); if (res) Logger_SysWarn(res, "decoding flag"); flag->meta = NULL; LBackend_ScaleFlag(&flag->bmp); } static void OnPointerMove(void* obj, int idx); void LBackend_SetScreen(struct LScreen* s) { int i; /* for hovering over active button etc */ for (i = 0; i < Pointers_Count; i++) { OnPointerMove(s, i); } } void LBackend_CloseScreen(struct LScreen* s) { } static void LBackend_LayoutDimensions(struct LWidget* w) { const struct LLayout* l = w->layouts + 2; while (l->type) { switch (l->type) { case LLAYOUT_WIDTH: w->width = Window_Main.Width - w->x - Display_ScaleX(l->offset); w->width = max(1, w->width); break; case LLAYOUT_HEIGHT: w->height = Window_Main.Height - w->y - Display_ScaleY(l->offset); w->height = max(1, w->height); break; } l++; } } void LBackend_LayoutWidget(struct LWidget* w) { const struct LLayout* l = w->layouts; w->x = Gui_CalcPos(l[0].type & 0xFF, Display_ScaleX(l[0].offset), w->width, Window_Main.Width); w->y = Gui_CalcPos(l[1].type & 0xFF, Display_ScaleY(l[1].offset), w->height, Window_Main.Height); /* e.g. Table widget needs adjusts width/height based on window */ if (l[1].type & LLAYOUT_EXTRA) LBackend_LayoutDimensions(w); if (w->type != LWIDGET_TABLE) return; LBackend_TableReposition((struct LTable*)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 = framebuffer.width; dirty_rect.y = 0; dirty_rect.height = framebuffer.height; } /* Marks the given area/region as needing to be redrawn. */ static CC_NOINLINE void MarkAreaDirty(int x, int y, int width, int height) { int x1, y1, x2, y2; if (!Drawer2D_Clamp(&framebuffer, &x, &y, &width, &height)) return; /* union with existing dirty area */ if (dirty_rect.width) { x1 = min(x, dirty_rect.x); y1 = min(y, dirty_rect.y); x2 = max(x + width, dirty_rect.x + dirty_rect.width); y2 = max(y + height, dirty_rect.y + dirty_rect.height); x = x1; width = x2 - x1; y = y1; height = y2 - y1; } dirty_rect.x = x; dirty_rect.width = width; dirty_rect.y = y; dirty_rect.height = height; } void LBackend_InitFramebuffer(void) { struct Bitmap bmp; int width = max(Window_Main.Width, 1); int height = max(Window_Main.Height, 1); Window_AllocFramebuffer(&bmp, width, height); Context2D_Wrap(&framebuffer, &bmp); /* Backing surface may be bigger then valid area */ framebuffer.width = width; framebuffer.height = height; } void LBackend_FreeFramebuffer(void) { Window_FreeFramebuffer(&framebuffer.bmp); } /*########################################################################################################################* *------------------------------------------------------Base drawing-------------------------------------------------------* *#########################################################################################################################*/ static void DrawBoxBounds(BitmapCol color, int x, int y, int width, int height) { Context2D_Clear(&framebuffer, color, x, y, width, yBorder); Context2D_Clear(&framebuffer, color, x, y + height - yBorder, width, yBorder); Context2D_Clear(&framebuffer, color, x, y, xBorder, height); Context2D_Clear(&framebuffer, color, x + width - xBorder, y, 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); MarkAreaDirty(w->x, w->y, w->width, w->height); } static CC_NOINLINE void RedrawAll(void) { struct LScreen* s = Launcher_Active; int i; s->DrawBackground(s, &framebuffer); for (i = 0; i < s->numWidgets; i++) { DrawWidget(s->widgets[i]); } MarkAllDirty(); } static CC_NOINLINE void RedrawDirty(void) { struct LScreen* s = Launcher_Active; 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) { s->ResetArea(&framebuffer, w->last.x, w->last.y, w->last.width, w->last.height); MarkAreaDirty(w->last.x, w->last.y, w->last.width, w->last.height); } DrawWidget(w); } } static CC_NOINLINE void DoRedraw(void) { if (pendingRedraw & REDRAW_ALL) { RedrawAll(); pendingRedraw = 0; } else if (pendingRedraw & REDRAW_SOME) { RedrawDirty(); pendingRedraw = 0; } } void LBackend_Redraw(void) { pendingRedraw = REDRAW_ALL; MarkAllDirty(); } void LBackend_ThemeChanged(void) { LBackend_Redraw(); } void LBackend_Tick(void) { DoRedraw(); if (!dirty_rect.width) return; OnscreenKeyboard_Draw2D(&dirty_rect, &framebuffer.bmp); Window_DrawFramebuffer(dirty_rect, &framebuffer.bmp); dirty_rect.x = 0; dirty_rect.width = 0; dirty_rect.y = 0; dirty_rect.height = 0; } /*########################################################################################################################* *-----------------------------------------------------Event handling------------------------------------------------------* *#########################################################################################################################*/ static void ReqeustRedraw(void* obj) { LBackend_Redraw(); } static void RedrawContents(void* obj) { DoRedraw(); } CC_NOINLINE static struct LWidget* GetWidgetAt(struct LScreen* s, int idx) { struct LWidget* w; int i, x = Pointers[idx].x, y = Pointers[idx].y; for (i = 0; i < s->numWidgets; i++) { w = s->widgets[i]; if (Gui_Contains(w->x, w->y, w->width, w->height, x, y)) return w; } return NULL; } static void OnPointerDown(void* obj, int idx) { struct LScreen* s = Launcher_Active; struct LWidget* over; struct LWidget* prev; if (Window_Main.SoftKeyboardFocus) return; if (!s) return; over = GetWidgetAt(s, idx); prev = s->selectedWidget; if (prev && over != prev) LScreen_UnselectWidget(s, idx, prev); if (over) LScreen_SelectWidget(s, idx, over, over == prev); } static void OnPointerUp(void* obj, int idx) { struct LScreen* s = Launcher_Active; struct LWidget* over; struct LWidget* prev; if (Window_Main.SoftKeyboardFocus) return; if (!s) return; over = GetWidgetAt(s, idx); prev = s->selectedWidget; /* if user moves mouse away, it doesn't count */ if (over != prev) { LScreen_UnselectWidget(s, idx, prev); } else if (over && over->OnClick) { over->OnClick(over); } /* TODO eliminate this hack */ s->MouseUp(s, idx); } static void OnPointerMove(void* obj, int idx) { struct LScreen* s = Launcher_Active; struct LWidget* over; struct LWidget* prev; cc_bool overSame; if (Window_Main.SoftKeyboardFocus) return; if (!s) return; over = GetWidgetAt(s, idx); prev = s->hoveredWidget; overSame = prev == over; if (prev && !overSame) { prev->hovered = false; s->hoveredWidget = NULL; if (prev->OnUnhover) prev->OnUnhover(prev); if (prev->VTABLE->MouseLeft) prev->VTABLE->MouseLeft(prev); } if (over) { over->hovered = true; s->hoveredWidget = over; if (over->OnHover) over->OnHover(over); if (!over->VTABLE->MouseMove) return; over->VTABLE->MouseMove(over, idx, overSame); } } static void OnKeyPress(void* obj, int cp) { struct LWidget* selected; char c; if (!Convert_TryCodepointToCP437(cp, &c)) return; selected = Launcher_Active->selectedWidget; if (!selected) return; if (!selected->VTABLE->KeyPress) return; selected->VTABLE->KeyPress(selected, c); } static void OnTextChanged(void* obj, const cc_string* str) { struct LWidget* selected = Launcher_Active->selectedWidget; if (!selected) return; if (!selected->VTABLE->TextChanged) return; selected->VTABLE->TextChanged(selected, str); } static void HookEvents(void) { Event_Register_(&PointerEvents.Down, NULL, OnPointerDown); Event_Register_(&PointerEvents.Up, NULL, OnPointerUp); Event_Register_(&PointerEvents.Moved, NULL, OnPointerMove); Event_Register_(&InputEvents.Press, NULL, OnKeyPress); Event_Register_(&InputEvents.TextChanged, NULL, OnTextChanged); Event_Register_(&WindowEvents.RedrawNeeded, NULL, ReqeustRedraw); Event_Register_(&WindowEvents.Redrawing, NULL, RedrawContents); } /*########################################################################################################################* *------------------------------------------------------ButtonWidget-------------------------------------------------------* *#########################################################################################################################*/ void LBackend_ButtonInit(struct LButton* w, int width, int height) { w->width = Display_ScaleX(width); w->height = Display_ScaleY(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); } void LBackend_ButtonDraw(struct LButton* w) { struct DrawTextArgs args; int xOffset, yOffset; cc_bool active = w->hovered || w->selected; LButton_DrawBackground(&framebuffer, w->x, w->y, w->width, w->height, active); xOffset = w->width - w->_textWidth; yOffset = w->height - w->_textHeight; DrawTextArgs_Make(&args, &w->text, &titleFont, true); if (!active) Drawer2D.Colors['f'] = Drawer2D.Colors['7']; Context2D_DrawText(&framebuffer, &args, w->x + xOffset / 2, w->y + yOffset / 2); if (!active) Drawer2D.Colors['f'] = Drawer2D.Colors['F']; } /*########################################################################################################################* *-----------------------------------------------------CheckboxWidget------------------------------------------------------* *#########################################################################################################################*/ #define CB_SIZE 24 #define CB_OFFSET 8 static void LCheckbox_OnClick(void* w) { struct LCheckbox* cb = (struct LCheckbox*)w; LBackend_MarkDirty(cb); cb->value = !cb->value; if (cb->ValueChanged) cb->ValueChanged(cb); } void LBackend_CheckboxInit(struct LCheckbox* w) { struct DrawTextArgs args; DrawTextArgs_Make(&args, &w->text, &textFont, true); w->width = Display_ScaleX(CB_SIZE + CB_OFFSET) + Drawer2D_TextWidth(&args); w->height = Display_ScaleY(CB_SIZE); w->OnClick = LCheckbox_OnClick; } void LBackend_CheckboxUpdate(struct LCheckbox* w) { LBackend_MarkDirty(w); } /* Based off checkbox from original ClassiCube Launcher */ static const cc_uint8 checkbox_indices[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x06, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x06, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x06, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0D, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x06, 0x10, 0x00, 0x11, 0x06, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x14, 0x15, 0x00, 0x16, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x06, 0x19, 0x06, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x06, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1D, 0x06, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; static const BitmapCol checkbox_palette[] = { BitmapCol_Make(0,0,0,0), BitmapColor_RGB(144, 144, 144), BitmapColor_RGB( 61, 61, 61), BitmapColor_RGB( 94, 94, 94), BitmapColor_RGB(197, 196, 197), BitmapColor_RGB( 57, 57, 57), BitmapColor_RGB( 33, 33, 33), BitmapColor_RGB(177, 177, 177), BitmapColor_RGB(189, 189, 189), BitmapColor_RGB( 67, 67, 67), BitmapColor_RGB(108, 108, 108), BitmapColor_RGB(171, 171, 171), BitmapColor_RGB(220, 220, 220), BitmapColor_RGB( 43, 43, 43), BitmapColor_RGB( 63, 63, 63), BitmapColor_RGB(100, 100, 100), BitmapColor_RGB(192, 192, 192), BitmapColor_RGB(132, 132, 132), BitmapColor_RGB(175, 175, 175), BitmapColor_RGB(217, 217, 217), BitmapColor_RGB( 42, 42, 42), BitmapColor_RGB( 86, 86, 86), BitmapColor_RGB( 56, 56, 56), BitmapColor_RGB( 76, 76, 76), BitmapColor_RGB(139, 139, 139), BitmapColor_RGB(130, 130, 130), BitmapColor_RGB(181, 181, 181), BitmapColor_RGB( 62, 62, 62), BitmapColor_RGB( 75, 75, 75), BitmapColor_RGB(184, 184, 184), }; static void DrawIndexed(int size, int x, int y, struct Context2D* ctx) { struct Bitmap* bmp = (struct Bitmap*)ctx; BitmapCol* row, color; int i, xx, yy; for (i = 0, yy = 0; yy < size; yy++) { if ((y + yy) < 0) { i += size; continue; } if ((y + yy) >= bmp->height) break; row = Bitmap_GetRow(bmp, y + yy); for (xx = 0; xx < size; xx++) { color = checkbox_palette[checkbox_indices[i++]]; if (color == 0) continue; /* transparent pixel */ if ((x + xx) < 0 || (x + xx) >= bmp->width) continue; row[x + xx] = color; } } } void LBackend_CheckboxDraw(struct LCheckbox* w) { BitmapCol boxTop = BitmapColor_RGB(255, 255, 255); BitmapCol boxBottom = BitmapColor_RGB(240, 240, 240); struct DrawTextArgs args; int x, y, width, height; width = Display_ScaleX(CB_SIZE); height = Display_ScaleY(CB_SIZE); Gradient_Vertical(&framebuffer, boxTop, boxBottom, w->x, w->y, width, height / 2); Gradient_Vertical(&framebuffer, boxBottom, boxTop, w->x, w->y + height / 2, width, height / 2); if (w->value) { const int size = 12; x = w->x + width / 2 - size / 2; y = w->y + height / 2 - size / 2; DrawIndexed(size, x, y, &framebuffer); } DrawBoxBounds(BITMAPCOLOR_BLACK, w->x, w->y, width, height); DrawTextArgs_Make(&args, &w->text, &textFont, true); x = w->x + Display_ScaleX(CB_SIZE + CB_OFFSET); y = w->y + (height - Drawer2D_TextHeight(&args)) / 2; Context2D_DrawText(&framebuffer, &args, x, y); } /*########################################################################################################################* *------------------------------------------------------InputWidget--------------------------------------------------------* *#########################################################################################################################*/ static cc_uint64 caretStart; static Rect2D caretRect, lastCaretRect; #define Rect2D_Equals(a, b) a.x == b.x && a.y == b.y && a.width == b.width && a.height == b.height void LBackend_InputInit(struct LInput* w, int width) { w->width = Display_ScaleX(width); w->height = Display_ScaleY(LINPUT_HEIGHT); w->minWidth = w->width; /* Text may end up being wider than minimum width */ if (w->text.length) LBackend_InputUpdate(w); } void LBackend_InputUpdate(struct LInput* w) { cc_string text; char textBuffer[STRING_SIZE]; struct DrawTextArgs args; int textWidth; 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); } static Rect2D LInput_MeasureCaret(struct LInput* w, cc_string* text) { struct DrawTextArgs args; Rect2D r; DrawTextArgs_Make(&args, text, &textFont, true); r.x = w->x + xInputOffset; r.y = w->y + w->height - caretOffset; r.height = caretHeight; if (w->caretPos == -1) { r.x += Drawer2D_TextWidth(&args); r.width = caretWidth; } else { args.text = String_UNSAFE_Substring(text, 0, w->caretPos); r.x += Drawer2D_TextWidth(&args); args.text = String_UNSAFE_Substring(text, w->caretPos, 1); r.width = Drawer2D_TextWidth(&args); } return r; } static void LInput_MoveCaretToCursor(struct LInput* w, int idx) { cc_string text; char textBuffer[STRING_SIZE]; int x = Pointers[idx].x, y = Pointers[idx].y; struct DrawTextArgs args; int i, charX, charWidth; /* Input widget may have been selected by pressing tab */ /* In which case cursor is completely outside, so ignore */ if (!Gui_Contains(w->x, w->y, w->width, w->height, x, y)) return; String_InitArray(text, textBuffer); LInput_UNSAFE_GetText(w, &text); x -= w->x; y -= w->y; DrawTextArgs_Make(&args, &text, &textFont, true); if (x >= Drawer2D_TextWidth(&args)) { w->caretPos = -1; return; } for (i = 0; i < text.length; i++) { args.text = String_UNSAFE_Substring(&text, 0, i); charX = Drawer2D_TextWidth(&args); args.text = String_UNSAFE_Substring(&text, i, 1); charWidth = Drawer2D_TextWidth(&args); if (x >= charX && x < charX + charWidth) { w->caretPos = i; return; } } } void LBackend_InputTick(struct LInput* w) { int elapsed; cc_bool caretShow; Rect2D r; if (!caretStart) return; elapsed = Stopwatch_ElapsedMS(caretStart, Stopwatch_Measure()); caretShow = (elapsed % 1000) < 500; if (caretShow == w->caretShow) return; w->caretShow = caretShow; LBackend_InputDraw(w); r = caretRect; if (Rect2D_Equals(r, lastCaretRect)) { /* Fast path, caret is blinking in same spot */ MarkAreaDirty(r.x, r.y, r.width, r.height); } else { /* Slow path (new widget, caret moved, etc) */ MarkAreaDirty(w->x, w->y, w->width, w->height); } lastCaretRect = r; } void LBackend_InputSelect(struct LInput* w, int idx, cc_bool wasSelected) { struct OpenKeyboardArgs args; caretStart = Stopwatch_Measure(); w->caretShow = true; LInput_MoveCaretToCursor(w, idx); LBackend_MarkDirty(w); if (wasSelected) return; OpenKeyboardArgs_Init(&args, &w->text, w->inputType); OnscreenKeyboard_Open(&args); } void LBackend_InputUnselect(struct LInput* w) { caretStart = 0; w->caretShow = false; LBackend_MarkDirty(w); OnscreenKeyboard_Close(); } static void LInput_DrawOuterBorder(struct LInput* w) { struct LScreen* s = Launcher_Active; struct Context2D* ctx = &framebuffer; BitmapCol color = Launcher_Theme.ButtonBorderColor; if (w->selected) { DrawBoxBounds(color, w->x, w->y, w->width, w->height); } else { s->ResetArea(ctx, w->x, w->y, w->width, yBorder); s->ResetArea(ctx, w->x, w->y + w->height - yBorder, w->width, yBorder); s->ResetArea(ctx, w->x, w->y, xBorder, w->height); s->ResetArea(ctx, w->x + w->width - xBorder, w->y, xBorder, w->height); } } static void LInput_DrawInnerBorder(struct LInput* w) { /* e.g. for modern theme: 162,131,186 --> 165,142,168 */ BitmapCol color = BitmapColor_Offset(Launcher_Theme.ButtonHighlightColor, 3,11,-18); Context2D_Clear(&framebuffer, color, w->x + xBorder, w->y + yBorder, w->width - xBorder2, yBorder); Context2D_Clear(&framebuffer, color, w->x + xBorder, w->y + w->height - yBorder2, w->width - xBorder2, yBorder); Context2D_Clear(&framebuffer, color, w->x + xBorder, w->y + yBorder, xBorder, w->height - yBorder2); Context2D_Clear(&framebuffer, color, w->x + w->width - xBorder2, w->y + yBorder, xBorder, w->height - yBorder2); } static void LInput_BlendBoxTop(struct LInput* w) { BitmapCol color = BitmapColor_RGB(0, 0, 0); Gradient_Blend(&framebuffer, color, 75, w->x + xBorder, w->y + yBorder, w->width - xBorder2, yBorder); Gradient_Blend(&framebuffer, color, 50, w->x + xBorder, w->y + yBorder2, w->width - xBorder2, yBorder); Gradient_Blend(&framebuffer, color, 25, w->x + xBorder, w->y + yBorder3, w->width - xBorder2, yBorder); } static void LInput_DrawText(struct LInput* w, struct DrawTextArgs* args) { int y, hintHeight; if (w->text.length || !w->hintText) { y = w->y + (w->height - w->_textHeight) / 2; Context2D_DrawText(&framebuffer, args, w->x + xInputOffset, y + yInputOffset); } else { args->text = String_FromReadonly(w->hintText); args->font = &hintFont; hintHeight = Drawer2D_TextHeight(args); y = w->y + (w->height - hintHeight) / 2; Drawer2D.Colors['f'] = BitmapColor_RGB(125, 125, 125); Context2D_DrawText(&framebuffer, args, w->x + xInputOffset, y); Drawer2D.Colors['f'] = BITMAPCOLOR_WHITE; } } void LBackend_InputDraw(struct LInput* w) { cc_string text; char textBuffer[STRING_SIZE]; struct DrawTextArgs args; String_InitArray(text, textBuffer); LInput_UNSAFE_GetText(w, &text); DrawTextArgs_Make(&args, &text, &textFont, false); LInput_DrawOuterBorder(w); LInput_DrawInnerBorder(w); Context2D_Clear(&framebuffer, BITMAPCOLOR_WHITE, w->x + xBorder2, w->y + yBorder2, w->width - xBorder4, w->height - yBorder4); LInput_BlendBoxTop(w); Drawer2D.Colors['f'] = Drawer2D.Colors['0']; LInput_DrawText(w, &args); Drawer2D.Colors['f'] = Drawer2D.Colors['F']; caretRect = LInput_MeasureCaret(w, &text); if (!w->caretShow) return; Context2D_Clear(&framebuffer, BITMAPCOLOR_BLACK, caretRect.x, caretRect.y, caretRect.width, caretRect.height); } /*########################################################################################################################* *------------------------------------------------------LabelWidget--------------------------------------------------------* *#########################################################################################################################*/ void LBackend_LabelInit(struct LLabel* w) { } #define LLabel_GetFont(w) (w->small ? &hintFont : &textFont) 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); } void LBackend_LabelDraw(struct LLabel* w) { struct DrawTextArgs args; DrawTextArgs_Make(&args, &w->text, LLabel_GetFont(w), true); Context2D_DrawText(&framebuffer, &args, w->x, w->y); } /*########################################################################################################################* *-------------------------------------------------------LineWidget--------------------------------------------------------* *#########################################################################################################################*/ void LBackend_LineInit(struct LLine* w, int width) { w->width = Display_ScaleX(width); w->height = Display_ScaleY(LLINE_HEIGHT); } void LBackend_LineDraw(struct LLine* w) { BitmapCol color = LLine_GetColor(); Gradient_Blend(&framebuffer, color, 128, w->x, w->y, w->width, w->height); } /*########################################################################################################################* *------------------------------------------------------SliderWidget-------------------------------------------------------* *#########################################################################################################################*/ void LBackend_SliderInit(struct LSlider* w, int width, int height) { w->width = Display_ScaleX(width); w->height = Display_ScaleY(height); } void LBackend_SliderUpdate(struct LSlider* w) { LBackend_MarkDirty(w); } static void LSlider_DrawBoxBounds(struct LSlider* w) { BitmapCol boundsTop = BitmapColor_RGB(119, 100, 132); BitmapCol boundsBottom = BitmapColor_RGB(150, 130, 165); /* TODO: Check these are actually right */ Context2D_Clear(&framebuffer, boundsTop, w->x, w->y, w->width, yBorder); Context2D_Clear(&framebuffer, boundsBottom, w->x, w->y + w->height - yBorder, w->width, yBorder); Gradient_Vertical(&framebuffer, boundsTop, boundsBottom, w->x, w->y, xBorder, w->height); Gradient_Vertical(&framebuffer, boundsTop, boundsBottom, w->x + w->width - xBorder, w->y, xBorder, w->height); } static void LSlider_DrawBox(struct LSlider* w) { BitmapCol progTop = BitmapColor_RGB(220, 204, 233); BitmapCol progBottom = BitmapColor_RGB(207, 181, 216); int halfHeight = (w->height - yBorder2) / 2; Gradient_Vertical(&framebuffer, progTop, progBottom, w->x + xBorder, w->y + yBorder, w->width - xBorder2, halfHeight); Gradient_Vertical(&framebuffer, progBottom, progTop, w->x + xBorder, w->y + yBorder + halfHeight, w->width - xBorder2, halfHeight); } #define LSLIDER_MAXVALUE 100 void LBackend_SliderDraw(struct LSlider* w) { int curWidth; LSlider_DrawBoxBounds(w); LSlider_DrawBox(w); curWidth = (int)((w->width - xBorder2) * w->value / LSLIDER_MAXVALUE); Context2D_Clear(&framebuffer, w->color, w->x + xBorder, w->y + yBorder, curWidth, w->height - yBorder2); } /*########################################################################################################################* *-------------------------------------------------------TableWidget-------------------------------------------------------* *#########################################################################################################################*/ static void InitRowFont(void) { if (rowFont.handle) return; Font_Make(&rowFont, 11, FONT_FLAGS_NONE); } void LBackend_TableInit(struct LTable* w) { } void LBackend_TableUpdate(struct LTable* w) { } void LBackend_TableReposition(struct LTable* w) { int rowsHeight; InitRowFont(); w->hdrHeight = Font_CalcHeight(&textFont, true) + hdrYPadding; w->rowHeight = Font_CalcHeight(&rowFont, true) + rowYPadding; w->rowsBegY = w->y + w->hdrHeight + gridlineHeight; w->rowsEndY = w->y + w->height; rowsHeight = w->height - (w->rowsBegY - w->y); w->visibleRows = rowsHeight / w->rowHeight; LTable_ClampTopRow(w); } void LBackend_TableFlagAdded(struct LTable* w) { /* TODO: Only redraw flags */ LBackend_MarkDirty(w); } /* Draws background behind column headers */ static void LTable_DrawHeaderBackground(struct LTable* w) { BitmapCol gridColor = BitmapColor_RGB(20, 20, 10); if (!Launcher_Theme.ClassicBackground) { Context2D_Clear(&framebuffer, gridColor, w->x, w->y, w->width, w->hdrHeight); } else { Launcher_DrawBackground(&framebuffer, w->x, w->y, w->width, w->hdrHeight); } } static BitmapCol LBackend_TableRowColor(struct LTable* w, int row) { struct ServerInfo* entry = row < w->rowsCount ? LTable_Get(row) : NULL; cc_bool selected = entry && String_Equals(&entry->hash, w->selectedHash); cc_bool featured = entry && entry->featured; return LTable_RowColor(row, selected, featured); } /* Draws background behind each row in the table */ static void LTable_DrawRowsBackground(struct LTable* w) { int y, height, row; BitmapCol color; y = w->rowsBegY; for (row = w->topRow; ; row++, y += w->rowHeight) { color = LBackend_TableRowColor(w, row); /* last row may get chopped off */ height = min(y + w->rowHeight, w->rowsEndY) - y; /* hit the end of the table */ if (height < 0) break; if (color) { Context2D_Clear(&framebuffer, color, w->x, y, w->width, height); } else { Launcher_DrawBackground(&framebuffer, w->x, y, w->width, height); } } } /* Draws a gridline below column headers and gridlines after each column */ static void LTable_DrawGridlines(struct LTable* w) { int i, x; if (Launcher_Theme.ClassicBackground) return; x = w->x; Context2D_Clear(&framebuffer, Launcher_Theme.BackgroundColor, x, w->y + w->hdrHeight, w->width, gridlineHeight); for (i = 0; i < w->numColumns; i++) { x += w->columns[i].width; if (!w->columns[i].hasGridline) continue; Context2D_Clear(&framebuffer, Launcher_Theme.BackgroundColor, x, w->y, gridlineWidth, w->height); x += gridlineWidth; } } /* Draws the entire background of the table */ static void LTable_DrawBackground(struct LTable* w) { LTable_DrawHeaderBackground(w); LTable_DrawRowsBackground(w); LTable_DrawGridlines(w); } /* Draws title of each column at top of the table */ static void LTable_DrawHeaders(struct LTable* w) { struct DrawTextArgs args; int i, x, y; DrawTextArgs_MakeEmpty(&args, &textFont, true); x = w->x; y = w->y; for (i = 0; i < w->numColumns; i++) { args.text = String_FromReadonly(w->columns[i].name); Drawer2D_DrawClippedText(&framebuffer, &args, x + cellXOffset, y + hdrYOffset, w->columns[i].width - cellXPadding); x += w->columns[i].width; if (w->columns[i].hasGridline) x += gridlineWidth; } } /* Draws contents of the currently visible rows in the table */ static void LTable_DrawRows(struct LTable* w) { cc_string str; char strBuffer[STRING_SIZE]; struct ServerInfo* entry; struct DrawTextArgs args; struct LTableCell cell; int i, x, y, row, end; InitRowFont(); String_InitArray(str, strBuffer); DrawTextArgs_Make(&args, &str, &rowFont, true); cell.table = w; y = w->rowsBegY; end = w->topRow + w->visibleRows; for (row = w->topRow; row < end; row++, y += w->rowHeight) { x = w->x; if (row >= w->rowsCount) break; if (y + w->rowHeight > w->rowsEndY) break; entry = LTable_Get(row); for (i = 0; i < w->numColumns; i++) { args.text = str; cell.x = x; cell.y = y; cell.width = w->columns[i].width; w->columns[i].DrawRow(entry, &args, &cell, &framebuffer); if (args.text.length) { Drawer2D_DrawClippedText(&framebuffer, &args, x + cellXOffset, y + rowYOffset, cell.width - cellXPadding); } x += w->columns[i].width; if (w->columns[i].hasGridline) x += gridlineWidth; } } } /* Draws scrollbar on the right edge of the table */ static void LTable_DrawScrollbar(struct LTable* w) { BitmapCol classicBack = BitmapColor_RGB( 80, 80, 80); BitmapCol classicScroll = BitmapColor_RGB(160, 160, 160); BitmapCol backCol = Launcher_Theme.ClassicBackground ? classicBack : Launcher_Theme.ButtonBorderColor; BitmapCol scrollCol = Launcher_Theme.ClassicBackground ? classicScroll : Launcher_Theme.ButtonForeActiveColor; int x, y, height; x = w->x + w->width - scrollbarWidth; LTable_GetScrollbarCoords(w, &y, &height); Context2D_Clear(&framebuffer, backCol, x, w->y, scrollbarWidth, w->height); Context2D_Clear(&framebuffer, scrollCol, x, w->y + y, scrollbarWidth, height); } void LBackend_TableDraw(struct LTable* w) { LTable_DrawBackground(w); LTable_DrawHeaders(w); LTable_DrawRows(w); LTable_DrawScrollbar(w); MarkAllDirty(); } static void LTable_RowsClick(struct LTable* w, int idx) { int mouseY = Pointers[idx].y - w->rowsBegY; int row = w->topRow + mouseY / w->rowHeight; LTable_RowClick(w, row); } /* Handles clicking on column headers (either resizes a column or sort rows) */ static void LTable_HeadersClick(struct LTable* w, int idx) { int x, i, mouseX = Pointers[idx].x; for (i = 0, x = w->x; i < w->numColumns; i++) { /* clicked on gridline, begin dragging */ if (mouseX >= (x - dragPad) && mouseX < (x + dragPad) && w->columns[i].draggable) { w->draggingColumn = i - 1; w->dragXStart = (mouseX - w->x) - w->columns[i - 1].width; return; } x += w->columns[i].width; if (w->columns[i].hasGridline) x += gridlineWidth; } for (i = 0, x = w->x; i < w->numColumns; i++) { if (mouseX >= x && mouseX < (x + w->columns[i].width) && w->columns[i].sortable) { w->sortingCol = i; w->columns[i].invertSort = !w->columns[i].invertSort; LTable_Sort(w); return; } x += w->columns[i].width; if (w->columns[i].hasGridline) x += gridlineWidth; } } /* Handles clicking on the scrollbar on right edge of table */ static void LTable_ScrollbarClick(struct LTable* w, int idx) { int y, height, mouseY = Pointers[idx].y - w->y; LTable_GetScrollbarCoords(w, &y, &height); if (mouseY < y) { w->topRow -= w->visibleRows; } else if (mouseY >= y + height) { w->topRow += w->visibleRows; } else { w->dragYOffset = mouseY - y; } w->draggingScrollbar = true; LTable_ClampTopRow(w); } void LBackend_TableMouseDown(struct LTable* w, int idx) { if (Pointers[idx].x >= Window_Main.Width - scrollbarWidth) { LTable_ScrollbarClick(w, idx); w->_lastRow = -1; } else if (Pointers[idx].y < w->rowsBegY) { LTable_HeadersClick(w, idx); w->_lastRow = -1; } else { LTable_RowsClick(w, idx); } LBackend_MarkDirty(w); } void LBackend_TableMouseMove(struct LTable* w, int idx) { int x = Pointers[idx].x - w->x, y = Pointers[idx].y - w->y; int i, col, width, maxW; if (w->draggingScrollbar) { float scale = w->height / (float)w->rowsCount; int row = (int)((y - w->dragYOffset) / scale); /* avoid expensive redraw when possible */ if (w->topRow == row) return; w->topRow = row; LTable_ClampTopRow(w); LBackend_MarkDirty(w); } else if (w->draggingColumn >= 0) { col = w->draggingColumn; width = x - w->dragXStart; /* Ensure this column doesn't expand past right side of table */ maxW = w->width; for (i = 0; i < col; i++) maxW -= w->columns[i].width; Math_Clamp(width, cellMinWidth, maxW - cellMinWidth); if (width == w->columns[col].width) return; w->columns[col].width = width; LBackend_MarkDirty(w); } } /* Stops an in-progress dragging of resizing column. */ void LBackend_TableMouseUp(struct LTable* w, int idx) { w->draggingColumn = -1; w->draggingScrollbar = false; w->dragYOffset = 0; } #endif