mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-20 12:44:46 +00:00
virtuals removed and replaced with override where necessary on the rest of the code base, clang-tidy to the rescue.
2939 lines
88 KiB
C++
2939 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"
|
|
#include "console/script.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() override
|
|
{
|
|
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() override
|
|
{
|
|
setUpdate();
|
|
}
|
|
|
|
void onMouseDown( const GuiEvent& event ) override
|
|
{
|
|
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) override
|
|
{
|
|
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"
|
|
);
|