/* * VSCREEN-HANDLER.C * * Creates virtual screens that can be larger than * the actual display area of your monitor. The virtual * screen scrolls when the mouse moves off the edge of * the display. * * Copyright 1988 by Davide P. Cervone, all rights reserved. * * You may may distibute this code as is, for non-commercial * purposes, provided that this notice remains intact and the * documentation file is distributed with it. * * You may not modify this code or incorporate it into your * own programs without the permission of the author. */ /* * WARNING: This code uses and even modifies the INTUITIONPRIVATE * area of the IntuitionBase structure. Use it at your own risk. It is * likely to break under a future release of Intuition. */ #include "vScreen.h" static char program[] = "vScreen-Handler"; /* the program name */ static char author[] = COPYRIGHT; /* the copyright notice */ #define MAJVER 1 /* major version number */ #define MINVER 0 /* minor version number */ /* * Macros for normalizing screen Y coordinates to absolute display * corrdinates (using the LaceShift and LaceScreen variables). */ #define TWICE_Y(y) ((y)<>LaceShift) #define NHALF_Y(y) ((LaceScreen)?(y)>>1:(y)) /* * Macros for normalizing screen X coordinates to absolute display * corrdinates (using the HiResShift and HiResScreen variables). */ #define TWICE_X(x) ((x)<>HiResShift) #define NHALF_X(x) ((HiResScreen)?(x)>>1:(x)) /* * The qualifiers used to check whether the left command key is pressed */ #define SHIFTQUALS (~IEQUALIFIER_RELATIVEMOUSE) static struct Screen *VScreen; /* pointer to the virtual screen */ static SHORT ScreenWidth,ScreenHeight; /* the new width and height */ static SHORT OldWidth,OldHeight; /* the old width and height */ static SHORT *RxOffset,*RyOffset; /* pointers to the RasInfo offsets */ static SHORT RxOffset2,RyOffset2; /* normalized RxOffset and RyOffset */ static LONG LaceScreen,LaceShift; /* used to normalize Y positions */ static LONG HiResScreen,HiResShift; /* used to normalize X positions */ static WORD OldMaxDH,OldMaxDR,OldMaxDW; /* Old MaxDisplay values */ static WORD MinXMouse,MaxXMouse,MinYMouse,MaxYMouse; /* the Display min and max values (as opposed to the Intuition values) */ static WORD Xshift = 0; /* how far to shift VScreen */ static WORD Yshift = 0; /* how far to shift VScreen */ static struct Screen *OldScreen = NULL; /* pointer to last active screen */ static WORD ScreenTop,ScreenBottom; /* Y position of vScreen borders */ static short NotVScreen = FALSE; /* TRUE when mouse not over VScreen */ #define BOTTOMSCREEN 1 /* Mouse over screen below VScreen */ #define TOPSCREEN -1 /* mouse over screen behind VScreen */ #define OVERVSCREEN (NotVScreen == FALSE) extern struct vScreenInfo vScreenData; /* the data neede by vScreen */ /* * Setup() * * This routine gets called by vScreen after vScreen-Handler gets LoadSeged. * Setup must return the pointer to the data structure that vScreen uses * to initialize vScreen-Handler's variables. * * vScreen passes its version number to Setup(). If Setup() detects a * version mismatch, it should return NULL. */ struct vScreenInfo *Setup(version) int version; { return(&vScreenData); } /* * SetScreenMinMax() * * Set the OldScreen to the current ActiveScreen. If it is VScreen, * the set the Min and Max Mouse values to the virtual screen sizes, * otherwise, set them to the current display edges (in the coordinate * system of the virtual screen; i.e., origin is offset by RxOffset and * RyOffset). */ static void SetScreenMinMax() { if ((OldScreen = IntuitionBase->ActiveScreen) == VScreen) { IntuitionBase->MinXMouse = 0; IntuitionBase->MaxXMouse = TWICE_X(ScreenWidth) - 1; IntuitionBase->MinYMouse = IntuitionBase->MouseYMinimum; IntuitionBase->MaxYMouse = TWICE_Y(ScreenHeight) - 1; } else { IntuitionBase->MinXMouse = RxOffset2; IntuitionBase->MaxXMouse = OldMaxDW + RxOffset2 - 1; IntuitionBase->MinYMouse = IntuitionBase->MouseYMinimum + RyOffset2; IntuitionBase->MaxYMouse = OldMaxDR + RyOffset2; } } /* * ResetVScreen() * * When the mouse moves over a screen other than the virtual screen, we need * to reset the modified Intuition fields to their normal values so that if * the mouse is pressed over the other screen, it hits the right position. * * Set OldScreen to the ActiveScreen. * Set the Min and Max Mouse values where scrolling will occur to the * display edges (relative to the non-virtual screen). * Set the Intuition MaxDisplay values to their original dimensions. * Subtract the RxOffset and RyOffsets from the Intuition mouse coordinates * (they are now relative to the screen the mouse is moving over). */ static void ResetVScreen() { OldScreen = IntuitionBase->ActiveScreen; MinXMouse = IntuitionBase->MinXMouse = 0; MaxXMouse = IntuitionBase->MaxXMouse = OldMaxDW - 1; MinYMouse = IntuitionBase->MinYMouse = IntuitionBase->MouseYMinimum; MaxYMouse = IntuitionBase->MaxYMouse = OldMaxDR; IntuitionBase->MaxDisplayHeight = OldMaxDH; IntuitionBase->MaxDisplayRow = OldMaxDR; IntuitionBase->MaxDisplayWidth = OldMaxDW; IntuitionBase->MouseX -= RxOffset2; IntuitionBase->MouseY -= RyOffset2; } /* * SetVScreen() * * When the mouse moves back over the virtual screen, we need to change to * Intuition Mouse corrdinates to be relative to the virtual screen again, * so that if the mouse is pressed, it will hit the right spot. * * Set the Min and Max values for the Intuition Mouse. * Set the Min and Max values where scrolling will occur (relative to * the virtual screen origin). * Set the Intuition MaxDisplay values to the full screen size (so that * Intuition will allow the mouse to move to the edge of the virtual * screen). * Add the RxOffset and RyOffset to the Intuition Mouse position so that * Intuition thinks the mouse is positioned relative correctly on the * virtual screen (we compensate for this in MoveSprite). */ static void SetVScreen() { SetScreenMinMax(); MinXMouse = RxOffset2; MaxXMouse = OldMaxDW + RxOffset2 - 1; MinYMouse = IntuitionBase->MouseYMinimum + RyOffset2; MaxYMouse = OldMaxDR + RyOffset2; IntuitionBase->MaxDisplayHeight = TWICE_Y(ScreenHeight); IntuitionBase->MaxDisplayRow = TWICE_Y(ScreenHeight) - 1; IntuitionBase->MaxDisplayWidth = TWICE_X(ScreenWidth); IntuitionBase->MouseX += RxOffset2; IntuitionBase->MouseY += RyOffset2; } /* * FindBounds() * * Locate the absolute display position of the top of the virtual screen * and of the top of the highest screen that is on top of the virtual * screen. That is, if the mouse is between the TopScreen and BottomScreen * Y values, then it is positioned over top pf the visible portion of the * virtual screen. If the screen is not showing, then BottomScreen will * be less that TopScreen. * * Note that TopScreen and BottomScreen are relative to a 320 x 200 screen, * since they are compared to the mouse pointer coordinates (since the pointer * is a sprite, its coordinates always are LoRes and Non-Interlaced). */ static void FindBounds() { struct Screen *theScreen = IntuitionBase->FirstScreen; WORD Top; ScreenTop = NHALF_Y(VScreen->TopEdge) - 1; ScreenBottom = IntuitionBase->MaxDisplayRow; while (theScreen && theScreen != VScreen) { Top = theScreen->TopEdge; if (theScreen->ViewPort.Modes & LACE) Top >>= 1; if (Top < ScreenBottom) ScreenBottom = Top; theScreen = theScreen->NextScreen; } } /* * FixView() * * When the virtual Screen is the first screen, its viewport will try to * display ALL of the vertical length of the screen. This might cause an * overscan display when there is not supposed to be one, so when this * occurs, we set the ViewPort height manually, remake the Screen, and * merge the copper list into the rest of the display. This routine gets * called whenever the intuition View is loaded. Since this happens * whenever a screen changes position, this is a good place to check the * screen top and bottom bounds. */ static void FixView(ForceUpdate) int ForceUpdate; { short TooBig = (VScreen->ViewPort.DHeight > OldHeight); if (TooBig || ForceUpdate) { if (TooBig) VScreen->ViewPort.DHeight = OldHeight; MakeScreen(VScreen); MrgCop(&(IntuitionBase->ViewLord)); } FindBounds(); } /* * CheckShift() * * Check that a forced screen shoft will not go too far. If it does, then * clear the InputEvent mouse movement so that the mouse doesn't move * when the screen can't scroll any more. * * If the mouse is not over the virtual screen, then we don't want the * mouse to move at all, so clear the InputEvent mouse movement. */ static void CheckShift(Xshift,Yshift,theEvent) WORD *Xshift,*Yshift; struct InputEvent *theEvent; { if (*Xshift < -RxOffset2 || *Xshift > TWICE_X(ScreenWidth-OldWidth)-RxOffset2) { *Xshift -= theEvent->ie_X; theEvent->ie_X = 0; } if (*Yshift < -RyOffset2 || *Yshift > TWICE_Y(ScreenHeight-OldHeight)-RyOffset2) { *Yshift -= theEvent->ie_Y; theEvent->ie_Y = 0; } if (NotVScreen) theEvent->ie_X = theEvent->ie_Y = 0; } /* * ShiftVScreen() * * Shift the virtual screen by the specified amount. * * If there is movement in the X direction, * Check that the screen does not scroll too far to the left or right. * If it does, then reduce the move so that the screen scrolls to the edge. * If the mouse is over the virtual screen, then * Adjust the Min and Max values for when scrolling occurs. * Increment the Screen's ViewPort RasInfo RxOffset by the amount of * the scroll, and record the normalized value for later use. * The RxOffset is what tells the graphics library which part of the * larger bitmap should actually be displayed. * * Similarly for the Y shift. * * Finally, if there was actually movement in either direction, * Fix the View (FixView calls MakeScreen() and MrgCop()) and * Load the new View. */ static void ShiftVScreen(Xmove,Ymove) WORD Xmove,Ymove; { WORD xOffset, yOffset; if (Xmove) { xOffset = *RxOffset + Xmove; if (xOffset < 0) Xmove -= xOffset; if (xOffset > ScreenWidth-OldWidth) Xmove += (ScreenWidth-OldWidth - xOffset); if (OVERVSCREEN) { MinXMouse += TWICE_X(Xmove); MaxXMouse += TWICE_X(Xmove); } *RxOffset += Xmove; RxOffset2 = TWICE_X(*RxOffset); } if (Ymove) { yOffset = *RyOffset + Ymove; if (yOffset < 0) Ymove -= yOffset; if (yOffset > ScreenHeight-OldHeight) Ymove += (ScreenHeight-OldHeight - yOffset); if (OVERVSCREEN) { MinYMouse += TWICE_Y(Ymove); MaxYMouse += TWICE_Y(Ymove); } *RyOffset += Ymove; RyOffset2 = TWICE_Y(*RyOffset); } if (Xmove || Ymove) { FixView(TRUE); LoadView(&(IntuitionBase->ViewLord)); } } /* * cLoadView() * * Replaces the LoadView function in the graphics library (the stub calls * the old LoadView() after cLoadView() runs). * * If the virtual screen is still open, and the view is the Intuition View, * the Fix the view (make sure that the height is no larger than the * old MaxDisplayHeight). * * Note: when you drag a screen up and down, Intuition calls RemakeDisplay() * which in turn calls MakeScreen() on each screen, MrgCop() and finally * LoadView(). cLoadView() gets called in place of LoadView(), and it will * call FixView(), which calls MakeScreen() and MrgCop() again. This are high * overhead calls, so when the virtual screen is the FirstScreen and there is * another screen showing behind it, this can nearly double the overhead of * dragging a screen. */ void cLoadView(view) struct View *view; { if (VScreen && view == &(IntuitionBase->ViewLord)) FixView(FALSE); } /* * cMoveSprite(); * * Repaces MoveSprite() in the graphics library (the stub calls the old * MoveSprive() after cMoveSprite() runs). * * If the sprite being moved is sprite zero (the pointer sprite), and * the virtual screen is still open, then * If the pointer is over the virtual screen * Subtract the offsets (relative to a 320 x 200 screen, since this * is a sprite). We must do this, since we added RxOffset and RyOffset * to the Intuition MouseX and MouseY values. Since Intuition does not * take the RxOffset and RyOffset into account, we have to subtract * the values here in order to keep the pointer on the display area. * * If the Min and Max X Mouse values are equal, a screen is being dragged. * If the LayerInfo Lock has a NestCount, then the layers are locked. * * If the mouse is not over the virtual screen then * Set the MinY for scrolling to the top of the view. * If we used to be over the virtual screen and * we're not currently dragging a screen, then * If the virtual screen is not locked, then * Reset the coordinate system to fit the screen we are over. * Save which type of screen we're over. * otherwise (the screen is locked; e.g., the user has the * menu button down, or is dragging a window), * so scroll the screen if we move off the top. * Otherwise (the mouse IS over the virtual screen) * Set the MinY for scrolling to the top of the view relative to the * virtual screen origin. * If we used to be over some other screen, and we're not dragging * a screen, and the current screen is not locked, then * (we need to change coordinates to the virtual screen coordinates) * If we are going from a bottom screen onto the virtual screen * then activate the cirtual screen (I couldn't find a way to do * the coordinate transfer without this). * Set the coordinates relative to the virtual screen. * Record that we are over the virtual screen. * * If a new screen has become active then * If we were over the virtual screen, check that we still are * Record the active screen * * If the user is dragging the screen, don't let him drag it off * the bottom of the display area (where he won;t be able to reach it). * * If the MouseY has gone past the top of the screen (i.e., if a screen * has closed and the pointer was above it), put the pointer at the * top of the display. */ void cMoveSprite(ss,x,y) struct SimpleSprite *ss; long x,y; { short NotDrag; short NotLocked; if (ss->num == 0 && VScreen) { if (OVERVSCREEN) { x -= NHALF_X(*RxOffset); y -= NHALF_Y(*RyOffset); } NotDrag = (IntuitionBase->MinXMouse != IntuitionBase->MaxXMouse); NotLocked = (IntuitionBase->ActiveScreen->LayerInfo.Lock.ss_NestCount == 0); if (y < ScreenTop || y >= ScreenBottom) { MinYMouse = IntuitionBase->MouseYMinimum; if (OVERVSCREEN && NotDrag) { if (NotLocked) { ResetVScreen(); NotVScreen = (y < ScreenTop)? TOPSCREEN: BOTTOMSCREEN; } else { if (y < ScreenTop) ShiftVScreen(0,NTWICE_Y(y-ScreenTop)); } } } else { MinYMouse = IntuitionBase->MouseYMinimum + RyOffset2; if (NotVScreen && NotDrag && NotLocked) { if (NotVScreen == BOTTOMSCREEN && IntuitionBase->ActiveScreen != VScreen && VScreen->FirstWindow) ActivateWindow(VScreen->FirstWindow); SetVScreen(); NotVScreen = FALSE; } } if (OldScreen != IntuitionBase->ActiveScreen) { if (OVERVSCREEN) SetScreenMinMax(); OldScreen = IntuitionBase->ActiveScreen; } if (NotDrag == FALSE && IntuitionBase->MaxYMouse > OldMaxDR + RyOffset2) IntuitionBase->MaxYMouse = OldMaxDR + RyOffset2; if (IntuitionBase->MouseY < IntuitionBase->MinYMouse) IntuitionBase->MouseY = IntuitionBase->MinYMouse; } } /* * cAutoRequest() * * Replaces AutoRequest() in the Intuition Library (the stub calls * AutoRequest() after cAutoRequest() runs). * * If the AutoRequest is to appear on the virtual screen, then shift * the virtual screen so that the System Request window will be showing. * Note that this could change the screen position even while the user is * doing critical work (e.g., dragging a window, pulling menus, etc). * It would be better to trap this through BuildSysRequest(), but since * Intuition does not use its own vector table, I can not trap all the * calls to BuildSysRequest, specifically, the ones called by AutoRequest(). */ void cAutoRequest(theWindow) struct Window *theWindow; { if (theWindow == NULL || theWindow->WScreen == VScreen) { IntuitionBase->MouseX -= RxOffset2; IntuitionBase->MouseY -= RyOffset2; ShiftVScreen(-(*RxOffset),-(*RyOffset)); } } /* * cBuildSysRequest() * * Replaces BuildSysRequest() in the Intuition Libaray (the stub routine * calls BuildSysRequest() before calling cBuildSysReques()). * * If the window (returned by BuildSysRequest) exists, then * If it is on the virtual screen, and it is not completely showing, then * Move the window so that it is in the upper left corner of the * displayed section of the screen. * * Note that this is better than what we do with AutoRequest, since the * screen itself does not scroll. */ void cBuildSysRequest(theWindow) struct Window *theWindow; { if (theWindow && theWindow != (struct Window *)TRUE) { if (theWindow->WScreen == VScreen && (theWindow->LeftEdge < *RxOffset || theWindow->TopEdge < *RyOffset)) MoveWindow(theWindow,*RxOffset-theWindow->LeftEdge, *RyOffset-theWindow->TopEdge); } } /* * cCloseScreen() * * Replaces CloseScreen() in the IntuitionLibrary (the stub routine calls * CloseScreen() after cCloseScreen() runs). * * If the virtual screen is still open, and it is the one being closed, * reset the coordinate system, and mark the virtual screen as closed. */ void cCloseScreen(theScreen) struct Screen *theScreen; { if (theScreen == VScreen && VScreen != NULL) { ResetVScreen(); VScreen = NULL; } } /* * myHandler() * * This is the input handler that gets added to the Input.Device input * chain. It is at priority 51, so it is ahead of Intuition. * * If a screen is not being dragged, and the virtual screen is still open, * then for each event in the event list, * If the event is a RAWMOUSE event, * If the left Amiga key is held down then * Add it into the forced shift count and * Check that the shift is valid. * Add the movement into the total movement so far. * * Find the new MouseX and MouseY positions. * Shift the screen by the forced shift amounts, checking that the * coordinates are shifted when needed, and retaining any un-used * shift amount (due to LoRes or Non-Interlaced screens). * * If the active screen is the virtual screen, then * if the mouse has moved off the left or right edge of the display, * or if the mouse has moved past the Intuition Min or Max values, * then shift the screen by the appropriate amount. Do this for * both the X and Y directions. * * Finally, return the original event list. */ struct InputEvent *myHandler(EventList,data) struct InputEvent *EventList; APTR data; { struct InputEvent *theEvent = EventList; WORD MouseX,MouseY; WORD Xmove = 0; WORD Ymove = 0; WORD Ychange = 0; WORD Xchange = 0; WORD dx,dy; if (IntuitionBase->MinXMouse != IntuitionBase->MaxXMouse && VScreen) { Forbid(); while (theEvent) { if (theEvent->ie_Class == IECLASS_RAWMOUSE) { if ((theEvent->ie_Qualifier & SHIFTQUALS) == IEQUALIFIER_LCOMMAND) { Xshift += theEvent->ie_X; Yshift += theEvent->ie_Y; CheckShift(&Xshift,&Yshift,theEvent); } Xchange += theEvent->ie_X; Ychange += theEvent->ie_Y; } theEvent = theEvent->ie_NextEvent; } Permit(); MouseX = IntuitionBase->MouseX + HALF_X(Xchange); MouseY = IntuitionBase->MouseY + HALF_Y(Ychange); if (Xshift < 0) dx = -HALF_X(-Xshift); else dx = HALF_X(Xshift); if (Yshift < 0) dy = -HALF_Y(-Yshift); else dy = HALF_Y(Yshift); if (dx || dy) { ShiftVScreen(dx,dy); if (IntuitionBase->ActiveScreen != VScreen && OVERVSCREEN) SetScreenMinMax(); if (HiResScreen) Xshift = 0; else Xshift %= 2; if (LaceScreen) Yshift = 0; else Yshift %= 2; } if (IntuitionBase->ActiveScreen == VScreen) { if (MouseX < MinXMouse) Xmove = MouseX - MinXMouse; if (MouseX > MaxXMouse) Xmove = MouseX - MaxXMouse; if (Xmove == 0) { if (MouseX < IntuitionBase->MinXMouse) Xmove = MouseX - IntuitionBase->MinXMouse; if (MouseX > IntuitionBase->MaxXMouse) Xmove = MouseX - IntuitionBase->MaxXMouse; } if (MouseY < MinYMouse) Ymove = MouseY - MinYMouse; if (MouseY > MaxYMouse) Ymove = MouseY - MaxYMouse; if (Ymove == 0) { if (MouseY < IntuitionBase->MinYMouse) Ymove = MouseY - IntuitionBase->MinYMouse; if (MouseY > IntuitionBase->MaxYMouse) Ymove = MouseY - IntuitionBase->MaxYMouse; } if (Xmove || Ymove) ShiftVScreen(Xmove,Ymove); } } return(EventList); } /* * These are the assembler stubs needed in order to SetFunction some * of the Intuiton and graphics library functions. */ extern void myHandlerStub(); extern void aCloseScreen(); extern void aBuildSysRequest(); extern void aAutoRequest(); extern void aLoadView(); extern void aMoveSprite(); /* * These are the jump addresses in the assembler routines that need to * be filled in by vScreen once the old SetFunction vectors are known. * This is a kludge, and is a form od self-modifying code, but I can't figure * out a better way that works. The JSR (Ax) form is only good if there is * a free A register to use, which is not always the case. */ extern unsigned char *CloseScreenJmpAddress; extern unsigned char *BuildSysRequestJmpAddress; extern unsigned char *AutoRequestJmpAddress; extern unsigned char *LoadViewJmpAddress; extern unsigned char *MoveSpriteJmpAddress; static char PortName[] = PORTNAME; /* the name of the named port */ static struct Interrupt HandlerInfo = /* the Interrupt needed to add an */ { /* input handler to the input chain */ {NULL, NULL, 0, 51, NULL}, /* ln_Pri = 51 (before Intuition) */ NULL, &myHandlerStub /* the handler to add */ }; /* * This is the structure passed from the Setup() routine to vScreen. * It includes pointers to the variables that vSCreen needs to initialize, * plus pointers to the routines that vScreen will SetFunction into the * appropriate libraries. */ struct vScreenInfo vScreenData = { MAJVER,MINVER,0, &program[0], &PortName[0], NULL, &HandlerInfo, &IntuitionBase, &GfxBase, &VScreen, &ScreenWidth,&ScreenHeight, &OldWidth,&OldHeight, &RxOffset,&RyOffset, &RxOffset2,&RyOffset2, &LaceScreen,&LaceShift, &HiResScreen,&HiResShift, &OldMaxDH,&OldMaxDR,&OldMaxDW, &SetVScreen, &ResetVScreen, &FixView, &aCloseScreen, &aBuildSysRequest, &aAutoRequest, &aLoadView, &aMoveSprite, (long *) &CloseScreenJmpAddress, (long *) &BuildSysRequestJmpAddress, (long *) &AutoRequestJmpAddress, (long *) &LoadViewJmpAddress, (long *) &MoveSpriteJmpAddress, 0,0,0,0,0 };