/*============================================================================= Name : region.c Purpose : Process the creation, deletion, processing and drawing of all region-based UI elements including buttons and menus. Created 6/25/1997 by lmoloney Copyright Relic Entertainment, Inc. All rights reserved. =============================================================================*/ #include #include #include #include #include "types.h" #include "debug.h" #include "memory.h" #include "task.h" #include "key.h" #include "prim2d.h" #include "mouse.h" #include "demo.h" #include "main.h" #include "utility.h" #include "region.h" /*============================================================================= Data: =============================================================================*/ void regNULLRenderFunction(regionhandle region); //the base, root region to which everything is linked. Even the main play //screen is a child of this region. Sometimes referring to a regionhandle //of NULL will really mean this region. region regRootRegion = { {0, 0, 0, 0}, //no physical size regNULLRenderFunction, //no draw function regNULLProcessFunction, //no processor functions NULL, NULL, NULL, NULL, //no links to start 0,//RFF_ModalBreak | RFF_NULLRegion, //the processing stops here 0, //no status flags 0, //zero accelerator keys {0}, //no key accelerator 0, //no user ID #if REG_ERROR_CHECKING REG_ValidationKey #endif }; sdword regModuleInit = FALSE; //module init flag sdword regDrawBorders = TRUE; //sdword regContinueTree; //region render event queue sdword regNumberRenderEvents = REG_RenderEventsDefault;//default number of render events sdword regRenderEventIndex; regrenderevent *regRenderEvent; taskhandle regTaskHandle; taskhandle regDrawTaskHandle; //regions the mouse was clicked in regionhandle regClickedLeft; regionhandle regClickedLeftLast; //for making double-clicks work properly regionhandle regClickedRight; regionhandle regClickedCentre; //flag that we are processing regions and we should continue not alter behaviour //of some functions which may be called from region handler functions sdword regProcessingRegions; #if REG_INDEX_ALLOCS sdword regAllocCounter = 0; #endif udword regRegionFrameDrawCount=0; bool regKeysFocussed = FALSE; /*============================================================================= Private functions: =============================================================================*/ /*----------------------------------------------------------------------------- Name : regDrawBorderFunction Description : Add a draw event to draw a border around specified region. Inputs : same as any render function Outputs : renders the border using primitives Return : void ----------------------------------------------------------------------------*/ #if REG_DRAW_REGION_BORDERS void regDrawBorderFunction(regionhandle reg) { primRectOutline2(®->rect, 1, REG_OutlineColor); } #endif //REG_DRAW_REGION_BORDERS /*----------------------------------------------------------------------------- Name : regFunctionCall Description : Calls the region's processor function with the specified event and data. Inputs : reg - region which generated message event - event message to generate data - data to pass onto processor function Outputs : calls function passing along same data Return : TRUE if message processed properly, FALSE otherwise ----------------------------------------------------------------------------*/ sdword regFunctionCall(regionhandle reg, udword event, udword data, udword *maskPointer) { udword returnValue; udword mask = *maskPointer; if ( (bitTest(reg->status, RSF_ToBeDeleted)) || (bitTest(reg->status, RSF_RegionDisabled))) { return(FALSE); } if (!regFlag(event)) //only pass events in filter { return(FALSE); } returnValue = reg->processFunction(reg, reg->userID, event, data); if (bitTest(returnValue, RPR_Redraw)) { #ifdef DEBUG_STOMP regVerify(reg); #endif bitSet(reg->status, RSF_DrawThisFrame); } return(TRUE); } /*----------------------------------------------------------------------------- Name : regKeysPressed/regKeysStuck/regKeysPressedOrStuck Description : Check if all keys for specified region pressed/stuck/either Inputs : reg - region to ckeck keys for Outputs : .. Return : TRUE if all region keys pressed, false otherwise. ----------------------------------------------------------------------------*/ sdword regKeysPressed(regionhandle reg) { sdword index; regVerify(reg); for (index = 0; index < reg->nKeys; index++) { if (!(keyIsHit(reg->key[index]))) { //if this key not pressed return(FALSE); } } return(TRUE); } sdword regKeysStuck(regionhandle reg) { sdword index; regVerify(reg); for (index = 0; index < reg->nKeys; index++) { if (!(keyIsStuck(reg->key[index]))) { //if this key not pressed return(FALSE); } } return(TRUE); } sdword regKeysPressedOrStuck(regionhandle reg) { sdword index; regVerify(reg); for (index = 0; index < reg->nKeys; index++) { if (!(keyIsHit(reg->key[index]) || keyIsStuck(reg->key[index]))) { //if this key not pressed return(FALSE); } } return(TRUE); } /*----------------------------------------------------------------------------- Name : regKeysUnstick Description : Clear sticky bit on keys for a region Inputs : reg - region with keys Outputs : Calls keyClearSticky for each key in region. Return : void Note : Later, it may be useful to only clear the sticky bit for the last key. That way, two keys of CTRL-Q and CTRL-C would work without having to ever release the CTRL key. ----------------------------------------------------------------------------*/ void regKeysUnstick(regionhandle reg) { sdword index; regVerify(reg); for (index = 0; index < reg->nKeys; index++) { keyClearSticky(reg->key[index]); keyClearRepeat(reg->key[index]); } } /*----------------------------------------------------------------------------- Name : regDrawFunctionAddPossibly Description : Add draw function to draw function list if it is not already there. Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void regDrawFunctionAddPossibly(regionhandle region) { if (region->lastframedrawn < regRegionFrameDrawCount) { regDrawFunctionAdd(region->drawFunction, region); region->lastframedrawn = regRegionFrameDrawCount; } } /*----------------------------------------------------------------------------- Name : regDirtyChildren Description : dirties all children of a region Inputs : reg - the region whose children will be recursively dirtied Outputs : Return : ----------------------------------------------------------------------------*/ void regDirtyChildren(regionhandle reg) { regionhandle child; if (reg == NULL) return; for (child = reg->child; child != NULL; child = child->next) { regDrawFunctionAddPossibly(child); regDirtyChildren(child->child); } } /*----------------------------------------------------------------------------- Name : regRecursiveSetDirty Description : Sets the RSF_DrawThisFrame bit for all children of a region Inputs : reg - the region whose children will be recursively dirtied Outputs : Return : ----------------------------------------------------------------------------*/ void regRecursiveSetDirty(regionhandle reg) { regionhandle child; if (reg == NULL) return; // now dirty all the siblings and their children for (child = reg->child; child != NULL; child = child->next) { #ifdef DEBUG_STOMP regVerify(child); #endif bitSet(child->status, RSF_DrawThisFrame); regRecursiveSetDirty(child); } #ifdef DEBUG_STOMP regVerify(reg); #endif bitSet(reg->status, RSF_DrawThisFrame); } /*----------------------------------------------------------------------------- Name : regRecursiveSetReallyDirty Description : Sets the RSF_ReallyDirty bit for all children of a region Inputs : reg - the region whose children will be recursively dirtied Outputs : Return : ----------------------------------------------------------------------------*/ void regRecursiveSetReallyDirty(regionhandle reg) { regionhandle child; if (reg == NULL) return; // now dirty all the siblings and their children for (child = reg->child; child != NULL; child = child->next) { bitSet(child->status, RSF_ReallyDirty); regRecursiveSetDirty(child); } bitSet(reg->status, RSF_ReallyDirty); } /*----------------------------------------------------------------------------- Name : regPointOnRegion Description : determines whether a point is on the border or inside a region Inputs : x, y - screen location of point to test region - the region to test against Outputs : Return : TRUE (on) or FALSE (not on) ----------------------------------------------------------------------------*/ bool regPointOnRegion(sdword x, sdword y, regionhandle region) { rectangle* r = ®ion->rect; if (x >= r->x0 && x <= r->x1 && y >= r->y0 && y <= r->y1) { return TRUE; } else { return FALSE; } } /*----------------------------------------------------------------------------- Name : regRegionInside Description : determines whether a region is entirely inside/on another Inputs : needle - subregion haystack - base region Outputs : Return : TRUE or FALSE ----------------------------------------------------------------------------*/ bool regRegionInside(regionhandle needle, regionhandle haystack) { #define TOTALLY_INSIDE 0 #if TOTALLY_INSIDE if (regPointOnRegion(needle->rect.x0, needle->rect.y0, haystack) && regPointOnRegion(needle->rect.x0, needle->rect.y1, haystack) && regPointOnRegion(needle->rect.x1, needle->rect.y1, haystack) && regPointOnRegion(needle->rect.x1, needle->rect.y0, haystack)) { return TRUE; } else { return FALSE; } #else //TOTALLY_INSIDE //!!! temporary hack to make button text visible. This function checks to see //if the top region is more or less on the bottom region: It must be within the //vertical limits and no more that 50% off horizontally. rectangle *nRect, *hRect; sdword nWidth, hWidth, overlap; nRect = &needle->rect; hRect = &haystack->rect; #define OVERLAP_FACTOR 2/3 if (nRect->y0 >= hRect->y0 && nRect->y1 <= hRect->y1) { //if within the vertical limits if (nRect->x0 >= hRect->x0 && nRect->x1 <= hRect->x1) { //if within the horizontal limits return(TRUE); //condition 3 } else { //not inside, look for overlap hWidth = hRect->x1 - hRect->x0; nWidth = nRect->x1 - nRect->x0; if (nRect->x0 < hRect->x0) { //condition 0, 1 or 4 if (nRect->x1 > hRect->x1) { //condition 0 overlap = hWidth; } else if (nRect->x1 > hRect->x0) { //condition 1 overlap = nRect->x1 - hRect->x0; } else { //condition 4 overlap = -1; } } else { //condition 2 or 5 if (hRect->x1 > nRect->x0) { //condition 2 overlap = hRect->x1 - nRect->x0; } else { //condition 5 overlap = -1; } } } if (overlap >= nWidth * OVERLAP_FACTOR) { //if it overlaps enough return(TRUE); } } return(FALSE); #endif //TOTALLY_INSIDE } /*----------------------------------------------------------------------------- Name : regDirtySiblingsInside Description : dirties all siblings inside another region, "upwards" Inputs : region - the region to start with Outputs : Return : ----------------------------------------------------------------------------*/ void regDirtySiblingsInside(regionhandle region) { regionhandle reg; dbgAssert(region != NULL); reg = region->previous; //dirty internal siblings while (reg != NULL) { if (regRegionInside(reg, region)) { regDrawFunctionAddPossibly(reg); } reg = reg->previous; } } /*----------------------------------------------------------------------------- Name : regDoRegionsOverlap Description : determines whether two provided regions overlap Inputs : a, b - the regions to test Outputs : Return : 0 (no overlap) or 1 (overlap) ----------------------------------------------------------------------------*/ bool regDoRegionsOverlap(regionhandle a, regionhandle b) { rectangle c; primRectUnion2(&c, &a->rect, &b->rect); return (c.x0 == c.x1 || c.y0 == c.y1); } /*----------------------------------------------------------------------------- Name : regDirtyEverythingUpwardsSelectively Description : dirties all parents of a region recursively, only if they overlap the provided topmost region Inputs : region - the region to proceed upwards from overlap - the region to overlap test against (topmost region) Outputs : Return : ----------------------------------------------------------------------------*/ void regDirtyEverythingUpwardsSelectively(regionhandle region, regionhandle overlap) { regionhandle reg = region; if (reg->parent == NULL) { return; } reg = reg->parent; for (;; reg = reg->next) { if (regDoRegionsOverlap(overlap, reg)) { #ifdef DEBUG_STOMP regVerify(reg); #endif bitSet(reg->status, RSF_DrawThisFrame); } if (reg->parent != NULL) { regDirtyEverythingUpwardsSelectively(reg, overlap); } if (reg->next == NULL) { break; } } } /*----------------------------------------------------------------------------- Name : regDirtyEverythingUpwards Description : dirties all parents of a region recursively Inputs : region - the region to proceed upwards from Outputs : Return : ----------------------------------------------------------------------------*/ void regDirtyEverythingUpwards(regionhandle region) { regionhandle reg = region; if (reg->parent == NULL) { return; } reg = reg->parent; for (;; reg = reg->next) { #ifdef DEBUG_STOMP regVerify(reg); #endif bitSet(reg->status, RSF_DrawThisFrame); if (reg->parent != NULL) { regDirtyEverythingUpwards(reg); } if (reg->next == NULL) { break; } } } /*----------------------------------------------------------------------------- Name : regDirtyScreensAboveRegion Description : Dirties the screens above (on top) of the region Inputs : region Outputs : Return : ----------------------------------------------------------------------------*/ void regDirtyScreensAboveRegion(regionhandle region) { sdword i,j; if (region->parent == NULL) { return; } for (i=0;iparent) { for (j=i+1;j<=feStackIndex;j++) { regRecursiveSetDirty(feStack[j].baseRegion); } return; } } return; } /*----------------------------------------------------------------------------- Name : regRegionProcess Description : Process a region, including it's subtree. Inputs : reg - region to process mask - region processing mask to be ANDed with the region's flags member. Outputs : processes events for region and all it's children Return : modified mask variable for use with parents Note : this function is recursive and regions process all their children before processing itself. This way, the function will progress from bottom to top. ----------------------------------------------------------------------------*/ udword regRegionProcess(regionhandle reg, udword mask) { regionhandle child, nextChild; udword returnMask; regVerify(reg); bitSet(reg->status, RSF_Processing); //we're currently using this region // Test to see if this and all children need updating if(bitTest(reg->status, RSF_ReallyDirty)) { regRecursiveSetDirty(reg); bitClear(reg->status, RSF_ReallyDirty); } for (child = reg->child; child != NULL; child = nextChild)//process all children first { mask = regRegionProcess(child, mask); nextChild = child->next; if (bitTest(child->status, RSF_ToBeDeleted)) //if this region marked for deletion { //delete it and all it's children regRegionDelete(child); } } //after processing children, process self #if REG_VERBOSE_LEVEL >= 2 dbgMessagef("\nProcessing region 0x%x", reg); #endif if (regFlag(RPE_ModalBreak)) { //if it's a modal break region mask &= ~(RPM_AllEvents); //process no more events } returnMask = mask; //perform mouse logic if ((regClickedLeft == NULL || regClickedLeft == reg) && bitTest(reg->flags, RPM_MouseEvents)) { if (mouseInRect(®->rect)) { //inside region if (keyIsHit(FLYWHEEL_UP)) { // keyScanCode[FLYWHEEL_UP].keypressed--; keyPressUp(FLYWHEEL_UP); regFunctionCall(reg, RPE_WheelUp, 0, &mask); } if (keyIsHit(FLYWHEEL_DOWN)) { // keyScanCode[FLYWHEEL_DOWN].keypressed--; keyPressUp(FLYWHEEL_DOWN); regFunctionCall(reg, RPE_WheelDown, 0, &mask); } if (regFlag(RPE_DoubleLeft)) { if (keyIsStuck(LMOUSE_DOUBLE)) { if (reg == regClickedLeftLast) { regFunctionCall(reg, RPE_DoubleLeft, 0, &mask); keyClearSticky(LMOUSE_DOUBLE); bitClear(returnMask, RPE_DoubleLeft); } else { dbgMessage("\nSecond of double click in new region"); keyClearSticky(LMOUSE_DOUBLE); bitClear(returnMask, RPE_DoubleLeft); } } } if (regFlag(RPE_DoubleRight)) { if (keyIsStuck(RMOUSE_DOUBLE)) { regFunctionCall(reg, RPE_DoubleRight, 0, &mask); keyClearSticky(RMOUSE_DOUBLE); bitClear(returnMask, RPE_DoubleRight); } } if (regFlag(RPE_DoubleCentre)) { if (keyIsStuck(MMOUSE_DOUBLE)) { regFunctionCall(reg, RPE_DoubleCentre, 0, &mask); keyClearSticky(MMOUSE_DOUBLE); bitClear(returnMask, RPE_DoubleCentre); } } if (!regInside(reg)) { //just entered region if (regLeftPressed(reg)) { //left held and entering region //...call hold-enter function... regFunctionCall(reg, RPE_EnterHoldLeft, 0, &mask); bitSet(reg->status, RSF_LeftPressed); //set pressed status bit regClickedLeft = reg; //no has mouse press focus regClickedLeftLast = reg; //save for doubleclick check } if (regRightPressed(reg)) { //right held and entering region //...call hold-enter function... regFunctionCall(reg, RPE_EnterHoldRight, 0, &mask); bitSet(reg->status, RSF_RightPressed); //set pressed status bit } if (regLeftPressed(reg)) { //centre held and entering region //...call hold-enter function... regFunctionCall(reg, RPE_EnterHoldCentre, 0, &mask); bitSet(reg->status, RSF_CentrePressed); //set pressed status bit } //...same for right/centre //...call enter function... regFunctionCall(reg, RPE_Enter, 0, &mask); bitSet(reg->status, RSF_MouseInside); //set inside bit // if (mouseLeftButton()) // { //if mouse button unpressed outside of region // bitSet(reg->status, RSF_LeftPressed); //set pressed status bit // regClickedLeft = NULL; //no has mouse press focus // } } else { //else still in region if (mouseLeftButton()) { //left button down in region if (!regLeftPressed(reg)) { //left button pressed in region //...call pressed function, set bit... if (regFunctionCall(reg, RPE_PressLeft, 0, &mask)) { bitSet(reg->status, RSF_LeftPressed); regClickedLeft = reg; //record clicked-in region regClickedLeftLast = reg; //save for doubleclick check } bitClear(returnMask, RPM_PressRelease); //disallow any further mouse clicks } else { //left button held in region //...call hold function... regFunctionCall(reg, RPE_HoldLeft, 0, &mask); } } else { //else button not pressed if (regLeftPressed(reg)) { //button just released //...call release function, clear bit... regFunctionCall(reg, RPE_ReleaseLeft, 0, &mask); regClickedLeft = NULL; //no buttons pressed bitClear(returnMask, RPM_PressRelease); //disallow any further mouse clicks bitClear(reg->status, RSF_LeftPressed); } } if (mouseRightButton()) { //right button down in region if (!regRightPressed(reg)) { //right button pressed in region //...call pressed function, set bit... if (regFunctionCall(reg, RPE_PressRight, 0, &mask)) { bitSet(reg->status, RSF_RightPressed); } bitClear(returnMask, RPM_PressRelease); //disallow any further mouse clicks } else { //right button held in region //...call hold function... regFunctionCall(reg, RPE_HoldRight, 0, &mask); } } else { //else button not pressed if (regRightPressed(reg)) { //button just released //...call release function, clear bit... regFunctionCall(reg, RPE_ReleaseRight, 0, &mask); bitClear(returnMask, RPM_PressRelease); //disallow any further mouse clicks bitClear(reg->status, RSF_RightPressed); } } if (mouseCentreButton()) { //centre button down in region if (!regCentrePressed(reg)) { //centre button pressed in region //...call pressed function, set bit... if (regFunctionCall(reg, RPE_PressCentre, 0, &mask)) { bitSet(reg->status, RSF_CentrePressed); } bitClear(returnMask, RPM_PressRelease); //disallow any further mouse clicks } else { //centre button held in region //...call hold function... regFunctionCall(reg, RPE_HoldCentre, 0, &mask); } } else { //else button not pressed if (regCentrePressed(reg)) { //button just released //...call release function, clear bit... regFunctionCall(reg, RPE_ReleaseCentre, 0, &mask); bitClear(returnMask, RPM_PressRelease); //disallow any further mouse clicks bitClear(reg->status, RSF_CentrePressed); } } } } else { //else outside region if (regInside(reg)) { //just exiting region bitClear(reg->status, RSF_MouseInside); //exiting, clear inside bit if (regLeftPressed(reg)) { //left held and exiting region //...call hold-exit function... regFunctionCall(reg, RPE_ExitHoldLeft, 0, &mask); } if (regRightPressed(reg)) { //right held and exiting region //...call hold-exit function... regFunctionCall(reg, RPE_ExitHoldRight, 0, &mask); } if (regCentrePressed(reg)) { //centre held and exiting region //...call hold-exit function... regFunctionCall(reg, RPE_ExitHoldCentre, 0, &mask); } //...call exit function... regFunctionCall(reg, RPE_Exit, 0, &mask); } if (!mouseLeftButton()) { //if mouse button unpressed outside of region if (bitTest(reg->status, RSF_LeftPressed)) { #ifdef DEBUG_STOMP regVerify(reg); #endif bitSet(reg->status, RSF_DrawThisFrame); } bitClear(reg->status, RSF_LeftPressed); //clear pressed status bit regClickedLeft = NULL; //no has mouse press focus } } } //perform key logic if (regStatus(RSF_KeyCapture)) { //if region has input forcus for keyboard sdword iKey; bool bShift; while ((iKey = keyBufferedKeyGet(&bShift)) != 0) { if (bShift) { iKey |= BIT9; } regFunctionCall(reg, RPE_KeyDown, iKey, &mask);//call the function } } else if (bitTest(reg->flags, RPM_KeyEvent) && (!regKeysFocussed)) { //if any key checking for this region if (!regKeyPressed(reg)) { //if region key bit not set if (regKeysStuck(reg)) { //if now pressed //...call key down function, set down bit... if (regFunctionCall(reg, RPE_KeyDown, 0, &mask))//call press function { regKeysUnstick(reg); //unstick keys bitSet(reg->status, RSF_KeyPressed); //set pressed bit } } else { //keys not pressed, clear pressed bit bitClear(reg->status, RSF_KeyPressed); } } else { //region key bit not set if (regKeysPressed(reg)) { //keys held, check hold or repeat if (regFlag(RPE_KeyHold)) { //if a hold region(hold and repeat are exclusive) regFunctionCall(reg, RPE_KeyHold, 0, &mask); } else if (regFlag(RPE_KeyRepeat)) { //if a repeat key if (regKeysStuck(reg)) { //unstick stuck keys regKeysUnstick(reg); //unstick keys regFunctionCall(reg, RPE_KeyRepeat, 0, &mask);//call repeat function } } } else { //region keys just released //...key up function, clear down bit... regFunctionCall(reg, RPE_KeyUp, 0, &mask); bitClear(reg->status, RSF_KeyPressed); } } } bitClear(reg->status, RSF_Processing); //no longer using this region if (feShouldSaveMouseCursor()) { if (bitTest(reg->flags, RPE_DrawEveryFrame)) //check permanent draw flag for this region { #ifdef DEBUG_STOMP regVerify(reg); #endif bitSet(reg->status, RSF_DrawThisFrame); } } else { //we must redraw the entire frontend #ifdef DEBUG_STOMP regVerify(reg); #endif bitSet(reg->status, RSF_DrawThisFrame); } #if REG_DRAW_REGION_BORDERS if (regDrawBorders) { regDrawFunctionAdd(regDrawBorderFunction, reg); //draw a dummy border in special debugging mode } #endif //REG_DRAW_REGION_BORDERS #ifdef DEBUG_STOMP regVerify(reg); #endif if (bitTest(reg->status, RSF_DrawThisFrame)) //if need to draw this frame { if (feShouldSaveMouseCursor()) { regDirtyChildren(reg); // regDirtySiblingsInside(reg); } regDrawFunctionAddPossibly(reg); #ifdef DEBUG_STOMP regVerify(reg); #endif bitClear(reg->status, RSF_DrawThisFrame); } return(returnMask); } /*----------------------------------------------------------------------------- Name : regProcessTask Description : Task function to act as region processor kernel. Inputs : void Outputs : .. Return : void ----------------------------------------------------------------------------*/ #pragma optimize("gy", off) //turn on stack frame (we need ebp for this function) void regProcessTask(void) { taskYield(0); while (1) { taskStackSaveCond(0); #if REG_VERBOSE_LEVEL >= 2 dbgMessage("\nProcessing regions..."); #endif mousePoll(); //poll mouse _asm xor eax,eax _asm mov regRenderEventIndex, eax // regRenderEventIndex = regRenderEventIndex - regRenderEventIndex; //no render events yet this frame //special-case code for double-clicks /* if (keyIsHit(LMOUSE_DOUBLE)) { keyPressUp(LMOUSE_DOUBLE); utyDoubleClick(); } */ if (demDemoRecording) { memcpy((ubyte *)&keyScanCode[0], (ubyte *)&keySaveScan[0], sizeof(keyScanCode));//freeze a snapshot of the key state demStateSave(); } else if (demDemoPlaying) { demStateLoad(); } regProcessingRegions = TRUE; regRegionProcess(®RootRegion, 0xffffffff); regProcessingRegions = FALSE; taskStackRestoreCond(); taskYield(0); } taskExit(); } #pragma optimize("", on) /*----------------------------------------------------------------------------- Test processing functions: -----------------------------------------------------------------------------*/ #if REG_TEST static udword regProcessCallback(regionhandle region, sdword ID, udword event, udword data) { dbgMessagef("\nregProcessCallback: region = 0x%x, ID = 0x%x, event = 0x%x, data = %d", region, ID, event, data); return 0; } #endif /*----------------------------------------------------------------------------- Null processor functions for processing and drawing -----------------------------------------------------------------------------*/ void regNULLRenderFunction(regionhandle region) { ; } udword regNULLProcessFunction(regionhandle region, sdword ID, udword event, udword data) { return(0); } /*============================================================================= Functions =============================================================================*/ /*----------------------------------------------------------------------------- Name : regStartup Description : Startup the region handling module. Inputs : void Outputs : Allocates render event list and starts the region handling task. Return : OKAY ----------------------------------------------------------------------------*/ sdword regStartup(void) { regNotInitCheck(); //allocate the render event list regRenderEvent = memAlloc(regNumberRenderEvents * sizeof(regrenderevent), "RegionRenderEvents", NonVolatile); //start the region processor task regTaskHandle = taskStart(regProcessTask, REG_TaskFrequencyOPF, TF_OncePerFrame); regModuleInit = TRUE; //flag module started regClickedLeft = regClickedRight = regClickedCentre = NULL;//mouse not clicked anywhere #if REG_TEST { regionhandle a, b, c, d, e, f, g, master; master = regChildAlloc(NULL, 'M', 0, 0, 200, 200, 0, RPE_LeftClickButton); //create root dummy regions regNameSet(master, "Master Region"); regFunctionSet(master, regProcessCallback); a = regChildAlloc(master, 'A', 0, 0, 10, 10, 0, RPE_LeftClickButton); //create some dummy regions regNameSet(a, "Region A"); regFunctionSet(a, regProcessCallback); b = regChildAlloc(master, 'B', 20, 0, 10, 10, 0, RPE_LeftClickButton); regNameSet(b, "Region B"); regFunctionSet(b, regProcessCallback); c = regChildAlloc(a, 'C', 0, 20, 10, 10, 0, RPE_LeftClickButton); regNameSet(c, "Region C"); regFunctionSet(c, regProcessCallback); d = regSiblingAlloc(b, 'D', 40, 0, 10, 10, 0, RPE_LeftClickButton); regNameSet(d, "Region D"); regFunctionSet(d, regProcessCallback); e = regSiblingAlloc(a, 'E', 12, 0, 6, 10, 0, RPE_LeftClickButton); regNameSet(e, "Region E"); regFunctionSet(e, regProcessCallback); f = regChildAlloc(c, 'F', 0, 40, 10, 10, 0, RPE_LeftClickButton); regNameSet(f, "Region F"); regFunctionSet(f, regProcessCallback); g = regChildAlloc(c, 'G', 20, 40, 10, 10, 0, RPE_LeftClickButton); regNameSet(g, "Region G"); regFunctionSet(g, regProcessCallback); // regRegionDelete(a); // regRegionDelete(b); // regRegionDelete(e); // regRegionDelete(d); } #endif return(OKAY); } /*----------------------------------------------------------------------------- Name : regShutdown Description : Shutdown the region processing module. Inputs : void Outputs : Stops the region processor task and frees all memory associated with this module. Return : void ----------------------------------------------------------------------------*/ void regShutdown(void) { regionhandle child, nextRegion; regInitCheck(); #if REG_INDEX_ALLOCS dbgMessagef("\nregShutdown: %d allocs", regAllocCounter); #endif for (child = regRootRegion.child; child != NULL;) //delete all children of the root region { nextRegion = child->next; regRegionDelete(child); child = nextRegion; } taskStop(regTaskHandle); //stop region processor task memFree(regRenderEvent); //free render event memory regRenderEventIndex = 0; //don't try to render any more regions regModuleInit = FALSE; //flag module not started } /*----------------------------------------------------------------------------- Name : regDrawFunctionAdd Description : Add a region draw event. Inputs : function - function to call reg - handle of region to draw Outputs : allocates and fills in an entry in regRenderEvent Return : void Notes : These queued functions will be drawn in order reverse of specification at render time. If there is no space left in the regRenderEvent array, an error is generated if error checking enabled. ----------------------------------------------------------------------------*/ void regDrawFunctionAdd(regiondrawfunction function, regionhandle reg) { regrenderevent *event; dbgAssert(function != NULL); if (regRenderEventIndex >= regNumberRenderEvents) //if no render event available { dbgFatalf(DBG_Loc, "Tried to add render function 0x%x for region 0x%x. All %d in use", function, reg, regNumberRenderEvents); } event = ®RenderEvent[regRenderEventIndex]; event->function = function; event->reg = reg; regRenderEventIndex++; } /*----------------------------------------------------------------------------- Name : regFunctionsDraw Description : Render all previously queued render events Inputs : void Outputs : Calls all queued render events in reverse order of which they were created and sets regRenderEventIndex to zero. Return : void ----------------------------------------------------------------------------*/ void regFunctionsDraw(void) { sdword regNumberDraws = regRenderEventIndex; for (regNumberDraws--; regNumberDraws >= 0; regNumberDraws--) { regRenderEvent[regNumberDraws].function(regRenderEvent[regNumberDraws].reg); } regRegionFrameDrawCount++; } /*----------------------------------------------------------------------------- Name : regChildAlloc Description : Create a region as a child of the specified region. Inputs : parent - regionhandle of the parent of the region to be created ID - user-defined ID for region to be passed event process function x, y, width, height - location and size of region extra - amount of extra RAM to allocate with the region structure. flags - control flags for new region Outputs : Allocates memory for the structure and inserts into linked list. Return : pointer to newly allocated region. ----------------------------------------------------------------------------*/ regionhandle regChildAlloc(regionhandle parent, sdword ID, sdword x, sdword y, sdword width, sdword height, sdword extra, udword flags) { regionhandle newRegion; regInitCheck(); #if REG_INDEX_ALLOCS { char string[32]; sprintf(string, "Region%d", regAllocCounter); regAllocCounter++; newRegion = memAlloc(sizeof(region) + extra, string, NonVolatile);//allocate memory for new region } #else newRegion = memAlloc(sizeof(region) + extra, "Region", NonVolatile);//allocate memory for new region #endif newRegion->rect.x0 = x; //set new rectangle newRegion->rect.y0 = y; newRegion->rect.x1 = x + width; newRegion->rect.y1 = y + height; newRegion->flags = flags; newRegion->status = RSF_DrawThisFrame; newRegion->drawFunction = regNULLRenderFunction; //blank processing functions newRegion->processFunction = regNULLProcessFunction; newRegion->userID = ID; newRegion->lastframedrawn = 0; newRegion->tabstop = 0; newRegion->atom = NULL; *((udword *)newRegion->key) = 0; newRegion->nKeys = 0; listInit(&newRegion->cutouts); #if REG_ERROR_CHECKING newRegion->validationKey = REG_ValidationKey; //set validation key #endif #if REG_VERBOSE_LEVEL >= 1 dbgMessagef("\nregChildAlloc: added child 0x%x of parent 0x%x at (%d, %d), size %d x %d, flags 0x%x", newRegion, parent, x, y, width, height, flags); #endif if (parent == NULL) { parent = ®RootRegion; } newRegion->child = NULL; regChildInsert(newRegion, parent); //insert region into linked list return(newRegion); //return newly allocated region } /*----------------------------------------------------------------------------- Name : regSiblingAlloc Description : Create a region as a sibling of the specified region. Inputs : sibling - regionhandle of the previous of the region to be created ID - user-defined ID for region to be passed event process function x, y, width, height - location and size of region extra - amount of extra RAM to allocate with the region structure. flags - control flags for new region Outputs : Allocates memory for the structure and inserts into linked list. Return : pointer to newly allocated region. ----------------------------------------------------------------------------*/ regionhandle regSiblingAlloc(regionhandle sibling, sdword ID, sdword x, sdword y, sdword width, sdword height, sdword extra, udword flags) { regionhandle newRegion; regInitCheck(); #if REG_INDEX_ALLOCS { char string[32]; sprintf(string, "Region%d", regAllocCounter); regAllocCounter++; newRegion = memAlloc(sizeof(region) + extra, string, NonVolatile);//allocate memory for new region } #else newRegion = memAlloc(sizeof(region) + extra, "Region", NonVolatile);//allocate memory for new region #endif newRegion->rect.x0 = x; //set new rectangle newRegion->rect.y0 = y; newRegion->rect.x1 = x + width; newRegion->rect.y1 = y + height; newRegion->flags = flags; newRegion->status = 0; newRegion->drawFunction = regNULLRenderFunction; //blank processing functions newRegion->processFunction = regNULLProcessFunction; newRegion->userID = ID; *((udword *)newRegion->key) = 0; newRegion->nKeys = 0; listInit(&newRegion->cutouts); #if REG_ERROR_CHECKING newRegion->validationKey = REG_ValidationKey; //set validation key #endif #if REG_VERBOSE_LEVEL >= 1 dbgMessagef("\nregSiblingAlloc: added sibling 0x%x to 0x%x at (%d, %d) size %d x %d, flags 0x%x", newRegion, sibling, x, y, width, height, flags); #endif dbgAssert(sibling != NULL); regSiblingInsert(newRegion, sibling); //insert region into linked list return(newRegion); //return newly allocated region } /*----------------------------------------------------------------------------- Name : regSiblingMoveToFront Description : Adjust links so region is first in it's sibling list Inputs : region - handle of region to adjust Outputs : .. Return : The displaced region which was at the head of the list. ----------------------------------------------------------------------------*/ regionhandle regSiblingMoveToFront(regionhandle region) { regionhandle sibling, parent; regVerify(region); parent = region->parent; sibling = parent->child; if (sibling == region) { //if already at front return(region); //do nothing } dbgAssert(sibling->previous == NULL); dbgAssert(region->parent == sibling->parent); //verify thay have the same parent //find what sibling to put it in front of if (!bitTest(region->status, RSF_PriorityRegion)) { //if this is not a priority region while (sibling->next != NULL && bitTest(sibling->status, RSF_PriorityRegion)) { //find first low-priority region sibling = sibling->next; } } //remove node from sibling list if (region->next != NULL) //remove region from list { (region->next)->previous = region->previous; } if (region->previous != NULL) { (region->previous)->next = region->next; } //insert the node back into list if (parent->child == sibling) { //if inserting at head of list parent->child = region; sibling->previous = region; region->previous = NULL; region->next = sibling; } else { //else inserting at some arbitrary point in the list region->previous = sibling->previous; dbgAssert(region->previous != NULL); //make sure not head of list (region->previous)->next = region; region->next = sibling; sibling->previous = region; } return(sibling); } /*----------------------------------------------------------------------------- Name : regKeyChildAlloc/regKeySiblingAlloc Description : Create a key-only region. Inputs : parent/sibling - region to link to ID - user-defined ID to pass to processor functions filter - event filter/flags function - region processor draw function nKeys - number of keys for this region ... - varaible number of keys. They are of keyindex type. Outputs : Allocates and initializes a new region structure. Return : Handle of new region. Notes : Filter flags other than key events can be passed in, however mouse events will never be genterated because the region has zero size. ----------------------------------------------------------------------------*/ regionhandle regKeyChildAlloc(regionhandle parent, sdword ID, udword filter, regionfunction function, sdword nKeys, ...) { regionhandle newRegion; sdword index; va_list argPointer; #if REG_VERBOSE_LEVEL >= 1 dbgMessagef("\nregKeyChildAlloc: adding child to 0x%x, flags 0x%x, function 0x%x", parent, filter, function); #endif newRegion = regChildAlloc(parent, ID, 0, 0, 0, 0, 0, filter); regFunctionSet(newRegion, function); dbgAssert(nKeys <= REG_NumberKeys); //verify validity of key info newRegion->nKeys = (sword)nKeys; //store number of keys va_start(argPointer, nKeys); for (index = 0; index < nKeys; index++) { //store each key newRegion->key[index] = va_arg(argPointer, keyindex); } va_end(argPointer); return(newRegion); } regionhandle regKeySiblingAlloc(regionhandle sibling, sdword ID, udword filter, regionfunction function, sdword nKeys, ...) { regionhandle newRegion; sdword index; va_list argPointer; #if REG_VERBOSE_LEVEL >= 1 dbgMessagef("\nregKeySiblingAlloc: adding sibling 0x%x to 0x%x, flags 0x%x, function 0x%x", sibling, filter, function); #endif newRegion = regSiblingAlloc(sibling, ID, 0, 0, 0, 0, 0, filter); regFunctionSet(newRegion, function); dbgAssert(nKeys <= REG_NumberKeys); //verify validity of key info newRegion->nKeys = (sword)nKeys; //store number of keys va_start(argPointer, nKeys); for (index = 0; index < nKeys; index++) { //store each key newRegion->key[index] = va_arg(argPointer, keyindex); } va_end(argPointer); return(newRegion); } /*----------------------------------------------------------------------------- Name : regRegionScroll Description : Relative scroll of all regions children of the one listed. Inputs : reg - region to scroll scrollX, scrollY - amount to add to the region rectangles Outputs : recursively scrolls all subchildren Return : void ----------------------------------------------------------------------------*/ void regRegionScroll(regionhandle reg, sdword scrollX, sdword scrollY) { regionhandle child; regVerify(reg); for (child = reg->child; child != NULL; child = child->next)//process all children first { regRegionScroll(child, scrollX, scrollY); //scroll all the children first } #ifdef DEBUG_STOMP regVerify(reg); #endif bitSet(reg->status, RSF_DrawThisFrame); reg->rect.x0 += scrollX; //scroll this region reg->rect.x1 += scrollX; reg->rect.y0 += scrollY; reg->rect.y1 += scrollY; } /*----------------------------------------------------------------------------- Name : regLinkRemove Description : Remove the parent and sibling links of a region. Inputs : region - region whose parent links we are to delete Outputs : Return : void ----------------------------------------------------------------------------*/ void regLinkRemove(regionhandle region) { if (region->parent != NULL) { if ((region->parent)->child == region) //if this is first sibling { if (region->next) //if there are more siblings { (region->parent)->child = region->next; //next sibling is first sibling } else { (region->parent)->child = NULL; //only child, clear parent's reference } } } if (region->previous != NULL) { (region->previous)->next = region->next; //clear previous sibling's reference } if (region->next != NULL) { (region->next)->previous = region->previous; //clear next sibling's reference } region->previous = region->next = region->parent = NULL; if (regClickedLeft == region) { regClickedLeft = NULL; } } /*----------------------------------------------------------------------------- Name : regMoveLinkChild Description : Alter the links of a region to reflect a new parent. Inputs : regionToMove - region to move. Can be NULL for no parent. newParent - new parent. If NULL, there is no new parent and this region will not be implicitly processed. Outputs : Return : void ----------------------------------------------------------------------------*/ void regMoveLinkChild(regionhandle regionToMove, regionhandle newParent) { if (regionToMove->parent != NULL) { regLinkRemove(regionToMove); //remove old links } if (newParent != NULL) { regChildInsert(regionToMove, newParent); //create new links } if (regClickedLeft == regionToMove) { regClickedLeft = NULL; } } /*----------------------------------------------------------------------------- Name : regRecursiveDisable Description : Disbales a region and all it's children Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void regRecursiveDisable(regionhandle reg) { regionhandle child; bitClear(reg->flags, RPM_AllEvents); //disable all events for this region for (child = reg->child; child != NULL; child = child->next) { regRecursiveDisable(child); } } /*----------------------------------------------------------------------------- Name : regRegionDelete Description : Delete a region and all it's child regions. Inputs : region - region who's subtree is to be deleted Outputs : Fixes up all dangling links from parents and siblings. Return : void Note : This function is recursive and will call itself for all child regions. ----------------------------------------------------------------------------*/ void regRegionDelete(regionhandle region) { regionhandle child, nextChild; sdword index; regInitCheck(); regVerify(region); listDeleteAll(®ion->cutouts); //dirty regions below us if we need to if (feShouldSaveMouseCursor()) { regDirtyEverythingUpwardsSelectively(region, region); } if (bitTest(region->status, RSF_Processing)) //if processing this region { bitSet(region->status, RSF_ToBeDeleted); //flag for later deletion if (bitTest(region->flags, RPM_KeyEvent)) { regKeysFocussed = FALSE; } for (child = region->child; child != NULL; child = nextChild) { //disable but don't delete all children nextChild = child->next; regRecursiveDisable(region); } return; //do no more } if (region->child) //if there are any children { for (child = region->child; child != NULL; child = nextChild) { nextChild = child->next; regRegionDelete(child); //delete all child nodes } } #if REG_VERBOSE_LEVEL >= 1 dbgMessagef("\nregRegionDelete: deleting region 0x%x", region); #endif regLinkRemove(region); if (regClickedLeft == region) { regClickedLeft = NULL; } //delete all references to region from render list for (index = 0; index < regRenderEventIndex; index++) { if (region == regRenderEvent[index].reg) { regRenderEvent[index].function = regNULLRenderFunction; } } memFree((void *)region); //free memory associated with this region } /*----------------------------------------------------------------------------- Name : regChildInsert Description : Insert a child of the specified region. Inputs : regionToInsert - handle of the region to be inserted into list. parent - handle of parent to make new child of Outputs : potentially sets link pointers for parent, regionToInsert and last child of parent. Return : void Notes : Inserts after last child of parent. If parent has no children, the new node is inserted as only child. The child pointer of inserted string is not modified, although the other links are, ----------------------------------------------------------------------------*/ void regChildInsert(regionhandle regionToInsert, regionhandle parent) { regionhandle sibling; regInitCheck(); dbgAssert(parent != NULL); //ensure valid nodes regVerify(regionToInsert); regVerify(parent); regionToInsert->parent = //regionToInsert->child = //start by clearing all link pointers to NULL regionToInsert->previous = regionToInsert->next = NULL; if (parent->child == NULL) //if parent has no children { parent->child = regionToInsert; } else { for (sibling = parent->child; sibling->next != NULL; ) { //scan for last sibling sibling = sibling->next; } sibling->next = regionToInsert; //create sibling links regionToInsert->previous = sibling; } regionToInsert->parent = parent; } /*----------------------------------------------------------------------------- Name : regSiblingInsert Description : Insert a sibling of the specified region. Inputs : regionToInsert - handle of the region to be inserted into list. sibling - handle of parent to make new sibling of Outputs : Sets link pointers for regionToInsert and sibling. Return : void Notes : If sibling specifed is not last sibling in list, new node will be inserted in between sibling region and it's next sibling. ----------------------------------------------------------------------------*/ void regSiblingInsert(regionhandle regionToInsert, regionhandle sibling) { regInitCheck(); dbgAssert(sibling != NULL); //ensure valid nodes regVerify(regionToInsert); regVerify(sibling); regionToInsert->parent = regionToInsert->child = //start by clearing all link pointers to NULL regionToInsert->previous = regionToInsert->next = NULL; if (sibling->next != NULL) //if not last node in list { (sibling->next)->previous = regionToInsert; regionToInsert->next = sibling->next; } sibling->next = regionToInsert; //set sibling to point to this node regionToInsert->previous = sibling; //this node points at sibling node regionToInsert->parent = sibling->parent; //duplicate parent link } /*----------------------------------------------------------------------------- Name : regDrawFunctionSet Description : Change the draw function for a region Inputs : region - regions whose draw function is to be changed function - new draw function Outputs : sets region->drawfunction to function or regNULLRenderFunction if NULL Return : provious draw function ----------------------------------------------------------------------------*/ regiondrawfunction regDrawFunctionSet(regionhandle region, regiondrawfunction function) { regiondrawfunction old; regVerify(region); old = region->drawFunction; if (function == NULL) { region->drawFunction = regNULLRenderFunction; } else { region->drawFunction = function; } return(old); } /*----------------------------------------------------------------------------- Name : regFunctionSet Description : Change the process function for a region Inputs : region - regions whose process function is to be changed function - new process function Outputs : guess Return : provious process function ----------------------------------------------------------------------------*/ regionfunction regFunctionSet(regionhandle region, regionfunction function) { regionfunction old; regVerify(region); dbgAssert(function != NULL); old = region->processFunction; region->processFunction = function; return(old); } /*----------------------------------------------------------------------------- Name : regKeysSet Description : Sets the key(s) for a particular region. Inputs : region - region whose keys we are modifying nKeys - number of keys for this region ... - varaible number of keys. They are of keyindex type. Outputs : Sets the key sequence for specified region. Return : void Notes : Region needs to have some key message filter flags enabled. ----------------------------------------------------------------------------*/ void regKeysSet(regionhandle region, sdword nKeys, ...) { sdword index; va_list argPointer; dbgAssert(nKeys <= REG_NumberKeys); //verify validity dbgAssert(region != NULL); regVerify(region); region->nKeys = (sword)nKeys; //store number of keys va_start(argPointer, nKeys); for (index = 0; index < nKeys; index++) { region->key[index] = va_arg(argPointer, keyindex); } va_end(argPointer); } /*----------------------------------------------------------------------------- Name : regFilterSet Description : Adjust the filter for a specified region Inputs : region - region to adjust filter - filter to set in region Outputs : sets the event filter amd flags for specified region Return : Previous filter ----------------------------------------------------------------------------*/ udword regFilterSet(regionhandle region, udword filter) { udword old; old = region->flags; region->flags = filter; return(old); } /*----------------------------------------------------------------------------- Name : regTabstopSet Description : Set the Tabstop for a specified region Inputs : region - region to adjust tabstop - tabstop to set in region Outputs : sets the tabstop for specified region Return : Previous tabstop ----------------------------------------------------------------------------*/ udword regTabstopSet(regionhandle region, udword tabstop) { udword old; old = region->tabstop; region->tabstop = tabstop; return(old); } /*----------------------------------------------------------------------------- Name : regFocusLost Description : Called when the mouse control has been relinquished to someone else, such as during an alt-tab. Inputs : region - the region to clear status bit on. Outputs : Return : ----------------------------------------------------------------------------*/ /* void regFocusLost(regionhandle reg) { regionhandle child, nextChild; regVerify(reg); for (child = reg->child; child != NULL; child = child->next)//process all children first { regVerify(child); regFocusLost(child); } reg->status &= ~(RSF_MouseInside | RSF_LeftPressed | RSF_RightPressed | RSF_CentrePressed | RSF_KeyPressed | RSF_CurrentSelected); } */ /*----------------------------------------------------------------------------- Name : regSiblingFindByFunction Description : Find a sibling of a particular region by it's process function. Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ regionhandle regSiblingFindByFunction(regionhandle reg, regionfunction find) { regionhandle sibling; sibling = reg->parent->child; while (sibling != NULL) { regVerify(sibling); if (sibling->processFunction == find) { return(sibling); } sibling = sibling->next; } return(NULL); } /*----------------------------------------------------------------------------- Name : regFindChildByAtomName Description : Find a region by the name of its atom Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ regionhandle regFindChildByAtomName(regionhandle reg, char *pAtomName) { regionhandle child; regionhandle Result = NULL; featom *pAtom; child = reg->child; while(child) { if(child->atom) { pAtom = (featom *)child->atom; if(pAtom->name && (stricmp(pAtom->name, pAtomName) == 0)) return child; } if(child->child) { Result = regFindChildByAtomName(child, pAtomName); if(Result) return Result; } child = child->next; } return(NULL); }