mirror of https://github.com/ClassiCube/ClassiCube
1179 lines
38 KiB
C
1179 lines
38 KiB
C
#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 */
|
|
#elif defined CC_BUILD_ANDROID
|
|
/* Android 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;
|
|
bmp.width = max(Window_Main.Width, 1);
|
|
bmp.height = max(Window_Main.Height, 1);
|
|
|
|
Window_AllocFramebuffer(&bmp);
|
|
Context2D_Wrap(&framebuffer, &bmp);
|
|
}
|
|
|
|
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
|