X-Git-Url: https://git.ucc.asn.au/?a=blobdiff_plain;f=Usermode%2FApplications%2Faxwin3_src%2FWM%2Frenderer_widget.c;h=c4799e99a109266e594b36f24f32501ca6b1c8da;hb=a644ed9dc9954091daf616dfe93ab2e2a920bf5d;hp=7407e5fd171474b0e27e68b2e794f5663eeab8c0;hpb=eb4f3a69b2ad7ccfe1428a91fd0686ba23f32203;p=tpg%2Facess2.git diff --git a/Usermode/Applications/axwin3_src/WM/renderer_widget.c b/Usermode/Applications/axwin3_src/WM/renderer_widget.c index 7407e5fd..c4799e99 100644 --- a/Usermode/Applications/axwin3_src/WM/renderer_widget.c +++ b/Usermode/Applications/axwin3_src/WM/renderer_widget.c @@ -7,28 +7,75 @@ */ #include #include +#include +#include +#include +#include +#include "include/image.h" -// === TYPES === -typedef struct sWidgetWin tWidgetWin; - -// === STRUCTURES === -struct sWidgetWin -{ - tElement RootElement; -}; +#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) @@ -38,22 +85,596 @@ 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(tWidgetWin_Info); + 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; + int nChildren = 0; + int nFixed = 0; + int maxCross = 0; + int fixedSize = 0; + int fullCross, dynWith; + + // Pass 1 + // - Get the fixed and minimum sizes of the element + for( child = Element->FirstChild; child; child = child->NextSibling ) + { + // Ignore elements that will not be rendered + if( child->Flags & ELEFLAG_NORENDER ) continue ; + + // Absolutely positioned elements don't affect dimensions + if( child->Flags & ELEFLAG_ABSOLUTEPOS ) continue ; + + // Fixed width elements + if( child->FixedWith ) + { + nFixed ++; + fixedSize += child->FixedWith; + } + else if( child->Flags & ELEFLAG_NOSTRETCH ) + { + nFixed ++; + fixedSize += child->MinWith; + } + + if( child->FixedCross && maxCross < child->FixedCross ) + maxCross = child->FixedCross; + if( child->MinCross && maxCross < child->MinCross ) + 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 = Element->CachedH - Element->PaddingT - Element->PaddingB; + else + dynWith = Element->CachedW - Element->PaddingL - Element->PaddingR; + dynWith -= fixedSize; + if( dynWith < 0 ) return ; + 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; + else + fullCross = Element->CachedH - Element->PaddingT - Element->PaddingB; + + // Pass 2 - Set sizes and recurse + for( child = Element->FirstChild; child; child = child->NextSibling ) + { + int cross, with; + + // Ignore elements that will not be rendered + if( child->Flags & ELEFLAG_NORENDER ) continue ; + + // --- Cross Size --- + // TODO: Expand to fill? + // TODO: Extra flag so options are (Expand, Equal, Wrap) + if( child->FixedCross ) + cross = child->FixedCross; + else if( child->Flags & ELEFLAG_NOEXPAND ) + cross = child->MinCross; + else + cross = fullCross; + + // --- With Size --- + 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 ) { + // If no change, don't recurse + if( child->CachedW == cross && child->CachedH == with ) + continue ; + child->CachedW = cross; + child->CachedH = with; + } + else { + // If no change, don't recurse + if( child->CachedW == with && child->CachedH == cross ) + continue ; + child->CachedW = with; + child->CachedH = cross; + } + + // Force the positions of child elements to be recalculated + child->CachedX = -1; + + // Recurse down so the child elements can be updated + Widget_UpdateDimensions(child); + } + +} + +/** + * \brief Update the position of child elements + */ +void Widget_UpdatePosition(tElement *Element) +{ + tElement *child; + 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; + y = Element->CachedY + Element->PaddingT; + + // Update each child + for(child = Element->FirstChild; child; child = child->NextSibling) + { + int newX, newY; + // Ignore elements that will not be rendered + if( child->Flags & ELEFLAG_NORENDER ) continue ; + + newX = x; newY = y; + + // Handle alignment + if( Element->Flags & ELEFLAG_ALIGN_CENTER ) { + if(Element->Flags & ELEFLAG_VERTICAL) + newX += Element->CachedW/2 - child->CachedW/2; + else + newY += Element->CachedH/2 - child->CachedH/2; + } + else if( Element->Flags & ELEFLAG_ALIGN_END ) { + if(Element->Flags & ELEFLAG_VERTICAL ) + newX += Element->CachedW - child->CachedW + - Element->PaddingL - Element->PaddingR; + else + newY += Element->CachedH - child->CachedH + - 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 ) + { + child->CachedX = newX; + child->CachedY = newY; + // Update child's children positions + Widget_UpdatePosition(child); + } + + // Increment + if(Element->Flags & ELEFLAG_VERTICAL ) { + y += child->CachedH + Element->GapSize; + } + else { + x += child->CachedW + Element->GapSize; + } + } +} + +/** + * \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]; + + ele = Info->ElementTable[ID % Info->TableSize]; + while(ele && ele->ID != ID) ele = ele->ListNext; + return ele; +} + +// --- Message Handlers --- +void Widget_NewWidget(tWidgetWin *Info, size_t Len, tWidgetMsg_Create *Msg) +{ + const int max_debugname_len = Len - sizeof(tWidgetMsg_Create); + tElement *parent, *new; + + // Sanity check + if( Len < sizeof(tWidgetMsg_Create) ) + return ; + 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) { - tClassfulInfo *info = Target->RendererInfo; + 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 }