SimsWorld/SimsWorld/BehaviorEditor.cpp

721 lines
21 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "BehaviorEditor.h"
#include <QDebug>
#include <QGraphicsSceneDragDropEvent>
#include <QMimeData>
#include <QApplication>
#include <QSplitter>
#include <QMenuBar>
#include <QMenu>
#include <QFileDialog>
#include <QVariant>
#include <QDrag>
#include <QPainter>
#include <QStyleOptionGraphicsItem>
#include <QStandardItem>
uint qHash(const std::shared_ptr<LogicalNode> data, uint seed) noexcept
{
return qHash((void*)data.get(), seed);
}
NodePresent::NodePresent(BehaviorsPresent* pwidget, QVector<double>& columns_set, std::shared_ptr<LogicalNode> bind)
: _widget_p(pwidget), _columns_width_seqs(columns_set), _node_bind(bind) {
this->setAcceptDrops(true);
this->setCacheMode(QGraphicsItem::NoCache);
}
std::shared_ptr<LogicalNode> NodePresent::logicalBind() const
{
return _node_bind;
}
QRectF NodePresent::contentMeasure() const
{
auto metrics = this->_widget_p->fontMetrics();
auto rect = metrics.boundingRect(this->_node_bind->rtName());
rect.moveTopLeft(QPoint(0, 0));
return rect;
}
AcceptType NodePresent::testAccept(const QPointF& local_pos, const QString& kind_str) const
{
auto outline = this->boundingRect();
auto width = outline.width();
auto origin = outline.topLeft();
auto left_rect = QRectF(origin + QPointF(0, 0), QSizeF(width / 3, outline.height()));
auto right_rect = QRectF(origin + QPointF(width * 2 / 3, 0), QSizeF(width / 3, outline.height()));
auto top_rect = QRectF(origin + QPointF(width / 3, outline.height() / 2), QSizeF(width / 3, outline.height() / 2));
auto bottom_rect = QRectF(origin + QPointF(width / 3, 0), QSizeF(width / 3, outline.height() / 2));
QList<NodeKind> no_child{ NodeKind::ACTIONNODE, NodeKind::COMPARENODE };
auto pnode = this->_node_bind->parent().lock();
decltype(pnode) new_type = this->_node_bind->getKernal()->getNode(kind_str);
// 当前节点存在父节点且不属于Action和Compare
if (left_rect.contains(local_pos) && pnode && !no_child.contains(new_type->nodeKind()))
return AcceptType::PREV_LEVEL;
QList<NodeKind> single_child{ NodeKind::MAPNODE, NodeKind::MODIFYNODE };
// 当前节点存在父节点且父节点可以存在多个节点
if (pnode && !single_child.contains(pnode->nodeKind())) {
if (top_rect.contains(local_pos))
return AcceptType::PREV_SIBLING;
if (bottom_rect.contains(local_pos))
return AcceptType::NEXT_SIBLING;
}
// 分辨当前节点的是否可以插入新节点
if (right_rect.contains(local_pos))
switch (this->_node_bind->nodeKind()) {
case NodeKind::MAPNODE: // 根节点只能容纳一个子节点
if (!this->_node_bind->children().size() && !this->_node_bind->parent().lock())
return AcceptType::NEXT_LEVEL;
break;
case NodeKind::MODIFYNODE: // 修饰节点只能容纳一个子节点
if (!this->_node_bind->children().size())
return AcceptType::NEXT_LEVEL;
break;
case NodeKind::PARALLELNODE:
case NodeKind::SELECTORNODE:
case NodeKind::SEQUENCENODE:
return AcceptType::NEXT_LEVEL;
default: // Action和Compare节点不能容纳节点
break;
}
return AcceptType::NONE;
}
void NodePresent::dragEnterEvent(QGraphicsSceneDragDropEvent* e)
{
QGraphicsItem::dragEnterEvent(e);
if (e->mimeData()->hasFormat("application/drag-node")) {
e->acceptProposedAction();
}
}
void NodePresent::dragLeaveEvent(QGraphicsSceneDragDropEvent* event)
{
QGraphicsItem::dragLeaveEvent(event);
this->_drop_target = AcceptType::NONE;
this->update();
}
void NodePresent::dragMoveEvent(QGraphicsSceneDragDropEvent* e)
{
QGraphicsItem::dragMoveEvent(e);
this->update();
if (e->mimeData()->hasFormat("application/drag-node")) {
auto kind_string = QString::fromUtf8(e->mimeData()->data("application/drag-node"));
this->_drop_target = this->testAccept(e->pos(), kind_string);
if (_drop_target != AcceptType::NONE) {
e->acceptProposedAction();
return;
}
}
e->ignore();
}
void NodePresent::dropEvent(QGraphicsSceneDragDropEvent* e)
{
auto kind_string = QString::fromUtf8(e->mimeData()->data("application/drag-node"));
auto parent_node = this->_node_bind->parent().lock();
decltype(parent_node) new_type = nullptr;
if (this->_node_bind->bindMap())
new_type = this->_node_bind->bindMap()->getKernal()->getNode(kind_string);
else
new_type = std::dynamic_pointer_cast<BehaviorMapNode>(this->_node_bind)->getKernal()->getNode(kind_string);
auto new_node = std::dynamic_pointer_cast<LogicalNode>(new_type->newDefault());
new_node->setID(++_widget_p->_node_id_max);
auto this_node = this->_node_bind;
auto appoint_index = 0;
if (parent_node)
appoint_index = parent_node->children().indexOf(this_node);
switch (_drop_target) {
case AcceptType::NONE:
break;
case AcceptType::PREV_LEVEL: {
parent_node->remove(this_node);
parent_node->insert(new_node, appoint_index);
new_node->insert(this_node);
}break;
case AcceptType::NEXT_LEVEL:
this_node->insert(new_node);
break;
case AcceptType::PREV_SIBLING:
parent_node->insert(new_node, appoint_index);
break;
case AcceptType::NEXT_SIBLING:
parent_node->insert(new_node, appoint_index + 1);
break;
}
this->_drop_target = AcceptType::NONE;
_widget_p->relayout();
this->update();
}
QRectF NodePresent::boundingRect() const
{
auto rect = contentMeasure();
auto depth = this->_node_bind->depth();
if (_columns_width_seqs.size() <= depth)
_columns_width_seqs.resize(depth + 1);
auto width_col = _columns_width_seqs[depth];
rect.setWidth(std::max(width_col, rect.width()));
_columns_width_seqs[depth] = rect.width();
return rect + QMargins(0, 0, 30, 30);
}
void NodePresent::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
painter->save();
//painter->drawRect(boundingRect());
painter->fillRect(option->rect - QMargins(padding, padding, padding, padding), Qt::gray);
auto outline = this->boundingRect();
auto width = outline.width();
auto origin = outline.topLeft();
painter->save();
painter->translate(QPointF(15, 15));
auto rect = contentMeasure();
painter->drawText(rect, 0, this->_node_bind->rtName());
painter->restore();
QRectF paint_target_rect;
switch (_drop_target) {
case AcceptType::NONE:
break;
case AcceptType::PREV_LEVEL:
paint_target_rect = QRectF(origin + QPointF(0, 0), QSizeF(width / 3, outline.height()));;
break;
case AcceptType::NEXT_LEVEL:
paint_target_rect = QRectF(origin + QPointF(width * 2 / 3, 0), QSizeF(width / 3, outline.height()));;
break;
case AcceptType::PREV_SIBLING:
paint_target_rect = QRectF(origin + QPointF(width / 3, outline.height() / 2), QSizeF(width / 3, outline.height() / 2));;
break;
case AcceptType::NEXT_SIBLING:
paint_target_rect = QRectF(origin + QPointF(width / 3, 0), QSizeF(width / 3, outline.height() / 2));;
break;
}
painter->fillRect(paint_target_rect, Qt::green);
painter->restore();
}
BehaviorsPresent::BehaviorsPresent(QWidget* parent /*= nullptr*/)
: QGraphicsView(parent) {
this->setScene(&_bind_scene);
this->setAcceptDrops(true);
this->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
auto font = this->font();
font.setPixelSize(20);
this->setFont(font);
}
void BehaviorsPresent::setRoot(std::shared_ptr<BehaviorMapNode> root)
{
this->_bind_maproot = root;
// 清除显示节点
qDeleteAll(_present_peers);
_present_peers.clear();
// 清空分支
qDeleteAll(this->_branch_list);
this->_branch_list.clear();
relayout();
}
NodePresent* BehaviorsPresent::presentAllocate(std::shared_ptr<LogicalNode> ins)
{
for (auto idx = _column_aligns.size(); idx < ins->depth() + 1; ++idx)
_column_aligns.append(0);
QList<NodePresent*> _children_set;
switch (ins->nodeKind()) {
case NodeKind::MAPNODE:
case NodeKind::MODIFYNODE:
case NodeKind::PARALLELNODE:
case NodeKind::SEQUENCENODE:
case NodeKind::SELECTORNODE:
if (ins->nodeKind() != NodeKind::MAPNODE || !ins->bindMap()) {
for (auto it : ins->children()) {
auto child_graph = presentAllocate(it);
if (child_graph) _children_set << child_graph;
}
}
default:
if (_present_peers.contains(ins)) {
for (auto child : _children_set) {
_branch_list << new BranchPresent(_present_peers, child);
this->_bind_scene.addItem(_branch_list.last());
}
return nullptr;
}
_present_peers[ins] = new NodePresent(this, _column_aligns, ins);
this->_bind_scene.addItem(_present_peers[ins]);
return _present_peers[ins];
}
}
//void BehaviorsPresent::presentRelease(std::shared_ptr<LogicalNode> ins)
//{
// switch (ins->nodeKind())
// {
// case NodeKind::MAPNODE:
// case NodeKind::PARALLELNODE:
// case NodeKind::SELECTORNODE:
// case NodeKind::SEQUENCENODE: {
// for (auto item : ins->children()) {
// presentRelease(item);
// }
// }
// default:
// if (this->_present_peers.contains(ins))
// delete this->_present_peers[ins];
// this->_present_peers.remove(ins);
//
// // 清除BranchPresent
// BranchPresent *_target = nullptr;
// for(auto node : this->_branch_list)
// if (node->headNode() == ins) {
// _target = node;
// delete node;
// }
// this->_branch_list.removeAll(_target);
// break;
// }
//}
const double NodePresent::padding = 8;
void BehaviorsPresent::relayout()
{
if (!_bind_maproot)
return;
// 初始化显示节点
presentAllocate(_bind_maproot);
QHash<std::shared_ptr<LogicalNode>, std::pair<QSizeF, QSizeF>> results;
// 尺寸测量
outlineMeasure(_bind_maproot, results);
// 元素排布
nodeRelayout(results, _bind_maproot, QPointF());
// 调整分支图形位置
for (auto ins : this->_branch_list) {
auto rect_s = ins->startOutline();
auto rect_e = ins->endOutline();
qDebug() << __FILE__ << __LINE__ << rect_s << rect_e;
auto start_pos = rect_s.bottomRight() - QPoint(NodePresent::padding, rect_s.height() / 2);
auto end_pos = rect_e.topLeft() + QPoint(NodePresent::padding, rect_e.height() / 2);
auto left_val = std::min(start_pos.x(), end_pos.x());
auto top_val = std::min(start_pos.y(), end_pos.y());
ins->setPos(left_val, top_val);
ins->resetArrow(start_pos, end_pos);
}
this->update();
}
QSizeF BehaviorsPresent::outlineMeasure(std::shared_ptr<LogicalNode> ins, QHash<std::shared_ptr<LogicalNode>, std::pair<QSizeF, QSizeF>>& _outline_occupy)
{
QSizeF outline_box(0, 0);
switch (ins->nodeKind()) {
case NodeKind::MAPNODE:
case NodeKind::MODIFYNODE:
case NodeKind::PARALLELNODE:
case NodeKind::SELECTORNODE:
case NodeKind::SEQUENCENODE:
// 测量子节点集合尺寸,根节点必须是行为树顶部根节点
if (ins->nodeKind() != NodeKind::MAPNODE || !ins->bindMap()) {
for (auto nit : ins->children()) {
auto nit_size = outlineMeasure(nit, _outline_occupy);
outline_box.setWidth(std::max(outline_box.width(), nit_size.width() + _space_h));
outline_box += QSizeF(0, nit_size.height());
}
}
default: {
auto root_peer = _present_peers[ins];
auto root_box = root_peer->boundingRect();
outline_box += QSizeF(root_box.width(), 0);
outline_box.setHeight(std::max(outline_box.height(), root_box.height()));
_outline_occupy[ins] = std::make_pair(outline_box, root_box.size());
return outline_box;
}break;
}
}
QPointF BehaviorsPresent::nodeRelayout(QHash<std::shared_ptr<LogicalNode>, std::pair<QSizeF, QSizeF>>& _outline_occupy,
std::shared_ptr<LogicalNode> ins, const QPointF& origin_offset) {
// 提取尺寸记录
auto outline_occupy = _outline_occupy[ins];
auto node_outline = outline_occupy.first;
auto node_occupy = outline_occupy.second;
// 提取显示节点
auto present_node = _present_peers[ins];
// 设置节点位置
QPointF local_origin_offset(0, (node_outline.height() - node_occupy.height()) / 2);
present_node->setPos(origin_offset + local_origin_offset);
// 计算子节点位置
QPointF child_origin = origin_offset + QPointF(node_occupy.width() + _space_h, 0);
switch (ins->nodeKind())
{
case NodeKind::ACTIONNODE:
case NodeKind::COMPARENODE:
break;
default:
// 布局子节点集合位置,根节点必须是顶端根节点
if (ins->nodeKind() != NodeKind::MAPNODE || !ins->bindMap()) {
auto child_list = ins->children();
for (auto iter = child_list.rbegin(); iter != child_list.rend(); ++iter) {
auto item = *iter;
child_origin = nodeRelayout(_outline_occupy, item, child_origin);
}
}break;
}
// 计算左下角位置
return origin_offset + QPointF(0, node_outline.height());
}
#include <QJsonDocument>
void BehaviorEditor::open_behavior_map()
{
_current_fileurl = QFileDialog::getOpenFileUrl(this, u8"打开行为树文件", QUrl(), "*.behw");
if (!_current_fileurl.isValid())
return;
QFile data_file(_current_fileurl.toLocalFile());
data_file.open(QIODevice::ReadOnly);
auto json = QJsonDocument::fromJson(data_file.readAll());
// 载入数据,回复节点内容
_map_root->recoveryFrom(json.object());
this->_logical_present->setRoot(_map_root);
}
void BehaviorEditor::new_behavior_map()
{
_current_fileurl = QFileDialog::getSaveFileUrl(this, u8"创建行为树文件", QUrl(), "*.behw");
if (!_current_fileurl.isValid())
return;
qDebug() << _current_fileurl;
// 清空所有子节点内容
auto childs = this->_map_root->children();
if (childs.size())
this->_map_root->remove(childs.first());
// 清空行为树节点变量列表
auto variable_key_set = this->_map_root->inputVariableKeys();
variable_key_set << this->_map_root->outputVariableKeys();
for (auto key : variable_key_set)
this->_map_root->removeVariable(key);
// 重置地图节点
this->_logical_present->setRoot(_map_root);
}
#include <QLineEdit>
#include <QLabel>
#include <QGridLayout>
#include <QComboBox>
QWidget* BehaviorEditor::newModifyNodeConfigration(QWidget* pwidget)
{
auto panel = new QFrame(this);
panel->setFrameStyle(QFrame::WinPanel | QFrame::Sunken);
auto p_layout = new QGridLayout(panel);
p_layout->addWidget(new QLabel(u8"修饰类型:", this));
p_layout->addWidget(new QComboBox(this), 0, 1, 1, 3);
p_layout->addWidget(new QWidget(this), 1, 0, 1, 4);
p_layout->setRowStretch(1, 1);
p_layout->setColumnStretch(1, 1);
return panel;
}
BehaviorEditor::BehaviorEditor(QWidget* parent /*= nullptr*/)
: QMainWindow(parent),
_type_view(new NodeTypesView(this)),
_type_model(new QStandardItemModel(this)),
_logical_present(new BehaviorsPresent(this)),
_message_panel(new QTabWidget(this)),
_stacked_panel(new QStackedWidget(this)),
_logs_present(new QTextBrowser(this)),
_map_configuration(new BehaviorMapConfigurationPanel(this))
{
_global_loader = std::make_shared<MessageLoader>();
_global_kernal = std::make_shared<MapKernel>(_global_loader);
_global_kernal->initial();
this->_map_root = std::make_shared<BehaviorMapNode>(_global_kernal);
auto mbar = this->menuBar();
auto _file = mbar->addMenu(u8"文件");
_file->addAction(u8"打开行为树", this, &BehaviorEditor::open_behavior_map);
_file->addAction(u8"新建行为树", this, &BehaviorEditor::new_behavior_map);
auto font = this->font();
font.setPixelSize(20);
_type_view->setFont(font);
auto split_v = new QSplitter(Qt::Vertical, this);
this->setCentralWidget(split_v);
auto split_h = new QSplitter(Qt::Horizontal, this);
split_v->addWidget(split_h);
split_v->addWidget(_message_panel);
split_v->setStretchFactor(0, 1);
split_v->setStretchFactor(1, 0);
split_h->addWidget(_logical_present);
split_h->addWidget(_type_view);
split_h->setStretchFactor(0, 1);
split_h->setStretchFactor(1, 0);
_type_view->setModel(_type_model);
nodeTypesViewInit(_type_model);
// 下方堆叠面板
_message_panel->addTab(_logs_present, u8"控制台");
_message_panel->addTab(_stacked_panel, u8"属性配置");
_stacked_panel->addWidget(new BehaviorMapConfigurationPanel(this));
_stacked_panel->addWidget(newModifyNodeConfigration(this));
_stacked_panel->addWidget(newActionNodeConfigration(this));
_stacked_panel->addWidget(newCompareNodeConfigration(this));
_stacked_panel->addWidget(newDefaultConfigration(this));
}
void BehaviorEditor::nodeTypesViewInit(QStandardItemModel* m)
{
auto types = this->_global_kernal->nodeTypes();
std::sort(types.begin(), types.end());
for (auto type : types) {
auto row_item = new QStandardItem(type);
row_item->setEditable(false);
m->appendRow(row_item);
}
}
#include <QGridLayout>
#include <QLineEdit>
#include <QLabel>
#include <QComboBox>
#include <QPushButton>
QWidget* BehaviorEditor::newDefaultConfigration(QWidget* pwidget)
{
auto ins = new QLabel(u8"该结点无需配置", pwidget);
ins->setAlignment(Qt::AlignCenter);
ins->setFrameStyle(QFrame::WinPanel | QFrame::Sunken);
return ins;
}
QWidget* BehaviorEditor::newCompareNodeConfigration(QWidget* pwidget)
{
auto panel = new QFrame(this);
panel->setFrameStyle(QFrame::WinPanel | QFrame::Sunken);
auto p_layout = new QGridLayout(panel);
auto compare_types = new QComboBox(this);
p_layout->addWidget(new QLabel(u8"类型筛选:", this), 0, 0);
p_layout->addWidget(new QComboBox(this), 0, 1, 1, 5);
p_layout->addWidget(new QLabel(u8"比较器类型:"), 1, 0);
p_layout->addWidget(compare_types, 1, 1, 1, 5);
p_layout->addWidget(new QLabel(u8"输入变量A", this), 2, 0);
p_layout->addWidget(new QComboBox(this), 2, 1, 1, 5);
p_layout->addWidget(new QLabel(u8"输入变量B", this), 3, 0);
p_layout->addWidget(new QComboBox(this), 3, 1, 1, 5);
p_layout->setColumnStretch(1, 1);
return panel;
}
QWidget* BehaviorEditor::newActionNodeConfigration(QWidget* pwidget)
{
auto panel = new QFrame(this);
panel->setFrameStyle(QFrame::WinPanel | QFrame::Sunken);
auto p_layout = new QGridLayout(panel);
p_layout->addWidget(new QLabel(u8"执行器类型:", this));
p_layout->addWidget(new QComboBox(this), 0, 1, 1, 4);
auto vars_tabw = new QTabWidget(this);
p_layout->addWidget(vars_tabw, 1, 0, 3, 5);
vars_tabw->setTabPosition(QTabWidget::West);
vars_tabw->addTab(new QTableView(this), u8"输入变量");
vars_tabw->addTab(new QTableView(this), u8"输出变量");
p_layout->setColumnStretch(1, 1);
return panel;
}
NodeTypesView::NodeTypesView(QWidget* parent /*= nullptr*/)
: QListView(parent)
{
this->setDragEnabled(true);
this->setDragDropMode(QAbstractItemView::DragOnly);
this->setSelectionBehavior(QAbstractItemView::SelectRows);
this->setSelectionMode(QAbstractItemView::SingleSelection);
}
void NodeTypesView::startDrag(Qt::DropActions supported)
{
auto selected = this->selectedIndexes();
if (selected.size() && selected.first().isValid()) {
auto val = this->model()->data(selected.first(), Qt::DisplayRole);
auto type_name = val.toString();
// 创建自定义MIME数据
auto mime_data = new QMimeData();
mime_data->setData("application/drag-node", type_name.toUtf8());
auto view_rect = this->visualRect(selected.first());
QPixmap pixmap(view_rect.size());
pixmap.fill(Qt::transparent);
QPainter p(&pixmap);
this->render(&p, QPoint(), QRegion(view_rect));
p.end();
// 创建拖动对象
auto drag = new QDrag(this);
drag->setMimeData(mime_data);
drag->setPixmap(pixmap);
auto result = drag->exec(supported);
if (result == Qt::CopyAction) {
qDebug() << __FILE__ << __LINE__;
}
}
}
BranchPresent::BranchPresent(const QHash<std::shared_ptr<LogicalNode>, NodePresent*>& present_set, NodePresent* head_anchor)
:_present_set_bind(present_set), _head_node(head_anchor) {
}
std::shared_ptr<LogicalNode> BranchPresent::headNode() const
{
return _head_node->logicalBind();
}
QRectF BranchPresent::startOutline() const
{
auto parent_node = _head_node->logicalBind()->parent().lock();
if (!parent_node)
return QRectF();
auto _start_node = _present_set_bind[parent_node];
auto _start_pos = _start_node->pos();
auto _s_rect = _start_node->boundingRect();
_s_rect.moveTopLeft(_start_pos);
return _s_rect;
}
QRectF BranchPresent::endOutline() const
{
auto parent_node = _head_node->logicalBind()->parent().lock();
if (!parent_node)
return QRectF();
auto _end_node = this->_head_node;
auto _end_pos = _end_node->pos();
auto _e_rect = _end_node->boundingRect();
_e_rect.moveTopLeft(_end_pos);
return _e_rect;
}
void BranchPresent::resetArrow(const QPointF& start, const QPointF& end)
{
this->_arrow_start = start;
this->_arrow_end = end;
qDebug() << __FILE__ << __LINE__ << _arrow_start << _arrow_end;
this->update();
}
QRectF BranchPresent::boundingRect() const
{
auto leftv = std::min(_arrow_start.x(), _arrow_end.x());
auto topv = std::min(_arrow_start.y(), _arrow_end.y());
auto width = std::abs(_arrow_start.x() - _arrow_end.x());
auto height = std::abs(_arrow_start.y() - _arrow_end.y());
return QRectF(0, 0, width, height);
}
void BranchPresent::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
painter->save();
painter->setRenderHint(QPainter::Antialiasing, true);
QPointF start(0, 0);
auto vtarget = _arrow_end - _arrow_start;
if (vtarget.y() < 0) {
start -= QPointF(0, vtarget.y());
vtarget -= QPointF(0, vtarget.y());
}
auto pt1 = start + QPointF(vtarget.x() / 2, 0);
auto pt2 = vtarget - QPointF(vtarget.x() / 2, 0);
QPainterPath bezier_curve;
bezier_curve.moveTo(start);
bezier_curve.cubicTo(pt1, pt2, vtarget);
QPen p(Qt::black);
p.setWidth(2);
painter->setPen(p);
painter->drawPath(bezier_curve);
//p.setColor(Qt::red);
//painter->setPen(p);
//painter->drawLine(start, pt1);
//painter->drawLine(pt1, pt2);
//painter->drawLine(pt1, vtarget);
//p.setColor(Qt::green);
//painter->setPen(p);
//painter->drawRect(option->rect);
painter->restore();
}