//	MarqueeUtils.c

#include "SmoothUpdater.h"
#include "FixedUtils.h"
#include "MarqueeUtils.h"

//	defines lower right corner hit
MU_HitRec	MU_NEW_MARQUEE			= { 0, 0, 1, 1, 0, 0, 1, 0, 0, 0 };
MU_HitRec	MU_NEW_MARQUEE_ERASE	= { 0, 0, 1, 1, 0, 0, 1, 1, 0, 0 };

/** StretchRect

	Given a delta-point and BEE_Hand handle-position, one or two of the rect's sides are modified
	by the appropriate delta.
	
	For example if the handle is BEE_Hand_MID_RIGHT, the rect's right edge is incremented by delta.h.
	
	The rect is guaranteed to never stretch smaller than zero width or height.

**/


void		MU_StretchRect(
	MU_HitRec		hitRec, 
	Boolean			size_from_center, 
	M_FixedPoint	origFixPoint, 
	M_FixedPoint	deltaFixPoint, 
	Boolean			can_constrain, 
	M_Ratio			*aspect_ratio0, 
	MU_CallbacksRec	*callbacks0, 
	M_FixedRect		*fixRect)
{
	Boolean			constrain	= can_constrain && U_ModKeyDown(U_ModKey_SHIFT);
	
	if (hitRec.hit_center) {
		
		if (constrain) {
			
			U_ASSERT(callbacks0 && callbacks0->SnapRectToViewArea);
			
			if (U_ModKeyDown(U_ModKey_OPTION)) {		// snap to center & edges
				M_FixedRect		tempFixRect = *fixRect;
				
				tempFixRect += deltaFixPoint;
				
				callbacks0->SnapRectToViewArea(&tempFixRect, &deltaFixPoint, callbacks0->data0);

			} else {									// constrain w/shift key
				M_FixedPoint	newFixPoint = origFixPoint + deltaFixPoint;

				if (M_ABS(deltaFixPoint.x) > M_ABS(deltaFixPoint.y))
					newFixPoint.y = origFixPoint.y;
				else
					newFixPoint.x = origFixPoint.x;
				
				deltaFixPoint = newFixPoint - origFixPoint;
			}
		}
				
		*fixRect += deltaFixPoint;
	} else {
		Boolean			tempHandle;
		
		if (!(hitRec.hit_edge && constrain)) {
			M_Fixed			temp;
			
			if (hitRec.left) {
				if (size_from_center) {
					M_InsetFixedRect(fixRect, deltaFixPoint.x, 0);
				} else {
					fixRect->left += deltaFixPoint.x;
				}
				
				if (fixRect->left > fixRect->right) {
					SWAP(fixRect->left, fixRect->right, temp);
					SWAP(hitRec.left, hitRec.right, tempHandle);
				}
			} else if (hitRec.right) {
				if (size_from_center) {
					M_InsetFixedRect(fixRect, -deltaFixPoint.x, 0);
				} else {
					fixRect->right += deltaFixPoint.x;
				}
				
				if (fixRect->right < fixRect->left) {
					SWAP(fixRect->left, fixRect->right, temp);
					SWAP(hitRec.left, hitRec.right, tempHandle);
				}
			}
			
			if (hitRec.top) {
				if (size_from_center) {
					M_InsetFixedRect(fixRect, 0, deltaFixPoint.y);
				} else {
					fixRect->top += deltaFixPoint.y;
				}

				if (fixRect->top > fixRect->bottom) {
					SWAP(fixRect->top, fixRect->bottom, temp);
					SWAP(hitRec.top, hitRec.bottom, tempHandle);
				}
			} else if (hitRec.bottom) {
				if (size_from_center) {
					M_InsetFixedRect(fixRect, 0, -deltaFixPoint.y);
				} else {
					fixRect->bottom += deltaFixPoint.y;
				}

				if (fixRect->bottom < fixRect->top) {
					SWAP(fixRect->top, fixRect->bottom, temp);
					SWAP(hitRec.top, hitRec.bottom, tempHandle);
				}
			}
	
			if (constrain) {
				M_FixedPoint	curSize, inset = { 0, 0 }, 
								vertConstrain, horizConstrain, 
								finalConstrain, centerConstrainInset;
				
				curSize.x = fixRect->right - fixRect->left;
				curSize.y = fixRect->bottom - fixRect->top;
	
				vertConstrain = curSize;
				horizConstrain = curSize;
				
				if (aspect_ratio0) {
					M_Fixed		fixAspectRatio = M_RATIO2FIX(*aspect_ratio0);
					
					vertConstrain.x		= M_FIX_DIV(vertConstrain.y, fixAspectRatio);
					horizConstrain.y	= M_FIX_MUL(horizConstrain.x, fixAspectRatio);
				} else {
					vertConstrain.x		= M_FIX_DIV(vertConstrain.y, M_Fixed_1);
					horizConstrain.y	= M_FIX_MUL(horizConstrain.x, M_Fixed_1);
				}
				
				if (horizConstrain.x < vertConstrain.x) {
					finalConstrain = horizConstrain;
				} else {
					finalConstrain = vertConstrain;
				}
				
				if (size_from_center) {
					centerConstrainInset.x = (curSize.x - finalConstrain.x) >> 1;
					centerConstrainInset.y = (curSize.y - finalConstrain.y) >> 1;
				}
							
				if (hitRec.top) {
					if (size_from_center) {
						inset.y = centerConstrainInset.y;
					} else {
						fixRect->top = fixRect->bottom - finalConstrain.y;
					}
				} else if (hitRec.bottom) {
					if (size_from_center) {
						inset.y = centerConstrainInset.y;
					} else {
						fixRect->bottom = fixRect->top + finalConstrain.y;
					}
				}
	
				if (hitRec.left) {
					if (size_from_center) {
						inset.x = centerConstrainInset.x;
					} else {
						fixRect->left = fixRect->right - finalConstrain.x;
					}
				} else if (hitRec.right) {
					if (size_from_center) {
						inset.x = centerConstrainInset.x;
					} else {
						fixRect->right = fixRect->left + finalConstrain.x;
					}
				}
				
				if (inset.x || inset.y) {
					M_InsetFixedRect(fixRect, inset.x, inset.y);
				}
			}
		}
	}
}

