diff options
author | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
---|---|---|
committer | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
commit | abef6da56913f1c55528103e60a50451a39628b1 (patch) | |
tree | b3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/LBackend.c |
initial commit
Diffstat (limited to 'src/LBackend.c')
-rw-r--r-- | src/LBackend.c | 1179 |
1 files changed, 1179 insertions, 0 deletions
diff --git a/src/LBackend.c b/src/LBackend.c new file mode 100644 index 0000000..b2c0412 --- /dev/null +++ b/src/LBackend.c @@ -0,0 +1,1179 @@ +#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 |