Torque3D/Engine/source/gui/editor/guiEditCtrl.cpp
Thomas "elfprince13" Dickerson bcc5459818 whitespace
2017-01-11 23:34:46 -05:00

2925 lines
88 KiB
C++

//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "gui/editor/guiEditCtrl.h"
#include "core/frameAllocator.h"
#include "core/stream/fileStream.h"
#include "core/stream/memStream.h"
#include "console/consoleTypes.h"
#include "gui/core/guiCanvas.h"
#include "gui/containers/guiScrollCtrl.h"
#include "core/strings/stringUnit.h"
#include "console/engineAPI.h"
IMPLEMENT_CONOBJECT( GuiEditCtrl );
ConsoleDocClass( GuiEditCtrl,
"@brief Native side of the GUI editor.\n\n"
"Editor use only.\n\n"
"@internal"
);
IMPLEMENT_CALLBACK( GuiEditCtrl, onHierarchyChanged, void, (), (),
"" );
IMPLEMENT_CALLBACK( GuiEditCtrl, onDelete, void, (), (),
"" );
IMPLEMENT_CALLBACK( GuiEditCtrl, onPreEdit, void, ( SimSet* selection ), ( selection ),
"" );
IMPLEMENT_CALLBACK( GuiEditCtrl, onPostEdit, void, ( SimSet* selection ), ( selection ),
"" );
IMPLEMENT_CALLBACK( GuiEditCtrl, onClearSelected, void, (), (),
"" )
IMPLEMENT_CALLBACK( GuiEditCtrl, onSelect, void, ( GuiControl* control ), ( control ),
"" );
IMPLEMENT_CALLBACK( GuiEditCtrl, onAddSelected, void, ( GuiControl* control ), ( control ),
"" );
IMPLEMENT_CALLBACK( GuiEditCtrl, onRemoveSelected, void, ( GuiControl* control ), ( control ),
"" );
IMPLEMENT_CALLBACK( GuiEditCtrl, onPreSelectionNudged, void, ( SimSet* selection ), ( selection ),
"" );
IMPLEMENT_CALLBACK( GuiEditCtrl, onPostSelectionNudged, void, ( SimSet* selection ), ( selection ),
"" );
IMPLEMENT_CALLBACK( GuiEditCtrl, onSelectionMoved, void, ( GuiControl* control ), ( control ),
"" );
IMPLEMENT_CALLBACK( GuiEditCtrl, onSelectionCloned, void, ( SimSet* selection ), ( selection ),
"" );
IMPLEMENT_CALLBACK( GuiEditCtrl, onTrashSelection, void, ( SimSet* selection ), ( selection ),
"" );
IMPLEMENT_CALLBACK( GuiEditCtrl, onAddNewCtrl, void, ( GuiControl* control ), ( control ),
"" );
IMPLEMENT_CALLBACK( GuiEditCtrl, onAddNewCtrlSet, void, ( SimSet* set ), ( set ),
"" );
IMPLEMENT_CALLBACK( GuiEditCtrl, onSelectionResized, void, ( GuiControl* control ), ( control ),
"" );
IMPLEMENT_CALLBACK( GuiEditCtrl, onFitIntoParent, void, ( bool width, bool height ), ( width, height ),
"" );
IMPLEMENT_CALLBACK( GuiEditCtrl, onMouseModeChange, void, (), (),
"" );
IMPLEMENT_CALLBACK( GuiEditCtrl, onControlInspectPreApply, void, ( GuiControl* control ), ( control ),
"" );
IMPLEMENT_CALLBACK( GuiEditCtrl, onControlInspectPostApply, void, ( GuiControl* control ), ( control ),
"" );
StringTableEntry GuiEditCtrl::smGuidesPropertyName[ 2 ];
//-----------------------------------------------------------------------------
GuiEditCtrl::GuiEditCtrl()
: mCurrentAddSet( NULL ),
mContentControl( NULL ),
mGridSnap( 0, 0 ),
mDragBeginPoint( -1, -1 ),
mSnapToControls( true ),
mSnapToEdges( true ),
mSnapToCenters( true ),
mSnapToGuides( true ),
mSnapToCanvas( true ),
mSnapSensitivity( 2 ),
mFullBoxSelection( false ),
mDrawBorderLines( true ),
mDrawGuides( true )
{
VECTOR_SET_ASSOCIATION( mSelectedControls );
VECTOR_SET_ASSOCIATION( mDragBeginPoints );
VECTOR_SET_ASSOCIATION( mSnapHits[ 0 ] );
VECTOR_SET_ASSOCIATION( mSnapHits[ 1 ] );
mActive = true;
mDotSB = NULL;
mSnapped[ SnapVertical ] = false;
mSnapped[ SnapHorizontal ] = false;
mDragGuide[ GuideVertical ] = false;
mDragGuide[ GuideHorizontal ] = false;
if( !smGuidesPropertyName[ GuideVertical ] )
smGuidesPropertyName[ GuideVertical ] = StringTable->insert( "guidesVertical" );
if( !smGuidesPropertyName[ GuideHorizontal ] )
smGuidesPropertyName[ GuideHorizontal ] = StringTable->insert( "guidesHorizontal" );
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::initPersistFields()
{
addGroup( "Snapping" );
addField( "snapToControls", TypeBool, Offset( mSnapToControls, GuiEditCtrl ),
"If true, edge and center snapping will work against controls." );
addField( "snapToGuides", TypeBool, Offset( mSnapToGuides, GuiEditCtrl ),
"If true, edge and center snapping will work against guides." );
addField( "snapToCanvas", TypeBool, Offset( mSnapToCanvas, GuiEditCtrl ),
"If true, edge and center snapping will work against canvas (toplevel control)." );
addField( "snapToEdges", TypeBool, Offset( mSnapToEdges, GuiEditCtrl ),
"If true, selection edges will snap into alignment when moved or resized." );
addField( "snapToCenters", TypeBool, Offset( mSnapToCenters, GuiEditCtrl ),
"If true, selection centers will snap into alignment when moved or resized." );
addField( "snapSensitivity", TypeS32, Offset( mSnapSensitivity, GuiEditCtrl ),
"Distance in pixels that edge and center snapping will work across." );
endGroup( "Snapping" );
addGroup( "Selection" );
addField( "fullBoxSelection", TypeBool, Offset( mFullBoxSelection, GuiEditCtrl ),
"If true, rectangle selection will only select controls fully inside the drag rectangle." );
endGroup( "Selection" );
addGroup( "Rendering" );
addField( "drawBorderLines", TypeBool, Offset( mDrawBorderLines, GuiEditCtrl ),
"If true, lines will be drawn extending along the edges of selected objects." );
addField( "drawGuides", TypeBool, Offset( mDrawGuides, GuiEditCtrl ),
"If true, guides will be included in rendering." );
endGroup( "Rendering" );
Parent::initPersistFields();
}
//=============================================================================
// Events.
//=============================================================================
// MARK: ---- Events ----
//-----------------------------------------------------------------------------
bool GuiEditCtrl::onAdd()
{
if( !Parent::onAdd() )
return false;
mTrash = new SimGroup();
mSelectedSet = new SimSet();
if( !mTrash->registerObject() )
return false;
if( !mSelectedSet->registerObject() )
return false;
return true;
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::onRemove()
{
Parent::onRemove();
mDotSB = NULL;
mTrash->deleteObject();
mSelectedSet->deleteObject();
mTrash = NULL;
mSelectedSet = NULL;
}
//-----------------------------------------------------------------------------
bool GuiEditCtrl::onWake()
{
if (! Parent::onWake())
return false;
// Set GUI Controls to DesignTime mode
GuiControl::smDesignTime = true;
GuiControl::smEditorHandle = this;
setEditMode(true);
return true;
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::onSleep()
{
// Set GUI Controls to run time mode
GuiControl::smDesignTime = false;
GuiControl::smEditorHandle = NULL;
Parent::onSleep();
}
//-----------------------------------------------------------------------------
bool GuiEditCtrl::onKeyDown(const GuiEvent &event)
{
if (! mActive)
return Parent::onKeyDown(event);
if (!(event.modifier & SI_PRIMARY_CTRL))
{
switch(event.keyCode)
{
case KEY_BACKSPACE:
case KEY_DELETE:
deleteSelection();
onDelete_callback();
return true;
default:
break;
}
}
return false;
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::onMouseDown(const GuiEvent &event)
{
if (! mActive)
{
Parent::onMouseDown(event);
return;
}
if(!mContentControl)
return;
setFirstResponder();
mouseLock();
mLastMousePos = globalToLocalCoord( event.mousePoint );
// Check whether we've hit a guide. If so, start a guide drag.
// Don't do this if SHIFT is down.
if( !( event.modifier & SI_SHIFT ) )
{
for( U32 axis = 0; axis < 2; ++ axis )
{
const S32 guide = findGuide( ( guideAxis ) axis, event.mousePoint, 1 );
if( guide != -1 )
{
setMouseMode( DragGuide );
mDragGuide[ axis ] = true;
mDragGuideIndex[ axis ] = guide;
}
}
if( mMouseDownMode == DragGuide )
return;
}
// Check whether we have hit a sizing knob on any of the currently selected
// controls.
for( U32 i = 0, num = mSelectedControls.size(); i < num; ++ i )
{
GuiControl* ctrl = mSelectedControls[ i ];
Point2I cext = ctrl->getExtent();
Point2I ctOffset = globalToLocalCoord( ctrl->localToGlobalCoord( Point2I( 0, 0 ) ) );
RectI box( ctOffset.x, ctOffset.y, cext.x, cext.y );
if( ( mSizingMode = ( GuiEditCtrl::sizingModes ) getSizingHitKnobs( mLastMousePos, box ) ) != 0 )
{
setMouseMode( SizingSelection );
mLastDragPos = event.mousePoint;
// undo
onPreEdit_callback( getSelectedSet() );
return;
}
}
// Find the control we have hit.
GuiControl* ctrl = mContentControl->findHitControl( mLastMousePos, getCurrentAddSet()->mLayer );
// Give the control itself the opportunity to handle the event
// to implement custom editing logic.
bool handledEvent = ctrl->onMouseDownEditor( event, localToGlobalCoord( Point2I(0,0) ) );
if( handledEvent == true )
{
// The Control handled the event and requested the edit ctrl
// *NOT* act on it.
return;
}
else if( event.modifier & SI_SHIFT )
{
// Shift is down. Start rectangle selection in add mode
// no matter what we have hit.
startDragRectangle( event.mousePoint );
mDragAddSelection = true;
}
else if( selectionContains( ctrl ) )
{
// We hit a selected control. If the multiselect key is pressed,
// deselect the control. Otherwise start a drag move.
if( event.modifier & SI_MULTISELECT )
{
removeSelection( ctrl );
//set the mode
setMouseMode( Selecting );
}
else if( event.modifier & SI_PRIMARY_ALT )
{
// Alt is down. Start a drag clone.
startDragClone( event.mousePoint );
}
else
{
startDragMove( event.mousePoint );
}
}
else
{
// We clicked an unselected control.
if( ctrl == getContentControl() )
{
// Clicked in toplevel control. Start a rectangle selection.
startDragRectangle( event.mousePoint );
mDragAddSelection = false;
}
else if( event.modifier & SI_PRIMARY_ALT && ctrl != getContentControl() )
{
// Alt is down. Start a drag clone.
clearSelection();
addSelection( ctrl );
startDragClone( event.mousePoint );
}
else if( event.modifier & SI_MULTISELECT )
addSelection( ctrl );
else
{
// Clicked on child control. Start move.
clearSelection();
addSelection( ctrl );
startDragMove( event.mousePoint );
}
}
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::onMouseUp(const GuiEvent &event)
{
if (! mActive || !mContentControl || !getCurrentAddSet() )
{
Parent::onMouseUp(event);
return;
}
//find the control we clicked
GuiControl *ctrl = mContentControl->findHitControl(mLastMousePos, getCurrentAddSet()->mLayer);
bool handledEvent = ctrl->onMouseUpEditor( event, localToGlobalCoord( Point2I(0,0) ) );
if( handledEvent == true )
{
// The Control handled the event and requested the edit ctrl
// *NOT* act on it. The dude abides.
return;
}
//unlock the mouse
mouseUnlock();
// Reset Drag Axis Alignment Information
mDragBeginPoint.set(-1,-1);
mDragBeginPoints.clear();
mLastMousePos = globalToLocalCoord(event.mousePoint);
if( mMouseDownMode == DragGuide )
{
// Check to see if the mouse has moved off the canvas. If so,
// remove the guides being dragged.
for( U32 axis = 0; axis < 2; ++ axis )
if( mDragGuide[ axis ] && !getContentControl()->getGlobalBounds().pointInRect( event.mousePoint ) )
mGuides[ axis ].erase( mDragGuideIndex[ axis ] );
}
else if( mMouseDownMode == DragSelecting )
{
// If not multiselecting, clear the current selection.
if( !( event.modifier & SI_MULTISELECT ) && !mDragAddSelection )
clearSelection();
RectI rect;
getDragRect( rect );
// If the region is somewhere less than at least 2x2, count this as a
// normal, non-rectangular selection.
if( rect.extent.x <= 2 && rect.extent.y <= 2 )
addSelectControlAt( rect.point );
else
{
// Use HIT_AddParentHits by default except if ALT is pressed.
// Use HIT_ParentPreventsChildHit if ALT+CTRL is pressed.
U32 hitFlags = 0;
if( !( event.modifier & SI_PRIMARY_ALT ) )
hitFlags |= HIT_AddParentHits;
if( event.modifier & SI_PRIMARY_ALT && event.modifier & SI_CTRL )
hitFlags |= HIT_ParentPreventsChildHit;
addSelectControlsInRegion( rect, hitFlags );
}
}
else if( ctrl == getContentControl() && mMouseDownMode == Selecting )
setCurrentAddSet( NULL, true );
// deliver post edit event if we've been editing
// note: paxorr: this may need to be moved earlier, if the selection has changed.
// undo
if( mMouseDownMode == SizingSelection || ( mMouseDownMode == MovingSelection && mDragMoveUndo ) )
onPostEdit_callback( getSelectedSet() );
//reset the mouse mode
setFirstResponder();
setMouseMode( Selecting );
mSizingMode = sizingNone;
// Clear snapping state.
mSnapped[ SnapVertical ] = false;
mSnapped[ SnapHorizontal ] = false;
mSnapTargets[ SnapVertical ] = NULL;
mSnapTargets[ SnapHorizontal ] = NULL;
// Clear guide drag state.
mDragGuide[ GuideVertical ] = false;
mDragGuide[ GuideHorizontal ] = false;
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::onMouseDragged( const GuiEvent &event )
{
if( !mActive || !mContentControl || !getCurrentAddSet() )
{
Parent::onMouseDragged(event);
return;
}
Point2I mousePoint = globalToLocalCoord( event.mousePoint );
//find the control we clicked
GuiControl *ctrl = mContentControl->findHitControl( mousePoint, getCurrentAddSet()->mLayer );
bool handledEvent = ctrl->onMouseDraggedEditor( event, localToGlobalCoord( Point2I(0,0) ) );
if( handledEvent == true )
{
// The Control handled the event and requested the edit ctrl
// *NOT* act on it. The dude abides.
return;
}
// If we're doing a drag clone, see if we have crossed the move threshold. If so,
// clone the selection and switch to move mode.
if( mMouseDownMode == DragClone )
{
// If we haven't yet crossed the mouse delta to actually start the
// clone, check if we have now.
S32 delta = mAbs( ( mousePoint - mDragBeginPoint ).len() );
if( delta >= 4 )
{
cloneSelection();
mLastMousePos = mDragBeginPoint;
mDragMoveUndo = false;
setMouseMode( MovingSelection );
}
}
if( mMouseDownMode == DragGuide )
{
for( U32 axis = 0; axis < 2; ++ axis )
if( mDragGuide[ axis ] )
{
// Set the guide to the coordinate of the mouse cursor
// on the guide's axis.
Point2I point = event.mousePoint;
point -= localToGlobalCoord( Point2I( 0, 0 ) );
point[ axis ] = mClamp( point[ axis ], 0, getExtent()[ axis ] - 1 );
mGuides[ axis ][ mDragGuideIndex[ axis ] ] = point[ axis ];
}
}
else if( mMouseDownMode == SizingSelection )
{
// Snap the mouse cursor to grid if active. Do this on the mouse cursor so that we handle
// incremental drags correctly.
Point2I mousePoint = event.mousePoint;
snapToGrid( mousePoint );
Point2I delta = mousePoint - mLastDragPos;
// If CTRL is down, apply smart snapping.
if( event.modifier & SI_CTRL )
{
RectI selectionBounds = getSelectionBounds();
doSnapping( event, selectionBounds, delta );
}
else
{
mSnapped[ SnapVertical ] = false;
mSnapped[ SnapHorizontal ] = false;
}
// If ALT is down, do a move instead of a resize on the control
// knob's axis. Otherwise resize.
if( event.modifier & SI_PRIMARY_ALT )
{
if( !( mSizingMode & sizingLeft ) && !( mSizingMode & sizingRight ) )
{
mSnapped[ SnapVertical ] = false;
delta.x = 0;
}
if( !( mSizingMode & sizingTop ) && !( mSizingMode & sizingBottom ) )
{
mSnapped[ SnapHorizontal ] = false;
delta.y = 0;
}
moveSelection( delta );
}
else
resizeControlsInSelectionBy( delta, mSizingMode );
// Remember drag point.
mLastDragPos = mousePoint;
}
else if (mMouseDownMode == MovingSelection && mSelectedControls.size())
{
Point2I delta = mousePoint - mLastMousePos;
RectI selectionBounds = getSelectionBounds();
// Apply snaps.
doSnapping( event, selectionBounds, delta );
//RDTODO: to me seems to be in need of revision
// Do we want to align this drag to the X and Y axes within a certain threshold?
if( event.modifier & SI_SHIFT && !( event.modifier & SI_PRIMARY_ALT ) )
{
Point2I dragTotalDelta = event.mousePoint - localToGlobalCoord( mDragBeginPoint );
if( dragTotalDelta.y < 10 && dragTotalDelta.y > -10 )
{
for(S32 i = 0; i < mSelectedControls.size(); i++)
{
Point2I selCtrlPos = mSelectedControls[i]->getPosition();
Point2I snapBackPoint( selCtrlPos.x, mDragBeginPoints[i].y);
// This is kind of nasty but we need to snap back if we're not at origin point with selection - JDD
if( selCtrlPos.y != mDragBeginPoints[i].y )
mSelectedControls[i]->setPosition( snapBackPoint );
}
delta.y = 0;
}
if( dragTotalDelta.x < 10 && dragTotalDelta.x > -10 )
{
for(S32 i = 0; i < mSelectedControls.size(); i++)
{
Point2I selCtrlPos = mSelectedControls[i]->getPosition();
Point2I snapBackPoint( mDragBeginPoints[i].x, selCtrlPos.y);
// This is kind of nasty but we need to snap back if we're not at origin point with selection - JDD
if( selCtrlPos.x != mDragBeginPoints[i].x )
mSelectedControls[i]->setPosition( snapBackPoint );
}
delta.x = 0;
}
}
if( delta.x || delta.y )
moveSelection( delta, mDragMoveUndo );
// find the current control under the mouse
canHitSelectedControls( false );
GuiControl *inCtrl = mContentControl->findHitControl(mousePoint, getCurrentAddSet()->mLayer);
canHitSelectedControls( true );
// find the nearest control up the heirarchy from the control the mouse is in
// that is flagged as a container.
while( !inCtrl->mIsContainer )
inCtrl = inCtrl->getParent();
// if the control under the mouse is not our parent, move the selected controls
// into the new parent.
if(mSelectedControls[0]->getParent() != inCtrl && inCtrl->mIsContainer)
{
moveSelectionToCtrl( inCtrl, mDragMoveUndo );
setCurrentAddSet( inCtrl, false );
}
mLastMousePos += delta;
}
else
mLastMousePos = mousePoint;
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::onRightMouseDown(const GuiEvent &event)
{
if (! mActive || !mContentControl)
{
Parent::onRightMouseDown(event);
return;
}
setFirstResponder();
//search for the control hit in any layer below the edit layer
GuiControl *hitCtrl = mContentControl->findHitControl(globalToLocalCoord(event.mousePoint), mLayer - 1);
if (hitCtrl != getCurrentAddSet())
{
setCurrentAddSet( hitCtrl );
}
// select the parent if we right-click on the current add set
else if( getCurrentAddSet() != mContentControl)
{
setCurrentAddSet( hitCtrl->getParent() );
select(hitCtrl);
}
//Design time mouse events
GuiEvent designEvent = event;
designEvent.mousePoint = mLastMousePos;
hitCtrl->onRightMouseDownEditor( designEvent, localToGlobalCoord( Point2I(0,0) ) );
}
//=============================================================================
// Rendering.
//=============================================================================
// MARK: ---- Rendering ----
//-----------------------------------------------------------------------------
void GuiEditCtrl::onPreRender()
{
setUpdate();
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::onRender(Point2I offset, const RectI &updateRect)
{
Point2I ctOffset;
Point2I cext;
bool keyFocused = isFirstResponder();
GFXDrawUtil *drawer = GFX->getDrawUtil();
if (mActive)
{
if( getCurrentAddSet() != getContentControl() )
{
// draw a white frame inset around the current add set.
cext = getCurrentAddSet()->getExtent();
ctOffset = getCurrentAddSet()->localToGlobalCoord(Point2I(0,0));
RectI box(ctOffset.x, ctOffset.y, cext.x, cext.y);
box.inset( -5, -5 );
drawer->drawRect( box, ColorI( 50, 101, 152, 128 ) );
box.inset( 1, 1 );
drawer->drawRect( box, ColorI( 50, 101, 152, 128 ) );
box.inset( 1, 1 );
drawer->drawRect( box, ColorI( 50, 101, 152, 128 ) );
box.inset( 1, 1 );
drawer->drawRect( box, ColorI( 50, 101, 152, 128 ) );
box.inset( 1, 1 );
drawer->drawRect( box, ColorI( 50, 101, 152, 128 ) );
}
Vector<GuiControl *>::iterator i;
bool multisel = mSelectedControls.size() > 1;
for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++)
{
GuiControl *ctrl = (*i);
cext = ctrl->getExtent();
ctOffset = ctrl->localToGlobalCoord(Point2I(0,0));
RectI box(ctOffset.x,ctOffset.y, cext.x, cext.y);
ColorI nutColor = multisel ? ColorI( 255, 255, 255, 100 ) : ColorI( 0, 0, 0, 100 );
ColorI outlineColor = multisel ? ColorI( 0, 0, 0, 100 ) : ColorI( 255, 255, 255, 100 );
if(!keyFocused)
nutColor.set( 128, 128, 128, 100 );
drawNuts(box, outlineColor, nutColor);
}
}
renderChildControls(offset, updateRect);
// Draw selection rectangle.
if( mActive && mMouseDownMode == DragSelecting )
{
RectI b;
getDragRect(b);
b.point += offset;
// Draw outline.
drawer->drawRect( b, ColorI( 100, 100, 100, 128 ) );
// Draw fill.
b.inset( 1, 1 );
drawer->drawRectFill( b, ColorI( 150, 150, 150, 128 ) );
}
// Draw grid.
if( mActive &&
( mMouseDownMode == MovingSelection || mMouseDownMode == SizingSelection ) &&
( mGridSnap.x || mGridSnap.y ) )
{
Point2I cext = getContentControl()->getExtent();
Point2I coff = getContentControl()->localToGlobalCoord(Point2I(0,0));
// create point-dots
const Point2I& snap = mGridSnap;
U32 maxdot = (U32)(mCeil(cext.x / (F32)snap.x) - 1) * (U32)(mCeil(cext.y / (F32)snap.y) - 1);
if( mDots.isNull() || maxdot != mDots->mNumVerts)
{
mDots.set(GFX, maxdot, GFXBufferTypeStatic);
U32 ndot = 0;
mDots.lock();
for(U32 ix = snap.x; ix < cext.x; ix += snap.x)
{
for(U32 iy = snap.y; ndot < maxdot && iy < cext.y; iy += snap.y)
{
mDots[ndot].color.set( 50, 50, 254, 100 );
mDots[ndot].point.x = F32(ix + coff.x);
mDots[ndot].point.y = F32(iy + coff.y);
mDots[ndot].point.z = 0.0f;
ndot++;
}
}
mDots.unlock();
AssertFatal(ndot <= maxdot, "dot overflow");
AssertFatal(ndot == maxdot, "dot underflow");
}
if (!mDotSB)
{
GFXStateBlockDesc dotdesc;
dotdesc.setBlend(true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha);
dotdesc.setCullMode( GFXCullNone );
mDotSB = GFX->createStateBlock( dotdesc );
}
GFX->setStateBlock(mDotSB);
// draw the points.
GFX->setVertexBuffer( mDots );
GFX->drawPrimitive( GFXPointList, 0, mDots->mNumVerts );
}
// Draw snapping lines.
if( mActive && getContentControl() )
{
RectI bounds = getContentControl()->getGlobalBounds();
// Draw guide lines.
if( mDrawGuides )
{
for( U32 axis = 0; axis < 2; ++ axis )
{
for( U32 i = 0, num = mGuides[ axis ].size(); i < num; ++ i )
drawCrossSection( axis, mGuides[ axis ][ i ] + bounds.point[ axis ],
bounds, ColorI( 0, 255, 0, 100 ), drawer );
}
}
// Draw smart snap lines.
for( U32 axis = 0; axis < 2; ++ axis )
{
if( mSnapped[ axis ] )
{
// Draw the snap line.
drawCrossSection( axis, mSnapOffset[ axis ],
bounds, ColorI( 0, 0, 255, 100 ), drawer );
// Draw a border around the snap target control.
if( mSnapTargets[ axis ] )
{
RectI bounds = mSnapTargets[ axis ]->getGlobalBounds();
drawer->drawRect( bounds, ColorF( .5, .5, .5, .5 ) );
}
}
}
}
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::drawNuts(RectI &box, ColorI &outlineColor, ColorI &nutColor)
{
GFXDrawUtil *drawer = GFX->getDrawUtil();
S32 lx = box.point.x, rx = box.point.x + box.extent.x - 1;
S32 cx = (lx + rx) >> 1;
S32 ty = box.point.y, by = box.point.y + box.extent.y - 1;
S32 cy = (ty + by) >> 1;
if( mDrawBorderLines )
{
ColorF lineColor( 0.7f, 0.7f, 0.7f, 0.25f );
ColorF lightLineColor( 0.5f, 0.5f, 0.5f, 0.1f );
if(lx > 0 && ty > 0)
{
drawer->drawLine(0, ty, lx, ty, lineColor); // Left edge to top-left corner.
drawer->drawLine(lx, 0, lx, ty, lineColor); // Top edge to top-left corner.
}
if(lx > 0 && by > 0)
drawer->drawLine(0, by, lx, by, lineColor); // Left edge to bottom-left corner.
if(rx > 0 && ty > 0)
drawer->drawLine(rx, 0, rx, ty, lineColor); // Top edge to top-right corner.
Point2I extent = localToGlobalCoord(getExtent());
if(lx < extent.x && by < extent.y)
drawer->drawLine(lx, by, lx, extent.y, lightLineColor); // Bottom-left corner to bottom edge.
if(rx < extent.x && by < extent.y)
{
drawer->drawLine(rx, by, rx, extent.y, lightLineColor); // Bottom-right corner to bottom edge.
drawer->drawLine(rx, by, extent.x, by, lightLineColor); // Bottom-right corner to right edge.
}
if(rx < extent.x && ty < extent.y)
drawer->drawLine(rx, ty, extent.x, ty, lightLineColor); // Top-right corner to right edge.
}
// Adjust nuts, so they dont straddle the controls.
lx -= NUT_SIZE + 1;
ty -= NUT_SIZE + 1;
rx += 1;
by += 1;
// Draw nuts.
drawNut( Point2I( lx - NUT_SIZE, ty - NUT_SIZE ), outlineColor, nutColor ); // Top left
drawNut( Point2I( lx - NUT_SIZE, cy - NUT_SIZE / 2 ), outlineColor, nutColor ); // Mid left
drawNut( Point2I( lx - NUT_SIZE, by ), outlineColor, nutColor ); // Bottom left
drawNut( Point2I( rx, ty - NUT_SIZE ), outlineColor, nutColor ); // Top right
drawNut( Point2I( rx, cy - NUT_SIZE / 2 ), outlineColor, nutColor ); // Mid right
drawNut( Point2I( rx, by ), outlineColor, nutColor ); // Bottom right
drawNut( Point2I( cx - NUT_SIZE / 2, ty - NUT_SIZE ), outlineColor, nutColor ); // Mid top
drawNut( Point2I( cx - NUT_SIZE / 2, by ), outlineColor, nutColor ); // Mid bottom
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::drawNut(const Point2I &nut, ColorI &outlineColor, ColorI &nutColor)
{
RectI r( nut.x, nut.y, NUT_SIZE * 2, NUT_SIZE * 2 );
GFX->getDrawUtil()->drawRect( r, outlineColor );
r.inset( 1, 1 );
GFX->getDrawUtil()->drawRectFill( r, nutColor );
}
//=============================================================================
// Selections.
//=============================================================================
// MARK: ---- Selections ----
//-----------------------------------------------------------------------------
void GuiEditCtrl::clearSelection(void)
{
mSelectedControls.clear();
onClearSelected_callback();
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::setSelection(GuiControl *ctrl, bool inclusive)
{
//sanity check
if( !ctrl )
return;
if( mSelectedControls.size() == 1 && mSelectedControls[ 0 ] == ctrl )
return;
if( !inclusive )
clearSelection();
if( mContentControl == ctrl )
setCurrentAddSet( ctrl, false );
else
addSelection( ctrl );
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::addSelection(S32 id)
{
GuiControl * ctrl;
if( Sim::findObject( id, ctrl ) )
addSelection( ctrl );
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::addSelection( GuiControl* ctrl )
{
// Only add if this isn't the content control and the
// control isn't yet in the selection.
if( ctrl != getContentControl() && !selectionContains( ctrl ) )
{
mSelectedControls.push_back( ctrl );
if( mSelectedControls.size() == 1 )
{
// Update the add set.
if( ctrl->mIsContainer )
setCurrentAddSet( ctrl, false );
else
setCurrentAddSet( ctrl->getParent(), false );
// Notify script.
onSelect_callback( ctrl );
}
else
{
// Notify script.
onAddSelected_callback( ctrl );
}
}
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::removeSelection( S32 id )
{
GuiControl * ctrl;
if ( Sim::findObject( id, ctrl ) )
removeSelection( ctrl );
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::removeSelection( GuiControl* ctrl )
{
if( selectionContains( ctrl ) )
{
Vector< GuiControl* >::iterator i = ::find( mSelectedControls.begin(), mSelectedControls.end(), ctrl );
if ( i != mSelectedControls.end() )
mSelectedControls.erase( i );
onRemoveSelected_callback( ctrl );
}
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::canHitSelectedControls( bool state )
{
for( U32 i = 0, num = mSelectedControls.size(); i < num; ++ i )
mSelectedControls[ i ]->setCanHit( state );
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::moveSelectionToCtrl( GuiControl *newParent, bool callback )
{
for( U32 i = 0; i < mSelectedControls.size(); ++ i )
{
GuiControl* ctrl = mSelectedControls[i];
if( ctrl->getParent() == newParent
|| ctrl->isLocked()
|| selectionContainsParentOf( ctrl ) )
continue;
Point2I globalpos = ctrl->localToGlobalCoord(Point2I(0,0));
newParent->addObject(ctrl);
Point2I newpos = ctrl->globalToLocalCoord(globalpos) + ctrl->getPosition();
ctrl->setPosition(newpos);
}
onHierarchyChanged_callback();
//TODO: undo
}
//-----------------------------------------------------------------------------
static Point2I snapPoint(Point2I point, Point2I delta, Point2I gridSnap)
{
S32 snap;
if(gridSnap.x && delta.x)
{
snap = point.x % gridSnap.x;
point.x -= snap;
if(delta.x > 0 && snap != 0)
point.x += gridSnap.x;
}
if(gridSnap.y && delta.y)
{
snap = point.y % gridSnap.y;
point.y -= snap;
if(delta.y > 0 && snap != 0)
point.y += gridSnap.y;
}
return point;
}
void GuiEditCtrl::moveAndSnapSelection( const Point2I &delta, bool callback )
{
// move / nudge gets a special callback so that multiple small moves can be
// coalesced into one large undo action.
// undo
if( callback )
onPreSelectionNudged_callback( getSelectedSet() );
Vector<GuiControl *>::iterator i;
Point2I newPos;
for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++)
{
GuiControl* ctrl = *i;
if( !ctrl->isLocked() && !selectionContainsParentOf( ctrl ) )
{
newPos = ctrl->getPosition() + delta;
newPos = snapPoint( newPos, delta, mGridSnap );
ctrl->setPosition( newPos );
}
}
// undo
if( callback )
onPostSelectionNudged_callback( getSelectedSet() );
// allow script to update the inspector
if( callback && mSelectedControls.size() > 0 )
onSelectionMoved_callback( mSelectedControls[ 0 ] );
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::moveSelection( const Point2I &delta, bool callback )
{
Vector<GuiControl *>::iterator i;
for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++)
{
GuiControl* ctrl = *i;
if( !ctrl->isLocked() && !selectionContainsParentOf( ctrl ) )
ctrl->setPosition( ctrl->getPosition() + delta );
}
// allow script to update the inspector
if( callback )
onSelectionMoved_callback( mSelectedControls[ 0 ] );
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::justifySelection( Justification j )
{
S32 minX, maxX;
S32 minY, maxY;
S32 extentX, extentY;
if (mSelectedControls.size() < 2)
return;
Vector<GuiControl *>::iterator i = mSelectedControls.begin();
minX = (*i)->getLeft();
maxX = minX + (*i)->getWidth();
minY = (*i)->getTop();
maxY = minY + (*i)->getHeight();
extentX = (*i)->getWidth();
extentY = (*i)->getHeight();
i++;
for(;i != mSelectedControls.end(); i++)
{
minX = getMin(minX, (*i)->getLeft());
maxX = getMax(maxX, (*i)->getLeft() + (*i)->getWidth());
minY = getMin(minY, (*i)->getTop());
maxY = getMax(maxY, (*i)->getTop() + (*i)->getHeight());
extentX += (*i)->getWidth();
extentY += (*i)->getHeight();
}
S32 deltaX = maxX - minX;
S32 deltaY = maxY - minY;
switch(j)
{
case JUSTIFY_LEFT:
for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++)
if( !( *i )->isLocked() )
(*i)->setLeft( minX );
break;
case JUSTIFY_TOP:
for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++)
if( !( *i )->isLocked() )
(*i)->setTop( minY );
break;
case JUSTIFY_RIGHT:
for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++)
if( !( *i )->isLocked() )
(*i)->setLeft( maxX - (*i)->getWidth() + 1 );
break;
case JUSTIFY_BOTTOM:
for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++)
if( !( *i )->isLocked() )
(*i)->setTop( maxY - (*i)->getHeight() + 1 );
break;
case JUSTIFY_CENTER_VERTICAL:
for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++)
if( !( *i )->isLocked() )
(*i)->setLeft( minX + ((deltaX - (*i)->getWidth()) >> 1 ));
break;
case JUSTIFY_CENTER_HORIZONTAL:
for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++)
if( !( *i )->isLocked() )
(*i)->setTop( minY + ((deltaY - (*i)->getHeight()) >> 1 ));
break;
case SPACING_VERTICAL:
{
Vector<GuiControl *> sortedList;
Vector<GuiControl *>::iterator k;
for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++)
{
for(k = sortedList.begin(); k != sortedList.end(); k++)
{
if ((*i)->getTop() < (*k)->getTop())
break;
}
sortedList.insert(k, *i);
}
S32 space = (deltaY - extentY) / (mSelectedControls.size() - 1);
S32 curY = minY;
for(k = sortedList.begin(); k != sortedList.end(); k++)
{
if( !( *k )->isLocked() )
(*k)->setTop( curY );
curY += (*k)->getHeight() + space;
}
}
break;
case SPACING_HORIZONTAL:
{
Vector<GuiControl *> sortedList;
Vector<GuiControl *>::iterator k;
for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++)
{
for(k = sortedList.begin(); k != sortedList.end(); k++)
{
if ((*i)->getLeft() < (*k)->getLeft())
break;
}
sortedList.insert(k, *i);
}
S32 space = (deltaX - extentX) / (mSelectedControls.size() - 1);
S32 curX = minX;
for(k = sortedList.begin(); k != sortedList.end(); k++)
{
if( !( *k )->isLocked() )
(*k)->setLeft( curX );
curX += (*k)->getWidth() + space;
}
}
break;
}
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::cloneSelection()
{
Vector< GuiControl* > newSelection;
// Clone the controls in the current selection.
const U32 numOldControls = mSelectedControls.size();
for( U32 i = 0; i < numOldControls; ++ i )
{
GuiControl* ctrl = mSelectedControls[ i ];
// If parent is in selection, too, skip to prevent multiple clones.
if( ctrl->getParent() && selectionContains( ctrl->getParent() ) )
continue;
// Clone and add to set.
GuiControl* clone = dynamic_cast< GuiControl* >( ctrl->deepClone() );
if( clone )
newSelection.push_back( clone );
}
// Exchange the selection set.
clearSelection();
const U32 numNewControls = newSelection.size();
for( U32 i = 0; i < numNewControls; ++ i )
addSelection( newSelection[ i ] );
// Callback for undo.
onSelectionCloned_callback( getSelectedSet() );
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::deleteSelection()
{
// Notify script for undo.
onTrashSelection_callback( getSelectedSet() );
// Move all objects in selection to trash.
Vector< GuiControl* >::iterator i;
for( i = mSelectedControls.begin(); i != mSelectedControls.end(); i ++ )
{
if( ( *i ) == getCurrentAddSet() )
setCurrentAddSet( getContentControl(), false );
mTrash->addObject( *i );
}
clearSelection();
// Notify script it needs to update its views.
onHierarchyChanged_callback();
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::loadSelection( const char* filename )
{
// Set redefine behavior to rename.
const char* oldRedefineBehavior = Con::getVariable( "$Con::redefineBehavior" );
Con::setVariable( "$Con::redefineBehavior", "renameNew" );
// Exec the file or clipboard contents with the saved selection set.
if( filename )
Con::executef( "exec", filename );
else
Con::evaluate( Platform::getClipboard() );
SimSet* set;
if( !Sim::findObject( "guiClipboard", set ) )
{
if( filename )
Con::errorf( "GuiEditCtrl::loadSelection() - could not find 'guiClipboard' in '%s'", filename );
else
Con::errorf( "GuiEditCtrl::loadSelection() - could not find 'guiClipboard'" );
return;
}
// Restore redefine behavior.
Con::setVariable( "$Con::redefineBehavior", oldRedefineBehavior );
// Add the objects in the set.
if( set->size() )
{
clearSelection();
GuiControlVector ctrls;
for( U32 i = 0, num = set->size(); i < num; ++ i )
{
GuiControl *ctrl = dynamic_cast< GuiControl* >( ( *set )[ i ] );
if( ctrl )
{
getCurrentAddSet()->addObject( ctrl );
ctrls.push_back( ctrl );
}
}
// Select all controls. We need to perform this here rather than in the
// loop above as addSelection() will modify the current add set.
for( U32 i = 0; i < ctrls.size(); ++ i )
{
addSelection( ctrls[i] );
}
// Undo
onAddNewCtrlSet_callback( getSelectedSet() );
// Notify the script it needs to update its treeview.
onHierarchyChanged_callback();
}
set->deleteObject();
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::saveSelection( const char* filename )
{
// If there are no selected objects, then don't save.
if( mSelectedControls.size() == 0 )
return;
// Open the stream.
Stream* stream;
if( filename )
{
stream = FileStream::createAndOpen( filename, Torque::FS::File::Write );
if( !stream )
{
Con::errorf( "GuiEditCtrl::saveSelection - could not open '%s' for writing", filename );
return;
}
}
else
stream = new MemStream( 4096 );
// Create a temporary SimSet.
SimSet* clipboardSet = new SimSet;
clipboardSet->registerObject();
Sim::getRootGroup()->addObject( clipboardSet, "guiClipboard" );
// Add the selected controls to the set.
for( Vector< GuiControl* >::iterator i = mSelectedControls.begin();
i != mSelectedControls.end(); ++ i )
{
GuiControl* ctrl = *i;
if( !selectionContainsParentOf( ctrl ) )
clipboardSet->addObject( ctrl );
}
// Write the SimSet. Use the IgnoreCanSave to ensure the controls
// get actually written out (also disables the default parent inheritance
// behavior for the flag).
clipboardSet->write( *stream, 0, IgnoreCanSave );
clipboardSet->deleteObject();
// If we were writing to a memory stream, copy to clipboard
// now.
if( !filename )
{
MemStream* memStream = static_cast< MemStream* >( stream );
memStream->write( U8( 0 ) );
Platform::setClipboard( ( const char* ) memStream->getBuffer() );
}
delete stream;
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::selectAll()
{
GuiControl::iterator i;
clearSelection();
for(i = getCurrentAddSet()->begin(); i != getCurrentAddSet()->end(); i++)
{
GuiControl *ctrl = dynamic_cast<GuiControl *>(*i);
addSelection( ctrl );
}
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::bringToFront()
{
if( getNumSelected() != 1 )
return;
GuiControl* ctrl = mSelectedControls.first();
ctrl->getParent()->pushObjectToBack( ctrl );
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::pushToBack()
{
if( getNumSelected() != 1 )
return;
GuiControl* ctrl = mSelectedControls.first();
ctrl->getParent()->bringObjectToFront( ctrl );
}
//-----------------------------------------------------------------------------
RectI GuiEditCtrl::getSelectionBounds() const
{
Vector<GuiControl *>::const_iterator i = mSelectedControls.begin();
Point2I minPos = (*i)->localToGlobalCoord( Point2I( 0, 0 ) );
Point2I maxPos = minPos;
for(; i != mSelectedControls.end(); i++)
{
Point2I iPos = (**i).localToGlobalCoord( Point2I( 0 , 0 ) );
minPos.x = getMin( iPos.x, minPos.x );
minPos.y = getMin( iPos.y, minPos.y );
Point2I iExt = ( **i ).getExtent();
iPos.x += iExt.x;
iPos.y += iExt.y;
maxPos.x = getMax( iPos.x, maxPos.x );
maxPos.y = getMax( iPos.y, maxPos.y );
}
minPos = getContentControl()->globalToLocalCoord( minPos );
maxPos = getContentControl()->globalToLocalCoord( maxPos );
return RectI( minPos.x, minPos.y, ( maxPos.x - minPos.x ), ( maxPos.y - minPos.y ) );
}
//-----------------------------------------------------------------------------
RectI GuiEditCtrl::getSelectionGlobalBounds() const
{
Point2I minb( S32_MAX, S32_MAX );
Point2I maxb( S32_MIN, S32_MIN );
for( U32 i = 0, num = mSelectedControls.size(); i < num; ++ i )
{
// Min.
Point2I pos = mSelectedControls[ i ]->localToGlobalCoord( Point2I( 0, 0 ) );
minb.x = getMin( minb.x, pos.x );
minb.y = getMin( minb.y, pos.y );
// Max.
const Point2I extent = mSelectedControls[ i ]->getExtent();
maxb.x = getMax( maxb.x, pos.x + extent.x );
maxb.y = getMax( maxb.y, pos.y + extent.y );
}
RectI bounds( minb.x, minb.y, maxb.x - minb.x, maxb.y - minb.y );
return bounds;
}
//-----------------------------------------------------------------------------
bool GuiEditCtrl::selectionContains( GuiControl *ctrl )
{
Vector<GuiControl *>::iterator i;
for (i = mSelectedControls.begin(); i != mSelectedControls.end(); i++)
if (ctrl == *i) return true;
return false;
}
//-----------------------------------------------------------------------------
bool GuiEditCtrl::selectionContainsParentOf( GuiControl* ctrl )
{
GuiControl* parent = ctrl->getParent();
while( parent && parent != getContentControl() )
{
if( selectionContains( parent ) )
return true;
parent = parent->getParent();
}
return false;
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::select( GuiControl* ctrl )
{
clearSelection();
addSelection( ctrl );
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::updateSelectedSet()
{
mSelectedSet->clear();
Vector<GuiControl*>::iterator i;
for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++)
{
mSelectedSet->addObject(*i);
}
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::addSelectControlsInRegion( const RectI& rect, U32 flags )
{
// Do a hit test on the content control.
canHitSelectedControls( false );
Vector< GuiControl* > hits;
if( mFullBoxSelection )
flags |= GuiControl::HIT_FullBoxOnly;
getContentControl()->findHitControls( rect, hits, flags );
canHitSelectedControls( true );
// Add all controls that got hit.
for( U32 i = 0, num = hits.size(); i < num; ++ i )
addSelection( hits[ i ] );
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::addSelectControlAt( const Point2I& pos )
{
// Run a hit test.
canHitSelectedControls( false );
GuiControl* hit = getContentControl()->findHitControl( pos );
canHitSelectedControls( true );
// Add to selection.
if( hit )
addSelection( hit );
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::resizeControlsInSelectionBy( const Point2I& delta, U32 mode )
{
for( U32 i = 0, num = mSelectedControls.size(); i < num; ++ i )
{
GuiControl *ctrl = mSelectedControls[ i ];
if( ctrl->isLocked() )
continue;
Point2I minExtent = ctrl->getMinExtent();
Point2I newPosition = ctrl->getPosition();
Point2I newExtent = ctrl->getExtent();
if( mSizingMode & sizingLeft )
{
newPosition.x += delta.x;
newExtent.x -= delta.x;
if( newExtent.x < minExtent.x )
{
newPosition.x -= minExtent.x - newExtent.x;
newExtent.x = minExtent.x;
}
}
else if( mSizingMode & sizingRight )
{
newExtent.x += delta.x;
if( newExtent.x < minExtent.x )
newExtent.x = minExtent.x;
}
if( mSizingMode & sizingTop )
{
newPosition.y += delta.y;
newExtent.y -= delta.y;
if( newExtent.y < minExtent.y )
{
newPosition.y -= minExtent.y - newExtent.y;
newExtent.y = minExtent.y;
}
}
else if( mSizingMode & sizingBottom )
{
newExtent.y += delta.y;
if( newExtent.y < minExtent.y )
newExtent.y = minExtent.y;
}
ctrl->resize( newPosition, newExtent );
}
if( mSelectedControls.size() == 1 )
onSelectionResized_callback( mSelectedControls[ 0 ] );
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::fitIntoParents( bool width, bool height )
{
// Record undo.
onFitIntoParent_callback( width, height );
// Fit.
for( U32 i = 0; i < mSelectedControls.size(); ++ i )
{
GuiControl* ctrl = mSelectedControls[ i ];
GuiControl* parent = ctrl->getParent();
Point2I position = ctrl->getPosition();
if( width )
position.x = 0;
if( height )
position.y = 0;
Point2I extents = ctrl->getExtent();
if( width )
extents.x = parent->getWidth();
if( height )
extents.y = parent->getHeight();
ctrl->resize( position, extents );
}
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::selectParents( bool addToSelection )
{
Vector< GuiControl* > parents;
// Collect all parents.
for( U32 i = 0; i < mSelectedControls.size(); ++ i )
{
GuiControl* ctrl = mSelectedControls[ i ];
if( ctrl != mContentControl && ctrl->getParent() != mContentControl )
parents.push_back( mSelectedControls[ i ]->getParent() );
}
// If there's no parents to select, don't
// change the selection.
if( parents.empty() )
return;
// Blast selection if need be.
if( !addToSelection )
clearSelection();
// Add the parents.
for( U32 i = 0; i < parents.size(); ++ i )
addSelection( parents[ i ] );
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::selectChildren( bool addToSelection )
{
Vector< GuiControl* > children;
// Collect all children.
for( U32 i = 0; i < mSelectedControls.size(); ++ i )
{
GuiControl* parent = mSelectedControls[ i ];
for( GuiControl::iterator iter = parent->begin(); iter != parent->end(); ++ iter )
{
GuiControl* child = dynamic_cast< GuiControl* >( *iter );
if( child )
children.push_back( child );
}
}
// If there's no children to select, don't
// change the selection.
if( children.empty() )
return;
// Blast selection if need be.
if( !addToSelection )
clearSelection();
// Add the children.
for( U32 i = 0; i < children.size(); ++ i )
addSelection( children[ i ] );
}
//=============================================================================
// Guides.
//=============================================================================
// MARK: ---- Guides ----
//-----------------------------------------------------------------------------
void GuiEditCtrl::readGuides( guideAxis axis, GuiControl* ctrl )
{
// Read the guide indices from the vector stored on the respective dynamic
// property of the control.
const char* guideIndices = ctrl->getDataField( smGuidesPropertyName[ axis ], NULL );
if( guideIndices && guideIndices[ 0 ] )
{
U32 index = 0;
while( true )
{
const char* posStr = StringUnit::getUnit( guideIndices, index, " \t" );
if( !posStr[ 0 ] )
break;
mGuides[ axis ].push_back( dAtoi( posStr ) );
index ++;
}
}
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::writeGuides( guideAxis axis, GuiControl* ctrl )
{
// Store the guide indices of the given axis in a vector on the respective
// dynamic property of the control.
StringBuilder str;
bool isFirst = true;
for( U32 i = 0, num = mGuides[ axis ].size(); i < num; ++ i )
{
if( !isFirst )
str.append( ' ' );
char buffer[ 32 ];
dSprintf( buffer, sizeof( buffer ), "%i", mGuides[ axis ][ i ] );
str.append( buffer );
isFirst = false;
}
String value = str.end();
ctrl->setDataField( smGuidesPropertyName[ axis ], NULL, value );
}
//-----------------------------------------------------------------------------
S32 GuiEditCtrl::findGuide( guideAxis axis, const Point2I& point, U32 tolerance )
{
const S32 p = ( point - localToGlobalCoord( Point2I( 0, 0 ) ) )[ axis ];
for( U32 i = 0, num = mGuides[ axis ].size(); i < num; ++ i )
{
const S32 g = mGuides[ axis ][ i ];
if( p >= ( g - tolerance ) &&
p <= ( g + tolerance ) )
return i;
}
return -1;
}
//=============================================================================
// Snapping.
//=============================================================================
// MARK: ---- Snapping ----
//-----------------------------------------------------------------------------
RectI GuiEditCtrl::getSnapRegion( snappingAxis axis, const Point2I& center ) const
{
RectI rootBounds = getContentControl()->getBounds();
RectI rect;
if( axis == SnapHorizontal )
rect = RectI( rootBounds.point.x,
center.y - mSnapSensitivity,
rootBounds.extent.x,
mSnapSensitivity * 2 );
else // SnapVertical
rect = RectI( center.x - mSnapSensitivity,
rootBounds.point.y,
mSnapSensitivity * 2,
rootBounds.extent.y );
// Clip against root bounds.
rect.intersect( rootBounds );
return rect;
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::registerSnap( snappingAxis axis, const Point2I& mousePoint, const Point2I& point, snappingEdges edge, GuiControl* ctrl )
{
bool takeNewSnap = false;
const Point2I globalPoint = getContentControl()->localToGlobalCoord( point );
// If we have no snap yet, just take this one.
if( !mSnapped[ axis ] )
takeNewSnap = true;
// Otherwise see if this snap is the better one.
else
{
// Compare deltas to pointer.
S32 deltaCurrent = mAbs( mSnapOffset[ axis ] - mousePoint[ axis ] );
S32 deltaNew = mAbs( globalPoint[ axis ] - mousePoint[ axis ] );
if( deltaCurrent > deltaNew )
takeNewSnap = true;
}
if( takeNewSnap )
{
mSnapped[ axis ] = true;
mSnapOffset[ axis ] = globalPoint[ axis ];
mSnapEdge[ axis ] = edge;
mSnapTargets[ axis ] = ctrl;
}
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::findSnaps( snappingAxis axis, const Point2I& mousePoint, const RectI& minRegion, const RectI& midRegion, const RectI& maxRegion )
{
// Find controls with edge in either minRegion, midRegion, or maxRegion
// (depending on snap settings).
for( U32 i = 0, num = mSnapHits[ axis ].size(); i < num; ++ i )
{
GuiControl* ctrl = mSnapHits[ axis ][ i ];
if( ctrl == getContentControl() && !mSnapToCanvas )
continue;
RectI bounds = ctrl->getGlobalBounds();
bounds.point = getContentControl()->globalToLocalCoord( bounds.point );
// Compute points on min, mid, and max lines of control.
Point2I min = bounds.point;
Point2I max = min + bounds.extent - Point2I( 1, 1 );
Point2I mid = min;
mid.x += bounds.extent.x / 2;
mid.y += bounds.extent.y / 2;
// Test edge snap cases.
if( mSnapToEdges )
{
// Min to min.
if( minRegion.pointInRect( min ) )
registerSnap( axis, mousePoint, min, SnapEdgeMin, ctrl );
// Max to max.
if( maxRegion.pointInRect( max ) )
registerSnap( axis, mousePoint, max, SnapEdgeMax, ctrl );
// Min to max.
if( minRegion.pointInRect( max ) )
registerSnap( axis, mousePoint, max, SnapEdgeMin, ctrl );
// Max to min.
if( maxRegion.pointInRect( min ) )
registerSnap( axis, mousePoint, min, SnapEdgeMax, ctrl );
}
// Test center snap cases.
if( mSnapToCenters )
{
// Mid to mid.
if( midRegion.pointInRect( mid ) )
registerSnap( axis, mousePoint, mid, SnapEdgeMid, ctrl );
}
// Test combined center+edge snap cases.
if( mSnapToEdges && mSnapToCenters )
{
// Min to mid.
if( minRegion.pointInRect( mid ) )
registerSnap( axis, mousePoint, mid, SnapEdgeMin, ctrl );
// Max to mid.
if( maxRegion.pointInRect( mid ) )
registerSnap( axis, mousePoint, mid, SnapEdgeMax, ctrl );
// Mid to min.
if( midRegion.pointInRect( min ) )
registerSnap( axis, mousePoint, min, SnapEdgeMid, ctrl );
// Mid to max.
if( midRegion.pointInRect( max ) )
registerSnap( axis, mousePoint, max, SnapEdgeMid, ctrl );
}
}
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::doControlSnap( const GuiEvent& event, const RectI& selectionBounds, const RectI& selectionBoundsGlobal, Point2I& delta )
{
if( !mSnapToControls || ( !mSnapToEdges && !mSnapToCenters ) )
return;
// Allow restricting to just vertical (ALT+SHIFT) or just horizontal (ALT+CTRL)
// snaps.
bool snapAxisEnabled[ 2 ];
if( event.modifier & SI_PRIMARY_ALT && event.modifier & SI_SHIFT )
snapAxisEnabled[ SnapHorizontal ] = false;
else
snapAxisEnabled[ SnapHorizontal ] = true;
if( event.modifier & SI_PRIMARY_ALT && event.modifier & SI_CTRL )
snapAxisEnabled[ SnapVertical ] = false;
else
snapAxisEnabled[ SnapVertical ] = true;
// Compute snap regions. There is one region centered on and aligned with
// each of the selection bounds edges plus two regions aligned on the selection
// bounds center. For the selection bounds origin, we use the point that the
// selection would be at, if we had already done the mouse drag.
RectI snapRegions[ 2 ][ 3 ];
Point2I projectedOrigin( selectionBounds.point + delta );
dMemset( snapRegions, 0, sizeof( snapRegions ) );
for( U32 axis = 0; axis < 2; ++ axis )
{
if( !snapAxisEnabled[ axis ] )
continue;
if( mSizingMode == sizingNone ||
( axis == 0 && mSizingMode & sizingLeft ) ||
( axis == 1 && mSizingMode & sizingTop ) )
snapRegions[ axis ][ 0 ] = getSnapRegion( ( snappingAxis ) axis, projectedOrigin );
if( mSizingMode == sizingNone )
snapRegions[ axis ][ 1 ] = getSnapRegion( ( snappingAxis ) axis, projectedOrigin + Point2I( selectionBounds.extent.x / 2, selectionBounds.extent.y / 2 ) );
if( mSizingMode == sizingNone ||
( axis == 0 && mSizingMode & sizingRight ) ||
( axis == 1 && mSizingMode & sizingBottom ) )
snapRegions[ axis ][ 2 ] = getSnapRegion( ( snappingAxis ) axis, projectedOrigin + selectionBounds.extent - Point2I( 1, 1 ) );
}
// Find hit controls.
canHitSelectedControls( false );
if( mSnapToEdges )
{
for( U32 axis = 0; axis < 2; ++ axis )
if( snapAxisEnabled[ axis ] )
{
getContentControl()->findHitControls( snapRegions[ axis ][ 0 ], mSnapHits[ axis ], HIT_NoCanHitNoRecurse );
getContentControl()->findHitControls( snapRegions[ axis ][ 2 ], mSnapHits[ axis ], HIT_NoCanHitNoRecurse );
}
}
if( mSnapToCenters && mSizingMode == sizingNone )
{
for( U32 axis = 0; axis < 2; ++ axis )
if( snapAxisEnabled[ axis ] )
getContentControl()->findHitControls( snapRegions[ axis ][ 1 ], mSnapHits[ axis ], HIT_NoCanHitNoRecurse );
}
canHitSelectedControls( true );
// Add the content control itself to the hit controls
// so we can always get a snap on it.
if( mSnapToCanvas )
{
mSnapHits[ 0 ].push_back( mContentControl );
mSnapHits[ 1 ].push_back( mContentControl );
}
// Find snaps.
for( U32 i = 0; i < 2; ++ i )
if( snapAxisEnabled[ i ] )
findSnaps( ( snappingAxis ) i,
event.mousePoint,
snapRegions[ i ][ 0 ],
snapRegions[ i ][ 1 ],
snapRegions[ i ][ 2 ] );
// Clean up.
mSnapHits[ 0 ].clear();
mSnapHits[ 1 ].clear();
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::doGridSnap( const GuiEvent& event, const RectI& selectionBounds, const RectI& selectionBoundsGlobal, Point2I& delta )
{
delta += selectionBounds.point;
if( mGridSnap.x )
delta.x -= delta.x % mGridSnap.x;
if( mGridSnap.y )
delta.y -= delta.y % mGridSnap.y;
delta -= selectionBounds.point;
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::doGuideSnap( const GuiEvent& event, const RectI& selectionBounds, const RectI& selectionBoundsGlobal, Point2I& delta )
{
if( !mSnapToGuides )
return;
Point2I min = getContentControl()->localToGlobalCoord( selectionBounds.point + delta );
Point2I mid = min + selectionBounds.extent / 2;
Point2I max = min + selectionBounds.extent - Point2I( 1, 1 );
for( U32 axis = 0; axis < 2; ++ axis )
{
if( mSnapToEdges )
{
S32 guideMin = -1;
S32 guideMax = -1;
if( mSizingMode == sizingNone ||
( axis == 0 && mSizingMode & sizingLeft ) ||
( axis == 1 && mSizingMode & sizingTop ) )
guideMin = findGuide( ( guideAxis ) axis, min, mSnapSensitivity );
if( mSizingMode == sizingNone ||
( axis == 0 && mSizingMode & sizingRight ) ||
( axis == 1 && mSizingMode & sizingBottom ) )
guideMax = findGuide( ( guideAxis ) axis, max, mSnapSensitivity );
Point2I pos( 0, 0 );
if( guideMin != -1 )
{
pos[ axis ] = mGuides[ axis ][ guideMin ];
registerSnap( ( snappingAxis ) axis, event.mousePoint, pos, SnapEdgeMin );
}
if( guideMax != -1 )
{
pos[ axis ] = mGuides[ axis ][ guideMax ];
registerSnap( ( snappingAxis ) axis, event.mousePoint, pos, SnapEdgeMax );
}
}
if( mSnapToCenters && mSizingMode == sizingNone )
{
const S32 guideMid = findGuide( ( guideAxis ) axis, mid, mSnapSensitivity );
if( guideMid != -1 )
{
Point2I pos( 0, 0 );
pos[ axis ] = mGuides[ axis ][ guideMid ];
registerSnap( ( snappingAxis ) axis, event.mousePoint, pos, SnapEdgeMid );
}
}
}
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::doSnapping( const GuiEvent& event, const RectI& selectionBounds, Point2I& delta )
{
// Clear snapping. If we have snapping on, we want to find a new best snap.
mSnapped[ SnapVertical ] = false;
mSnapped[ SnapHorizontal ] = false;
// Compute global bounds.
RectI selectionBoundsGlobal = selectionBounds;
selectionBoundsGlobal.point = getContentControl()->localToGlobalCoord( selectionBoundsGlobal.point );
// Apply the snaps.
doGridSnap( event, selectionBounds, selectionBoundsGlobal, delta );
doGuideSnap( event, selectionBounds, selectionBoundsGlobal, delta );
doControlSnap( event, selectionBounds, selectionBoundsGlobal, delta );
// If we have a horizontal snap, compute a delta.
if( mSnapped[ SnapVertical ] )
snapDelta( SnapVertical, mSnapEdge[ SnapVertical ], mSnapOffset[ SnapVertical ], selectionBoundsGlobal, delta );
// If we have a vertical snap, compute a delta.
if( mSnapped[ SnapHorizontal ] )
snapDelta( SnapHorizontal, mSnapEdge[ SnapHorizontal ], mSnapOffset[ SnapHorizontal ], selectionBoundsGlobal, delta );
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::snapToGrid( Point2I& point )
{
if( mGridSnap.x )
point.x -= point.x % mGridSnap.x;
if( mGridSnap.y )
point.y -= point.y % mGridSnap.y;
}
//=============================================================================
// Misc.
//=============================================================================
// MARK: ---- Misc ----
//-----------------------------------------------------------------------------
void GuiEditCtrl::setContentControl(GuiControl *root)
{
mContentControl = root;
if( root != NULL )
root->mIsContainer = true;
setCurrentAddSet( root );
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::setEditMode(bool value)
{
mActive = value;
clearSelection();
if( mActive && mAwake )
mCurrentAddSet = NULL;
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::setMouseMode( mouseModes mode )
{
if( mMouseDownMode != mode )
{
mMouseDownMode = mode;
onMouseModeChange_callback();
}
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::setCurrentAddSet(GuiControl *ctrl, bool doclearSelection)
{
if (ctrl != mCurrentAddSet)
{
if(doclearSelection)
clearSelection();
mCurrentAddSet = ctrl;
}
}
//-----------------------------------------------------------------------------
GuiControl* GuiEditCtrl::getCurrentAddSet()
{
if( !mCurrentAddSet )
setCurrentAddSet( mContentControl, false );
return mCurrentAddSet;
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::addNewControl(GuiControl *ctrl)
{
getCurrentAddSet()->addObject(ctrl);
select( ctrl );
// undo
onAddNewCtrl_callback( ctrl );
}
//-----------------------------------------------------------------------------
S32 GuiEditCtrl::getSizingHitKnobs(const Point2I &pt, const RectI &box)
{
S32 lx = box.point.x, rx = box.point.x + box.extent.x - 1;
S32 cx = (lx + rx) >> 1;
S32 ty = box.point.y, by = box.point.y + box.extent.y - 1;
S32 cy = (ty + by) >> 1;
// adjust nuts, so they dont straddle the controls
lx -= NUT_SIZE;
ty -= NUT_SIZE;
rx += NUT_SIZE;
by += NUT_SIZE;
if (inNut(pt, lx, ty))
return sizingLeft | sizingTop;
if (inNut(pt, cx, ty))
return sizingTop;
if (inNut(pt, rx, ty))
return sizingRight | sizingTop;
if (inNut(pt, lx, by))
return sizingLeft | sizingBottom;
if (inNut(pt, cx, by))
return sizingBottom;
if (inNut(pt, rx, by))
return sizingRight | sizingBottom;
if (inNut(pt, lx, cy))
return sizingLeft;
if (inNut(pt, rx, cy))
return sizingRight;
return sizingNone;
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::getDragRect(RectI &box)
{
box.point.x = getMin(mLastMousePos.x, mSelectionAnchor.x);
box.extent.x = getMax(mLastMousePos.x, mSelectionAnchor.x) - box.point.x + 1;
box.point.y = getMin(mLastMousePos.y, mSelectionAnchor.y);
box.extent.y = getMax(mLastMousePos.y, mSelectionAnchor.y) - box.point.y + 1;
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::getCursor(GuiCursor *&cursor, bool &showCursor, const GuiEvent &lastGuiEvent)
{
GuiCanvas *pRoot = getRoot();
if( !pRoot )
return;
showCursor = false;
cursor = NULL;
Point2I ctOffset;
Point2I cext;
GuiControl *ctrl;
Point2I mousePos = globalToLocalCoord(lastGuiEvent.mousePoint);
PlatformWindow *pWindow = static_cast<GuiCanvas*>(getRoot())->getPlatformWindow();
AssertFatal(pWindow != NULL,"GuiControl without owning platform window! This should not be possible.");
PlatformCursorController *pController = pWindow->getCursorController();
AssertFatal(pController != NULL,"PlatformWindow without an owned CursorController!");
S32 desiredCursor = PlatformCursorController::curArrow;
// first see if we hit a sizing knob on the currently selected control...
if (mSelectedControls.size() == 1 )
{
ctrl = mSelectedControls.first();
cext = ctrl->getExtent();
ctOffset = globalToLocalCoord(ctrl->localToGlobalCoord(Point2I(0,0)));
RectI box(ctOffset.x,ctOffset.y,cext.x, cext.y);
GuiEditCtrl::sizingModes sizeMode = (GuiEditCtrl::sizingModes)getSizingHitKnobs(mousePos, box);
if( mMouseDownMode == SizingSelection )
{
if ( ( mSizingMode == ( sizingBottom | sizingRight ) ) || ( mSizingMode == ( sizingTop | sizingLeft ) ) )
desiredCursor = PlatformCursorController::curResizeNWSE;
else if ( ( mSizingMode == ( sizingBottom | sizingLeft ) ) || ( mSizingMode == ( sizingTop | sizingRight ) ) )
desiredCursor = PlatformCursorController::curResizeNESW;
else if ( mSizingMode == sizingLeft || mSizingMode == sizingRight )
desiredCursor = PlatformCursorController::curResizeVert;
else if (mSizingMode == sizingTop || mSizingMode == sizingBottom )
desiredCursor = PlatformCursorController::curResizeHorz;
}
else
{
// Check for current mouse position after checking for actual sizing mode
if ( ( sizeMode == ( sizingBottom | sizingRight ) ) || ( sizeMode == ( sizingTop | sizingLeft ) ) )
desiredCursor = PlatformCursorController::curResizeNWSE;
else if ( ( sizeMode == ( sizingBottom | sizingLeft ) ) || ( sizeMode == ( sizingTop | sizingRight ) ) )
desiredCursor = PlatformCursorController::curResizeNESW;
else if (sizeMode == sizingLeft || sizeMode == sizingRight )
desiredCursor = PlatformCursorController::curResizeVert;
else if (sizeMode == sizingTop || sizeMode == sizingBottom )
desiredCursor = PlatformCursorController::curResizeHorz;
}
}
if( mMouseDownMode == MovingSelection && cursor == NULL )
desiredCursor = PlatformCursorController::curResizeAll;
if( pRoot->mCursorChanged != desiredCursor )
{
// We've already changed the cursor,
// so set it back before we change it again.
if(pRoot->mCursorChanged != -1)
pController->popCursor();
// Now change the cursor shape
pController->pushCursor(desiredCursor);
pRoot->mCursorChanged = desiredCursor;
}
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::setSnapToGrid(U32 gridsize)
{
if( gridsize != 0 )
gridsize = getMax( gridsize, ( U32 ) MIN_GRID_SIZE );
mGridSnap.set( gridsize, gridsize );
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::controlInspectPreApply(GuiControl* object)
{
// undo
onControlInspectPreApply_callback( object );
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::controlInspectPostApply(GuiControl* object)
{
// undo
onControlInspectPostApply_callback( object );
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::startDragMove( const Point2I& startPoint )
{
mDragMoveUndo = true;
// For calculating mouse delta
mDragBeginPoint = globalToLocalCoord( startPoint );
// Allocate enough space for our selected controls
mDragBeginPoints.reserve( mSelectedControls.size() );
// For snapping to origin
Vector<GuiControl *>::iterator i;
for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++)
mDragBeginPoints.push_back( (*i)->getPosition() );
// Set Mouse Mode
setMouseMode( MovingSelection );
// undo
onPreEdit_callback( getSelectedSet() );
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::startDragRectangle( const Point2I& startPoint )
{
mSelectionAnchor = globalToLocalCoord( startPoint );
setMouseMode( DragSelecting );
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::startDragClone( const Point2I& startPoint )
{
mDragBeginPoint = globalToLocalCoord( startPoint );
setMouseMode( DragClone );
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::startMouseGuideDrag( guideAxis axis, U32 guideIndex, bool lockMouse )
{
mDragGuideIndex[ axis ] = guideIndex;
mDragGuide[ axis ] = true;
setMouseMode( DragGuide );
// Grab the mouse.
if( lockMouse )
mouseLock();
}
//=============================================================================
// Console Methods.
//=============================================================================
// MARK: ---- Console Methods ----
//-----------------------------------------------------------------------------
DefineConsoleMethod( GuiEditCtrl, getContentControl, S32, (), , "() - Return the toplevel control edited inside the GUI editor." )
{
GuiControl* ctrl = object->getContentControl();
if( ctrl )
return ctrl->getId();
else
return 0;
}
//-----------------------------------------------------------------------------
DefineConsoleMethod( GuiEditCtrl, setContentControl, void, (GuiControl *ctrl ), , "( GuiControl ctrl ) - Set the toplevel control to edit in the GUI editor." )
{
if (ctrl)
object->setContentControl(ctrl);
}
//-----------------------------------------------------------------------------
DefineConsoleMethod( GuiEditCtrl, addNewCtrl, void, (GuiControl *ctrl), , "(GuiControl ctrl)")
{
if (ctrl)
object->addNewControl(ctrl);
}
//-----------------------------------------------------------------------------
DefineConsoleMethod( GuiEditCtrl, addSelection, void, (S32 id), , "selects a control.")
{
object->addSelection(id);
}
//-----------------------------------------------------------------------------
DefineConsoleMethod( GuiEditCtrl, removeSelection, void, (S32 id), , "deselects a control.")
{
object->removeSelection(id);
}
//-----------------------------------------------------------------------------
DefineConsoleMethod( GuiEditCtrl, clearSelection, void, (), , "Clear selected controls list.")
{
object->clearSelection();
}
//-----------------------------------------------------------------------------
DefineConsoleMethod( GuiEditCtrl, select, void, (GuiControl *ctrl), , "(GuiControl ctrl)")
{
if (ctrl)
object->setSelection(ctrl, false);
}
//-----------------------------------------------------------------------------
DefineConsoleMethod( GuiEditCtrl, setCurrentAddSet, void, (GuiControl *addSet), , "(GuiControl ctrl)")
{
if (addSet)
object->setCurrentAddSet(addSet);
}
//-----------------------------------------------------------------------------
DefineConsoleMethod( GuiEditCtrl, getCurrentAddSet, S32, (), , "Returns the set to which new controls will be added")
{
const GuiControl* add = object->getCurrentAddSet();
return add ? add->getId() : 0;
}
//-----------------------------------------------------------------------------
DefineConsoleMethod( GuiEditCtrl, toggle, void, (), , "Toggle activation.")
{
object->setEditMode( !object->isActive() );
}
//-----------------------------------------------------------------------------
DefineConsoleMethod( GuiEditCtrl, justify, void, (U32 mode), , "(int mode)" )
{
object->justifySelection( (GuiEditCtrl::Justification)mode );
}
//-----------------------------------------------------------------------------
DefineConsoleMethod( GuiEditCtrl, bringToFront, void, (), , "")
{
object->bringToFront();
}
//-----------------------------------------------------------------------------
DefineConsoleMethod( GuiEditCtrl, pushToBack, void, (), , "")
{
object->pushToBack();
}
//-----------------------------------------------------------------------------
DefineConsoleMethod( GuiEditCtrl, deleteSelection, void, (), , "() - Delete the selected controls.")
{
object->deleteSelection();
}
//-----------------------------------------------------------------------------
DefineConsoleMethod( GuiEditCtrl, moveSelection, void, (S32 dx, S32 dy), , "Move all controls in the selection by (dx,dy) pixels.")
{
object->moveAndSnapSelection(Point2I(dx, dy));
}
//-----------------------------------------------------------------------------
DefineConsoleMethod( GuiEditCtrl, saveSelection, void, (const char * filename), (nullAsType<const char*>()), "( string fileName=null ) - Save selection to file or clipboard.")
{
object->saveSelection( filename );
}
//-----------------------------------------------------------------------------
DefineConsoleMethod( GuiEditCtrl, loadSelection, void, (const char * filename), (nullAsType<const char*>()), "( string fileName=null ) - Load selection from file or clipboard.")
{
object->loadSelection( filename );
}
//-----------------------------------------------------------------------------
DefineConsoleMethod( GuiEditCtrl, selectAll, void, (), , "()")
{
object->selectAll();
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiEditCtrl, getSelection, SimSet*, (),,
"Gets the set of GUI controls currently selected in the editor." )
{
return object->getSelectedSet();
}
//-----------------------------------------------------------------------------
DefineConsoleMethod( GuiEditCtrl, getNumSelected, S32, (), , "() - Return the number of controls currently selected." )
{
return object->getNumSelected();
}
//-----------------------------------------------------------------------------
DefineConsoleMethod( GuiEditCtrl, getSelectionGlobalBounds, const char*, (), , "() - Returns global bounds of current selection as vector 'x y width height'." )
{
RectI bounds = object->getSelectionGlobalBounds();
String str = String::ToString( "%i %i %i %i", bounds.point.x, bounds.point.y, bounds.extent.x, bounds.extent.y );
char* buffer = Con::getReturnBuffer( str.length() );
dStrcpy( buffer, str.c_str() );
return buffer;
}
//-----------------------------------------------------------------------------
DefineConsoleMethod( GuiEditCtrl, selectParents, void, ( bool addToSelection ), (false), "( bool addToSelection=false ) - Select parents of currently selected controls." )
{
object->selectParents( addToSelection );
}
//-----------------------------------------------------------------------------
DefineConsoleMethod( GuiEditCtrl, selectChildren, void, ( bool addToSelection ), (false), "( bool addToSelection=false ) - Select children of currently selected controls." )
{
object->selectChildren( addToSelection );
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiEditCtrl, getTrash, SimGroup*, (),,
"Gets the GUI controls(s) that are currently in the trash.")
{
return object->getTrash();
}
//-----------------------------------------------------------------------------
DefineConsoleMethod(GuiEditCtrl, setSnapToGrid, void, (U32 gridsize), , "GuiEditCtrl.setSnapToGrid(gridsize)")
{
object->setSnapToGrid(gridsize);
}
//-----------------------------------------------------------------------------
DefineConsoleMethod( GuiEditCtrl, readGuides, void, ( GuiControl* ctrl, S32 axis ), (-1), "( GuiControl ctrl [, int axis ] ) - Read the guides from the given control." )
{
// Find the control.
if( !ctrl )
{
return;
}
// Read the guides.
if( axis != -1 )
{
if( axis < 0 || axis > 1 )
{
Con::errorf( "GuiEditCtrl::readGuides - invalid axis '%s'", axis );
return;
}
object->readGuides( ( GuiEditCtrl::guideAxis ) axis, ctrl );
}
else
{
object->readGuides( GuiEditCtrl::GuideHorizontal, ctrl );
object->readGuides( GuiEditCtrl::GuideVertical, ctrl );
}
}
//-----------------------------------------------------------------------------
DefineConsoleMethod( GuiEditCtrl, writeGuides, void, ( GuiControl* ctrl, S32 axis ), ( -1), "( GuiControl ctrl [, int axis ] ) - Write the guides to the given control." )
{
// Find the control.
if( ! ctrl )
{
return;
}
// Write the guides.
if( axis != -1 )
{
if( axis < 0 || axis > 1 )
{
Con::errorf( "GuiEditCtrl::writeGuides - invalid axis '%s'", axis );
return;
}
object->writeGuides( ( GuiEditCtrl::guideAxis ) axis, ctrl );
}
else
{
object->writeGuides( GuiEditCtrl::GuideHorizontal, ctrl );
object->writeGuides( GuiEditCtrl::GuideVertical, ctrl );
}
}
//-----------------------------------------------------------------------------
DefineConsoleMethod( GuiEditCtrl, clearGuides, void, ( S32 axis ), (-1), "( [ int axis ] ) - Clear all currently set guide lines." )
{
if( axis != -1 )
{
if( axis < 0 || axis > 1 )
{
Con::errorf( "GuiEditCtrl::clearGuides - invalid axis '%i'", axis );
return;
}
object->clearGuides( ( GuiEditCtrl::guideAxis ) axis );
}
else
{
object->clearGuides( GuiEditCtrl::GuideHorizontal );
object->clearGuides( GuiEditCtrl::GuideVertical );
}
}
//-----------------------------------------------------------------------------
DefineConsoleMethod( GuiEditCtrl, fitIntoParents, void, (bool width, bool height), (true, true), "( bool width=true, bool height=true ) - Fit selected controls into their parents." )
{
object->fitIntoParents( width, height );
}
//-----------------------------------------------------------------------------
DefineConsoleMethod( GuiEditCtrl, getMouseMode, const char*, (), , "() - Return the current mouse mode." )
{
switch( object->getMouseMode() )
{
case GuiEditCtrl::Selecting:
return "Selecting";
case GuiEditCtrl::DragSelecting:
return "DragSelecting";
case GuiEditCtrl::MovingSelection:
return "MovingSelection";
case GuiEditCtrl::SizingSelection:
return "SizingSelection";
case GuiEditCtrl::DragGuide:
return "DragGuide";
case GuiEditCtrl::DragClone:
return "DragClone";
default:
return "";
}
}
//=============================================================================
// GuiEditorRuler.
//=============================================================================
class GuiEditorRuler : public GuiControl
{
public:
typedef GuiControl Parent;
protected:
String mRefCtrlName;
String mEditCtrlName;
GuiScrollCtrl* mRefCtrl;
GuiEditCtrl* mEditCtrl;
public:
enum EOrientation
{
ORIENTATION_Horizontal,
ORIENTATION_Vertical
};
GuiEditorRuler()
: mRefCtrl( 0 ),
mEditCtrl( 0 )
{
}
EOrientation getOrientation() const
{
if( getWidth() > getHeight() )
return ORIENTATION_Horizontal;
else
return ORIENTATION_Vertical;
}
bool onWake()
{
if( !Parent::onWake() )
return false;
if( !mEditCtrlName.isEmpty() && !Sim::findObject( mEditCtrlName, mEditCtrl ) )
Con::errorf( "GuiEditorRuler::onWake() - no GuiEditCtrl '%s'", mEditCtrlName.c_str() );
if( !mRefCtrlName.isEmpty() && !Sim::findObject( mRefCtrlName, mRefCtrl ) )
Con::errorf( "GuiEditorRuler::onWake() - no GuiScrollCtrl '%s'", mRefCtrlName.c_str() );
return true;
}
void onPreRender()
{
setUpdate();
}
void onMouseDown( const GuiEvent& event )
{
if( !mEditCtrl )
return;
// Determine the guide axis.
GuiEditCtrl::guideAxis axis;
if( getOrientation() == ORIENTATION_Horizontal )
axis = GuiEditCtrl::GuideHorizontal;
else
axis = GuiEditCtrl::GuideVertical;
// Start dragging a new guide out in the editor.
U32 guideIndex = mEditCtrl->addGuide( axis, 0 );
mEditCtrl->startMouseGuideDrag( axis, guideIndex );
}
void onRender(Point2I offset, const RectI &updateRect)
{
GFX->getDrawUtil()->drawRectFill(updateRect, ColorF(1,1,1,1));
Point2I choffset(0,0);
if( mRefCtrl != NULL )
choffset = mRefCtrl->getChildPos();
if( getOrientation() == ORIENTATION_Horizontal )
{
// it's horizontal.
for(U32 i = 0; i < getWidth(); i++)
{
S32 x = offset.x + i;
S32 pos = i - choffset.x;
if(!(pos % 10))
{
S32 start = 6;
if(!(pos %20))
start = 4;
if(!(pos % 100))
start = 1;
GFX->getDrawUtil()->drawLine(x, offset.y + start, x, offset.y + 10, ColorF(0,0,0,1));
}
}
}
else
{
// it's vertical.
for(U32 i = 0; i < getHeight(); i++)
{
S32 y = offset.y + i;
S32 pos = i - choffset.y;
if(!(pos % 10))
{
S32 start = 6;
if(!(pos %20))
start = 4;
if(!(pos % 100))
start = 1;
GFX->getDrawUtil()->drawLine(offset.x + start, y, offset.x + 10, y, ColorF(0,0,0,1));
}
}
}
}
static void initPersistFields()
{
addField( "refCtrl", TypeRealString, Offset( mRefCtrlName, GuiEditorRuler ) );
addField( "editCtrl", TypeRealString, Offset( mEditCtrlName, GuiEditorRuler ) );
Parent::initPersistFields();
}
DECLARE_CONOBJECT(GuiEditorRuler);
DECLARE_CATEGORY( "Gui Editor" );
};
IMPLEMENT_CONOBJECT(GuiEditorRuler);
ConsoleDocClass( GuiEditorRuler,
"@brief Visual representation of markers on top and left sides of GUI Editor\n\n"
"Editor use only.\n\n"
"@internal"
);