#define		MU_SU_START_GET_SMOOTH_UPDATE(SCREEN_RECT, SU_WORLD, ERR)	\
{																		\
	SU_GraphicsState	_graphics_state;								\
	Err					_ERR;											\
																		\
	ERR = SU_StartDrawingOffscreen(&SU_WORLD, TRUE, 					\
				&SCREEN_RECT, &_graphics_state);						\
	if (!ERR) {
		
#define		MU_SU_END_GET_SMOOTH_UPDATE(SU_WORLD, ERR)					\
		_ERR = SU_StopDrawingOffscreen(&SU_WORLD, &_graphics_state);	\
		if (!ERR) ERR = _ERR;											\
	}																	\
}


static	void		MU_DrawMarqueeShape(
	Boolean				oval, 
	MU_CallbacksRec 	*callbacks0, 
	M_FixedRect			*newFixRect)
{
	if (callbacks0 && callbacks0->DrawMarqueeShape) {
		(*callbacks0->DrawMarqueeShape)(oval, newFixRect, callbacks0->data0);
	} else {
		ClipRect(&callbacks0->frameRect);

		PenPat(&qd.gray);
		FRAME_FIXED_RECT_OR_OVAL(oval, *newFixRect);
		PenPat(&qd.black);
	}
}

Err			MU_Marquee(
	MU_HitRec			hitRec, 
	Boolean				size_from_center, 
	M_FixedPoint		origFixPoint, 
	Boolean				can_constrain, 
	M_Ratio				*aspect_ratio0, 	//	iff can_constrain
	Boolean				oval, 
	MU_CallbacksRec		*callbacks0, 
	M_FixedRect			*destFixRect)
{
	Err				err = Err_NONE, err2;
	M_FixedPoint	oldFixPoint, newFixPoint, fixOffset = { 0, 0 };
	M_FixedRect		origFixRect, oldFixRect, newFixRect;
	Point			newPoint;
	Boolean			constrain = FALSE;
	SU_World		newSu, oldSu, swapSu;
	
	SU_World_MEMCLR(newSu);
	SU_World_MEMCLR(oldSu);
	
	U_ASSERT(sizeof(MU_HitRec) == 4);
	
	err = SU_NewWorld(&qd.thePort->portRect, TRUE, &newSu);

	if (!err) err = SU_NewWorld(&qd.thePort->portRect, TRUE, &oldSu);

	if (!err) {
		if (hitRec.new_marquee) {
			origFixRect.left = origFixRect.right = origFixPoint.x;
			origFixRect.top = origFixRect.bottom = origFixPoint.y;
		} else {
			origFixRect = *destFixRect;
		}
		
		oldFixRect = origFixRect;
			
		MU_SU_START_GET_SMOOTH_UPDATE(qd.thePort->portRect, oldSu, err) {
			MU_DrawMarqueeShape(oval, callbacks0, &oldFixRect);
		} MU_SU_END_GET_SMOOTH_UPDATE(oldSu, err);
		
		if (!err && hitRec.new_marquee) {
			if (!err) err = SU_InvertWorldOnscreen(&oldSu, TRUE, qd.thePort);
		}
	
		if (!err) do {
			GetMouse(&newPoint);
			SHORT2FIXPT(newPoint, newFixPoint);
			
			newFixRect = origFixRect + fixOffset;
			
			MU_StretchRect(hitRec, size_from_center, origFixPoint, 
				newFixPoint - fixOffset - origFixPoint, 
				can_constrain, aspect_ratio0, callbacks0, &newFixRect);
						
			if ((
					callbacks0
					&& callbacks0->StateChanged
					&& callbacks0->StateChanged(&newFixRect, callbacks0->data0)
				) || !M_EqualFixedRect(&newFixRect, &oldFixRect)) {
				
				if (callbacks0
					&& callbacks0->WillAutoScroll 
					&& callbacks0->DoAutoScroll
					&& (*callbacks0->WillAutoScroll)(newFixPoint, &callbacks0->frameRect, callbacks0->data0)) {
	
					MU_SU_START_GET_SMOOTH_UPDATE(qd.thePort->portRect, newSu, err) {
						MU_DrawMarqueeShape(oval, callbacks0, &oldFixRect);
					} MU_SU_END_GET_SMOOTH_UPDATE(newSu, err);
					
					if (!err) {
						err = SU_InvertWorldOnscreen(&newSu, TRUE, qd.thePort);
						
						if (!err) {
							M_FixedPoint			cur_offset = { 0, 0 };
							
							(*callbacks0->DoAutoScroll)(newFixPoint, 
								&callbacks0->frameRect, &cur_offset, callbacks0->data0);
							
							newFixRect	-= cur_offset;
							fixOffset	-= cur_offset;
							
							MU_SU_START_GET_SMOOTH_UPDATE(qd.thePort->portRect, newSu, err) {
								MU_DrawMarqueeShape(oval, callbacks0, &newFixRect);
							} MU_SU_END_GET_SMOOTH_UPDATE(newSu, err);
							
							if (!err) err = SU_BlitWorld(BM_Blit_COPY, &newSu, &oldSu);
						}
					}
				} else {
					MU_SU_START_GET_SMOOTH_UPDATE(qd.thePort->portRect, newSu, err) {
						MU_DrawMarqueeShape(oval, callbacks0, &newFixRect);
					} MU_SU_END_GET_SMOOTH_UPDATE(newSu, err);
					
					if (!err) err = SU_XorWorlds(&newSu, &oldSu, &oldSu);
				}
	
				if (!err) {
					err = SU_InvertWorldOnscreen(&oldSu, TRUE, qd.thePort);
					
					if (callbacks0 && callbacks0->UpdateScreenInfo) {
						callbacks0->UpdateScreenInfo(&origFixRect, &newFixRect, callbacks0->data0);
					}
					
					SWAP(newSu, oldSu, swapSu);
					oldFixRect	= newFixRect;
					oldFixPoint	= newFixPoint;
				}
			}
		} while (!err && StillDown());
				
		*destFixRect = newFixRect;
		
		if (!err && hitRec.erase_when_done) {
			if (!err) err = SU_InvertWorldOnscreen(&oldSu, TRUE, qd.thePort);
		}
	
		if (SU_World_CREATED(newSu)) {
			err2 = SU_DisposeWorld(&newSu);
			if (!err)
				err = err2;
		}

		if (SU_World_CREATED(oldSu)) {
			err2 = SU_DisposeWorld(&oldSu);
			if (!err)
				err = err2;
		}
	}
	
	return err;
}

void		MU_LimitFixRect(M_FixedRect *limitFixBounds, M_FixedRect *theFixRect)
{
	if (theFixRect->left < limitFixBounds->left)
		theFixRect->left = limitFixBounds->left;
	
	if (theFixRect->top < limitFixBounds->top)
		theFixRect->top = limitFixBounds->top;
	
	if (theFixRect->right > limitFixBounds->right)
		theFixRect->right = limitFixBounds->right;
	
	if (theFixRect->bottom > limitFixBounds->bottom)
		theFixRect->bottom = limitFixBounds->bottom;
}

void		MU_LimitRect(Rect *limitBounds, Rect *theRect)
{
	M_FixedRect		limitFixBounds, theFixRect;
	
	M_RECT_2_FIXEDRECT(*limitBounds, limitFixBounds);
	M_RECT_2_FIXEDRECT(*theRect, theFixRect);
	
	MU_LimitFixRect(&limitFixBounds, &theFixRect);
	
	M_FIXEDRECT_2_RECT(theFixRect, *theRect);
	M_FIXEDRECT_2_RECT(limitFixBounds, *limitBounds);
}
