//----------------------------------------------------------------------------- // Copyright (c) 2013 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 "console/console.h" #include "console/consoleTypes.h" #include "graphics/dgl.h" #include "sim/simBase.h" #include "gui/guiCanvas.h" #include "gui/editor/guiEditCtrl.h" #include "platform/event.h" #include "io/fileStream.h" #include "gui/containers/guiScrollCtrl.h" IMPLEMENT_CONOBJECT(GuiEditCtrl); GuiEditCtrl::GuiEditCtrl() : mCurrentAddSet(NULL), mEditorRoot(NULL), mGridSnap(10, 10), mDragBeginPoint(-1, -1) { VECTOR_SET_ASSOCIATION(mSelectedControls); VECTOR_SET_ASSOCIATION(mDragBeginPoints); mActive = true; mDragBeginPoints.clear(); mSelectedControls.clear(); mUseGridSnap = true; mMouseLockedEditCtrl = nullptr; mDefaultCursor = NULL; mLeftRightCursor = NULL; mUpDownCursor = NULL; mNWSECursor = NULL; mNESWCursor = NULL; mMoveCursor = NULL; } bool GuiEditCtrl::onAdd() { if (!Parent::onAdd()) return false; mTrash.registerObject(); mSelectedSet.registerObject(); mUndoManager.registerObject(); return true; } void GuiEditCtrl::onRemove() { Parent::onRemove(); mTrash.unregisterObject(); mSelectedSet.unregisterObject(); mUndoManager.unregisterObject(); } ConsoleMethod(GuiEditCtrl, setRoot, void, 3, 3, "(GuiControl root) Sets the given control as root\n" "@return No return value.") { GuiControl* ctrl; if (!Sim::findObject(argv[2], ctrl)) return; object->setRoot(ctrl); } ConsoleMethod(GuiEditCtrl, addNewCtrl, void, 3, 3, "(GuiControl ctrl) Adds the given control to the control list\n" "@return No return value.") { GuiControl* ctrl; if (!Sim::findObject(argv[2], ctrl)) return; object->addNewControl(ctrl); } ConsoleMethod(GuiEditCtrl, addSelection, void, 3, 3, "(ctrlID) Adds the selected control.\n" "@return No return value.") { S32 id = dAtoi(argv[2]); object->addSelection(id); } ConsoleMethod(GuiEditCtrl, removeSelection, void, 3, 3, "(ctrlID) Removes the selected control from list.\n" "@return No return value.") { S32 id = dAtoi(argv[2]); object->removeSelection(id); } ConsoleMethod(GuiEditCtrl, clearSelection, void, 2, 2, "() Clear selected controls list.\n" "@return No return value.") { object->clearSelection(); } ConsoleMethod(GuiEditCtrl, select, void, 3, 3, "(GuiControl ctrl) Finds and selects given object\n" "@return No return value.") { GuiControl* ctrl; if (!Sim::findObject(argv[2], ctrl)) return; object->setSelection(ctrl); } ConsoleMethod(GuiEditCtrl, setCurrentAddSet, void, 3, 3, "(GuiControl ctrl) Set the current control set in which controls are added.\n" "@param ctrl The addset\n" "@return No return value.") { GuiControl* addSet; if (!Sim::findObject(argv[2], addSet)) { Con::printf("%s(): Invalid control: %s", argv[0], argv[2]); return; } object->setCurrentAddSet(addSet); } ConsoleMethod(GuiEditCtrl, getCurrentAddSet, S32, 2, 2, "()\n @return Returns the set to which new controls will be added") { const GuiControl* add = object->getCurrentAddSet(); return add ? add->getId() : 0; } ConsoleMethod(GuiEditCtrl, toggle, void, 2, 2, "() Toggle activation.\n" "@return No return value.") { object->setEditMode(!object->mActive); } ConsoleMethod(GuiEditCtrl, justify, void, 3, 3, "(int mode) Sets justification mode of selection\n" "@return No return value.") { object->justifySelection((GuiEditCtrl::Justification)dAtoi(argv[2])); } ConsoleMethod(GuiEditCtrl, bringToFront, void, 2, 2, "() Brings control to front\n" "@return No return value.") { object->bringToFront(); } ConsoleMethod(GuiEditCtrl, pushToBack, void, 2, 2, "() Sends control to back\n" "@return No return value.") { object->pushToBack(); } ConsoleMethod(GuiEditCtrl, deleteSelection, void, 2, 2, "Delete the selected text.\n" "@return No return value.") { object->deleteSelection(); } ConsoleMethod(GuiEditCtrl, moveSelection, void, 4, 4, "(int deltax, int deltay) Moves selection to given (relative to current position) point\n" "@param deltax,deltay The change in coordinates.\n" "@return No return value.") { if (object->hasSnapToGrid()) { object->moveAndSnapSelection(Point2I(dAtoi(argv[2]), dAtoi(argv[3]))); } else { object->moveSelection(Point2I(dAtoi(argv[2]), dAtoi(argv[3]))); } } ConsoleMethod(GuiEditCtrl, saveSelection, void, 3, 3, "(string fileName) Saves the current selection to given filename\n" "@return No return value.") { object->saveSelection(argv[2]); } ConsoleMethod(GuiEditCtrl, loadSelection, void, 3, 3, "(string fileName) Loads from given filename\n" "@return No return value.") { object->loadSelection(argv[2]); } ConsoleMethod(GuiEditCtrl, selectAll, void, 2, 2, "() Selects all controls\n" "@return No return value.") { object->selectAll(); } ConsoleMethod(GuiEditCtrl, getSelected, S32, 2, 2, "() Gets the GUI control(s) the editor is currently selecting\n" "@return Returns the ID of the control.") { return object->getSelectedSet().getId(); } ConsoleMethod(GuiEditCtrl, getTrash, S32, 2, 2, "() Gets the GUI controls(s) that are currently in the trash.\n" "@return Returns the ID of the control") { return object->getTrash().getId(); } ConsoleMethod(GuiEditCtrl, getUndoManager, S32, 2, 2, "() Gets the Gui Editor's UndoManager object\n" "@return Returns the ID of the object.") { return object->getUndoManager().getId(); } 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(); } void GuiEditCtrl::setRoot(GuiControl* root) { mEditorRoot = root; if (root != NULL) root->mIsContainer = true; mCurrentAddSet = mEditorRoot; Con::executef(this, 1, "onClearSelected"); mSelectedControls.clear(); } enum GuiEditConstants { GUI_BLACK = 0, GUI_WHITE = 255, NUT_SIZE = 4 }; // Sizing Cursors bool GuiEditCtrl::initCursors() { if (mMoveCursor == NULL || mUpDownCursor == NULL || mLeftRightCursor == NULL || mDefaultCursor == NULL || mNWSECursor == NULL || mNESWCursor == NULL) { SimObject* obj; obj = Sim::findObject("MoveCursor"); mMoveCursor = dynamic_cast(obj); obj = Sim::findObject("UpDownCursor"); mUpDownCursor = dynamic_cast(obj); obj = Sim::findObject("LeftRightCursor"); mLeftRightCursor = dynamic_cast(obj); obj = Sim::findObject("DefaultCursor"); mDefaultCursor = dynamic_cast(obj); obj = Sim::findObject("NESWCursor"); mNESWCursor = dynamic_cast(obj); obj = Sim::findObject("NWSECursor"); mNWSECursor = dynamic_cast(obj); obj = Sim::findObject("MoveCursor"); mMoveCursor = dynamic_cast(obj); return(mMoveCursor != NULL && mUpDownCursor != NULL && mLeftRightCursor != NULL && mDefaultCursor != NULL && mNWSECursor != NULL && mNESWCursor != NULL && mMoveCursor != NULL); } else return(true); } void GuiEditCtrl::setEditMode(bool value) { mActive = value; Con::executef(this, 1, "onClearSelected"); mSelectedControls.clear(); if (mActive && mAwake) mCurrentAddSet = mEditorRoot; } void GuiEditCtrl::setCurrentAddSet(GuiControl* ctrl, bool clearSelection) { if (ctrl != mCurrentAddSet) { if (clearSelection) { Con::executef(this, 1, "onClearSelected"); mSelectedControls.clear(); } mCurrentAddSet = ctrl; } } const GuiControl* GuiEditCtrl::getCurrentAddSet() const { return mCurrentAddSet ? mCurrentAddSet : mEditorRoot; } void GuiEditCtrl::clearSelection(void) { mSelectedControls.clear(); if (isMethod("onClearSelected")) { Con::executef(this, 1, "onClearSelected"); } } void GuiEditCtrl::setSelection(GuiControl* ctrl) { //sanity check if (!ctrl) return; if (mEditorRoot == ctrl) { mCurrentAddSet = ctrl; Con::executef(this, 1, "onClearSelected"); mSelectedControls.clear(); } else { // otherwise, we hit a new control... GuiControl* newAddSet = ctrl->getParent(); //see if we should clear the old selection set if (newAddSet != mCurrentAddSet) { Con::executef(this, 1, "onClearSelected"); mSelectedControls.clear(); } //set the selection mCurrentAddSet = newAddSet; mSelectedControls.push_back(ctrl); Con::executef(this, 2, "onAddSelected", Con::getIntArg(ctrl->getId())); } } void GuiEditCtrl::addNewControl(GuiControl* ctrl) { if (!mCurrentAddSet) mCurrentAddSet = mEditorRoot; mCurrentAddSet->addObject(ctrl); Con::executef(this, 1, "onClearSelected"); mSelectedControls.clear(); //if (!(ctrl->isLocked())) { mSelectedControls.push_back(ctrl); Con::executef(this, 2, "onAddSelected", Con::getIntArg(ctrl->getId())); //} // undo Con::executef(this, 2, "onAddNewCtrl", Con::getIntArg(ctrl->getId())); } void GuiEditCtrl::drawNut(const Point2I& nut, ColorI& outlineColor, ColorI& nutColor) { RectI r(nut.x - NUT_SIZE, nut.y - NUT_SIZE, 2 * NUT_SIZE, 2 * NUT_SIZE); r.inset(1, 1); dglDrawRectFill(r, nutColor); r.inset(-1, -1); dglDrawRect(r, outlineColor); } static inline bool inNut(const Point2I& pt, S32 x, S32 y) { S32 dx = pt.x - x; S32 dy = pt.y - y; return dx <= NUT_SIZE && dx >= -NUT_SIZE && dy <= NUT_SIZE && dy >= -NUT_SIZE; } 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::drawControlDecoration(GuiControl* ctrl, RectI& box, ColorI& outlineColor, ColorI& nutColor) { 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; ColorI fillColor = mProfile->getFillColor(DisabledState); ColorI weakColor(fillColor.red, fillColor.green, fillColor.blue, 120); ColorI strongColor(fillColor.red, fillColor.green, fillColor.blue, 200); ColorI strongestColor(fillColor.red, fillColor.green, fillColor.blue, 255); drawTargetLines(lx, ty, weakColor, by, rx, strongColor); if (ctrl->isHidden()) { box.inset(-1, -1); drawDashedLine(10, lx, ty, rx, ty, strongestColor); drawDashedLine(10, lx, ty, lx, by, strongestColor); drawDashedLine(10, rx, ty, rx, by, strongestColor); drawDashedLine(10, lx, by, rx, by, strongestColor); if (ctrl->isLocked()) { box.inset(-1, -1); dglDrawRect(box, outlineColor); } } else if (ctrl->isLocked()) { box.inset(-1, -1); dglDrawRect(box, strongestColor); box.inset(-1,-1); dglDrawRect(box, outlineColor); } else { drawNuts(lx, ty, rx, by, outlineColor, nutColor, cy, cx); } } void GuiEditCtrl::drawDashedLine(const F32& dashLength, const S32& x1, const S32& y1, const S32& x2, const S32& y2, ColorI& color) { F32 dx = x2 - x1; F32 dy = y2 - y1; F32 lineLength = mSqrt(dx * dx + dy * dy); S32 numSegments = mCeil(lineLength / dashLength); F32 unitX = dx / lineLength; F32 unitY = dy / lineLength; for (int i = 0; i < numSegments; i++) { F32 startX = x1 + (i * dashLength * unitX); F32 startY = y1 + (i * dashLength * unitY); F32 remainingLength = lineLength - (i * dashLength); F32 currentDashLength = getMin((dashLength), remainingLength); F32 endX = startX + (currentDashLength * unitX); F32 endY = startY + (currentDashLength * unitY); if (i % 2 == 0) { dglDrawLine(startX, startY, endX, endY, color); } } } void GuiEditCtrl::drawTargetLines(const S32& lx, const S32& ty, ColorI& weakColor, const S32& by, const S32& rx, ColorI& strongColor) { if (lx > 0 && ty > 0) { dglDrawLine(0, ty, lx, ty, weakColor); dglDrawLine(lx, 0, lx, ty, weakColor); } if (lx > 0 && by > 0) dglDrawLine(0, by, lx, by, weakColor); if (rx > 0 && ty > 0) dglDrawLine(rx, 0, rx, ty, weakColor); Point2I extent = localToGlobalCoord(mBounds.extent); if (lx < extent.x && by < extent.y) dglDrawLine(lx, by, lx, extent.y, strongColor); if (rx < extent.x && by < extent.y) { dglDrawLine(rx, by, rx, extent.y, strongColor); dglDrawLine(rx, by, extent.x, by, strongColor); } if (rx < extent.x && ty < extent.y) dglDrawLine(rx, ty, extent.x, ty, strongColor); } void GuiEditCtrl::drawNuts(S32& lx, S32& ty, S32& rx, S32& by, ColorI& outlineColor, ColorI& nutColor, const S32& cy, const S32& cx) { // adjust nuts, so they dont straddle the controls lx -= NUT_SIZE; ty -= NUT_SIZE; rx += NUT_SIZE; by += NUT_SIZE; drawNut(Point2I(lx, ty), outlineColor, nutColor); drawNut(Point2I(lx, cy), outlineColor, nutColor); drawNut(Point2I(lx, by), outlineColor, nutColor); drawNut(Point2I(rx, ty), outlineColor, nutColor); drawNut(Point2I(rx, cy), outlineColor, nutColor); drawNut(Point2I(rx, by), outlineColor, nutColor); drawNut(Point2I(cx, ty), outlineColor, nutColor); drawNut(Point2I(cx, by), outlineColor, nutColor); } 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::onPreRender() { setUpdate(); } void GuiEditCtrl::onRender(Point2I offset, const RectI& updateRect) { Point2I ctOffset; Point2I cext; bool keyFocused = isFirstResponder(); if (mActive) { if (mCurrentAddSet) { // draw a white frame inset around the current add set. cext = mCurrentAddSet->getExtent(); ctOffset = mCurrentAddSet->localToGlobalCoord(Point2I(0, 0)); RectI box(ctOffset.x, ctOffset.y, cext.x, cext.y); ColorI fillColor = mProfile->getFillColor(DisabledState); bool isRoot = (getCurrentAddSet() == mEditorRoot); S32 d = isRoot ? 1 : -1; if (!isRoot) { box.inset(d, d); } dglDrawRect(box, ColorI(fillColor.red, fillColor.green, fillColor.blue, 200)); box.inset(d, d); dglDrawRect(box, ColorI(fillColor.red, fillColor.green, fillColor.blue, 180)); box.inset(d, d); dglDrawRect(box, ColorI(fillColor.red, fillColor.green, fillColor.blue, 160)); box.inset(d, d); dglDrawRect(box, ColorI(fillColor.red, fillColor.green, fillColor.blue, 140)); box.inset(d, d); dglDrawRect(box, ColorI(fillColor.red, fillColor.green, fillColor.blue, 120)); } Vector::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); auto border = mProfile->getTopBorder(); ColorI nutColor = multisel ? mProfile->getFillColor(HighlightState) : mProfile->getFillColor(NormalState); ColorI outlineColor = multisel ? border->getBorderColor(HighlightState) : border->getBorderColor(NormalState); if (keyFocused) { nutColor = mProfile->getFillColor(SelectedState); outlineColor = border->getBorderColor(SelectedState); } drawControlDecoration(ctrl, box, outlineColor, nutColor); } if (mMouseDownMode == DragSelecting) { RectI b; getDragRect(b); b.point += offset; ColorI fillColor = mProfile->getFillColor(DisabledState); dglDrawRect(b, fillColor); } } RectI contentRect = RectI(offset, mBounds.extent); renderChildControls(offset, contentRect, updateRect); if (mActive && mCurrentAddSet && mUseGridSnap && (mMouseDownMode == MovingSelection || mMouseDownMode == SizingSelection)) { Point2I cext = mCurrentAddSet->getExtent(); Point2I coff = mCurrentAddSet->localToGlobalCoord(Point2I(0, 0)); // create point-dots Point2I snap = mGridSnap; if (snap.x < 6) snap *= 2; if (snap.x < 6) snap *= 2; U32 maxdot = (cext.x / snap.x) * (cext.y / snap.y); Point2F* dots = new Point2F[maxdot]; U32 ndot = 0; for (U32 ix = (U32)snap.x; ix < (U32)cext.x; ix += snap.x) { for (U32 iy = snap.y; iy < (U32)cext.y; iy += snap.y) { dots[ndot].x = (F32)(ix + coff.x); dots[ndot++].y = (F32)(iy + coff.y); } } AssertFatal(ndot <= maxdot, "dot overflow"); // draw the points. ColorI fillColor = mProfile->getFillColor(DisabledState); glEnableClientState(GL_VERTEX_ARRAY); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glVertexPointer(2, GL_FLOAT, 0, dots); glColor4ub(fillColor.red, fillColor.green, fillColor.blue, 200); glDrawArrays(GL_POINTS, 0, ndot); glDisableClientState(GL_VERTEX_ARRAY); glDisable(GL_BLEND); delete[] dots; } } bool GuiEditCtrl::selectionContains(GuiControl* ctrl) { Vector::iterator i; for (i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) if (ctrl == *i) return true; return false; } void GuiEditCtrl::onRightMouseDown(const GuiEvent& event) { if (!mActive || !mEditorRoot) { Parent::onRightMouseDown(event); return; } setFirstResponder(); //search for the control hit in any layer below the edit layer GuiControl* hitCtrl = mEditorRoot->findHitControl(globalToLocalCoord(event.mousePoint), mLayer - 1, true, true); if (hitCtrl != mCurrentAddSet) { Con::executef(this, 1, "onClearSelected"); mSelectedControls.clear(); mCurrentAddSet = hitCtrl; } // select the parent if we right-click on the current add set else if (mCurrentAddSet != mEditorRoot) { mCurrentAddSet = hitCtrl->getParent(); select(hitCtrl); } //Design time mouse events GuiEvent designEvent = event; designEvent.mousePoint = mLastMousePos; Point2I localOffset = localToGlobalCoord(Point2I(0, 0)); hitCtrl->onRightMouseDownEditor(designEvent, localOffset); } void GuiEditCtrl::select(GuiControl* ctrl) { Con::executef(this, 1, "onClearSelected"); mSelectedControls.clear(); if (ctrl != mEditorRoot) { //if (!(ctrl->isLocked())) { mSelectedControls.push_back(ctrl); Con::executef(this, 2, "onAddSelected", Con::getIntArg(ctrl->getId())); //} } else mCurrentAddSet = mEditorRoot; } void GuiEditCtrl::getCursor(GuiCursor*& cursor, bool& showCursor, const GuiEvent& lastGuiEvent) { showCursor = true; Point2I ctOffset; Point2I cext; GuiControl* ctrl; Point2I mousePos = globalToLocalCoord(lastGuiEvent.mousePoint); // first see if we hit a sizing knob on the currently selected control... if (mSelectedControls.size() == 1 && initCursors() == true) { 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))) cursor = mNWSECursor; else if ((mSizingMode == (sizingBottom | sizingLeft)) || (mSizingMode == (sizingTop | sizingRight))) cursor = mNESWCursor; else if (mSizingMode == sizingLeft || mSizingMode == sizingRight) cursor = mLeftRightCursor; else if (mSizingMode == sizingTop || mSizingMode == sizingBottom) cursor = mUpDownCursor; else cursor = NULL; } else { // Check for current mouse position after checking for actual sizing mode if ((sizeMode == (sizingBottom | sizingRight)) || (sizeMode == (sizingTop | sizingLeft))) cursor = mNWSECursor; else if ((sizeMode == (sizingBottom | sizingLeft)) || (sizeMode == (sizingTop | sizingRight))) cursor = mNESWCursor; else if (sizeMode == sizingLeft || sizeMode == sizingRight) cursor = mLeftRightCursor; else if (sizeMode == sizingTop || sizeMode == sizingBottom) cursor = mUpDownCursor; else cursor = NULL; } } if (mMouseDownMode == MovingSelection && cursor == NULL) cursor = mMoveCursor; } void GuiEditCtrl::onTouchDown(const GuiEvent& event) { if (!mActive) { Parent::onTouchDown(event); return; } if (!mEditorRoot) return; setFirstResponder(); //lock the mouse mouseLock(); Point2I ctOffset; Point2I cext; GuiControl* ctrl; mLastMousePos = globalToLocalCoord(event.mousePoint); // 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); if ((mSizingMode = (GuiEditCtrl::sizingModes)getSizingHitKnobs(mLastMousePos, box)) != 0) { mMouseDownMode = SizingSelection; // undo Con::executef(this, 2, "onPreEdit", Con::getIntArg(getSelectedSet().getId())); return; } } if (!mCurrentAddSet) mCurrentAddSet = mEditorRoot; bool handledEvent = false; Point2I editorOffset = localToGlobalCoord(Point2I(0, 0)); if (mMouseLockedEditCtrl) { handledEvent = mMouseLockedEditCtrl->onMouseDownEditor(event, editorOffset); } else { //find the control we clicked ctrl = mEditorRoot->findHitControl(mLastMousePos, mCurrentAddSet->mLayer, true, true); handledEvent = ctrl->onMouseDownEditor(event, editorOffset); } if (handledEvent) { // The Control handled the event and requested the edit ctrl // *NOT* act on it. The dude abides. return; } else if (selectionContains(ctrl)) { //if we're holding shift, de-select the clicked ctrl if (event.modifier & SI_SHIFT) { Vector::iterator i; for (i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) { if (*i == ctrl) { Con::executef(this, 2, "onRemoveSelected", Con::getIntArg(ctrl->getId())); mSelectedControls.erase(i); break; } } //set the mode mMouseDownMode = Selecting; } else //else we hit a ctrl we've already selected, so set the mode to moving { // For calculating mouse delta mDragBeginPoint = event.mousePoint; // Allocate enough space for our selected controls mDragBeginPoints.reserve(mSelectedControls.size()); // For snapping to origin Vector::iterator i; for (i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) mDragBeginPoints.push_back((*i)->mBounds.point); // Set Mouse Mode mMouseDownMode = MovingSelection; // undo Con::executef(this, 2, "onPreEdit", Con::getIntArg(getSelectedSet().getId())); } } else //else we clicked on an unselected control { //if we clicked in the current add set if (ctrl == mCurrentAddSet) { // start dragging a rectangle // if the shift is not down, nuke prior selection if (!(event.modifier & SI_SHIFT)) { Con::executef(this, 1, "onClearSelected"); mSelectedControls.clear(); } mSelectionAnchor = mLastMousePos; mMouseDownMode = DragSelecting; } else { //find the new add set GuiControl* newAddSet = ctrl->getParent(); //if we're holding shift and the ctrl is in the same add set if (event.modifier & SI_SHIFT && newAddSet == mCurrentAddSet) { mSelectedControls.push_back(ctrl); Con::executef(this, 2, "onAddSelected", Con::getIntArg(ctrl->getId())); mMouseDownMode = Selecting; } else if (ctrl != mEditorRoot) { //find and set the new add set mCurrentAddSet = newAddSet; //clear and set the selected controls Con::executef(this, 1, "onClearSelected"); mSelectedControls.clear(); mSelectedControls.push_back(ctrl); Con::executef(this, 2, "onAddSelected", Con::getIntArg(ctrl->getId())); mMouseDownMode = Selecting; } else mMouseDownMode = Selecting; } } } void GuiEditCtrl::addSelection(S32 id) { GuiControl* ctrl; if (Sim::findObject(id, ctrl)) mSelectedControls.push_back(ctrl); } void GuiEditCtrl::removeSelection(S32 id) { GuiControl* ctrl; if (Sim::findObject(id, ctrl)) { Vector::iterator i; for (i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) { if (*i == ctrl) { mSelectedControls.erase(i); break; } } } } void GuiEditCtrl::onTouchUp(const GuiEvent& event) { if (!mActive || !mEditorRoot || !mCurrentAddSet) { Parent::onTouchUp(event); return; } //find the control we clicked GuiControl* ctrl = mEditorRoot->findHitControl(mLastMousePos, mCurrentAddSet->mLayer, true, true); Point2I localOffset = localToGlobalCoord(Point2I(0, 0)); bool handledEvent = false; if (mMouseLockedEditCtrl) { handledEvent = mMouseLockedEditCtrl->onMouseUpEditor(event, localOffset); } else { handledEvent = ctrl->onMouseUpEditor(event, localOffset); } 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(); editMouseUnlock(); // Reset Drag Axis Alignment Information mDragBeginPoint.set(-1, -1); mDragBeginPoints.clear(); mLastMousePos = globalToLocalCoord(event.mousePoint); if (mMouseDownMode == DragSelecting) { RectI b; getDragRect(b); GuiControl::iterator i; for (i = mCurrentAddSet->begin(); i != mCurrentAddSet->end(); i++) { GuiControl* ctrl = dynamic_cast(*i); Point2I upperL = globalToLocalCoord(ctrl->localToGlobalCoord(Point2I(0, 0))); Point2I lowerR = upperL + ctrl->mBounds.extent - Point2I(1, 1); if (b.pointInRect(upperL) && b.pointInRect(lowerR) && !selectionContains(ctrl)) { mSelectedControls.push_back(ctrl); Con::executef(this, 2, "onAddSelected", Con::getIntArg(ctrl->getId())); } } } if (isMethod("onEdit") && mSelectedControls.size() > 0) { Con::executef(this, 2, "onEdit", Con::getIntArg(mSelectedControls[0]->getId())); } // 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) Con::executef(this, 2, "onPostEdit", Con::getIntArg(getSelectedSet().getId())); //reset the mouse mode setFirstResponder(); mMouseDownMode = Selecting; } void GuiEditCtrl::onTouchDragged(const GuiEvent& event) { if (!mActive || !mEditorRoot || !mCurrentAddSet) { Parent::onTouchDragged(event); return; } if (!mCurrentAddSet) mCurrentAddSet = mEditorRoot; Point2I mousePoint = globalToLocalCoord(event.mousePoint); Point2I localOffset = localToGlobalCoord(Point2I(0, 0)); bool handledEvent = false; if (mMouseLockedEditCtrl) { handledEvent = mMouseLockedEditCtrl->onMouseDraggedEditor(event, localOffset); } else { GuiControl* ctrl = mEditorRoot->findHitControl(mousePoint, mCurrentAddSet->mLayer, true, true); handledEvent = ctrl->onMouseDraggedEditor(event, localOffset); } if (handledEvent) { // The Control handled the event and requested the edit ctrl // *NOT* act on it. The dude abides. return; } if (mMouseDownMode == SizingSelection) { if (mGridSnap.x) mousePoint.x -= mousePoint.x % mGridSnap.x; if (mGridSnap.y) mousePoint.y -= mousePoint.y % mGridSnap.y; GuiControl* ctrl = mSelectedControls.first(); // can't resize a locked control if (ctrl && ctrl->isLocked()) return; Point2I ctrlPoint = mCurrentAddSet->globalToLocalCoord(event.mousePoint); if (mGridSnap.x) ctrlPoint.x -= ctrlPoint.x % mGridSnap.x; if (mGridSnap.y) ctrlPoint.y -= ctrlPoint.y % mGridSnap.y; Point2I newPosition = ctrl->getPosition(); Point2I newExtent = ctrl->getExtent(); Point2I minExtent = ctrl->getMinExtent(); if (mSizingMode & sizingLeft) { newPosition.x = ctrlPoint.x; newExtent.x = ctrl->mBounds.extent.x + ctrl->mBounds.point.x - ctrlPoint.x; if (newExtent.x < minExtent.x) { newPosition.x -= minExtent.x - newExtent.x; newExtent.x = minExtent.x; } } else if (mSizingMode & sizingRight) { newExtent.x = ctrlPoint.x - ctrl->mBounds.point.x; if (mGridSnap.x) newExtent.x -= newExtent.x % mGridSnap.x; if (newExtent.x < minExtent.x) newExtent.x = minExtent.x; } if (mSizingMode & sizingTop) { newPosition.y = ctrlPoint.y; newExtent.y = ctrl->mBounds.extent.y + ctrl->mBounds.point.y - ctrlPoint.y; if (newExtent.y < minExtent.y) { newPosition.y -= minExtent.y - newExtent.y; newExtent.y = minExtent.y; } } else if (mSizingMode & sizingBottom) { newExtent.y = ctrlPoint.y - ctrl->mBounds.point.y; if (newExtent.y < minExtent.y) newExtent.y = minExtent.y; } if (mGridSnap.x) { newPosition.x -= newPosition.x % mGridSnap.x; newExtent.x -= newExtent.x % mGridSnap.x; } if (mGridSnap.y) { newPosition.y -= newPosition.y % mGridSnap.y; newExtent.y -= newExtent.y % mGridSnap.y; } ctrl->resize(newPosition, newExtent); } else if (mMouseDownMode == MovingSelection && mSelectedControls.size()) { Vector::iterator i = mSelectedControls.begin(); //Point2I minPos = (*i)->mBounds.point; Point2I minPos(S32_MAX, S32_MAX); for (; i != mSelectedControls.end(); i++) { // skip locked controls if ((*i)->isLocked()) continue; if ((*i)->mBounds.point.x < minPos.x) minPos.x = (*i)->mBounds.point.x; if ((*i)->mBounds.point.y < minPos.y) minPos.y = (*i)->mBounds.point.y; } Point2I delta = mousePoint - mLastMousePos; delta += minPos; // find new minPos; if (mGridSnap.x) delta.x -= delta.x % mGridSnap.x; if (mGridSnap.y) delta.y -= delta.y % mGridSnap.y; delta -= minPos; // Do we want to align this drag to the X and Y axes within a certain threshold? if (event.modifier & SI_SHIFT) { Point2I dragTotalDelta = event.mousePoint - mDragBeginPoint; if (dragTotalDelta.y < 10 && dragTotalDelta.y > -10) { for (S32 i = 0; i < mSelectedControls.size(); i++) { // skip locked controls if (mSelectedControls[i]->isLocked()) continue; Point2I snapBackPoint(mSelectedControls[i]->mBounds.point.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 (mSelectedControls[i]->mBounds.point.y != mDragBeginPoints[i].y) mSelectedControls[i]->resize(snapBackPoint, mSelectedControls[i]->mBounds.extent); } delta.y = 0; } if (dragTotalDelta.x < 10 && dragTotalDelta.x > -10) { for (S32 i = 0; i < mSelectedControls.size(); i++) { // skip locked controls if (mSelectedControls[i]->isLocked()) continue; Point2I snapBackPoint(mDragBeginPoints[i].x, mSelectedControls[i]->mBounds.point.y); // This is kind of nasty but we need to snap back if we're not at origin point with selection - JDD if (mSelectedControls[i]->mBounds.point.x != mDragBeginPoints[i].x) mSelectedControls[i]->resize(snapBackPoint, mSelectedControls[i]->mBounds.extent); } delta.x = 0; } } moveSelection(delta); // find the current control under the mouse but not in the selected set. GuiControl* inCtrl = mEditorRoot->findHitControl(mousePoint, mCurrentAddSet->mLayer, true, false); // 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); setCurrentAddSet(inCtrl, false); } mLastMousePos += delta; } else { mLastMousePos = mousePoint; } if (isMethod("onEdit") && mSelectedControls.size() > 0) { Con::executef(this, 2, "onEdit", Con::getIntArg(mSelectedControls[0]->getId())); } } void GuiEditCtrl::moveSelectionToCtrl(GuiControl* newParent) { for (int i = 0; i < mSelectedControls.size(); i++) { GuiControl* ctrl = mSelectedControls[i]; if (ctrl->getParent() == newParent) continue; // skip locked controls if (ctrl->isLocked()) continue; Point2I globalpos = ctrl->localToGlobalCoord(Point2I(0, 0)); newParent->addObject(ctrl); Point2I newpos = ctrl->globalToLocalCoord(globalpos) + ctrl->mBounds.point; ctrl->mBounds.set(newpos, ctrl->mBounds.extent); } Con::executef(this, 2, "onSelectionParentChange", Con::getIntArg(newParent->getId())); } 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) { // move / nudge gets a special callback so that multiple small moves can be // coalesced into one large undo action. // undo Con::executef(this, 2, "onPreSelectionNudged", Con::getIntArg(getSelectedSet().getId())); Vector::iterator i; Point2I newPos; for (i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) { newPos = (*i)->mBounds.point + delta; newPos = snapPoint(newPos, delta, mGridSnap); (*i)->resize(newPos, (*i)->mBounds.extent); } // undo Con::executef(this, 2, "onPostSelectionNudged", Con::getIntArg(getSelectedSet().getId())); // allow script to update the inspector if (mSelectedControls.size() == 1) Con::executef(this, 2, "onSelectionMoved", Con::getIntArg(mSelectedControls[0]->getId())); } void GuiEditCtrl::moveSelection(const Point2I& delta) { // move / nudge gets a special callback so that multiple small moves can be // coalesced into one large undo action. // undo Con::executef(this, 2, "onPreSelectionNudged", Con::getIntArg(getSelectedSet().getId())); Vector::iterator i; for (i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) { // skip locked controls if ((*i)->isLocked()) continue; (*i)->resize((*i)->mBounds.point + delta, (*i)->mBounds.extent); } // undo Con::executef(this, 2, "onPostSelectionNudged", Con::getIntArg(getSelectedSet().getId())); // allow script to update the inspector if (mSelectedControls.size() == 1) Con::executef(this, 2, "onSelectionMoved", Con::getIntArg(mSelectedControls[0]->getId())); } void GuiEditCtrl::justifySelection(Justification j) { S32 minX, maxX; S32 minY, maxY; S32 extentX, extentY; if (mSelectedControls.size() < 2) return; Vector::iterator i = mSelectedControls.begin(); minX = (*i)->mBounds.point.x; maxX = minX + (*i)->mBounds.extent.x; minY = (*i)->mBounds.point.y; maxY = minY + (*i)->mBounds.extent.y; extentX = (*i)->mBounds.extent.x; extentY = (*i)->mBounds.extent.y; i++; for (; i != mSelectedControls.end(); i++) { minX = getMin(minX, (*i)->mBounds.point.x); maxX = getMax(maxX, (*i)->mBounds.point.x + (*i)->mBounds.extent.x); minY = getMin(minY, (*i)->mBounds.point.y); maxY = getMax(maxY, (*i)->mBounds.point.y + (*i)->mBounds.extent.y); extentX += (*i)->mBounds.extent.x; extentY += (*i)->mBounds.extent.y; } S32 deltaX = maxX - minX; S32 deltaY = maxY - minY; switch (j) { case JUSTIFY_LEFT: for (i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) (*i)->resize(Point2I(minX, (*i)->mBounds.point.y), (*i)->mBounds.extent); break; case JUSTIFY_TOP: for (i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) (*i)->resize(Point2I((*i)->mBounds.point.x, minY), (*i)->mBounds.extent); break; case JUSTIFY_RIGHT: for (i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) (*i)->resize(Point2I(maxX - (*i)->mBounds.extent.x + 1, (*i)->mBounds.point.y), (*i)->mBounds.extent); break; case JUSTIFY_BOTTOM: for (i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) (*i)->resize(Point2I((*i)->mBounds.point.x, maxY - (*i)->mBounds.extent.y + 1), (*i)->mBounds.extent); break; case JUSTIFY_CENTER: for (i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) (*i)->resize(Point2I(minX + ((deltaX - (*i)->mBounds.extent.x) >> 1), (*i)->mBounds.point.y), (*i)->mBounds.extent); break; case SPACING_VERTICAL: { Vector sortedList; Vector::iterator k; for (i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) { for (k = sortedList.begin(); k != sortedList.end(); k++) { if ((*i)->mBounds.point.y < (*k)->mBounds.point.y) break; } sortedList.insert(k, *i); } S32 space = (deltaY - extentY) / (mSelectedControls.size() - 1); S32 curY = minY; for (k = sortedList.begin(); k != sortedList.end(); k++) { (*k)->resize(Point2I((*k)->mBounds.point.x, curY), (*k)->mBounds.extent); curY += (*k)->mBounds.extent.y + space; } } break; case SPACING_HORIZONTAL: { Vector sortedList; Vector::iterator k; for (i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) { for (k = sortedList.begin(); k != sortedList.end(); k++) { if ((*i)->mBounds.point.x < (*k)->mBounds.point.x) break; } sortedList.insert(k, *i); } S32 space = (deltaX - extentX) / (mSelectedControls.size() - 1); S32 curX = minX; for (k = sortedList.begin(); k != sortedList.end(); k++) { (*k)->resize(Point2I(curX, (*k)->mBounds.point.y), (*k)->mBounds.extent); curX += (*k)->mBounds.extent.x + space; } } break; } } void GuiEditCtrl::deleteSelection(void) { // undo Con::executef(this, 2, "onTrashSelection", Con::getIntArg(getSelectedSet().getId())); Vector::iterator i; for (i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) { mTrash.addObject(*i); } mSelectedControls.clear(); } void GuiEditCtrl::loadSelection(const char* filename) { if (!mCurrentAddSet) mCurrentAddSet = mEditorRoot; Con::executef(2, "exec", filename); SimSet* set; if (!Sim::findObject("guiClipboard", set)) return; if (set->size()) { Con::executef(this, 1, "onClearSelected"); mSelectedControls.clear(); for (U32 i = 0; i < (U32)set->size(); i++) { GuiControl* ctrl = dynamic_cast((*set)[i]); if (ctrl) { mCurrentAddSet->addObject(ctrl); mSelectedControls.push_back(ctrl); Con::executef(this, 2, "onAddSelected", Con::getIntArg(ctrl->getId())); } } // Undo Con::executef(this, 2, "onAddNewCtrlSet", Con::getIntArg(getSelectedSet().getId())); } set->deleteObject(); } void GuiEditCtrl::saveSelection(const char* filename) { // if there are no selected objects, then don't save if (mSelectedControls.size() == 0) return; FileStream stream; if (!ResourceManager->openFileForWrite(stream, filename)) return; SimSet* clipboardSet = new SimSet; clipboardSet->registerObject(); Sim::getRootGroup()->addObject(clipboardSet, "guiClipboard"); Vector::iterator i; for (i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) clipboardSet->addObject(*i); clipboardSet->write(stream, 0); clipboardSet->deleteObject(); } void GuiEditCtrl::selectAll() { if (!mCurrentAddSet) return; mSelectedControls.clear(); if (isMethod("onClearSelected")) { Con::executef(this, 1, "onClearSelected"); } for (GuiControl::iterator i = mCurrentAddSet->begin(); i != mCurrentAddSet->end(); i++) { GuiControl* ctrl = dynamic_cast(*i); if (ctrl) { mSelectedControls.push_back(ctrl); if (isMethod("onAddSelected")) { Con::executef(this, 2, "onAddSelected", Con::getIntArg(ctrl->getId())); } } } } void GuiEditCtrl::bringToFront() { // undo if (mSelectedControls.size() != 1) return; GuiControl* ctrl = *(mSelectedControls.begin()); mCurrentAddSet->pushObjectToBack(ctrl); } void GuiEditCtrl::pushToBack() { // undo if (mSelectedControls.size() != 1) return; GuiControl* ctrl = *(mSelectedControls.begin()); mCurrentAddSet->bringObjectToFront(ctrl); } bool GuiEditCtrl::onKeyDown(const GuiEvent& event) { if (!mActive) return Parent::onKeyDown(event); if (!(event.modifier & SI_CTRL) && !(event.modifier & SI_SHIFT)) { switch (event.keyCode) { case KEY_BACKSPACE: case KEY_DELETE: deleteSelection(); Con::executef(this, 1, "onDelete"); return true; } } else if (event.modifier & SI_SHIFT && !(event.modifier & SI_CTRL)) { //holding shift while using arrow keys toggles the grid snap switch (event.keyCode) { case KEY_LEFT: moveSelection(Point2I(mUseGridSnap ? -1 : -mGridSnap.x, 0)); return true; case KEY_RIGHT: moveSelection(Point2I(mUseGridSnap ? 1 : mGridSnap.x, 0)); return true; case KEY_UP: moveSelection(Point2I(0, mUseGridSnap ? -1 : -mGridSnap.y)); return true; case KEY_DOWN: moveSelection(Point2I(0, mUseGridSnap ? 1 : mGridSnap.y)); return true; } } return false; } ConsoleMethod(GuiEditCtrl, setSnapToGrid, void, 3, 3, "(gridsize) Set the size of the snap-to grid.\n" "@return No return value.") { U32 gridsize = dAtoi(argv[2]); object->setSnapToGrid(gridsize); } ConsoleMethod(GuiEditCtrl, getGridSize, S32, 2, 2, "() Returns the grid size even if the grid is off.\n" "@return A single int as the grid size.") { U32 gridSize = object->getGridSize(); return gridSize; } void GuiEditCtrl::setSnapToGrid(U32 gridsize) { if (gridsize == 0) { mUseGridSnap = false; } else { mGridSnap.set(gridsize, gridsize); mUseGridSnap = true; } } void GuiEditCtrl::controlInspectPreApply(GuiControl* object) { // undo Con::executef(this, 2, "onControlInspectPreApply", Con::getIntArg(object->getId())); } void GuiEditCtrl::controlInspectPostApply(GuiControl* object) { // undo Con::executef(this, 2, "onControlInspectPostApply", Con::getIntArg(object->getId())); } void GuiEditCtrl::updateSelectedSet() { mSelectedSet.clear(); Vector::iterator i; for (i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) { mSelectedSet.addObject(*i); } } // ----------------------------------------------------------------------------- // GuiEditor Ruler // ----------------------------------------------------------------------------- class GuiEditorRuler : public GuiControl { StringTableEntry refCtrl; typedef GuiControl Parent; public: void onPreRender() { setUpdate(); } void onRender(Point2I offset, const RectI& updateRect) { dglDrawRectFill(updateRect, ColorF(1, 1, 1, 1)); GuiScrollCtrl* ref; SimObject* o = Sim::findObject(refCtrl); //Sim::findObject(refCtrl, &ref); ref = dynamic_cast(o); Point2I choffset(0, 0); //if(ref) // choffset = ref->getChildPos(); if (mBounds.extent.x > mBounds.extent.y) { // it's horizontal. for (U32 i = 0; i < (U32)mBounds.extent.x; 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; dglDrawLine(x, offset.y + start, x, offset.y + 10, ColorF(0, 0, 0, 1)); } } } else { // it's vertical. for (U32 i = 0; i < (U32)mBounds.extent.y; 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; dglDrawLine(offset.x + start, y, offset.x + 10, y, ColorF(0, 0, 0, 1)); } } } } static void initPersistFields() { Parent::initPersistFields(); addField("refCtrl", TypeString, Offset(refCtrl, GuiEditorRuler)); } DECLARE_CONOBJECT(GuiEditorRuler); }; IMPLEMENT_CONOBJECT(GuiEditorRuler);