#include <wm_renderer.h>
#include <renderer_widget.h>
#include <string.h>
+#include <wm_messages.h>
+#include <stdlib.h>
+#include "include/image.h"
-// === TYPES ===
-typedef struct sWidgetWin tWidgetWin;
-typedef struct sAxWin_Element tElement;
-
-// === STRUCTURES ===
-struct sAxWin_Element
-{
- enum eElementTypes Type;
-
- uint32_t ID; //!< Application provided ID number
- tElement *ListNext; //!< Next element in bucket
-
- // Element Tree
- tElement *Parent;
- tElement *FirstChild;
- tElement *LastChild;
- tElement *NextSibling;
-
- // User modifiable attributes
- short PaddingL, PaddingR;
- short PaddingT, PaddingB;
- short GapSize;
-
- uint32_t Flags;
-
- short FixedWith; //!< Fixed lengthways Size attribute (height)
- short FixedCross; //!< Fixed Cross Size attribute (width)
-
- char *Text;
-
- // -- Attributes maitained by the element code
- // Not touched by the user
- short MinWith; //!< Minimum long size
- short MinCross; //!< Minimum cross size
- void *Data; //!< Per-type data
-
- // -- Render Cache
- short CachedX, CachedY;
- short CachedW, CachedH;
-
- char DebugName[];
-};
-struct sWidgetWin
-{
- tElement RootElement;
-
- int TableSize; //!< Number of entries, anything over will wrap
- tElement *ElementTable[]; //!< Hash table essentially
-};
+#define DEFAULT_ELETABLE_SIZE 64
// === PROTOTYPES ===
-tWindow *Renderer_Widget_Create(int Width, int Height, int Flags);
+ int Renderer_Widget_Init(void);
+tWindow *Renderer_Widget_Create(int Flags);
void Renderer_Widget_Redraw(tWindow *Window);
-int Renderer_Widget_HandleMessage(tWindow *Target, int Msg, int Len, void *Data);
+
+void Widget_RenderWidget(tWindow *Window, tElement *Element);
+void Widget_UpdateDimensions(tElement *Element);
+void Widget_UpdatePosition(tElement *Element);
+// --- Messages
+tElement *Widget_GetElementById(tWidgetWin *Info, uint32_t ID);
+void Widget_NewWidget(tWidgetWin *Info, size_t Len, tWidgetMsg_Create *Msg);
+void Widget_SetFlags(tWidgetWin *Info, int Len, tWidgetMsg_SetFlags *Msg);
+void Widget_SetSize(tWidgetWin *Info, int Len, tWidgetMsg_SetSize *Msg);
+void Widget_SetText(tWidgetWin *Info, int Len, tWidgetMsg_SetText *Msg);
+ int Renderer_Widget_HandleMessage(tWindow *Target, int Msg, int Len, void *Data);
+// --- Type helpers
+void Widget_DispText_UpdateText(tElement *Element, const char *Text);
+void Widget_Image_UpdateText(tElement *Element, const char *Text);
+ int Widget_Button_MouseButton(tElement *Element, int X, int Y, int Button, int bPress);
+void Widget_TextInput_Init(tElement *Element);
// === GLOBALS ===
tWMRenderer gRenderer_Widget = {
- .Name = "Classful",
+ .Name = "Widget",
.CreateWindow = Renderer_Widget_Create,
.Redraw = Renderer_Widget_Redraw,
.HandleMessage = Renderer_Widget_HandleMessage
};
+
+// --- Element callbacks
+struct {
+ void (*Init)(tElement *Ele);
+ void (*Delete)(tElement *Ele);
+
+ void (*UpdateFlags)(tElement *Ele);
+ void (*UpdateSize)(tElement *Ele);
+ void (*UpdateText)(tElement *Ele, const char *Text); // This should update Ele->Text
+
+ /**
+ * \name Input handlers
+ * \note Returns boolean unhandled
+ * \{
+ */
+ int (*MouseButton)(tElement *Ele, int X, int Y, int Button, int bPressed);
+ int (*MouseMove)(tElement *Ele, int X, int Y);
+ int (*KeyDown)(tElement *Ele, int KeySym, int Character);
+ int (*KeyUp)(tElement *Ele, int KeySym);
+ int (*KeyFire)(tElement *Ele, int KeySym, int Character);
+ /**
+ * \}
+ */
+} gaWM_WidgetTypes[NUM_ELETYPES] = {
+ {0}, // NULL
+ {0}, // Box
+ {.UpdateText = Widget_DispText_UpdateText}, // Text
+ {.UpdateText = Widget_Image_UpdateText}, // Image
+ {.MouseButton = Widget_Button_MouseButton}, // Button
+ {0}, // Spacer
+ {.Init = Widget_TextInput_Init}, // Text Box (Single Line)
+};
+const int ciWM_NumWidgetTypes = sizeof(gaWM_WidgetTypes)/sizeof(gaWM_WidgetTypes[0]);
// === CODE ===
int Renderer_Widget_Init(void)
return 0;
}
-tWindow *Renderer_Widget_Create(int Width, int Height, int Flags)
+tWindow *Renderer_Widget_Create(int Flags)
{
- // TODO: Add info
- return WM_CreateWindowStruct( sizeof(tWidgetWin) );
+ tWindow *ret;
+ tWidgetWin *info;
+ int eletable_size = DEFAULT_ELETABLE_SIZE;
+
+ _SysDebug("Renderer_Widget_Create: (Flags = 0x%x)", Flags);
+
+ // TODO: Use `Flags` as default element count?
+ // - Actaully, it's taken by the root ele flags
+ // - Use the upper bits?
+
+ ret = WM_CreateWindowStruct( sizeof(tWidgetWin) + sizeof(tElement*)*eletable_size );
+ info = ret->RendererInfo;
+
+ info->TableSize = eletable_size;
+ info->RootElement.Window = ret;
+ info->RootElement.ID = -1;
+ info->RootElement.BackgroundColour = 0xCCCCCC;
+ info->RootElement.Flags = Flags;
+
+ return ret;
}
void Renderer_Widget_Redraw(tWindow *Window)
{
+ tWidgetWin *info = Window->RendererInfo;
+ WM_Render_FillRect(Window, 0, 0, 0xFFF, 0xFFF, info->RootElement.BackgroundColour);
+
+ Widget_UpdateDimensions(&info->RootElement);
+ Widget_UpdatePosition(&info->RootElement);
+
+ Widget_RenderWidget(Window, &info->RootElement);
}
// --- Render / Resize ---
+void Widget_RenderWidget(tWindow *Window, tElement *Element)
+{
+ tElement *child;
+
+ if( Element->Flags & ELEFLAG_NORENDER ) return ;
+ if( Element->Flags & ELEFLAG_INVISIBLE ) return ;
+
+ Widget_Decorator_RenderWidget(Window, Element);
+
+ for(child = Element->FirstChild; child; child = child->NextSibling)
+ {
+ Widget_RenderWidget(Window, child);
+ }
+}
+
void Widget_UpdateDimensions(tElement *Element)
{
tElement *child;
nFixed ++;
fixedSize += child->FixedWith;
}
+ else if( child->Flags & ELEFLAG_NOSTRETCH )
+ {
+ nFixed ++;
+ fixedSize += child->MinWith;
+ }
if( child->FixedCross && maxCross < child->FixedCross )
maxCross = child->FixedCross;
maxCross = child->MinCross;
nChildren ++;
}
-
+
// Get the dynamic with size from the unused space in the element
if( nChildren > nFixed ) {
if( Element->Flags & ELEFLAG_VERTICAL )
dynWith /= nChildren - nFixed;
}
+// _SysDebug("%i - nChildren = %i, nFixed = %i, dynWith = %i, fixedSize = %i",
+// Element->ID, nChildren, nFixed, dynWith, fixedSize);
+
// Get the cross size
if( Element->Flags & ELEFLAG_VERTICAL )
fullCross = Element->CachedW - Element->PaddingL - Element->PaddingR;
cross = fullCross;
// --- With Size ---
- if( child->FixedWith)
+ if( child->FixedWith )
with = child->FixedWith;
else if( child->Flags & ELEFLAG_NOSTRETCH )
with = child->MinWith;
else
with = dynWith;
-
+
+
+ if(with < child->MinWith) with = child->MinWith;
+ if(cross < child->MinCross) cross = child->MinCross;
// Update the dimensions if they have changed
if( Element->Flags & ELEFLAG_VERTICAL ) {
int x, y;
if( Element->Flags & ELEFLAG_NORENDER ) return ;
+
+// _SysDebug("Widget_UpdatePosition: (Element=%p(%i Type=%i Flags=0x%x))",
+// Element, Element->ID, Element->Type, Element->Flags);
// Initialise
x = Element->CachedX + Element->PaddingL;
- Element->PaddingT - Element->PaddingB;
}
+// _SysDebug(" Widget_UpdatePosition[%i]: newX = %i, newY = %i", Element->ID, newX, newY);
+
// Check for changes, and don't update if there was no change
if( newX != child->CachedX || newY != child->CachedY )
{
}
}
+/**
+ * \brief Update the minimum dimensions of the element
+ * \note Called after a child's minimum dimensions have changed
+ */
+void Widget_UpdateMinDims(tElement *Element)
+{
+ tElement *child;
+
+ if(!Element) return;
+
+ Element->MinCross = 0;
+ Element->MinWith = 0;
+
+ for(child = Element->FirstChild; child; child = child->NextSibling)
+ {
+ if( Element->Parent &&
+ (Element->Flags & ELEFLAG_VERTICAL) == (Element->Parent->Flags & ELEFLAG_VERTICAL)
+ )
+ {
+ if(child->FixedCross)
+ Element->MinCross += child->FixedCross;
+ else
+ Element->MinCross += child->MinCross;
+ if(child->FixedWith)
+ Element->MinWith += child->FixedWith;
+ else
+ Element->MinWith += child->MinWith;
+ }
+ else
+ {
+ if(child->FixedCross)
+ Element->MinWith += child->FixedCross;
+ else
+ Element->MinWith += child->MinCross;
+ if(child->FixedWith)
+ Element->MinCross += child->FixedWith;
+ else
+ Element->MinCross += child->MinWith;
+ }
+ }
+
+ // Recurse upwards
+ Widget_UpdateMinDims(Element->Parent);
+}
+
+tElement *Widget_GetElementByPos(tWidgetWin *Info, int X, int Y)
+{
+ tElement *ret, *next, *ele;
+
+ next = &Info->RootElement;
+ while(next)
+ {
+ ret = next;
+ next = NULL;
+ for(ele = ret->FirstChild; ele; ele = ele->NextSibling)
+ {
+ if(ele->Flags & ELEFLAG_NORENDER) continue;
+ if(X < ele->CachedX) continue;
+ if(Y < ele->CachedY) continue;
+ if(X >= ele->CachedX + ele->CachedW) continue;
+ if(Y >= ele->CachedY + ele->CachedH) continue;
+ next = ele;
+ }
+ }
+ return ret;
+}
// --- Helpers ---
tElement *Widget_GetElementById(tWidgetWin *Info, uint32_t ID)
{
tElement *ele;
+
+ if( ID == -1 ) return &Info->RootElement;
if( ID < Info->TableSize ) return Info->ElementTable[ID];
void Widget_NewWidget(tWidgetWin *Info, size_t Len, tWidgetMsg_Create *Msg)
{
const int max_debugname_len = Len - sizeof(tWidgetMsg_Create);
- tElement *parent;
+ tElement *parent, *new;
// Sanity check
if( Len < sizeof(tWidgetMsg_Create) )
if( strnlen(Msg->DebugName, max_debugname_len) == max_debugname_len )
return ;
+ _SysDebug("Widget_NewWidget (%i %i Type %i Flags 0x%x)",
+ Msg->Parent, Msg->NewID, Msg->Type, Msg->Flags);
+
// Create
parent = Widget_GetElementById(Info, Msg->Parent);
+ if(!parent)
+ {
+ _SysDebug("Widget_NewWidget - Bad parent ID %i", Msg->Parent);
+ return ;
+ }
+
+ // Check if the ID is already in use
+ if( Widget_GetElementById(Info, Msg->NewID) )
+ return ;
+
+ // Create new element
+ new = calloc(sizeof(tElement), 1);
+ new->Window = parent->Window;
+ new->ID = Msg->NewID;
+ new->Type = Msg->Type;
+ new->Parent = parent;
+ new->Flags = Msg->Flags;
+ new->PaddingT = 2;
+ new->PaddingB = 2;
+ new->PaddingL = 2;
+ new->PaddingR = 2;
+ new->CachedX = -1;
+ if( new->Type < ciWM_NumWidgetTypes && gaWM_WidgetTypes[new->Type].Init )
+ gaWM_WidgetTypes[new->Type].Init(new);
+
+ // Add to parent's list
+ if(parent->LastChild)
+ parent->LastChild->NextSibling = new;
+ else
+ parent->FirstChild = new;
+ parent->LastChild = new;
+
+ // Add to info
+ {
+ tElement *ele, *prev = NULL;
+ for(ele = Info->ElementTable[new->ID % Info->TableSize]; ele; prev = ele, ele = ele->ListNext);
+ if(prev)
+ prev->ListNext = new;
+ else
+ Info->ElementTable[new->ID % Info->TableSize] = new;
+ }
+
+ Widget_UpdateMinDims(parent);
+}
+
+void Widget_SetFlags(tWidgetWin *Info, int Len, tWidgetMsg_SetFlags *Msg)
+{
+ tElement *ele;
+
+ if( Len < sizeof(tWidgetMsg_SetFlags) )
+ return ;
+
+ _SysDebug("Widget_SetFlags: (%i 0x%x 0x%x)", Msg->WidgetID, Msg->Value, Msg->Mask);
+
+ ele = Widget_GetElementById(Info, Msg->WidgetID);
+ if(!ele) return;
+
+ Msg->Value &= Msg->Mask;
+
+ ele->Flags &= ~Msg->Mask;
+ ele->Flags |= Msg->Value;
+}
+
+void Widget_SetSize(tWidgetWin *Info, int Len, tWidgetMsg_SetSize *Msg)
+{
+ tElement *ele;
+
+ if( Len < sizeof(tWidgetMsg_SetSize) )
+ return ;
+
+ ele = Widget_GetElementById(Info, Msg->WidgetID);
+ if(!ele) return ;
+
+ ele->FixedWith = Msg->Value;
+}
+
+void Widget_SetText(tWidgetWin *Info, int Len, tWidgetMsg_SetText *Msg)
+{
+ tElement *ele;
+
+ if( Len < sizeof(tWidgetMsg_SetText) + 1 )
+ return ;
+ if( Msg->Text[Len - sizeof(tWidgetMsg_SetText) - 1] != '\0' )
+ return ;
+
+ ele = Widget_GetElementById(Info, Msg->WidgetID);
+ if(!ele) return ;
+
+
+ if( ele->Type < ciWM_NumWidgetTypes && gaWM_WidgetTypes[ele->Type].UpdateText )
+ {
+ gaWM_WidgetTypes[ele->Type].UpdateText( ele, Msg->Text );
+ }
+// else
+// {
+// if(ele->Text) free(ele->Text);
+// ele->Text = strdup(Msg->Text);
+// }
}
int Renderer_Widget_HandleMessage(tWindow *Target, int Msg, int Len, void *Data)
tWidgetWin *info = Target->RendererInfo;
switch(Msg)
{
+ case WNDMSG_RESIZE: {
+ struct sWndMsg_Resize *msg = Data;
+ if(Len < sizeof(*msg)) return -1;
+
+ info->RootElement.CachedW = msg->W;
+ info->RootElement.CachedH = msg->H;
+
+ // TODO: Update dimensions of all child elements?
+
+ return 0; }
+
+ case WNDMSG_MOUSEMOVE: {
+ _SysDebug("TODO: Support widget mouse move events");
+ return 0; }
+
+ case WNDMSG_MOUSEBTN: {
+ struct sWndMsg_MouseButton *msg = Data;
+ tWidgetMsg_MouseBtn client_msg;
+ tElement *ele;
+ int x, y;
+ int rv;
+
+ if(Len < sizeof(*msg)) return -1;
+
+ x = msg->X; y = msg->Y;
+ client_msg.Button = msg->Button;
+ client_msg.bPressed = msg->bPressed;
+
+ ele = Widget_GetElementByPos(info, x, y);
+ // Send event to all elements from `ele` upwards
+ for( ; ele; ele = ele->Parent )
+ {
+ if(ele->Type < ciWM_NumWidgetTypes && gaWM_WidgetTypes[ele->Type].MouseButton)
+ {
+ rv = gaWM_WidgetTypes[ele->Type].MouseButton(
+ ele,
+ x - ele->CachedX, y - ele->CachedY,
+ msg->Button, msg->bPressed
+ );
+ // Allow a type to trap the input from going any higher
+ if(rv == 0) break;
+ }
+ else
+ {
+ // Pass to user
+ client_msg.X = x - ele->CachedX;
+ client_msg.Y = y - ele->CachedY;
+ client_msg.WidgetID = ele->ID;
+ WM_SendMessage(Target, Target, MSG_WIDGET_MOUSEBTN, sizeof(client_msg), &client_msg);
+ }
+ }
+ return 0; }
+
// New Widget
case MSG_WIDGET_CREATE:
Widget_NewWidget(info, Len, Data);
return 0;
+
+ case MSG_WIDGET_DELETE:
+ _SysDebug("TODO: Implement MSG_WIDGET_DELETE");
+ return 0;
+
+ // Set Flags
+ case MSG_WIDGET_SETFLAGS:
+ Widget_SetFlags(info, Len, Data);
+ return 0;
+
+ // Set length
+ case MSG_WIDGET_SETSIZE:
+ Widget_SetSize(info, Len, Data);
+ return 0;
+
+ // Set text
+ case MSG_WIDGET_SETTEXT:
+ Widget_SetText(info, Len, Data);
+ return 0;
+
//
default:
return 1; // Unhandled, pass to user
}
}
+void Widget_Fire(tElement *Element)
+{
+ tWidgetMsg_Fire msg;
+ msg.WidgetID = Element->ID;
+ WM_SendMessage(Element->Window, Element->Window, MSG_WIDGET_FIRE, sizeof(msg), &msg);
+}
+// --- Type Helpers
+void Widget_DispText_UpdateText(tElement *Element, const char *Text)
+{
+ int w=0, h=0;
+ if(Element->Text) free(Element->Text);
+ Element->Text = strdup(Text);
+
+ WM_Render_GetTextDims(NULL, Element->Text, &w, &h);
+ if(Element->Parent && (Element->Parent->Flags & ELEFLAG_VERTICAL)) {
+ Element->MinCross = w;
+ Element->MinWith = h;
+ }
+ else {
+ Element->MinWith = w;
+ Element->MinCross = h;
+ }
+
+ Widget_UpdateMinDims(Element->Parent);
+}
+
+void Widget_Image_UpdateText(tElement *Element, const char *Text)
+{
+ if(Element->Data) free(Element->Data);
+ Element->Data = Image_Load( Text );
+ if(!Element->Data) {
+// Element->Flags &= ~ELEFLAG_FIXEDSIZE;
+ return ;
+ }
+
+ Element->CachedW = ((tImage*)Element->Data)->Width;
+ Element->CachedH = ((tImage*)Element->Data)->Height;
+
+ if(Element->Parent && (Element->Parent->Flags & ELEFLAG_VERTICAL) ) {
+ Element->MinCross = ((tImage*)Element->Data)->Width;
+ Element->MinWith = ((tImage*)Element->Data)->Height;
+ }
+ else {
+ Element->MinWith = ((tImage*)Element->Data)->Width;
+ Element->MinCross = ((tImage*)Element->Data)->Height;
+ }
+
+ Widget_UpdateMinDims(Element->Parent);
+
+ // NOTE: Doesn't update Element->Text because it's useless
+}
+
+int Widget_Button_MouseButton(tElement *Element, int X, int Y, int Button, int bPress)
+{
+ _SysDebug("Ele %i - Button %i %s",
+ Element->ID, Button,
+ (bPress ? "pressed" : "released")
+ );
+ if(!bPress) Widget_Fire(Element);
+ return 0; // Handled
+}
+
+void Widget_TextInput_Init(tElement *Element)
+{
+ int h;
+
+ // TODO: Select font correctly
+ WM_Render_GetTextDims(NULL, "jJ", NULL, &h);
+
+ if( Element->Parent && (Element->Parent->Flags & ELEFLAG_VERTICAL) )
+ Element->MinWith = h;
+ else
+ Element->MinCross = h;
+
+ // No need to explicitly update parent min dims, as the AddElement routine does that
+}