/* * Copyright (c) 2008 Hypertriton, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "window.h" #include "scrollview.h" #include "primitive.h" #include "gui_math.h" #include #include /* * Clip widgets completely outside of the view in a more efficient way, * and adjust the sensitivity rectangle of partially hidden widgets. */ static void ClipWidgets(AG_Scrollview *sv, AG_Widget *wt) { AG_Widget *chld; AG_Rect2 rView = WIDGET(sv)->rView; AG_Rect2 rx; rView.w -= sv->wBar; rView.h -= sv->hBar; rView.x2 = rView.x1+rView.w; rView.y2 = rView.y1+rView.h; if (rView.w < 0 || rView.h < 0) return; rx = AG_RectIntersect2(&rView, &wt->rView); if (rx.w == 0 || rx.h == 0) { wt->flags |= AG_WIDGET_HIDE; } else { wt->flags &= ~(AG_WIDGET_HIDE); } OBJECT_FOREACH_CHILD(chld, wt, ag_widget) ClipWidgets(sv, chld); } /* Place child widgets at the current offset in the Scrollview. */ static void PlaceWidgets(AG_Scrollview *sv, int *wTot, int *hTot) { AG_SizeReq rChld; AG_SizeAlloc aChld; AG_Widget *chld; aChld.x = -sv->xOffs; aChld.y = -sv->yOffs; OBJECT_FOREACH_CHILD(chld, sv, ag_widget) { if (chld == WIDGET(sv->vbar) || chld == WIDGET(sv->hbar)) { continue; } AG_WidgetSizeReq(chld, &rChld); aChld.w = rChld.w; aChld.h = rChld.h; AG_WidgetSizeAlloc(chld, &aChld); switch (sv->pack) { case AG_PACK_HORIZ: aChld.x += aChld.w; break; case AG_PACK_VERT: aChld.y += aChld.h; break; } ClipWidgets(sv, chld); } switch (sv->pack) { case AG_PACK_HORIZ: if (wTot != NULL) *wTot = aChld.x + sv->xOffs; if (hTot != NULL) *hTot = aChld.y+aChld.h + sv->yOffs; break; case AG_PACK_VERT: if (wTot != NULL) *wTot = aChld.x+aChld.w + sv->xOffs; if (hTot != NULL) *hTot = aChld.y + sv->yOffs; break; } } static void PanView(AG_Event *event) { AG_Scrollview *sv = AG_PTR(1); AG_Window *pWin = AG_ParentWindow(sv); if (pWin != NULL) { AG_WindowUpdate(pWin); } PlaceWidgets(sv, NULL, NULL); if (pWin != NULL) { AG_WindowUpdate(pWin); } } static void MouseMotion(AG_Event *event) { AG_Scrollview *sv = AG_SELF(); int dx = AG_INT(3); int dy = AG_INT(4); AG_Event ev; if (sv->flags & AG_SCROLLVIEW_PANNING) { sv->xOffs -= dx; sv->yOffs -= dy; if (sv->xOffs+sv->r.w > sv->xMax) sv->xOffs = sv->xMax-sv->r.w; if (sv->yOffs+sv->r.h > sv->yMax) sv->yOffs = sv->yMax-sv->r.h; if (sv->xOffs < 0) sv->xOffs = 0; if (sv->yOffs < 0) sv->yOffs = 0; } AG_EventInit(&ev); AG_EventPushPointer(&ev, NULL, sv); PanView(&ev); } static void MouseButtonUp(AG_Event *event) { AG_Scrollview *sv = AG_SELF(); int button = AG_INT(1); switch (button) { case SDL_BUTTON_MIDDLE: sv->flags &= ~(AG_SCROLLVIEW_PANNING); break; } } static void MouseButtonDown(AG_Event *event) { AG_Scrollview *sv = AG_SELF(); int button = AG_INT(1); switch (button) { case SDL_BUTTON_MIDDLE: sv->flags |= AG_SCROLLVIEW_PANNING; AG_WidgetFocus(sv); break; default: break; } } AG_Scrollview * AG_ScrollviewNew(void *parent, Uint flags) { AG_Scrollview *sv; sv = Malloc(sizeof(AG_Scrollview)); AG_ObjectInit(sv, &agScrollviewClass); sv->flags |= flags; if (flags & AG_SCROLLVIEW_HFILL) { AG_ExpandHoriz(sv); } if (flags & AG_SCROLLVIEW_VFILL) { AG_ExpandVert(sv); } if (!(flags & AG_SCROLLVIEW_NOPAN_X)) { sv->hbar = AG_ScrollbarNew(sv, AG_SCROLLBAR_HORIZ, 0); AG_BindInt(sv->hbar, "value", &sv->xOffs); AG_BindInt(sv->hbar, "min", &sv->xMin); AG_BindInt(sv->hbar, "max", &sv->xMax); AG_BindInt(sv->hbar, "visible", &sv->r.w); AG_SetEvent(sv->hbar, "scrollbar-changed", PanView, "%p", sv); } if (!(flags & AG_SCROLLVIEW_NOPAN_Y)) { sv->vbar = AG_ScrollbarNew(sv, AG_SCROLLBAR_VERT, 0); AG_BindInt(sv->vbar, "value", &sv->yOffs); AG_BindInt(sv->vbar, "min", &sv->yMin); AG_BindInt(sv->vbar, "max", &sv->yMax); AG_BindInt(sv->vbar, "visible", &sv->r.h); AG_SetEvent(sv->vbar, "scrollbar-changed", PanView, "%p", sv); } if (flags & AG_SCROLLVIEW_BY_MOUSE) { WIDGET(sv)->flags |= AG_WIDGET_FOCUSABLE; AG_SetEvent(sv, "window-mousebuttondown", MouseButtonDown, NULL); AG_SetEvent(sv, "window-mousebuttonup", MouseButtonUp, NULL); AG_SetEvent(sv, "window-mousemotion", MouseMotion, NULL); } AG_ScrollviewSetIncrement(sv, 10); AG_ObjectAttach(parent, sv); return (sv); } void AG_ScrollviewSetIncrement(AG_Scrollview *sv, int incr) { AG_ObjectLock(sv); sv->incr = incr; if (sv->hbar != NULL) { AG_ScrollbarSetIntIncrement(sv->hbar, incr); } if (sv->vbar != NULL) { AG_ScrollbarSetIntIncrement(sv->vbar, incr); } AG_ObjectUnlock(sv); } static void Init(void *obj) { AG_Scrollview *sv = obj; sv->flags = 0; sv->wPre = 256; sv->hPre = 256; sv->xOffs = 0; sv->yOffs = 0; sv->xMin = 0; sv->yMin = 0; sv->xMax = 0; sv->yMax = 0; sv->hbar = NULL; sv->vbar = NULL; sv->wBar = 0; sv->hBar = 0; sv->pack = AG_PACK_VERT; sv->r = AG_RECT(0,0,0,0); sv->incr = 10; } void AG_ScrollviewSizeHint(AG_Scrollview *sv, Uint w, Uint h) { AG_ObjectLock(sv); sv->wPre = w; sv->hPre = h; AG_ObjectUnlock(sv); } static void SizeRequest(void *p, AG_SizeReq *r) { AG_Scrollview *sv = p; AG_SizeReq rBar, rChld; AG_Widget *chld; int wMax = 0, hMax = 0; r->w = sv->wPre; r->h = sv->hPre; if (sv->hbar != NULL) { AG_WidgetSizeReq(sv->hbar, &rBar); r->h += rBar.h; } if (sv->vbar != NULL) { AG_WidgetSizeReq(sv->vbar, &rBar); r->w += rBar.w; } OBJECT_FOREACH_CHILD(chld, sv, ag_widget) { if (chld == WIDGET(sv->vbar) || chld == WIDGET(sv->hbar)) { continue; } AG_WidgetSizeReq(chld, &rChld); if (rChld.w > wMax) { wMax = rChld.w; } if (rChld.h > hMax) { hMax = rChld.h; } switch (sv->pack) { case AG_PACK_HORIZ: r->h = MAX(r->h, hMax); r->w += rChld.w; break; case AG_PACK_VERT: r->w = MAX(r->w, wMax); r->h += rChld.h; break; } } } static int SizeAllocate(void *p, const AG_SizeAlloc *a) { AG_Scrollview *sv = p; AG_SizeReq rBar; AG_SizeAlloc aBar; int wTot, hTot; sv->r.w = a->w; sv->r.h = a->h; if (sv->hbar != NULL) { AG_WidgetSizeReq(sv->hbar, &rBar); aBar.w = a->w - rBar.h; aBar.h = rBar.h; aBar.x = 0; aBar.y = a->h - rBar.h; AG_WidgetSizeAlloc(sv->hbar, &aBar); sv->r.h -= aBar.h; sv->hBar = aBar.h; if (sv->r.h < 0) { sv->r.h = 0; } } else { sv->hBar = 0; } if (sv->vbar != NULL) { AG_WidgetSizeReq(sv->vbar, &rBar); aBar.w = rBar.w; aBar.h = a->h - rBar.w; aBar.x = a->w - rBar.w; aBar.y = 0; AG_WidgetSizeAlloc(sv->vbar, &aBar); sv->r.w -= aBar.w; sv->wBar = aBar.w; if (sv->r.w < 0) { sv->r.w = 0; } } else { sv->wBar = 0; } PlaceWidgets(sv, &wTot, &hTot); sv->xMax = wTot; sv->yMax = hTot; if (sv->hbar != NULL) { if ((sv->xMax - sv->r.w - sv->xOffs) < 0) sv->xOffs = MAX(0, sv->xMax - sv->r.w); } if (sv->vbar != NULL) { if ((sv->yMax - sv->r.h - sv->yOffs) < 0) sv->yOffs = MAX(0, sv->yMax - sv->r.h); } #if 0 if (a->w >= (wTot - sv->xOffs)) { sv->xOffs = wTot - a->w; if (sv->xOffs < 0) { sv->xOffs = 0; } } if (a->h >= (hTot - sv->yOffs)) { sv->yOffs = hTot - a->h; if (sv->yOffs < 0) { sv->yOffs = 0; } } #endif return (0); } static void Draw(void *p) { AG_Scrollview *sv = p; AG_Widget *chld; if (sv->flags & AG_SCROLLVIEW_FRAME) { AG_DrawBox(sv, AG_RECT(0, 0, WIDTH(sv), HEIGHT(sv)), -1, AG_COLOR(FRAME_COLOR)); } if (sv->hbar != NULL) { AG_WidgetDraw(sv->hbar); } if (sv->vbar != NULL) { AG_WidgetDraw(sv->vbar); } AG_PushClipRect(sv, sv->r); WIDGET_FOREACH_CHILD(chld, sv) { if (chld->flags & AG_WIDGET_HIDE || chld == WIDGET(sv->hbar) || chld == WIDGET(sv->vbar)) { continue; } AG_WidgetDraw(chld); if (chld->rView.x2 > WIDGET(sv)->rView.x2 - sv->wBar) { chld->rSens.w = WIDGET(sv)->rView.x2 - sv->wBar - WIDGET(chld)->rView.x1; } else { chld->rSens.w = chld->w; } chld->rSens.x2 = chld->rSens.x1+chld->rSens.w; if (chld->rView.y2 > WIDGET(sv)->rView.y2 - sv->hBar) { chld->rSens.h = WIDGET(sv)->rView.y2 - sv->hBar - WIDGET(chld)->rView.y1; } else { chld->rSens.h = chld->h; } chld->rSens.y2 = chld->rSens.y1+chld->rSens.h; } AG_PopClipRect(); } AG_WidgetClass agScrollviewClass = { { "Agar(Widget:Scrollview)", sizeof(AG_Scrollview), { 0,0 }, Init, NULL, /* free */ NULL, /* destroy */ NULL, /* load */ NULL, /* save */ NULL /* edit */ }, Draw, SizeRequest, SizeAllocate };