/** * @file llxyvector.cpp * @author Andrey Lihatskiy * @brief Implementation for LLXYVector * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2018, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ // A control that allows to set two related vector magnitudes by manipulating a single vector on a plane. #include "linden_common.h" #include "llxyvector.h" #include "llstring.h" #include "llrect.h" #include "lluictrlfactory.h" #include "llrender.h" #include "llmath.h" // Globals static LLDefaultChildRegistry::Register register_xy_vector("xy_vector"); const F32 CENTER_CIRCLE_RADIUS = 2; const S32 ARROW_ANGLE = 30; const S32 ARROW_LENGTH_LONG = 10; const S32 ARROW_LENGTH_SHORT = 6; LLXYVector::Params::Params() : x_entry("x_entry"), y_entry("y_entry"), touch_area("touch_area"), border("border"), edit_bar_height("edit_bar_height", 18), padding("padding", 4), min_val_x("min_val_x", -1.0f), max_val_x("max_val_x", 1.0f), increment_x("increment_x", 0.05f), min_val_y("min_val_y", -1.0f), max_val_y("max_val_y", 1.0f), increment_y("increment_y", 0.05f), label_width("label_width", 16), arrow_color("arrow_color", LLColor4::white), ghost_color("ghost_color"), area_color("area_color", LLColor4::grey4), grid_color("grid_color", LLColor4::grey % 0.25f), logarithmic("logarithmic", false) { } LLXYVector::LLXYVector(const LLXYVector::Params& p) : LLUICtrl(p), mArrowColor(p.arrow_color()), mAreaColor(p.area_color), mGridColor(p.grid_color), mMinValueX(p.min_val_x), mMaxValueX(p.max_val_x), mIncrementX(p.increment_x), mMinValueY(p.min_val_y), mMaxValueY(p.max_val_y), mIncrementY(p.increment_y), mLogarithmic(p.logarithmic), mValueX(0), mValueY(0) { mGhostColor = p.ghost_color.isProvided() ? p.ghost_color() % 0.3f : p.arrow_color() % 0.3f; LLRect border_rect = getLocalRect(); LLViewBorder::Params params = p.border; params.rect(border_rect); mBorder = LLUICtrlFactory::create(params); addChild(mBorder); LLTextBox::Params x_label_params; x_label_params.initial_value(p.x_entry.label()); x_label_params.rect = LLRect(p.padding, border_rect.mTop - p.padding, p.label_width, border_rect.getHeight() - p.edit_bar_height); mXLabel = LLUICtrlFactory::create(x_label_params); addChild(mXLabel); LLLineEditor::Params x_params = p.x_entry; x_params.rect = LLRect(p.padding + p.label_width, border_rect.mTop - p.padding, border_rect.getCenterX(), border_rect.getHeight() - p.edit_bar_height); x_params.commit_callback.function(boost::bind(&LLXYVector::onEditChange, this)); mXEntry = LLUICtrlFactory::create(x_params); mXEntry->setPrevalidateInput(LLTextValidate::validateFloat); addChild(mXEntry); LLTextBox::Params y_label_params; y_label_params.initial_value(p.y_entry.label()); y_label_params.rect = LLRect(border_rect.getCenterX() + p.padding, border_rect.mTop - p.padding, border_rect.getCenterX() + p.label_width, border_rect.getHeight() - p.edit_bar_height); mYLabel = LLUICtrlFactory::create(y_label_params); addChild(mYLabel); LLLineEditor::Params y_params = p.y_entry; y_params.rect = LLRect(border_rect.getCenterX() + p.padding + p.label_width, border_rect.getHeight() - p.padding, border_rect.getWidth() - p.padding, border_rect.getHeight() - p.edit_bar_height); y_params.commit_callback.function(boost::bind(&LLXYVector::onEditChange, this)); mYEntry = LLUICtrlFactory::create(y_params); mYEntry->setPrevalidateInput(LLTextValidate::validateFloat); addChild(mYEntry); LLPanel::Params touch_area = p.touch_area; touch_area.rect = LLRect(p.padding, border_rect.mTop - p.edit_bar_height - p.padding, border_rect.getWidth() - p.padding, p.padding); mTouchArea = LLUICtrlFactory::create(touch_area); addChild(mTouchArea); } LLXYVector::~LLXYVector() { } bool LLXYVector::postBuild() { mLogScaleX = (2 * log(mMaxValueX)) / mTouchArea->getRect().getWidth(); mLogScaleY = (2 * log(mMaxValueY)) / mTouchArea->getRect().getHeight(); return true; } void drawArrow(S32 tailX, S32 tailY, S32 tipX, S32 tipY, LLColor4 color) { gl_line_2d(tailX, tailY, tipX, tipY, color); S32 dx = tipX - tailX; S32 dy = tipY - tailY; S32 arrowLength = (abs(dx) < ARROW_LENGTH_LONG && abs(dy) < ARROW_LENGTH_LONG) ? ARROW_LENGTH_SHORT : ARROW_LENGTH_LONG; F32 theta = std::atan2(dy, dx); F32 rad = ARROW_ANGLE * std::atan(1) * 4 / 180; F32 x = tipX - arrowLength * cos(theta + rad); F32 y = tipY - arrowLength * sin(theta + rad); F32 rad2 = -1 * ARROW_ANGLE * std::atan(1) * 4 / 180; F32 x2 = tipX - arrowLength * cos(theta + rad2); F32 y2 = tipY - arrowLength * sin(theta + rad2); gl_triangle_2d(tipX, tipY, x, y, x2, y2, color, true); } void LLXYVector::draw() { S32 centerX = mTouchArea->getRect().getCenterX(); S32 centerY = mTouchArea->getRect().getCenterY(); S32 pointX; S32 pointY; if (mLogarithmic) { pointX = (log(llabs(mValueX) + 1)) / mLogScaleX; pointX *= (mValueX < 0) ? -1 : 1; pointX += centerX; pointY = (log(llabs(mValueY) + 1)) / mLogScaleY; pointY *= (mValueY < 0) ? -1 : 1; pointY += centerY; } else // linear { pointX = centerX + (mValueX * mTouchArea->getRect().getWidth() / (2 * mMaxValueX)); pointY = centerY + (mValueY * mTouchArea->getRect().getHeight() / (2 * mMaxValueY)); } // fill gl_rect_2d(mTouchArea->getRect(), mAreaColor, true); // draw grid gl_line_2d(centerX, mTouchArea->getRect().mTop, centerX, mTouchArea->getRect().mBottom, mGridColor); gl_line_2d(mTouchArea->getRect().mLeft, centerY, mTouchArea->getRect().mRight, centerY, mGridColor); // draw ghost if (hasMouseCapture()) { drawArrow(centerX, centerY, mGhostX, mGhostY, mGhostColor); } else { mGhostX = pointX; mGhostY = pointY; } if (abs(mValueX) >= mIncrementX || abs(mValueY) >= mIncrementY) { // draw the vector arrow drawArrow(centerX, centerY, pointX, pointY, mArrowColor); } else { // skip the arrow, set color for center circle gGL.color4fv(mArrowColor.get().mV); } // draw center circle gl_circle_2d(centerX, centerY, CENTER_CIRCLE_RADIUS, 12, true); LLView::draw(); } void LLXYVector::onEditChange() { if (getEnabled()) { setValueAndCommit(mXEntry->getValue().asReal(), mYEntry->getValue().asReal()); } } void LLXYVector::setValue(const LLSD& value) { if (value.isArray()) { setValue(value[0].asReal(), value[1].asReal()); } } void LLXYVector::setValue(F32 x, F32 y) { mValueX = ll_round(llclamp(x, mMinValueX, mMaxValueX), mIncrementX); mValueY = ll_round(llclamp(y, mMinValueY, mMaxValueY), mIncrementY); update(); } void LLXYVector::setValueAndCommit(F32 x, F32 y) { if (mValueX != x || mValueY != y) { setValue(x, y); onCommit(); } } LLSD LLXYVector::getValue() const { LLSD value; value.append(mValueX); value.append(mValueY); return value; } void LLXYVector::update() { mXEntry->setValue(mValueX); mYEntry->setValue(mValueY); } bool LLXYVector::handleHover(S32 x, S32 y, MASK mask) { if (hasMouseCapture()) { if (mLogarithmic) { F32 valueX = llfastpow(F_E, mLogScaleX*(llabs(x - mTouchArea->getRect().getCenterX()))) - 1; valueX *= (x < mTouchArea->getRect().getCenterX()) ? -1 : 1; F32 valueY = llfastpow(F_E, mLogScaleY*(llabs(y - mTouchArea->getRect().getCenterY()))) - 1; valueY *= (y < mTouchArea->getRect().getCenterY()) ? -1 : 1; setValueAndCommit(valueX, valueY); } else //linear { F32 valueX = 2 * mMaxValueX * F32(x - mTouchArea->getRect().getCenterX()) / mTouchArea->getRect().getWidth(); F32 valueY = 2 * mMaxValueY * F32(y - mTouchArea->getRect().getCenterY()) / mTouchArea->getRect().getHeight(); setValueAndCommit(valueX, valueY); } } return true; } bool LLXYVector::handleMouseUp(S32 x, S32 y, MASK mask) { if (hasMouseCapture()) { gFocusMgr.setMouseCapture(NULL); make_ui_sound("UISndClickRelease"); } if (mTouchArea->getRect().pointInRect(x, y)) { return true; } else { return LLUICtrl::handleMouseUp(x, y, mask); } } bool LLXYVector::handleMouseDown(S32 x, S32 y, MASK mask) { if (mTouchArea->getRect().pointInRect(x, y)) { gFocusMgr.setMouseCapture(this); make_ui_sound("UISndClick"); return true; } else { return LLUICtrl::handleMouseDown(x, y, mask); } }