diff --git a/lib/betawidget/widget.c b/lib/betawidget/widget.c index 6a681864c..d06f6f14d 100644 --- a/lib/betawidget/widget.c +++ b/lib/betawidget/widget.c @@ -240,10 +240,16 @@ static bool widgetAnimationTimerCallback(widget *self, const event *evt, switch (i) { case ANI_TYPE_TRANSLATE: - // Update the widgets position - self->offset = widgetAnimationInterpolateTranslate(self, k1, - k2, time); + { + // Get the new position + point pos = widgetAnimationInterpolateTranslate(self, k1, + k2, time); + + // Set the new position + widgetReposition(self, pos.x, pos.y); + break; + } case ANI_TYPE_ROTATE: // Update the widgets rotation self->rotate = widgetAnimationInterpolateRotate(self, k1, diff --git a/lib/betawidget/window.c b/lib/betawidget/window.c index dcf5a668b..81c5e7bef 100644 --- a/lib/betawidget/window.c +++ b/lib/betawidget/window.c @@ -44,6 +44,7 @@ static void windowInitVtbl(window *self) vtbl.widgetVtbl.addChild = windowAddChildImpl; vtbl.widgetVtbl.doLayout = windowDoLayoutImpl; vtbl.widgetVtbl.doDraw = windowDoDrawImpl; + vtbl.widgetVtbl.resize = windowResizeImpl; vtbl.widgetVtbl.getMinSize = windowGetMinSizeImpl; vtbl.widgetVtbl.getMaxSize = windowGetMaxSizeImpl; @@ -77,6 +78,11 @@ void windowInit(window *self, const char *id, int w, int h) // Set our size to (w,h) widgetResize(WIDGET(self), w, h); + + // Default anchor state is static (as opposed to dynamic) + self->anchorState = ANCHOR_STATIC; + self->anchorWindow = NULL; + self->anchorRepositionId = self->anchorResizeId = -1; } void windowDestroyImpl(widget *self) @@ -240,9 +246,36 @@ void windowRepositionFromScreen(window *self, hAlign hAlign, int xOffset, widgetReposition(WIDGET(self), x, y); } -void windowRepositionFromAnchor(window *self, const window *anchor, - hAlign hAlign, int xOffset, - vAlign vAlign, int yOffset) +void windowSetAnchorState(window *self, anchorState state) +{ + // If the anchor is being disabled, remove any active event handlers + if (state == ANCHOR_STATIC && self->anchorWindow) + { + widget *anchorWidget = WIDGET(self->anchorWindow); + + // Remove the reposition event handler + if (widgetIsEventHandler(anchorWidget, self->anchorRepositionId)) + { + widgetRemoveEventHandler(anchorWidget, self->anchorRepositionId); + } + + // Remove the resize event handler + if (widgetIsEventHandler(anchorWidget, self->anchorResizeId)) + { + widgetRemoveEventHandler(anchorWidget, self->anchorResizeId); + } + + // We no longer have an anchor window + self->anchorWindow = NULL; + } + + // Update the state + self->anchorState = state; +} + +static void windowDoRepositionFromAnchor(window *self, const window *anchor, + hAlign hAlign, int xOffset, + vAlign vAlign, int yOffset) { int x = 0, y = 0; size anchorSize = WIDGET(anchor)->size; @@ -285,3 +318,109 @@ void windowRepositionFromAnchor(window *self, const window *anchor, // Reposition widgetReposition(WIDGET(self), x, y); } + +void windowResizeImpl(widget *self, int w, int h) +{ + window *windowSelf = WINDOW(self); + + // Call our parents resize method + widgetResizeImpl(self, w, h); + + // If dynamic anchoring is enabled, reposition ourself + if (windowSelf->anchorState == ANCHOR_DYNAMIC && windowSelf->anchorWindow) + { + windowDoRepositionFromAnchor(windowSelf, windowSelf->anchorWindow, + windowSelf->anchorHAlign, + windowSelf->anchorXOffset, + windowSelf->anchorVAlign, + windowSelf->anchorYOffset); + } +} + +static bool windowAnchorCallback(widget *self, const event *evt, int handlerId, + void *userData) +{ + // The window to be repositioned is stored in userData + window *anchoredWindow = WINDOW(userData); + + // We should only be called to respond to resize and reposition eventd + assert(evt->type == EVT_REPOSITION || evt->type == EVT_RESIZE); + + // Call windowDoRepositionFromAnchor to update the position + windowDoRepositionFromAnchor(anchoredWindow, WINDOW(self), + anchoredWindow->anchorHAlign, + anchoredWindow->anchorXOffset, + anchoredWindow->anchorVAlign, + anchoredWindow->anchorYOffset); + + return true; +} + +static bool windowAnchorDestroyCallback(widget *self, const event *evt, + int handlerId, void *userData) +{ + // The window that installed us is in userData + window *anchoredWindow = WINDOW(userData); + + // Make sure the event is a destruct event + assert(evt->type == EVT_DESTRUCT); + + // We are being removed and therefore are no longer anchored + anchoredWindow->anchorWindow = NULL; + anchoredWindow->anchorRepositionId = anchoredWindow->anchorResizeId = -1; + + return true; +} + +void windowRepositionFromAnchor(window *self, const window *anchor, + hAlign hAlign, int xOffset, + vAlign vAlign, int yOffset) +{ + // If dynamic anchors are enabled, set up dynamic anchoring + if (self->anchorState == ANCHOR_DYNAMIC) + { + // Get the current anchor (so that we may remove the event handlers) + widget *anchorWidget = WIDGET(self->anchorWindow); + + if (anchorWidget) + { + // Remove any current event handlers we've installed + if (widgetIsEventHandler(anchorWidget, self->anchorRepositionId)) + { + widgetRemoveEventHandler(anchorWidget, self->anchorRepositionId); + } + if (widgetIsEventHandler(anchorWidget, self->anchorResizeId)) + { + widgetRemoveEventHandler(anchorWidget, self->anchorResizeId); + } + } + + // Save the window/alignment/offsets + self->anchorWindow = self; + self->anchorHAlign = hAlign; + self->anchorXOffset = xOffset; + + self->anchorVAlign = vAlign; + self->anchorYOffset = yOffset; + + /* + * Install the event handlers, which are required to track changes to + * the size and position of the anchor window. + * + * Here we make the assumption that the event handlers when removed, + * will be done so in pairs (and therefore only need to install a single + * destructor). The same callback is used for both resize and reposition + * events. + */ + self->anchorRepositionId = widgetAddEventHandler(WIDGET(anchor), EVT_REPOSITION, + windowAnchorCallback, + NULL, self); + self->anchorResizeId = widgetAddEventHandler(WIDGET(anchor), EVT_RESIZE, + windowAnchorCallback, + windowAnchorDestroyCallback, + self); + } + + // Reposition the window + windowDoRepositionFromAnchor(self, anchor, hAlign, xOffset, vAlign, yOffset); +} diff --git a/lib/betawidget/window.h b/lib/betawidget/window.h index 3cac1532d..857e206a7 100644 --- a/lib/betawidget/window.h +++ b/lib/betawidget/window.h @@ -29,6 +29,18 @@ typedef struct _window window; typedef struct _windowVtbl windowVtbl; +/** + * The possible ways in which windowRepositionFromAnchor can act + */ +typedef enum +{ + /// The window does not track changes in the anchor windows + ANCHOR_STATIC, + + /// The window tracks and updates to changes in the anchor window + ANCHOR_DYNAMIC +} anchorState; + struct _windowVtbl { widgetVtbl widgetVtbl; @@ -47,6 +59,42 @@ struct _window * Our vtable */ windowVtbl *vtbl; + + /** + * Current anchor state + */ + anchorState anchorState; + + /** + * Anchor window + */ + window *anchorWindow; + + /** + * Anchor horizontal alignment + */ + hAlign anchorHAlign; + + /** + * Anchor horizontal offset + */ + int anchorXOffset; + + /** + * Anchor vertical alignment + */ + vAlign anchorVAlign; + + /** + * Anchor vertical offset + */ + int anchorYOffset; + + /** + * Event handler IDs for anchor events + */ + int anchorRepositionId; + int anchorResizeId; }; /* @@ -68,6 +116,7 @@ void windowDestroyImpl(widget *self); bool windowDoLayoutImpl(widget *self); void windowDoDrawImpl(widget *self); bool windowAddChildImpl(widget *self, widget *child); +void windowResizeImpl(widget *self, int w, int h); size windowGetMinSizeImpl(widget *self); size windowGetMaxSizeImpl(widget *self); @@ -130,6 +179,23 @@ void windowSetScreenSize(int w, int h); void windowRepositionFromScreen(window *self, hAlign hAlign, int xOffset, vAlign vAlign, int yOffset); +/** + * Sets the behaviour of windowRepositionFromAchor (with the default being + * ANCHOR_STATIC) to state. If the state is the same as the current anchor state + * then this method is a no-op. + * + * It is important to note that: + * - setting the state to ANCHOR_STATIC will break the current anchor (if any); + * - a state of ANCHOR_DYNAMIC will only take effect after the next call to + * windowRepositionFromAnchor; + * - calling widgetReposition with an anchor state of ANCHOR_DYNAMIC will cause + * strange behaviour - the anchor should be set to ANCHOR_STATIC first. + * + * @param self The window to set the anchor state for. + * @param state The new state to set the anchor behaviour to. + */ +void windowSetAnchorState(window *self, anchorState state); + /** * Positions the window relative to the position of another window, anchor. *