From a0758ede912bb3f6241b26bfdd4c2f316d459725 Mon Sep 17 00:00:00 2001 From: Rye Mutt <rye@alchemyviewer.org> Date: Mon, 30 Mar 2020 11:19:51 -0400 Subject: [PATCH] Return of the particle editor --- indra/newview/CMakeLists.txt | 2 + indra/newview/alfloaterparticleeditor.cpp | 645 +++++++++++++ indra/newview/alfloaterparticleeditor.h | 150 +++ indra/newview/llviewerfloaterreg.cpp | 3 + .../xui/en/floater_particle_editor.xml | 876 ++++++++++++++++++ .../skins/default/xui/en/menu_object.xml | 10 + .../skins/default/xui/en/menu_viewer.xml | 8 + .../skins/default/xui/en/notifications.xml | 59 ++ 8 files changed, 1753 insertions(+) create mode 100644 indra/newview/alfloaterparticleeditor.cpp create mode 100644 indra/newview/alfloaterparticleeditor.h create mode 100644 indra/newview/skins/default/xui/en/floater_particle_editor.xml diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index baa7cc1b8c8..b0309d274a9 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -112,6 +112,7 @@ include_directories(SYSTEM set(viewer_SOURCE_FILES alavataractions.cpp alchatcommand.cpp + alfloaterparticleeditor.cpp alunzip.cpp alviewermenu.cpp groupchatlistener.cpp @@ -750,6 +751,7 @@ set(viewer_HEADER_FILES ViewerInstall.cmake alavataractions.h alchatcommand.h + alfloaterparticleeditor.h alunzip.h alviewermenu.h groupchatlistener.h diff --git a/indra/newview/alfloaterparticleeditor.cpp b/indra/newview/alfloaterparticleeditor.cpp new file mode 100644 index 00000000000..ba1a018adeb --- /dev/null +++ b/indra/newview/alfloaterparticleeditor.cpp @@ -0,0 +1,645 @@ +/** + * @file alfloaterparticleeditor.cpp + * @brief Particle Editor + * + * Copyright (C) 2011, Zi Ree @ Second Life + * Copyright (C) 2015, Cinder Roxley <cinder@sdf.org> + * Copyright (C) 2020, Rye Mutt <rye@alchemyviewer.org> + * + * 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 + */ + +#include "llviewerprecompiledheaders.h" +#include "alfloaterparticleeditor.h" + +#include "llagent.h" +#include "llappviewer.h" +#include "llcheckboxctrl.h" +#include "llclipboard.h" +#include "llcolorswatch.h" +#include "llcombobox.h" +#include "llfoldertype.h" +#include "llinventorymodel.h" +#include "llinventorytype.h" +#include "llnotificationsutil.h" +#include "llpermissions.h" +#include "llpreviewscript.h" +#include "llsd.h" +#include "lltexturectrl.h" +#include "lltoolmgr.h" +#include "lltoolobjpicker.h" +#include "llviewerassetupload.h" +#include "llviewerobject.h" +#include "llviewerobjectlist.h" +#include "llviewerpartsim.h" +#include "llviewerpartsource.h" +#include "llviewerregion.h" +#include "llviewertexture.h" +#include "llwindow.h" + +struct lsl_part_st { + U8 flag; + const char* script_const; + lsl_part_st(const U8 f, const char* sc) + { + flag = f, script_const = sc; + } +}; + +static const std::map<std::string, lsl_part_st> sParticlePatterns{ + { "drop", lsl_part_st(LLPartSysData::LL_PART_SRC_PATTERN_DROP, "PSYS_SRC_PATTERN_DROP") }, + { "explode", lsl_part_st(LLPartSysData::LL_PART_SRC_PATTERN_EXPLODE, "PSYS_SRC_PATTERN_EXPLODE") }, + { "angle", lsl_part_st(LLPartSysData::LL_PART_SRC_PATTERN_ANGLE, "PSYS_SRC_PATTERN_ANGLE") }, + { "angle_cone", lsl_part_st(LLPartSysData::LL_PART_SRC_PATTERN_ANGLE_CONE, "PSYS_SRC_PATTERN_ANGLE_CONE") }, + { "angle_cone_empty", lsl_part_st(LLPartSysData::LL_PART_SRC_PATTERN_ANGLE_CONE_EMPTY, "PSYS_SRC_PATTERN_ANGLE_CONE_EMPTY") } +}; + +static const std::map<std::string, lsl_part_st> sParticleBlends{ + { "blend_one", lsl_part_st(LLPartData::LL_PART_BF_ONE, "PSYS_PART_BF_ONE") }, + { "blend_zero", lsl_part_st(LLPartData::LL_PART_BF_ZERO, "PSYS_PART_BF_ZERO") }, + { "blend_dest_color", lsl_part_st(LLPartData::LL_PART_BF_DEST_COLOR, "PSYS_PART_BF_DEST_COLOR") }, + { "blend_src_color", lsl_part_st(LLPartData::LL_PART_BF_SOURCE_COLOR, "PSYS_PART_BF_SOURCE_COLOR") }, + { "blend_one_minus_dest_color", lsl_part_st(LLPartData::LL_PART_BF_ONE_MINUS_DEST_COLOR, "PSYS_PART_BF_ONE_MINUS_DEST_COLOR") }, + { "blend_one_minus_src_color", lsl_part_st(LLPartData::LL_PART_BF_ONE_MINUS_SOURCE_COLOR, "PSYS_PART_BF_SOURCE_ALPHA") }, + { "blend_src_alpha", lsl_part_st(LLPartData::LL_PART_BF_SOURCE_ALPHA, "PSYS_PART_BF_SOURCE_ALPHA") }, + { "blend_one_minus_src_alpha", lsl_part_st(LLPartData::LL_PART_BF_ONE_MINUS_SOURCE_ALPHA, "PSYS_PART_BF_ONE_MINUS_SOURCE_ALPHA") } +}; + +static const std::string PARTICLE_SCRIPT_NAME = "New Particle Script"; + +ALFloaterParticleEditor::ALFloaterParticleEditor(const LLSD& key) + : LLFloater(key), mChanged(false), mCloseAfterSave(false), mObject(nullptr), mTexture(nullptr) + , mParticleScriptInventoryItem(nullptr), mPatternTypeCombo(nullptr), mTexturePicker(nullptr) + , mBurstRateCtrl(nullptr), mBurstCountCtrl(nullptr), mBurstRadiusCtrl(nullptr) + , mAngleBeginCtrl(nullptr), mAngleEndCtrl(nullptr), mBurstSpeedMinCtrl(nullptr) + , mBurstSpeedMaxCtrl(nullptr), mStartAlphaCtrl(nullptr), mEndAlphaCtrl(nullptr) + , mScaleStartXCtrl(nullptr), mScaleStartYCtrl(nullptr), mScaleEndXCtrl(nullptr) + , mScaleEndYCtrl(nullptr), mSourceMaxAgeCtrl(nullptr), mParticlesMaxAgeCtrl(nullptr) + , mStartGlowCtrl(nullptr), mEndGlowCtrl(nullptr), mAcellerationXCtrl(nullptr) + , mAcellerationYCtrl(nullptr), mAcellerationZCtrl(nullptr), mOmegaXCtrl(nullptr) + , mOmegaYCtrl(nullptr), mOmegaZCtrl(nullptr), mBlendFuncSrcCombo(nullptr) + , mBlendFuncDestCombo(nullptr), mBounceCheckBox(nullptr), mEmissiveCheckBox(nullptr) + , mFollowSourceCheckBox(nullptr), mFollowVelocityCheckBox(nullptr), mInterpolateColorCheckBox(nullptr) + , mInterpolateScaleCheckBox(nullptr), mTargetPositionCheckBox(nullptr), mTargetLinearCheckBox(nullptr) + , mWindCheckBox(nullptr), mRibbonCheckBox(nullptr), mTargetKeyInput(nullptr) + , mClearTargetButton(nullptr), mPickTargetButton(nullptr), mCopyToLSLButton(nullptr) + , mInjectScriptButton(nullptr), mStartColorSelector(nullptr), mEndColorSelector(nullptr) +{ + mCommitCallbackRegistrar.add("Particle.Edit", [this](LLUICtrl* ctrl, const LLSD& param) { onParameterChange(); }); + mDefaultParticleTexture = LLViewerFetchedTexture::sPixieSmallImagep; +} + +ALFloaterParticleEditor::~ALFloaterParticleEditor() +{ + clearParticles(); +} + +BOOL ALFloaterParticleEditor::postBuild() +{ + LLPanel* panel = getChild<LLPanel>("burst_panel"); + mBurstRateCtrl = panel->getChild<LLUICtrl>("burst_rate"); + mBurstCountCtrl = panel->getChild<LLUICtrl>("burst_count"); + mBurstRadiusCtrl = panel->getChild<LLUICtrl>("burst_radius"); + mBurstSpeedMinCtrl = panel->getChild<LLUICtrl>("burst_speed_min"); + mBurstSpeedMaxCtrl = panel->getChild<LLUICtrl>("burst_speed_max"); + mSourceMaxAgeCtrl = panel->getChild<LLUICtrl>("source_max_age"); + mParticlesMaxAgeCtrl = panel->getChild<LLUICtrl>("particle_max_age"); + + panel = getChild<LLPanel>("angle_panel"); + mAngleBeginCtrl = panel->getChild<LLUICtrl>("angle_begin"); + mAngleEndCtrl = panel->getChild<LLUICtrl>("angle_end"); + mScaleStartXCtrl = panel->getChild<LLUICtrl>("scale_start_x"); + mScaleStartYCtrl = panel->getChild<LLUICtrl>("scale_start_y"); + mScaleEndXCtrl = panel->getChild<LLUICtrl>("scale_end_x"); + mScaleEndYCtrl = panel->getChild<LLUICtrl>("scale_end_y"); + + panel = getChild<LLPanel>("alpha_panel"); + mStartAlphaCtrl = panel->getChild<LLUICtrl>("start_alpha"); + mEndAlphaCtrl = panel->getChild<LLUICtrl>("end_alpha"); + mStartGlowCtrl = panel->getChild<LLUICtrl>("start_glow"); + mEndGlowCtrl = panel->getChild<LLUICtrl>("end_glow"); + + panel = getChild<LLPanel>("omega_panel"); + mAcellerationXCtrl = panel->getChild<LLUICtrl>("acceleration_x"); + mAcellerationYCtrl = panel->getChild<LLUICtrl>("acceleration_y"); + mAcellerationZCtrl = panel->getChild<LLUICtrl>("acceleration_z"); + mOmegaXCtrl = panel->getChild<LLUICtrl>("omega_x"); + mOmegaYCtrl = panel->getChild<LLUICtrl>("omega_y"); + mOmegaZCtrl = panel->getChild<LLUICtrl>("omega_z"); + + panel = getChild<LLPanel>("color_panel"); + mStartColorSelector = panel->getChild<LLColorSwatchCtrl>("start_color_selector"); + mEndColorSelector = panel->getChild<LLColorSwatchCtrl>("end_color_selector"); + mTexturePicker = panel->getChild<LLTextureCtrl>("texture_picker"); + mBlendFuncSrcCombo = panel->getChild<LLComboBox>("blend_func_src_combo"); + mBlendFuncDestCombo = panel->getChild<LLComboBox>("blend_func_dest_combo"); + + panel = getChild<LLPanel>("checkbox_panel"); + mPatternTypeCombo = panel->getChild<LLComboBox>("pattern_type_combo"); + mBounceCheckBox = panel->getChild<LLCheckBoxCtrl>("bounce_checkbox"); + mEmissiveCheckBox = panel->getChild<LLCheckBoxCtrl>("emissive_checkbox"); + mFollowSourceCheckBox = panel->getChild<LLCheckBoxCtrl>("follow_source_checkbox"); + mFollowVelocityCheckBox = panel->getChild<LLCheckBoxCtrl>("follow_velocity_checkbox"); + mInterpolateColorCheckBox = panel->getChild<LLCheckBoxCtrl>("interpolate_color_checkbox"); + mInterpolateScaleCheckBox = panel->getChild<LLCheckBoxCtrl>("interpolate_scale_checkbox"); + mTargetPositionCheckBox = panel->getChild<LLCheckBoxCtrl>("target_position_checkbox"); + mTargetLinearCheckBox = panel->getChild<LLCheckBoxCtrl>("target_linear_checkbox"); + mWindCheckBox = panel->getChild<LLCheckBoxCtrl>("wind_checkbox"); + mRibbonCheckBox = panel->getChild<LLCheckBoxCtrl>("ribbon_checkbox"); + + mTargetKeyInput = panel->getChild<LLUICtrl>("target_key_input"); + mClearTargetButton = panel->getChild<LLButton>("clear_target_button"); + mPickTargetButton = panel->getChild<LLButton>("pick_target_button"); + mInjectScriptButton = panel->getChild<LLButton>("inject_button"); + mCopyToLSLButton = panel->getChild<LLButton>("copy_button"); + + mClearTargetButton->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onClickClearTarget(); }); + mPickTargetButton->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onClickTargetPicker(); }); + mInjectScriptButton->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { injectScript(); }); + mCopyToLSLButton->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onClickCopy(); }); + + mBlendFuncSrcCombo->setValue("blend_src_alpha"); + mBlendFuncDestCombo->setValue("blend_one_minus_src_alpha"); + + onParameterChange(); + + return TRUE; +} + +BOOL ALFloaterParticleEditor::canClose() +{ + if (!hasChanged()) + { + return TRUE; + } + else + { + // Bring up view-modal dialog: Save changes? Yes, No, Cancel + LLNotificationsUtil::add("ParticleSaveChanges", LLSD(), LLSD(), boost::bind(&ALFloaterParticleEditor::handleSaveDialog, this, _1, _2)); + return FALSE; + } +} + +bool ALFloaterParticleEditor::handleSaveDialog(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + switch (option) + { + case 0: // "Yes" + // close after saving + mCloseAfterSave = true; + injectScript(); + break; + + case 1: // "No" + mChanged = false; + closeFloater(); + break; + + case 2: // "Cancel" + default: + break; + } + return false; +} + +void ALFloaterParticleEditor::clearParticles() +{ + if (!mObject) + return; + + LL_DEBUGS("ParticleEditor") << "clearing particles from " << mObject->getID() << LL_ENDL; + + LLViewerPartSim::getInstance()->clearParticlesByOwnerID(mObject->getID()); +} + +void ALFloaterParticleEditor::updateParticles() +{ + if (!mObject) + return; + + clearParticles(); + LLPointer<LLViewerPartSourceScript> pss = LLViewerPartSourceScript::createPSS(mObject, mParticles); + + pss->setOwnerUUID(mObject->getID()); + pss->setImage(mTexture); + + LLViewerPartSim::getInstance()->addPartSource(pss); +} + +void ALFloaterParticleEditor::setObject(LLViewerObject* objectp) +{ + if (objectp) + { + mObject = objectp; + + LL_DEBUGS("ParticleEditor") << "adding particles to " << mObject->getID() << LL_ENDL; + + updateParticles(); + } +} + +void ALFloaterParticleEditor::onParameterChange() +{ + mChanged = true; + mParticles.mPattern = sParticlePatterns.at(mPatternTypeCombo->getSelectedValue()).flag; + mParticles.mPartImageID = mTexturePicker->getImageAssetID(); + + // remember the selected texture here to give updateParticles() a UUID to work with + mTexture = LLViewerTextureManager::getFetchedTexture(mTexturePicker->getImageAssetID()); + + if (mTexture->getID() == IMG_DEFAULT || mTexture->getID().isNull()) + { + mTexture = mDefaultParticleTexture; + } + + // limit burst rate to 0.01 to avoid internal freeze, script still gets the real value + mParticles.mBurstRate = llmax<float>(0.01f, mBurstRateCtrl->getValue().asReal()); + mParticles.mBurstPartCount = mBurstCountCtrl->getValue().asInteger(); + mParticles.mBurstRadius = mBurstRadiusCtrl->getValue().asReal(); + mParticles.mInnerAngle = mAngleBeginCtrl->getValue().asReal(); + mParticles.mOuterAngle = mAngleEndCtrl->getValue().asReal(); + mParticles.mBurstSpeedMin = mBurstSpeedMinCtrl->getValue().asReal(); + mParticles.mBurstSpeedMax = mBurstSpeedMaxCtrl->getValue().asReal(); + mParticles.mPartData.setStartAlpha(mStartAlphaCtrl->getValue().asReal()); + mParticles.mPartData.setEndAlpha(mEndAlphaCtrl->getValue().asReal()); + mParticles.mPartData.setStartScale(mScaleStartXCtrl->getValue().asReal(), + mScaleStartYCtrl->getValue().asReal()); + mParticles.mPartData.setEndScale(mScaleEndXCtrl->getValue().asReal(), + mScaleEndYCtrl->getValue().asReal()); + mParticles.mMaxAge = mSourceMaxAgeCtrl->getValue().asReal(); + mParticles.mPartData.setMaxAge(mParticlesMaxAgeCtrl->getValue().asReal()); + + mParticles.mPartData.mStartGlow = mStartGlowCtrl->getValue().asReal(); + mParticles.mPartData.mEndGlow = mEndGlowCtrl->getValue().asReal(); + + mParticles.mPartData.mBlendFuncSource = sParticleBlends.at(mBlendFuncSrcCombo->getSelectedValue()).flag; + mParticles.mPartData.mBlendFuncDest = sParticleBlends.at(mBlendFuncDestCombo->getSelectedValue()).flag; + + U32 flags = 0; + if (mBounceCheckBox->getValue().asBoolean()) flags |= LLPartData::LL_PART_BOUNCE_MASK; + if (mEmissiveCheckBox->getValue().asBoolean()) flags |= LLPartData::LL_PART_EMISSIVE_MASK; + if (mFollowSourceCheckBox->getValue().asBoolean()) flags |= LLPartData::LL_PART_FOLLOW_SRC_MASK; + if (mFollowVelocityCheckBox->getValue().asBoolean()) flags |= LLPartData::LL_PART_FOLLOW_VELOCITY_MASK; + if (mInterpolateColorCheckBox->getValue().asBoolean()) flags |= LLPartData::LL_PART_INTERP_COLOR_MASK; + if (mInterpolateScaleCheckBox->getValue().asBoolean()) flags |= LLPartData::LL_PART_INTERP_SCALE_MASK; + if (mTargetPositionCheckBox->getValue().asBoolean()) flags |= LLPartData::LL_PART_TARGET_POS_MASK; + if (mTargetLinearCheckBox->getValue().asBoolean()) flags |= LLPartData::LL_PART_TARGET_LINEAR_MASK; + if (mWindCheckBox->getValue().asBoolean()) flags |= LLPartData::LL_PART_WIND_MASK; + if (mRibbonCheckBox->getValue().asBoolean()) flags |= LLPartData::LL_PART_RIBBON_MASK; + mParticles.mPartData.setFlags(flags); + mParticles.setUseNewAngle(); + + mParticles.mTargetUUID = mTargetKeyInput->getValue().asUUID(); + + mParticles.mPartAccel = LLVector3(mAcellerationXCtrl->getValue().asReal(), + mAcellerationYCtrl->getValue().asReal(), + mAcellerationZCtrl->getValue().asReal()); + mParticles.mAngularVelocity = LLVector3(mOmegaXCtrl->getValue().asReal(), + mOmegaYCtrl->getValue().asReal(), + mOmegaZCtrl->getValue().asReal()); + + LLColor4 color = mStartColorSelector->get(); + mParticles.mPartData.setStartColor(LLVector3(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE])); + color = mEndColorSelector->get(); + mParticles.mPartData.setEndColor(LLVector3(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE])); + + updateUI(); + updateParticles(); +} + +void ALFloaterParticleEditor::updateUI() +{ + U8 pattern = sParticlePatterns.at(mPatternTypeCombo->getValue()).flag; + BOOL drop_pattern = (pattern == LLPartSysData::LL_PART_SRC_PATTERN_DROP); + BOOL explode_pattern = (pattern == LLPartSysData::LL_PART_SRC_PATTERN_EXPLODE); + BOOL target_linear = mTargetLinearCheckBox->getValue(); + BOOL interpolate_color = mInterpolateColorCheckBox->getValue(); + BOOL interpolate_scale = mInterpolateScaleCheckBox->getValue(); + BOOL target_enabled = target_linear | (mTargetPositionCheckBox->getValue().asBoolean() ? TRUE : FALSE); + + mBurstRadiusCtrl->setEnabled(!(target_linear | (mFollowSourceCheckBox->getValue().asBoolean() ? TRUE : FALSE) | drop_pattern)); + mBurstSpeedMinCtrl->setEnabled(!(target_linear | drop_pattern)); + mBurstSpeedMaxCtrl->setEnabled(!(target_linear | drop_pattern)); + + // disabling a color swatch does nothing visually, so we also set alpha + LLColor4 end_color = mEndColorSelector->get(); + end_color.setAlpha(interpolate_color ? 1.0f : 0.0f); + + mEndAlphaCtrl->setEnabled(interpolate_color); + mEndColorSelector->set(end_color); + mEndColorSelector->setEnabled(interpolate_color); + + mScaleEndXCtrl->setEnabled(interpolate_scale); + mScaleEndYCtrl->setEnabled(interpolate_scale); + + mTargetPositionCheckBox->setEnabled(!target_linear); + mTargetKeyInput->setEnabled(target_enabled); + mPickTargetButton->setEnabled(target_enabled); + mClearTargetButton->setEnabled(target_enabled); + + mAcellerationXCtrl->setEnabled(!target_linear); + mAcellerationYCtrl->setEnabled(!target_linear); + mAcellerationZCtrl->setEnabled(!target_linear); + + mOmegaXCtrl->setEnabled(!target_linear); + mOmegaYCtrl->setEnabled(!target_linear); + mOmegaZCtrl->setEnabled(!target_linear); + + mAngleBeginCtrl->setEnabled(!(explode_pattern | drop_pattern)); + mAngleEndCtrl->setEnabled(!(explode_pattern | drop_pattern)); +} + +void ALFloaterParticleEditor::onClickClearTarget() +{ + mTargetKeyInput->clear(); + onParameterChange(); +} + +void ALFloaterParticleEditor::onClickTargetPicker() +{ + mPickTargetButton->setToggleState(TRUE); + mPickTargetButton->setEnabled(FALSE); + LLToolObjPicker::getInstance()->setExitCallback(onTargetPicked, this); + LLToolMgr::getInstance()->setTransientTool(LLToolObjPicker::getInstance()); +} + +// static +void ALFloaterParticleEditor::onTargetPicked(void* userdata) +{ + ALFloaterParticleEditor* self = static_cast<ALFloaterParticleEditor*>(userdata); + + const LLUUID picked = LLToolObjPicker::getInstance()->getObjectID(); + + LLToolMgr::getInstance()->clearTransientTool(); + + self->mPickTargetButton->setEnabled(TRUE); + self->mPickTargetButton->setToggleState(FALSE); + + if (picked.notNull()) + { + self->mTargetKeyInput->setValue(picked.asString()); + self->onParameterChange(); + } +} + +/* static */ +std::string ALFloaterParticleEditor::lslVector(const F32 x, const F32 y, const F32 z) +{ + return llformat("<%f,%f,%f>", x, y, z); +} + +/* static */ +std::string ALFloaterParticleEditor::lslColor(const LLColor4& color) +{ + return lslVector(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE]); +} + +std::string ALFloaterParticleEditor::createScript() +{ + std::string script( +"\ +default\n\ +{\n\ + state_entry()\n\ + {\n\ + llParticleSystem(\n\ + [\n\ + PSYS_SRC_PATTERN,[PATTERN],\n\ + PSYS_SRC_BURST_RADIUS,[BURST_RADIUS],\n\ + PSYS_SRC_ANGLE_BEGIN,[ANGLE_BEGIN],\n\ + PSYS_SRC_ANGLE_END,[ANGLE_END],\n\ + PSYS_SRC_TARGET_KEY,[TARGET_KEY],\n\ + PSYS_PART_START_COLOR,[START_COLOR],\n\ + PSYS_PART_END_COLOR,[END_COLOR],\n\ + PSYS_PART_START_ALPHA,[START_ALPHA],\n\ + PSYS_PART_END_ALPHA,[END_ALPHA],\n\ + PSYS_PART_START_GLOW,[START_GLOW],\n\ + PSYS_PART_END_GLOW,[END_GLOW],\n\ + PSYS_PART_BLEND_FUNC_SOURCE,[BLEND_FUNC_SOURCE],\n\ + PSYS_PART_BLEND_FUNC_DEST,[BLEND_FUNC_DEST],\n\ + PSYS_PART_START_SCALE,[START_SCALE],\n\ + PSYS_PART_END_SCALE,[END_SCALE],\n\ + PSYS_SRC_TEXTURE,\"[TEXTURE]\",\n\ + PSYS_SRC_MAX_AGE,[SOURCE_MAX_AGE],\n\ + PSYS_PART_MAX_AGE,[PART_MAX_AGE],\n\ + PSYS_SRC_BURST_RATE,[BURST_RATE],\n\ + PSYS_SRC_BURST_PART_COUNT,[BURST_COUNT],\n\ + PSYS_SRC_ACCEL,[ACCELERATION],\n\ + PSYS_SRC_OMEGA,[OMEGA],\n\ + PSYS_SRC_BURST_SPEED_MIN,[BURST_SPEED_MIN],\n\ + PSYS_SRC_BURST_SPEED_MAX,[BURST_SPEED_MAX],\n\ + PSYS_PART_FLAGS,\n\ + 0[FLAGS]\n\ + ]);\n\ + }\n\ +}\n"); + + const LLUUID target_key = mTargetKeyInput->getValue().asUUID(); + std::string key_string = "llGetKey()"; + + if (target_key.notNull() && target_key != mObject->getID()) + { + key_string = "(key) \"" + target_key.asString() + "\""; + } + + LLUUID texture_key = mTexture->getID(); + std::string texture_string; + if (texture_key.notNull() && texture_key != IMG_DEFAULT && texture_key != mDefaultParticleTexture->getID()) + { + texture_string = texture_key.asString(); + } + + LLStringUtil::replaceString(script, "[PATTERN]", sParticlePatterns.at(mPatternTypeCombo->getValue()).script_const); + LLStringUtil::replaceString(script, "[BURST_RADIUS]", mBurstRadiusCtrl->getValue().asString()); + LLStringUtil::replaceString(script, "[ANGLE_BEGIN]", mAngleBeginCtrl->getValue().asString()); + LLStringUtil::replaceString(script, "[ANGLE_END]", mAngleEndCtrl->getValue().asString()); + LLStringUtil::replaceString(script, "[TARGET_KEY]", key_string); + LLStringUtil::replaceString(script, "[START_COLOR]", lslColor(mStartColorSelector->get())); + LLStringUtil::replaceString(script, "[END_COLOR]", lslColor(mEndColorSelector->get())); + LLStringUtil::replaceString(script, "[START_ALPHA]", mStartAlphaCtrl->getValue().asString()); + LLStringUtil::replaceString(script, "[END_ALPHA]", mEndAlphaCtrl->getValue().asString()); + LLStringUtil::replaceString(script, "[START_GLOW]", mStartGlowCtrl->getValue().asString()); + LLStringUtil::replaceString(script, "[END_GLOW]", mEndGlowCtrl->getValue().asString()); + LLStringUtil::replaceString(script, "[START_SCALE]", lslVector(mScaleStartXCtrl->getValue().asReal(), + mScaleStartYCtrl->getValue().asReal(), + 0.0f)); + LLStringUtil::replaceString(script, "[END_SCALE]", lslVector(mScaleEndXCtrl->getValue().asReal(), + mScaleEndYCtrl->getValue().asReal(), + 0.0f)); + LLStringUtil::replaceString(script, "[TEXTURE]", texture_string); + LLStringUtil::replaceString(script, "[SOURCE_MAX_AGE]", mSourceMaxAgeCtrl->getValue().asString()); + LLStringUtil::replaceString(script, "[PART_MAX_AGE]", mParticlesMaxAgeCtrl->getValue().asString()); + LLStringUtil::replaceString(script, "[BURST_RATE]", mBurstRateCtrl->getValue().asString()); + LLStringUtil::replaceString(script, "[BURST_COUNT]", mBurstCountCtrl->getValue()); + LLStringUtil::replaceString(script, "[ACCELERATION]", lslVector(mAcellerationXCtrl->getValue().asReal(), + mAcellerationYCtrl->getValue().asReal(), + mAcellerationZCtrl->getValue().asReal())); + LLStringUtil::replaceString(script, "[OMEGA]", lslVector(mOmegaXCtrl->getValue().asReal(), + mOmegaYCtrl->getValue().asReal(), + mOmegaZCtrl->getValue().asReal())); + LLStringUtil::replaceString(script, "[BURST_SPEED_MIN]", mBurstSpeedMinCtrl->getValue().asString()); + LLStringUtil::replaceString(script, "[BURST_SPEED_MAX]", mBurstSpeedMaxCtrl->getValue().asString()); + LLStringUtil::replaceString(script, "[BLEND_FUNC_SOURCE]", sParticleBlends.at(mBlendFuncSrcCombo->getValue().asString()).script_const); + LLStringUtil::replaceString(script, "[BLEND_FUNC_DEST]", sParticleBlends.at(mBlendFuncDestCombo->getValue().asString()).script_const); + + const std::string delimiter = " |\n "; + std::string flags_string = ""; + + if (mBounceCheckBox->getValue()) + flags_string += delimiter + "PSYS_PART_BOUNCE_MASK"; + if (mEmissiveCheckBox->getValue()) + flags_string += delimiter + "PSYS_PART_EMISSIVE_MASK"; + if (mFollowSourceCheckBox->getValue()) + flags_string += delimiter + "PSYS_PART_FOLLOW_SRC_MASK"; + if (mFollowVelocityCheckBox->getValue()) + flags_string += delimiter + "PSYS_PART_FOLLOW_VELOCITY_MASK"; + if (mInterpolateColorCheckBox->getValue()) + flags_string += delimiter + "PSYS_PART_INTERP_COLOR_MASK"; + if (mInterpolateScaleCheckBox->getValue()) + flags_string += delimiter + "PSYS_PART_INTERP_SCALE_MASK"; + if (mTargetLinearCheckBox->getValue()) + flags_string += delimiter + "PSYS_PART_TARGET_LINEAR_MASK"; + if (mTargetPositionCheckBox->getValue()) + flags_string += delimiter + "PSYS_PART_TARGET_POS_MASK"; + if (mWindCheckBox->getValue()) + flags_string += delimiter + "PSYS_PART_WIND_MASK"; + if (mRibbonCheckBox->getValue()) + flags_string += delimiter + "PSYS_PART_RIBBON_MASK"; + + LLStringUtil::replaceString(script, "[FLAGS]", flags_string); + LL_DEBUGS("ParticleEditor") << "\n" << script << LL_ENDL; + + return script; +} + +void ALFloaterParticleEditor::onClickCopy() +{ + mChanged = false; + const std::string script = createScript(); + if (!script.empty()) + { + getWindow()->copyTextToClipboard(utf8str_to_wstring(script)); + LLNotificationsUtil::add("ParticleScriptCopiedToClipboard"); + } +} + +void ALFloaterParticleEditor::injectScript() +{ + mChanged = false; + const LLUUID categoryID = gInventory.findCategoryUUIDForType(LLFolderType::FT_LSL_TEXT); + + // if no valid folder found bail out and complain + if (categoryID.isNull()) + { + LLNotificationsUtil::add("ParticleScriptFindFolderFailed"); + return; + } + + // setup permissions + LLPermissions perm; + perm.init(gAgentID, gAgentID, LLUUID::null, LLUUID::null); + perm.initMasks(PERM_ALL, PERM_ALL, PERM_ALL, PERM_ALL, PERM_ALL); + + // create new script inventory item and wait for it to come back (callback) + LLPointer<LLInventoryCallback> callback = new LLParticleScriptCreationCallback(this); + create_inventory_item( + gAgentID, + gAgentSessionID, + categoryID, + LLTransactionID::tnull, + PARTICLE_SCRIPT_NAME, + LLStringUtil::null, + LLAssetType::AT_LSL_TEXT, + LLInventoryType::IT_LSL, + NO_INV_SUBTYPE, + perm.getMaskNextOwner(), + callback); + + setCanClose(FALSE); +} + +void ALFloaterParticleEditor::callbackReturned(const LLUUID& inventoryItemID) +{ + setCanClose(TRUE); + + if (inventoryItemID.isNull()) + { + LLNotificationsUtil::add("ParticleScriptCreationFailed"); + return; + } + + mParticleScriptInventoryItem = gInventory.getItem(inventoryItemID); + if (!mParticleScriptInventoryItem) + { + LLNotificationsUtil::add("ParticleScriptNotFound"); + return; + } + gInventory.updateItem(mParticleScriptInventoryItem); + gInventory.notifyObservers(); + + //caps import + const std::string url = gAgent.getRegionCapability("UpdateScriptAgent"); + + if (url.empty()) + { + LLNotificationsUtil::add("ParticleScriptFailed"); + return; + } + + + const std::string script = createScript(); + + LLBufferedAssetUploadInfo::taskUploadFinish_f proc = + boost::bind(&ALFloaterParticleEditor::finishUpload, _1, _2, _3, _4, true, mObject->getID()); + LLResourceUploadInfo::ptr_t uploadInfo(new LLScriptAssetUpload(mObject->getID(), inventoryItemID, + LLScriptAssetUpload::MONO, true, LLUUID::null, script, proc)); + LLViewerAssetUpload::EnqueueInventoryUpload(url, uploadInfo); + + if (mCloseAfterSave) closeFloater(); +} + +// ---------------------------------- Callbacks ---------------------------------- + +ALFloaterParticleEditor::LLParticleScriptCreationCallback:: +LLParticleScriptCreationCallback(ALFloaterParticleEditor* editor) + : mEditor(editor) +{ +} + +void ALFloaterParticleEditor::LLParticleScriptCreationCallback::fire(const LLUUID& inventoryItem) +{ + if (!gDisconnected && !LLAppViewer::instance()->quitRequested() && mEditor) + { + mEditor->callbackReturned(inventoryItem); + } +} + +/* static */ +void ALFloaterParticleEditor::finishUpload(LLUUID itemId, LLUUID taskId, LLUUID newAssetId, + LLSD response, bool isRunning, LLUUID objectId) +{ + // make sure there's still an object rezzed + LLViewerObject* object = gObjectList.findObject(objectId); + if (!object || object->isDead()) + { + LL_WARNS("ParticleEditor") << "Failed to inject script in object: " << objectId.asString() << LL_ENDL; + return; + } + auto* script = gInventory.getItem(itemId); + object->saveScript(script, TRUE, FALSE); + + LLNotificationsUtil::add("ParticleScriptInjected"); +} diff --git a/indra/newview/alfloaterparticleeditor.h b/indra/newview/alfloaterparticleeditor.h new file mode 100644 index 00000000000..8c856959f55 --- /dev/null +++ b/indra/newview/alfloaterparticleeditor.h @@ -0,0 +1,150 @@ +/** + * @file alfloaterparticleeditor.h + * @brief Particle Editor + * + * Copyright (C) 2011, Zi Ree @ Second Life + * Copyright (C) 2015, Cinder Roxley <cinder@sdf.org> + * Copyright (C) 2020, Rye Mutt <rye@alchemyviewer.org> + * + * 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 + */ + +#ifndef AL_FLOATERPARTICLEEDITOR_H +#define AL_FLOATERPARTICLEEDITOR_H + +#include "llfloater.h" +#include "llpartdata.h" +#include "llviewerinventory.h" + +class LLButton; +class LLCheckBoxCtrl; +class LLColorSwatchCtrl; +class LLComboBox; +class LLTextureCtrl; +class LLUICtrl; +class LLViewerObject; +class LLViewerTexture; + +class ALFloaterParticleEditor final : public LLFloater +{ +public: + ALFloaterParticleEditor(const LLSD& key); + ~ALFloaterParticleEditor(); + + BOOL postBuild() override; + BOOL canClose() override; + + void setObject(LLViewerObject* objectp); + +private: + void clearParticles(); + void updateParticles(); + void updateUI(); + bool hasChanged() const { return mChanged; } + bool handleSaveDialog(const LLSD& notification, const LLSD& response); + + std::string createScript(); + + void onParameterChange(); + void onClickCopy(); + void injectScript(); + + void onClickClearTarget(); + void onClickTargetPicker(); + static void onTargetPicked(void* userdata); + + void callbackReturned(const LLUUID& inv_item); + + static std::string lslVector(F32 x, F32 y, F32 z); + static std::string lslColor(const LLColor4& color); + + bool mChanged; + bool mCloseAfterSave; + LLViewerObject* mObject; + LLViewerTexture* mTexture; + LLViewerInventoryItem* mParticleScriptInventoryItem; + + LLViewerTexture* mDefaultParticleTexture; + + LLPartSysData mParticles; + + LLComboBox* mPatternTypeCombo; + LLTextureCtrl* mTexturePicker; + + LLUICtrl* mBurstRateCtrl; + LLUICtrl* mBurstCountCtrl; + LLUICtrl* mBurstRadiusCtrl; + LLUICtrl* mAngleBeginCtrl; + LLUICtrl* mAngleEndCtrl; + LLUICtrl* mBurstSpeedMinCtrl; + LLUICtrl* mBurstSpeedMaxCtrl; + LLUICtrl* mStartAlphaCtrl; + LLUICtrl* mEndAlphaCtrl; + LLUICtrl* mScaleStartXCtrl; + LLUICtrl* mScaleStartYCtrl; + LLUICtrl* mScaleEndXCtrl; + LLUICtrl* mScaleEndYCtrl; + LLUICtrl* mSourceMaxAgeCtrl; + LLUICtrl* mParticlesMaxAgeCtrl; + LLUICtrl* mStartGlowCtrl; + LLUICtrl* mEndGlowCtrl; + + LLUICtrl* mAcellerationXCtrl; + LLUICtrl* mAcellerationYCtrl; + LLUICtrl* mAcellerationZCtrl; + + LLUICtrl* mOmegaXCtrl; + LLUICtrl* mOmegaYCtrl; + LLUICtrl* mOmegaZCtrl; + + LLComboBox* mBlendFuncSrcCombo; + LLComboBox* mBlendFuncDestCombo; + + LLCheckBoxCtrl* mBounceCheckBox; + LLCheckBoxCtrl* mEmissiveCheckBox; + LLCheckBoxCtrl* mFollowSourceCheckBox; + LLCheckBoxCtrl* mFollowVelocityCheckBox; + LLCheckBoxCtrl* mInterpolateColorCheckBox; + LLCheckBoxCtrl* mInterpolateScaleCheckBox; + LLCheckBoxCtrl* mTargetPositionCheckBox; + LLCheckBoxCtrl* mTargetLinearCheckBox; + LLCheckBoxCtrl* mWindCheckBox; + LLCheckBoxCtrl* mRibbonCheckBox; + + LLUICtrl* mTargetKeyInput; + LLButton* mClearTargetButton; + LLButton* mPickTargetButton; + LLButton* mCopyToLSLButton; + LLButton* mInjectScriptButton; + + LLColorSwatchCtrl* mStartColorSelector; + LLColorSwatchCtrl* mEndColorSelector; + + static void finishUpload(LLUUID itemId, LLUUID taskId, LLUUID newAssetId, LLSD response, bool isRunning, LLUUID objectId); + + class LLParticleScriptCreationCallback : public LLInventoryCallback + { + public: + LLParticleScriptCreationCallback(ALFloaterParticleEditor* editor); + void fire(const LLUUID& inventoryItem) override; + + protected: + ~LLParticleScriptCreationCallback() = default; + + ALFloaterParticleEditor* mEditor; + }; +}; + +#endif // LL_FLOATERPARTICLEEDITOR_H diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index 083ce0856e1..23bca9df10b 100644 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -30,6 +30,7 @@ #include "llfloaterreg.h" #include "llviewerfloaterreg.h" +#include "alfloaterparticleeditor.h" #include "llcommandhandler.h" #include "llcompilequeue.h" #include "llfasttimerview.h" @@ -381,6 +382,8 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("world_map", "floater_world_map.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterWorldMap>); // *NOTE: Please keep these alphabetized for easier merges + + LLFloaterReg::add("particle_editor", "floater_particle_editor.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<ALFloaterParticleEditor>); LLFloaterReg::registerControlVariables(); // Make sure visibility and rect controls get preserved when saving } diff --git a/indra/newview/skins/default/xui/en/floater_particle_editor.xml b/indra/newview/skins/default/xui/en/floater_particle_editor.xml new file mode 100644 index 00000000000..f97636029ed --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_particle_editor.xml @@ -0,0 +1,876 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<floater + can_resize="true" + height="357" + min_height="359" + min_width="480" + name="particle_editor_floater" + save_rect="true" + save_visibility="false" + single_instance="false" + title="PARTICLE EDITOR" + width="480"> + <layout_stack + follows="all" + animate="false" + top="0" + height="357" + left="0" + right="-1" + drag_handle_gap="0" + drag_handle_first_indent="1" + drag_handle_second_indent="1" + layout="topleft" + name="master_stack" + orientation="horizontal" + show_drag_handle="true"> + <layout_panel + bg_alpha_color="DkGray2" + bg_opaque_color="DkGray2" + background_visible="true" + background_opaque="true" + name="layout_master" + follows="all" + layout="topleft" + auto_resize="false" + user_resize="false" + height="357" + width="300"> + <accordion + left="0" + top="0" + single_expansion="false" + follows="all" + layout="topleft" + name="accordion_master" + height="357" + width="300"> + <accordion_tab + expanded="true" + layout="topleft" + height="152" + name="burst_tab" + title="Burst/Age" + fit_panel="true"> + <panel + follows="all" + height="152" + left="0" + name="burst_panel" + top="0" + right="-1"> + <slider + follows="left|top" + decimal_digits="2" + initial_value="0.3" + increment="0.1" + name="burst_rate" + label="Rate:" + label_width="50" + can_edit_text="true" + left="0" + width="275" + min_val="0" + height="16" + max_val="1000" + top="0"> + <slider.commit_callback + function="Particle.Edit" /> + </slider> + <slider + follows="left|top" + decimal_digits="0" + initial_value="5" + increment="1" + name="burst_count" + label="Count:" + label_width="50" + can_edit_text="true" + left="0" + width="275" + min_val="1" + height="16" + max_val="1000" + top_pad="2"> + <slider.commit_callback + function="Particle.Edit" /> + </slider> + <slider + follows="left|top" + decimal_digits="3" + initial_value="0" + increment="0.001" + name="burst_radius" + label="Radius:" + label_width="50" + can_edit_text="true" + left="0" + width="275" + min_val="0" + max_val="50" + height="16" + top_pad="2"> + <slider.commit_callback + function="Particle.Edit" /> + </slider> + <slider + follows="left|top" + decimal_digits="3" + initial_value="0.5" + increment="0.001" + name="burst_speed_max" + label="Speed Max:" + label_width="70" + can_edit_text="true" + left="0" + width="275" + min_val="0" + max_val="100" + height="16" + top_pad="2"> + <slider.commit_callback + function="Particle.Edit" /> + </slider> + <slider + follows="left|top" + decimal_digits="3" + initial_value="0.5" + increment="0.001" + name="burst_speed_min" + label="Speed Min:" + label_width="70" + can_edit_text="true" + left="0" + width="275" + min_val="0" + max_val="100" + height="16" + top_pad="2"> + <slider.commit_callback + function="Particle.Edit" /> + </slider> + <slider + follows="left|top" + decimal_digits="3" + initial_value="0" + increment="0.001" + name="source_max_age" + label="Source Max Age:" + label_width="100" + can_edit_text="true" + left="0" + width="275" + min_val="0" + max_val="1000" + height="16" + top_pad="2"> + <slider.commit_callback + function="Particle.Edit" /> + </slider> + <slider + follows="left|top" + decimal_digits="3" + initial_value="5" + increment="0.001" + name="particle_max_age" + label="Particle Max Age:" + label_width="100" + can_edit_text="true" + left="0" + width="275" + min_val="0" + max_val="30" + height="16" + top_pad="2"> + <slider.commit_callback + function="Particle.Edit" /> + </slider> + </panel> + </accordion_tab> + <accordion_tab + expanded="false" + layout="topleft" + height="140" + name="angle_tab" + title="Scale/Angle" + fit_panel="true"> + <panel + follows="all" + height="140" + left="0" + name="angle_panel" + top="0" + width="313"> + <slider + follows="left|top" + decimal_digits="3" + initial_value="0.5" + increment="0.001" + name="scale_start_x" + label="Scale Start X:" + label_width="72" + can_edit_text="true" + left="0" + width="275" + min_val="0.03125" + max_val="4" + height="16" + top_pad="2"> + <slider.commit_callback + function="Particle.Edit" /> + </slider> + <slider + follows="left|top" + decimal_digits="3" + initial_value="0.5" + increment="0.001" + name="scale_start_y" + label="Scale Start Y:" + label_width="72" + can_edit_text="true" + left="0" + width="275" + min_val="0.03125" + max_val="4" + height="16" + top_pad="2"> + <slider.commit_callback + function="Particle.Edit" /> + </slider> + <slider + follows="left|top" + decimal_digits="3" + initial_value="0.5" + increment="0.001" + name="scale_end_x" + label="Scale End Y:" + label_width="70" + can_edit_text="true" + left="0" + width="275" + min_val="0.03125" + max_val="4" + height="16" + top_pad="2"> + <slider.commit_callback + function="Particle.Edit" /> + </slider> + <slider + follows="left|top" + decimal_digits="3" + initial_value="0.5" + increment="0.001" + name="scale_end_y" + label="Scale End Y:" + label_width="70" + can_edit_text="true" + left="0" + width="275" + min_val="0.03125" + max_val="4" + height="16" + top_pad="2"> + <slider.commit_callback + function="Particle.Edit" /> + </slider> + <slider + follows="left|top" + decimal_digits="3" + initial_value="0" + increment="0.001" + name="angle_begin" + label="Angle Begin:" + label_width="70" + can_edit_text="true" + left="0" + width="275" + min_val="-3.14" + max_val="3.14" + height="16" + top_pad="2"> + <slider.commit_callback + function="Particle.Edit" /> + </slider> + <slider + follows="left|top" + decimal_digits="3" + initial_value="0" + increment="0.001" + name="angle_end" + label="Angle End:" + label_width="70" + can_edit_text="true" + left="0" + width="275" + min_val="-3.14" + max_val="3.14" + height="16" + top_pad="2"> + <slider.commit_callback + function="Particle.Edit" /> + </slider> + </panel> + </accordion_tab> + <accordion_tab + expanded="false" + layout="topleft" + height="100" + name="alpha_tab" + title="Alpha/Glow" + fit_panel="true"> + <panel + follows="all" + height="100" + left="0" + name="alpha_panel" + top="0" + width="313"> + <slider + follows="left|top" + decimal_digits="3" + initial_value="1" + increment="0.001" + name="start_alpha" + label="Start Alpha:" + label_width="70" + can_edit_text="true" + left="0" + width="275" + min_val="0" + max_val="1" + height="16" + top_pad="2"> + <slider.commit_callback + function="Particle.Edit" /> + </slider> + <slider + follows="left|top" + decimal_digits="3" + initial_value="1" + increment="0.001" + name="end_alpha" + label="End Alpha:" + label_width="70" + can_edit_text="true" + left="0" + width="275" + min_val="0" + max_val="1" + height="16" + top_pad="2"> + <slider.commit_callback + function="Particle.Edit" /> + </slider> + <slider + follows="left|top" + decimal_digits="3" + initial_value="0" + increment="0.001" + name="start_glow" + label="Start Glow:" + label_width="70" + can_edit_text="true" + left="0" + width="275" + min_val="0" + max_val="1" + height="16" + top_pad="2"> + <slider.commit_callback + function="Particle.Edit" /> + </slider> + <slider + follows="left|top" + decimal_digits="3" + initial_value="0" + increment="0.001" + name="end_glow" + label="End Glow:" + label_width="70" + can_edit_text="true" + left="0" + width="275" + min_val="0" + max_val="1" + height="16" + top_pad="2"> + <slider.commit_callback + function="Particle.Edit" /> + </slider> + </panel> + </accordion_tab> + <accordion_tab + expanded="false" + layout="topleft" + height="100" + name="omega_tab" + title="Acelleration/Omega" + fit_panel="true"> + <panel + follows="all" + height="100" + left="0" + name="omega_panel" + top="0" + width="313"> + <text + name="Acceleration_Label" + follows="left|top" + height="16" + left="0" + top="0" + width="76"> + Acceleration: + </text> + <slider + follows="left|top" + decimal_digits="2" + initial_value="0" + increment="0.01" + name="acceleration_x" + label="X:" + label_width="10" + can_edit_text="true" + left="0" + width="90" + min_val="-100" + max_val="100" + height="16" + top_pad="2"> + <slider.commit_callback + function="Particle.Edit" /> + </slider> + <slider + follows="left|top" + decimal_digits="2" + initial_value="0" + increment="0.01" + name="acceleration_y" + can_edit_text="true" + label="Y:" + label_width="10" + left_pad="2" + width="90" + min_val="-100" + max_val="100" + height="16" + top_delta="0"> + <slider.commit_callback + function="Particle.Edit" /> + </slider> + <slider + follows="left|top" + decimal_digits="2" + initial_value="0" + increment="0.01" + name="acceleration_z" + can_edit_text="true" + label="Z:" + label_width="10" + left_pad="2" + width="90" + min_val="-100" + max_val="100" + height="16" + top_delta="0"> + <slider.commit_callback + function="Particle.Edit" /> + </slider> + <text + name="Omega_Label" + follows="left|top" + height="16" + left="0" + top_pad="4" + width="76"> + Omega: + </text> + <slider + follows="left|top" + decimal_digits="2" + initial_value="0" + increment="0.01" + name="omega_x" + label="X:" + label_width="10" + can_edit_text="true" + left="0" + width="90" + min_val="-100" + max_val="100" + height="16" + top_pad="2"> + <slider.commit_callback + function="Particle.Edit" /> + </slider> + <slider + follows="left|top" + decimal_digits="2" + initial_value="0" + increment="0.01" + name="omega_y" + label="Y:" + label_width="10" + can_edit_text="true" + left_pad="2" + width="90" + min_val="-100" + max_val="100" + height="16" + top_delta="0"> + <slider.commit_callback + function="Particle.Edit" /> + </slider> + <slider + follows="left|top" + decimal_digits="2" + initial_value="0" + increment="0.01" + name="omega_z" + label="Z:" + label_width="10" + can_edit_text="true" + left_pad="2" + width="90" + min_val="-100" + max_val="100" + height="16" + top_delta="0"> + <slider.commit_callback + function="Particle.Edit" /> + </slider> + </panel> + </accordion_tab> + <accordion_tab + expanded="true" + layout="topleft" + height="100" + name="color_tab" + title="Color/Texture" + fit_panel="true"> + <panel + follows="all" + height="100" + left="0" + name="color_panel" + top="0" + width="313"> + <texture_picker + follows="left|top" + height="70" + width="62" + left="0" + can_apply_immediately="true" + name="texture_picker" + label="Texture" + top="4"> + <texture_picker.commit_callback + function="Particle.Edit" /> + </texture_picker> + <color_swatch + follows="left|top" + height="70" + width="62" + label="Start Color" + left_pad="5" + can_apply_immediately="true" + name="start_color_selector" + top_delta="0"> + <color_swatch.commit_callback + function="Particle.Edit" /> + </color_swatch> + <color_swatch + follows="left|top" + height="70" + width="62" + label="End Color" + left_pad="5" + name="end_color_selector" + can_apply_immediately="true" + top_delta="0"> + <color_swatch.commit_callback + function="Particle.Edit" /> + </color_swatch> + <combo_box + name="blend_func_src_combo" + follows="left|top" + height="22" + left_pad="4" + top_delta="0" + width="74"> + <combo_box.commit_callback + function="Particle.Edit" /> + <combo_box.item + label="Blend One" + name="blend_one" + value="blend_one" /> + <combo_box.item + label="Blend Zero" + name="blend_zero" + value="blend_zero" /> + <combo_box.item + label="Blend Dest Color" + name="blend_dest_color" + value="blend_dest_color" /> + <combo_box.item + label="Blend Src Color" + name="blend_src_color" + value="blend_src_color" /> + <combo_box.item + label="Blend 1 - Dest Color" + name="blend_one_minus_dest_color" + value="blend_one_minus_dest_color" /> + <combo_box.item + label="Blend 1 - Src Color" + name="blend_one_minus_src_color" + value="blend_one_minus_src_color" /> + <combo_box.item + label="Blend Src Alpha" + name="blend_src_alpha" + value="blend_src_alpha" /> + <combo_box.item + label="Blend 1 - Src Alpha" + name="blend_one_minus_src_alpha" + value="blend_one_minus_src_alpha" /> + </combo_box> + <combo_box + name="blend_func_dest_combo" + follows="left|top" + height="22" + left_delta="0" + top_pad="4" + width="74"> + <combo_box.commit_callback + function="Particle.Edit" /> + <combo_box.item + label="Blend One" + name="blend_one" + value="blend_one" /> + <combo_box.item + label="Blend Zero" + name="blend_zero" + value="blend_zero" /> + <combo_box.item + label="Blend Dest Color" + name="blend_dest_color" + value="blend_dest_color" /> + <combo_box.item + label="Blend Src Color" + name="blend_src_color" + value="blend_src_color" /> + <combo_box.item + label="Blend 1 - Dest Color" + name="blend_one_minus_dest_color" + value="blend_one_minus_dest_color" /> + <combo_box.item + label="Blend 1 - Src Color" + name="blend_one_minus_src_color" + value="blend_one_minus_src_color" /> + <combo_box.item + label="Blend Src Alpha" + name="blend_src_alpha" + value="blend_src_alpha" /> + <combo_box.item + label="Blend 1 - Src Alpha" + name="blend_one_minus_src_alpha" + value="blend_one_minus_src_alpha" /> + </combo_box> + </panel> + </accordion_tab> + </accordion> + </layout_panel> + <layout_panel + layout="topleft" + follows="all" + height="258" + left_pad="0" + name="checkbox_panel" + width="120"> + <text + name="Pattern_Label" + follows="left|top" + height="16" + left="0" + top="0" + width="100"> + Pattern: + </text> + <combo_box + follows="left|right|top" + name="pattern_type_combo" + top_pad="2" + left_delta="0" + right="-2"> + <combo_box.commit_callback + function="Particle.Edit" /> + <combo_box.item + label="Explode" + name="explode" + value="explode" /> + <combo_box.item + label="Angle" + name="angle" + value="angle" /> + <combo_box.item + label="Angle Cone" + name="angle_cone" + value="angle_cone" /> + <combo_box.item + label="Angle Cone Empty" + name="angle_cone_empty" + value="angle_cone_empty" /> + <combo_box.item + label="Drop" + name="drop" + value="drop" /> + </combo_box> + <check_box + follows="left|top" + height="16" + name="bounce_checkbox" + label="Bounce" + left="0" + top_pad="4" + width="80"> + <check_box.commit_callback + function="Particle.Edit" /> + </check_box> + <check_box + follows="left|top" + height="16" + name="emissive_checkbox" + label="Emissive" + top_pad="4" + left="0" + width="80"> + <check_box.commit_callback + function="Particle.Edit" /> + </check_box> + <check_box + follows="left|top" + height="16" + name="wind_checkbox" + label="Wind" + top_pad="4" + left="0" + width="80"> + <check_box.commit_callback + function="Particle.Edit" /> + </check_box> + <check_box + follows="left|top" + height="16" + name="ribbon_checkbox" + label="Ribbon" + top_pad="4" + left="0" + width="80"> + <check_box.commit_callback + function="Particle.Edit" /> + </check_box> + <check_box + follows="left|top" + height="16" + name="follow_source_checkbox" + label="Follow Source" + top_pad="4" + left="0" + width="120"> + <check_box.commit_callback + function="Particle.Edit" /> + </check_box> + <check_box + follows="left|top" + height="16" + name="follow_velocity_checkbox" + label="Follow Velocity" + top_pad="4" + width="120"> + <check_box.commit_callback + function="Particle.Edit" /> + </check_box> + <check_box + follows="left|top" + height="16" + name="interpolate_color_checkbox" + label="Interpolate Color" + top_pad="4" + width="120"> + <check_box.commit_callback + function="Particle.Edit" /> + </check_box> + <check_box + follows="left|top" + height="16" + name="interpolate_scale_checkbox" + label="Interpolate Scale" + top_pad="4" + width="120"> + <check_box.commit_callback + function="Particle.Edit" /> + </check_box> + <check_box + follows="left|top" + height="16" + name="target_position_checkbox" + label="Target Position" + top_pad="4" + width="120"> + <check_box.commit_callback + function="Particle.Edit" /> + </check_box> + <check_box + follows="left|top" + height="16" + name="target_linear_checkbox" + label="Target Linear" + top_pad="4" + width="120"> + <check_box.commit_callback + function="Particle.Edit" /> + </check_box> + <line_editor + follows="left|right|top" + left="0" + name="target_key_input" + height="20" + right="-48"> + <line_editor.commit_callback + function="Particle.Edit" /> + </line_editor> + <button + follows="right|top" + image_overlay="StopReload_Over" + left_pad="4" + name="clear_target_button" + tool_tip="Clear target object or avatar" + width="20" + height="20" /> + <button + follows="right|top" + image_overlay="Inv_Object" + left_pad="2" + name="pick_target_button" + tool_tip="Click here to select the particle target object or avatar." + width="20" + height="20" /> + <button + follows="left|top" + height="24" + name="copy_button" + label="Copy" + tool_tip="Copies the particle system's parameter as LSL script to the clipboard." + top_pad="7" + left="2" + width="85" /> + <button + follows="left|top" + height="24" + name="inject_button" + label="Inject" + tool_tip="Inject this particle system into the selected object." + left_pad="4" + width="85" /> + </layout_panel> + </layout_stack> +</floater> diff --git a/indra/newview/skins/default/xui/en/menu_object.xml b/indra/newview/skins/default/xui/en/menu_object.xml index ce34508303b..0a895faa573 100644 --- a/indra/newview/skins/default/xui/en/menu_object.xml +++ b/indra/newview/skins/default/xui/en/menu_object.xml @@ -29,6 +29,16 @@ <menu_item_call.on_enable function="EnableEdit"/> </menu_item_call> + <menu_item_call + label="Edit Particles" + name="Menu Object Edit Particles"> + <menu_item_call.on_click + function="Object.EditParticles" /> + <menu_item_call.on_enable + function="Object.EnableEditParticles" /> + <menu_item_call.on_visible + function="Object.EnableEditParticles" /> + </menu_item_call> <menu_item_call enabled="false" label="Open" diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index a8aaf26fec3..d8d94ffaf00 100644 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -1034,6 +1034,14 @@ function="World.EnvPreset" <menu_item_call.on_enable function="Tools.EnableTakeCopy" /> </menu_item_call> + <menu_item_call + label="Edit Particles" + name="Menu Object Edit Particles"> + <menu_item_call.on_click + function="Object.EditParticles" /> + <menu_item_call.on_enable + function="Object.EnableEditParticles" /> + </menu_item_call> <menu_item_call label="Save Back to Object Contents" name="Save Object Back to Object Contents"> diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index 1febca05611..9a3daf52651 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -11748,5 +11748,64 @@ Unable to load the track from [TRACK1] into [TRACK2]. Auto-accepted [ITEM] from [NAME] and placed in inventory. </notification> + <notification + icon="alertmodal.tga" + name="ParticleSaveChanges" + type="alertmodal"> +Inject with particle script before closing? + <tag>confirm</tag> + <usetemplate + canceltext="Cancel" + name="yesnocancelbuttons" + notext="Don't Inject" + yestext="Inject"/> + </notification> + + <notification + icon="alertmodal.tga" + name="ParticleScriptFindFolderFailed" + type="alertmodal"> +Could not find a folder for the new script in inventory. + </notification> + + <notification + icon="alertmodal.tga" + name="ParticleScriptCreationFailed" + type="alertmodal"> +Could not create new script for this particle system. + </notification> + + <notification + icon="alertmodal.tga" + name="ParticleScriptNotFound" + type="alertmodal"> +Could not find the newly created script for this particle system. + </notification> + + <notification + icon="notify.tga" + name="ParticleScriptInjected" + type="notify"> +Particle script was injected successfully. + </notification> + + <notification + icon="alertmodal.tga" + name="ParticleScriptFailed" + type="alertmodal"> +Failed to inject script into object. + </notification> + + <notification + icon="notify.tga" + name="ParticleScriptCopiedToClipboard" + type="notify"> +The LSL script to create this particle system has been copied to your clipboard. You can now paste it into a new script to use it. + <form name="form"> + <ignore name="ignore" + text="A particle script was copied to my clipboard"/> + </form> + </notification> + </notifications> -- GitLab