Torque3D/Engine/source/gui/editor/guiEditCtrl.cpp
Areloch d0f914b3e6 From Nils' UI work
Adds ability to control button margin of IconButtons
Adds ability to define button offset of window controls
Add mouse drag callback for window controls
Tweaks colors of gui slider control
Tweak to line split for text edit slider bitmap control
Tweaks to colors for text edit slider control
Tweaks to colors for Edit control
Tweaks to default menubar height
2023-09-04 22:50:45 -05:00

2938 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 ),
mSnapToGuides( true ),
mSnapToCenters( true ),
mSnapToCanvas( true ),
mDrawBorderLines( true ),
mFullBoxSelection( false ),
mSnapSensitivity( 2 ),
mDrawGuides( true ),
mDragAddSelection(false),
mDragMoveUndo(false)
{
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;
mDragGuideIndex[0] = 0;
mDragGuideIndex[1] = 1;
std::fill_n(mSnapOffset, 2, 0);
std::fill_n(mSnapEdge, 2, SnapEdgeMin);
if( !smGuidesPropertyName[ GuideVertical ] )
smGuidesPropertyName[ GuideVertical ] = StringTable->insert( "guidesVertical" );
if( !smGuidesPropertyName[ GuideHorizontal ] )
smGuidesPropertyName[ GuideHorizontal ] = StringTable->insert( "guidesHorizontal" );
mTrash = NULL;
mSelectedSet = NULL;
mMouseDownMode = GuiEditCtrl::Selecting;
mSizingMode = GuiEditCtrl::sizingNone;
}
//-----------------------------------------------------------------------------
void GuiEditCtrl::initPersistFields()
{
docsURL;
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 dragPoint = event.mousePoint;
snapToGrid(dragPoint);
Point2I delta = dragPoint - 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 = dragPoint;
}
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 ) )
{
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 snapBounds = mSnapTargets[ axis ]->getGlobalBounds();
drawer->drawRect(snapBounds, ColorI( 128, 128, 128, 128 ) );
}
}
}
}
}
//-----------------------------------------------------------------------------
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 )
{
ColorI lineColor( 179, 179, 179, 64 );
ColorI lightLineColor( 128, 128, 128, 26);
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 = T3D::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 ----
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiEditCtrl, getContentControl, S32, (), , "() - Return the toplevel control edited inside the GUI editor." )
{
GuiControl* ctrl = object->getContentControl();
if( ctrl )
return ctrl->getId();
else
return 0;
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiEditCtrl, setContentControl, void, (GuiControl *ctrl ), , "( GuiControl ctrl ) - Set the toplevel control to edit in the GUI editor." )
{
if (ctrl)
object->setContentControl(ctrl);
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiEditCtrl, addNewCtrl, void, (GuiControl *ctrl), , "(GuiControl ctrl)")
{
if (ctrl)
object->addNewControl(ctrl);
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiEditCtrl, addSelection, void, (S32 id), , "selects a control.")
{
object->addSelection(id);
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiEditCtrl, removeSelection, void, (S32 id), , "deselects a control.")
{
object->removeSelection(id);
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiEditCtrl, clearSelection, void, (), , "Clear selected controls list.")
{
object->clearSelection();
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiEditCtrl, select, void, (GuiControl *ctrl), , "(GuiControl ctrl)")
{
if (ctrl)
object->setSelection(ctrl, false);
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiEditCtrl, setCurrentAddSet, void, (GuiControl *addSet), , "(GuiControl ctrl)")
{
if (addSet)
object->setCurrentAddSet(addSet);
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiEditCtrl, getCurrentAddSet, S32, (), , "Returns the set to which new controls will be added")
{
const GuiControl* add = object->getCurrentAddSet();
return add ? add->getId() : 0;
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiEditCtrl, toggle, void, (), , "Toggle activation.")
{
object->setEditMode( !object->isActive() );
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiEditCtrl, justify, void, (U32 mode), , "(int mode)" )
{
object->justifySelection( (GuiEditCtrl::Justification)mode );
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiEditCtrl, bringToFront, void, (), , "")
{
object->bringToFront();
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiEditCtrl, pushToBack, void, (), , "")
{
object->pushToBack();
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiEditCtrl, deleteSelection, void, (), , "() - Delete the selected controls.")
{
object->deleteSelection();
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiEditCtrl, moveSelection, void, (S32 dx, S32 dy), , "Move all controls in the selection by (dx,dy) pixels.")
{
object->moveAndSnapSelection(Point2I(dx, dy));
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiEditCtrl, saveSelection, void, (const char * filename), (nullAsType<const char*>()), "( string fileName=null ) - Save selection to file or clipboard.")
{
object->saveSelection( filename );
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiEditCtrl, loadSelection, void, (const char * filename), (nullAsType<const char*>()), "( string fileName=null ) - Load selection from file or clipboard.")
{
object->loadSelection( filename );
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiEditCtrl, selectAll, void, (), , "()")
{
object->selectAll();
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiEditCtrl, getSelection, SimSet*, (),,
"Gets the set of GUI controls currently selected in the editor." )
{
return object->getSelectedSet();
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiEditCtrl, getNumSelected, S32, (), , "() - Return the number of controls currently selected." )
{
return object->getNumSelected();
}
//-----------------------------------------------------------------------------
DefineEngineMethod( 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.size() );
dStrcpy( buffer, str.c_str(), str.size() );
return buffer;
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiEditCtrl, selectParents, void, ( bool addToSelection ), (false), "( bool addToSelection=false ) - Select parents of currently selected controls." )
{
object->selectParents( addToSelection );
}
//-----------------------------------------------------------------------------
DefineEngineMethod( 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();
}
//-----------------------------------------------------------------------------
DefineEngineMethod(GuiEditCtrl, setSnapToGrid, void, (U32 gridsize), , "GuiEditCtrl.setSnapToGrid(gridsize)")
{
object->setSnapToGrid(gridsize);
}
//-----------------------------------------------------------------------------
DefineEngineMethod( 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 );
}
}
//-----------------------------------------------------------------------------
DefineEngineMethod( 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 );
}
}
//-----------------------------------------------------------------------------
DefineEngineMethod( 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 );
}
}
//-----------------------------------------------------------------------------
DefineEngineMethod( 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 );
}
//-----------------------------------------------------------------------------
DefineEngineMethod( 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, ColorI::DARK);
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, ColorI::LIGHT);
}
}
}
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, ColorI::LIGHT);
}
}
}
}
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"
);