summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
authorAndrey Kleshchev <andreykproductengine@lindenlab.com>2024-07-01 17:10:13 +0300
committerAndrey Kleshchev <117672381+akleshchev@users.noreply.github.com>2024-07-01 20:05:31 +0300
commit279ec534dd999420544b672cf84841b86d1306ae (patch)
treed52fc55d3d82bd795c2bd46e3b052b3626c7ecd4 /indra
parente75595a1c193abb95075b27ab5641a3b28556c2e (diff)
viewer#1131 gltf model upload UI WIP #3
Diffstat (limited to 'indra')
-rw-r--r--indra/newview/llfloatergltfasseteditor.cpp269
-rw-r--r--indra/newview/llfloatergltfasseteditor.h38
-rw-r--r--indra/newview/llgltffolderitem.h2
-rw-r--r--indra/newview/skins/default/xui/en/floater_gltf_asset_editor.xml271
4 files changed, 566 insertions, 14 deletions
diff --git a/indra/newview/llfloatergltfasseteditor.cpp b/indra/newview/llfloatergltfasseteditor.cpp
index 62546c6ed9..ec9ccc1324 100644
--- a/indra/newview/llfloatergltfasseteditor.cpp
+++ b/indra/newview/llfloatergltfasseteditor.cpp
@@ -31,7 +31,9 @@
#include "gltf/asset.h"
#include "llcallbacklist.h"
+#include "llmenubutton.h"
#include "llselectmgr.h"
+#include "llspinctrl.h"
#include "llviewerobject.h"
const LLColor4U DEFAULT_WHITE(255, 255, 255);
@@ -43,16 +45,69 @@ LLFloaterGLTFAssetEditor::LLFloaterGLTFAssetEditor(const LLSD& key)
, mUIColor(LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE))
{
setTitle("GLTF Asset Editor (WIP)");
+
+ mCommitCallbackRegistrar.add("PanelObject.menuDoToSelected", [this](LLUICtrl* ctrl, const LLSD& data) { onMenuDoToSelected(data); });
+ mEnableCallbackRegistrar.add("PanelObject.menuEnable", [this](LLUICtrl* ctrl, const LLSD& data) { return onMenuEnableItem(data); });
}
LLFloaterGLTFAssetEditor::~LLFloaterGLTFAssetEditor()
{
gIdleCallbacks.deleteFunction(idle, this);
+
+ if (mScroller)
+ {
+ removeChild(mScroller);
+ delete mScroller;
+ mScroller = NULL;
+ }
}
bool LLFloaterGLTFAssetEditor::postBuild()
{
- mItemListPanel = getChild<LLPanel>("item_list_panel");
+ // Position
+ mMenuClipboardPos = getChild<LLMenuButton>("clipboard_pos_btn");
+ mCtrlPosX = getChild<LLSpinCtrl>("Pos X", true);
+ mCtrlPosX->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCommitTransform(); });
+ mCtrlPosY = getChild<LLSpinCtrl>("Pos Y", true);
+ mCtrlPosY->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCommitTransform(); });
+ mCtrlPosZ = getChild<LLSpinCtrl>("Pos Z", true);
+ mCtrlPosZ->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCommitTransform(); });
+
+ // Scale
+ mMenuClipboardScale = getChild<LLMenuButton>("clipboard_size_btn");
+ mCtrlScaleX = getChild<LLSpinCtrl>("Scale X", true);
+ mCtrlScaleX->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCommitTransform(); });
+ mCtrlScaleY = getChild<LLSpinCtrl>("Scale Y", true);
+ mCtrlScaleY->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCommitTransform(); });
+ mCtrlScaleZ = getChild<LLSpinCtrl>("Scale Z", true);
+ mCtrlScaleZ->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCommitTransform(); });
+
+ // Rotation
+ mMenuClipboardRot = getChild<LLMenuButton>("clipboard_rot_btn");
+ mCtrlRotX = getChild<LLSpinCtrl>("Rot X", true);
+ mCtrlRotX->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCommitTransform(); });
+ mCtrlRotY = getChild<LLSpinCtrl>("Rot Y", true);
+ mCtrlRotY->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCommitTransform(); });
+ mCtrlRotZ = getChild<LLSpinCtrl>("Rot Z", true);
+ mCtrlPosZ->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCommitTransform(); });
+ setTransformsEnabled(false);
+ // todo: do multiple panels based on selected element.
+ mTransformsPanel = getChild<LLPanel>("transform_panel", true);
+ mTransformsPanel->setVisible(false);
+
+ mItemListPanel = getChild<LLPanel>("item_list_panel", true);
+ initFolderRoot();
+
+ return true;
+}
+
+void LLFloaterGLTFAssetEditor::initFolderRoot()
+{
+ if (mScroller || mFolderRoot)
+ {
+ LL_ERRS() << "Folder root already initialized" << LL_ENDL;
+ return;
+ }
LLRect scroller_view_rect = mItemListPanel->getRect();
scroller_view_rect.translate(-scroller_view_rect.mLeft, -scroller_view_rect.mBottom);
@@ -87,11 +142,10 @@ bool LLFloaterGLTFAssetEditor::postBuild()
mFolderRoot->setScrollContainer(mScroller);
mFolderRoot->setFollowsAll();
mFolderRoot->setOpen(true);
+ mFolderRoot->setSelectCallback([this](const std::deque<LLFolderViewItem*>& items, bool user_action) { onFolderSelectionChanged(items, user_action); });
mScroller->setVisible(true);
gIdleCallbacks.addFunction(idle, this);
-
- return true;
}
void LLFloaterGLTFAssetEditor::onOpen(const LLSD& key)
@@ -252,3 +306,212 @@ void LLFloaterGLTFAssetEditor::loadFromSelection()
mFolderRoot->update();
}
+void LLFloaterGLTFAssetEditor::onFolderSelectionChanged(const std::deque<LLFolderViewItem*>& items, bool user_action)
+{
+ if (items.empty())
+ {
+ setTransformsEnabled(false);
+ return;
+ }
+
+ LLFolderViewItem* item = items.front();
+ LLGLTFFolderItem* vmi = static_cast<LLGLTFFolderItem*>(item->getViewModelItem());
+
+ switch (vmi->getType())
+ {
+ case LLGLTFFolderItem::TYPE_NODE:
+ {
+ setTransformsEnabled(true);
+ loadNodeTransforms(vmi->getItemId());
+ break;
+ }
+ default:
+ {
+ setTransformsEnabled(false);
+ break;
+ }
+ }
+}
+
+void LLFloaterGLTFAssetEditor::setTransformsEnabled(bool val)
+{
+ mMenuClipboardPos->setEnabled(val);
+ mCtrlPosX->setEnabled(val);
+ mCtrlPosY->setEnabled(val);
+ mCtrlPosZ->setEnabled(val);
+ mMenuClipboardScale->setEnabled(val);
+ mCtrlScaleX->setEnabled(val);
+ mCtrlScaleY->setEnabled(val);
+ mCtrlScaleZ->setEnabled(val);
+ mMenuClipboardRot->setEnabled(val);
+ mCtrlRotX->setEnabled(val);
+ mCtrlRotY->setEnabled(val);
+ mCtrlRotZ->setEnabled(val);
+}
+
+void LLFloaterGLTFAssetEditor::loadNodeTransforms(S32 node_id)
+{
+ if (node_id < 0 || node_id >= mAsset->mNodes.size())
+ {
+ LL_ERRS() << "Node id out of range: " << node_id << LL_ENDL;
+ return;
+ }
+
+ LL::GLTF::Node& node = mAsset->mNodes[node_id];
+
+ mCtrlPosX->set(node.mTranslation[0]);
+ mCtrlPosY->set(node.mTranslation[1]);
+ mCtrlPosZ->set(node.mTranslation[2]);
+
+ mCtrlScaleX->set(node.mScale[0]);
+ mCtrlScaleY->set(node.mScale[1]);
+ mCtrlScaleZ->set(node.mScale[2]);
+
+ LLQuaternion object_rot = LLQuaternion(node.mRotation[0], node.mRotation[1], node.mRotation[2], node.mRotation[3]);
+ object_rot.getEulerAngles(&(mLastEulerDegrees.mV[VX]), &(mLastEulerDegrees.mV[VY]), &(mLastEulerDegrees.mV[VZ]));
+ mLastEulerDegrees *= RAD_TO_DEG;
+ mLastEulerDegrees.mV[VX] = fmod(ll_round(mLastEulerDegrees.mV[VX], OBJECT_ROTATION_PRECISION) + 360.f, 360.f);
+ mLastEulerDegrees.mV[VY] = fmod(ll_round(mLastEulerDegrees.mV[VY], OBJECT_ROTATION_PRECISION) + 360.f, 360.f);
+ mLastEulerDegrees.mV[VZ] = fmod(ll_round(mLastEulerDegrees.mV[VZ], OBJECT_ROTATION_PRECISION) + 360.f, 360.f);
+
+ mCtrlRotX->set(mLastEulerDegrees.mV[VX]);
+ mCtrlRotY->set(mLastEulerDegrees.mV[VY]);
+ mCtrlRotZ->set(mLastEulerDegrees.mV[VZ]);
+}
+
+void LLFloaterGLTFAssetEditor::onCommitTransform()
+{
+ if (!mFolderRoot)
+ {
+ LL_ERRS() << "Folder root not initialized" << LL_ENDL;
+ return;
+ }
+
+ LLFolderViewItem* item = mFolderRoot->getCurSelectedItem();
+ if (!item)
+ {
+ LL_ERRS() << "Nothing selected" << LL_ENDL;
+ return;
+ }
+
+ LLGLTFFolderItem* vmi = static_cast<LLGLTFFolderItem*>(item->getViewModelItem());
+
+ if (!vmi || vmi->getType() != LLGLTFFolderItem::TYPE_NODE)
+ {
+ LL_ERRS() << "Only nodes implemented" << LL_ENDL;
+ return;
+ }
+ S32 node_id = vmi->getItemId();
+ LL::GLTF::Node& node = mAsset->mNodes[node_id];
+
+ LL::GLTF::vec3 tr(mCtrlPosX->get(), mCtrlPosY->get(), mCtrlPosZ->get());
+ node.setTranslation(tr);
+
+ LL::GLTF::vec3 scale(mCtrlScaleX->get(), mCtrlScaleY->get(), mCtrlScaleZ->get());
+ node.setScale(scale);
+
+ LLVector3 new_rot(mCtrlRotX->get(), mCtrlRotY->get(), mCtrlRotZ->get());
+ new_rot.mV[VX] = ll_round(new_rot.mV[VX], OBJECT_ROTATION_PRECISION);
+ new_rot.mV[VY] = ll_round(new_rot.mV[VY], OBJECT_ROTATION_PRECISION);
+ new_rot.mV[VZ] = ll_round(new_rot.mV[VZ], OBJECT_ROTATION_PRECISION);
+
+ // Note: must compare before conversion to radians, some value can go 'around' 360
+ LLVector3 delta = new_rot - mLastEulerDegrees;
+
+ if (delta.magVec() >= 0.0005f)
+ {
+ mLastEulerDegrees = new_rot;
+ new_rot *= DEG_TO_RAD;
+
+ LLQuaternion rotation;
+ rotation.setQuat(new_rot.mV[VX], new_rot.mV[VY], new_rot.mV[VZ]);
+ LL::GLTF::quat q;
+ q[0] = rotation.mQ[VX];
+ q[1] = rotation.mQ[VY];
+ q[2] = rotation.mQ[VZ];
+ q[3] = rotation.mQ[VW];
+
+ node.setRotation(q);
+ }
+
+ mAsset->updateTransforms();
+}
+
+void LLFloaterGLTFAssetEditor::onMenuDoToSelected(const LLSD& userdata)
+{
+ std::string command = userdata.asString();
+
+ if (command == "psr_paste")
+ {
+ // todo: implement
+ // onPastePos();
+ // onPasteSize();
+ // onPasteRot();
+ }
+ else if (command == "pos_paste")
+ {
+ // todo: implement
+ }
+ else if (command == "size_paste")
+ {
+ // todo: implement
+ }
+ else if (command == "rot_paste")
+ {
+ // todo: implement
+ }
+ else if (command == "psr_copy")
+ {
+ // onCopyPos();
+ // onCopySize();
+ // onCopyRot();
+ }
+ else if (command == "pos_copy")
+ {
+ // todo: implement
+ }
+ else if (command == "size_copy")
+ {
+ // todo: implement
+ }
+ else if (command == "rot_copy")
+ {
+ // todo: implement
+ }
+}
+
+bool LLFloaterGLTFAssetEditor::onMenuEnableItem(const LLSD& userdata)
+{
+ if (!mFolderRoot)
+ {
+ return false;
+ }
+
+ LLFolderViewItem* item = mFolderRoot->getCurSelectedItem();
+ if (!item)
+ {
+ return false;
+ }
+
+ LLGLTFFolderItem* vmi = static_cast<LLGLTFFolderItem*>(item->getViewModelItem());
+
+ if (!vmi || vmi->getType() != LLGLTFFolderItem::TYPE_NODE)
+ {
+ return false;
+ }
+
+ std::string command = userdata.asString();
+ if (command == "pos_paste" || command == "size_paste" || command == "rot_paste")
+ {
+ // todo: implement
+ return true;
+ }
+ if (command == "psr_copy")
+ {
+ // todo: implement
+ return true;
+ }
+
+ return false;
+}
+
diff --git a/indra/newview/llfloatergltfasseteditor.h b/indra/newview/llfloatergltfasseteditor.h
index 49c5aa4ae7..8164928824 100644
--- a/indra/newview/llfloatergltfasseteditor.h
+++ b/indra/newview/llfloatergltfasseteditor.h
@@ -40,6 +40,9 @@ namespace LL
}
}
+class LLSpinCtrl;
+class LLMenuButton;
+
class LLFloaterGLTFAssetEditor : public LLFloater
{
public:
@@ -48,6 +51,7 @@ public:
bool postBuild() override;
void onOpen(const LLSD& key) override;
+ void initFolderRoot();
LLGLTFViewModel& getRootViewModel() { return mGLTFViewModel; }
@@ -56,14 +60,42 @@ public:
void loadFromNode(S32 node, LLFolderViewFolder* parent);
void loadFromSelection();
+protected:
+ void onFolderSelectionChanged(const std::deque<LLFolderViewItem*>& items, bool user_action);
+ void onCommitTransform();
+ void onMenuDoToSelected(const LLSD& userdata);
+ bool onMenuEnableItem(const LLSD& userdata);
+
+ void setTransformsEnabled(bool val);
+ void loadNodeTransforms(S32 id);
+
private:
- LLGLTFViewModel mGLTFViewModel;
- LLUIColor mUIColor;
+ std::shared_ptr<LL::GLTF::Asset> mAsset;
+
+ // Folder view related
+ LLUIColor mUIColor;
+ LLGLTFViewModel mGLTFViewModel;
LLPanel* mItemListPanel = nullptr;
LLFolderView* mFolderRoot = nullptr;
LLScrollContainer* mScroller = nullptr;
- std::shared_ptr<LL::GLTF::Asset> mAsset;
+
+ // Transforms panel
+ LLVector3 mLastEulerDegrees;
+
+ LLPanel* mTransformsPanel = nullptr;
+ LLMenuButton* mMenuClipboardPos = nullptr;
+ LLSpinCtrl* mCtrlPosX = nullptr;
+ LLSpinCtrl* mCtrlPosY = nullptr;
+ LLSpinCtrl* mCtrlPosZ = nullptr;
+ LLMenuButton* mMenuClipboardScale = nullptr;
+ LLSpinCtrl* mCtrlScaleX = nullptr;
+ LLSpinCtrl* mCtrlScaleY = nullptr;
+ LLSpinCtrl* mCtrlScaleZ = nullptr;
+ LLMenuButton* mMenuClipboardRot = nullptr;
+ LLSpinCtrl* mCtrlRotX = nullptr;
+ LLSpinCtrl* mCtrlRotY = nullptr;
+ LLSpinCtrl* mCtrlRotZ = nullptr;
};
#endif LL_LLFLOATERGLTFASSETEDITOR_H
diff --git a/indra/newview/llgltffolderitem.h b/indra/newview/llgltffolderitem.h
index be6e264368..83c6449f96 100644
--- a/indra/newview/llgltffolderitem.h
+++ b/indra/newview/llgltffolderitem.h
@@ -112,7 +112,7 @@ public:
bool filter(LLFolderViewFilter& filter) override;
EType getType() const { return mItemType; }
- S32 getId() const { return mItemId; }
+ S32 getItemId() const { return mItemId; }
private:
LLUIImagePtr pIcon;
diff --git a/indra/newview/skins/default/xui/en/floater_gltf_asset_editor.xml b/indra/newview/skins/default/xui/en/floater_gltf_asset_editor.xml
index 4dc0dffcfd..82e0e325fb 100644
--- a/indra/newview/skins/default/xui/en/floater_gltf_asset_editor.xml
+++ b/indra/newview/skins/default/xui/en/floater_gltf_asset_editor.xml
@@ -3,9 +3,9 @@
legacy_header_height="18"
can_resize="true"
default_tab_group="1"
- height="400"
+ height="530"
width="256"
- min_height="200"
+ min_height="400"
min_width="200"
layout="topleft"
name="gltf asset editor"
@@ -14,13 +14,270 @@
<floater.string name="node_tittle" value="Node"/>
<floater.string name="mesh_tittle" value="Mesh"/>
<floater.string name="skin_tittle" value="Skin"/>
- <panel
+
+ <layout_stack
+ name="main_layout"
+ orientation="vertical"
follows="all"
- layout="topleft"
- name="item_list_panel"
- visible="true"
bottom="-1"
top="16"
left="5"
- right="-1"/>
+ right="-1"
+ border_size="0">
+
+ <layout_panel
+ name="top_lp"
+ border="true"
+ bevel_style="in"
+ auto_resize="false"
+ user_resize="true"
+ visible="true"
+ height="200">
+ <panel
+ follows="all"
+ layout="topleft"
+ name="item_list_panel"
+ visible="true"
+ bottom="-1"
+ top="16"
+ left="5"
+ right="-1"/>
+ </layout_panel>
+
+ <layout_panel
+ name="transforms_panel"
+ border="true"
+ bevel_style="in"
+ auto_resize="true"
+ user_resize="true"
+ visible="true"
+ height="150">
+ <menu_button
+ menu_filename="menu_copy_paste_pos.xml"
+ follows="top|left"
+ height="11"
+ image_disabled="ClipboardSmallMenu_Disabled"
+ image_selected="ClipboardSmallMenu_Press"
+ image_unselected="ClipboardSmallMenu_Off"
+ layout="topleft"
+ left_delta="0"
+ top_pad="13"
+ name="clipboard_pos_btn"
+ tool_tip="Paste options"
+ width="19"/>
+ <text
+ type="string"
+ length="1"
+ follows="left|top"
+ height="10"
+ layout="topleft"
+ name="label position"
+ tool_tip="Position (meters)"
+ left_pad="8"
+ top_delta="0"
+ width="121">
+ Position (m)
+ </text>
+ <spinner
+ follows="left|top"
+ height="19"
+ increment="0.01"
+ initial_value="0"
+ label="X"
+ label_width="10"
+ layout="topleft"
+ left_delta="-27"
+ max_val="512"
+ min_val="-256"
+ name="Pos X"
+ text_enabled_color="1 0 0.3 .7"
+ top_pad="8"
+ width="87" />
+ <spinner
+ follows="left|top"
+ height="19"
+ increment="0.01"
+ initial_value="0"
+ label="Y"
+ label_width="10"
+ layout="topleft"
+ left_delta="0"
+ max_val="512"
+ min_val="-256"
+ name="Pos Y"
+ text_enabled_color="EmphasisColor"
+ top_pad="3"
+ width="87" />
+ <spinner
+ follows="left|top"
+ height="19"
+ increment="0.01"
+ initial_value="0"
+ label="Z"
+ label_width="10"
+ layout="topleft"
+ left_delta="0"
+ max_val="4096"
+ min_val="-32"
+ name="Pos Z"
+ text_enabled_color="0 0.8 1 .65"
+ top_pad="3"
+ width="87" />
+ <menu_button
+ menu_filename="menu_copy_paste_size.xml"
+ follows="top|left"
+ height="11"
+ image_disabled="ClipboardSmallMenu_Disabled"
+ image_selected="ClipboardSmallMenu_Press"
+ image_unselected="ClipboardSmallMenu_Off"
+ layout="topleft"
+ left_delta="0"
+ top_pad="13"
+ name="clipboard_size_btn"
+ tool_tip="Paste options"
+ width="19"/>
+ <text
+ type="string"
+ length="1"
+ follows="left|top"
+ height="10"
+ layout="topleft"
+ left_pad="8"
+ top_delta="0"
+ name="label size"
+ tool_tip="Size (meters)"
+ width="121">
+ Size (m)
+ </text>
+ <spinner
+ follows="left|top"
+ height="19"
+ increment="0.01"
+ initial_value="0"
+ label="X"
+ label_width="10"
+ layout="topleft"
+ left_delta="-27"
+ max_val="64"
+ min_val="0.01"
+ name="Scale X"
+ text_enabled_color="1 1 1 1"
+ top_pad="8"
+ width="87" />
+ <spinner
+ follows="left|top"
+ height="19"
+ increment="0.01"
+ initial_value="0"
+ label="Y"
+ label_width="10"
+ layout="topleft"
+ left_delta="0"
+ max_val="64"
+ min_val="0.01"
+ name="Scale Y"
+ text_enabled_color="1 1 1 1"
+ top_pad="3"
+ width="87" />
+ <spinner
+ follows="left|top"
+ height="19"
+ increment="0.01"
+ initial_value="0"
+ label="Z"
+ label_width="10"
+ layout="topleft"
+ left_delta="0"
+ max_val="64"
+ min_val="0.01"
+ name="Scale Z"
+ text_enabled_color="1 1 1 1"
+ top_pad="3"
+ width="87" />
+ <menu_button
+ menu_filename="menu_copy_paste_rot.xml"
+ follows="top|left"
+ height="11"
+ image_disabled="ClipboardSmallMenu_Disabled"
+ image_selected="ClipboardSmallMenu_Press"
+ image_unselected="ClipboardSmallMenu_Off"
+ layout="topleft"
+ left_delta="0"
+ top_pad="13"
+ name="clipboard_rot_btn"
+ tool_tip="Paste options"
+ width="19"/>
+ <text
+ type="string"
+ length="1"
+ follows="left|top"
+ height="10"
+ layout="topleft"
+ left_pad="8"
+ top_delta="0"
+ name="label rotation"
+ tool_tip="Rotation (degrees)"
+ width="121">
+ Rotation (°)
+ </text>
+ <spinner
+ decimal_digits="2"
+ follows="left|top"
+ height="19"
+ increment="1"
+ initial_value="0"
+ label="X"
+ label_width="10"
+ layout="topleft"
+ left_delta="-27"
+ max_val="9999"
+ min_val="-9999"
+ name="Rot X"
+ text_enabled_color="1 1 1 1"
+ top_pad="8"
+ width="87" />
+ <spinner
+ decimal_digits="2"
+ follows="left|top"
+ height="19"
+ increment="1"
+ initial_value="0"
+ label="Y"
+ label_width="10"
+ layout="topleft"
+ left_delta="0"
+ max_val="9999"
+ min_val="-9999"
+ name="Rot Y"
+ text_enabled_color="1 1 1 1"
+ top_pad="3"
+ width="87" />
+ <spinner
+ decimal_digits="2"
+ follows="left|top"
+ height="19"
+ increment="1"
+ initial_value="0"
+ label="Z"
+ label_width="10"
+ layout="topleft"
+ left_delta="0"
+ max_val="9999"
+ min_val="-9999"
+ name="Rot Z"
+ text_enabled_color="1 1 1 1"
+ top_pad="3"
+ width="87" />
+ </layout_panel>
+
+ <layout_panel
+ name="blank_panel"
+ border="true"
+ bevel_style="in"
+ auto_resize="true"
+ user_resize="true"
+ visible="false"
+ height="150">
+ </layout_panel>
+ </layout_stack>
</floater>