added split resizing and splitting

This commit is contained in:
fourtf
2018-05-16 14:55:45 +02:00
parent 28fb877020
commit 5b26cdaa07
9 changed files with 856 additions and 629 deletions
+80 -400
View File
@@ -19,23 +19,26 @@
#include <pajlada/signals/signal.hpp>
#include <pajlada/signals/signalholder.hpp>
// remove
#include "application.hpp"
#include "singletons/thememanager.hpp"
class QJsonObject;
namespace chatterino {
namespace widgets {
//
// Note: This class is a spaghetti container. There is a lot of spaghetti code inside but it doesn't
// expose any of it publicly.
//
class SplitContainer : public BaseWidget, pajlada::Signals::SignalHolder
{
Q_OBJECT
struct Node;
public:
// fourtf: !!! preserve the order of left, up, right and down
enum Direction { Left, Above, Right, Below };
struct Node;
struct Position {
private:
Position() = default;
@@ -52,6 +55,7 @@ public:
friend class SplitContainer;
};
private:
struct DropRect {
QRect rect;
Position position;
@@ -63,404 +67,76 @@ public:
}
};
struct ResizeRect {
QRect rect;
Node *node;
bool vertical;
ResizeRect(const QRect &_rect, Node *_node, bool _vertical)
: rect(_rect)
, node(_node)
, vertical(_vertical)
{
}
};
public:
struct Node final {
enum Type { EmptyRoot, _Split, VerticalContainer, HorizontalContainer };
Type getType()
{
return this->type;
}
Split *getSplit()
{
return this->split;
}
const std::vector<std::unique_ptr<Node>> &getChildren()
{
return this->children;
}
Type getType();
Split *getSplit();
Node *getParent();
qreal getHorizontalFlex();
qreal getVerticalFlex();
const std::vector<std::unique_ptr<Node>> &getChildren();
private:
Type type;
Split *split;
Node *parent;
QRectF geometry;
qreal flexH = 1;
qreal flexV = 1;
std::vector<std::unique_ptr<Node>> children;
Node()
: type(Type::EmptyRoot)
, split(nullptr)
, parent(nullptr)
{
}
Node();
Node(Split *_split, Node *_parent);
Node(Split *_split, Node *_parent)
: type(Type::_Split)
, split(_split)
, parent(_parent)
{
}
bool isOrContainsNode(Node *_node);
Node *findNodeContainingSplit(Split *_split);
void insertSplitRelative(Split *_split, Direction _direction);
void nestSplitIntoCollection(Split *_split, Direction _direction);
void _insertNextToThis(Split *_split, Direction _direction);
void setSplit(Split *_split);
Position releaseSplit();
qreal getFlex(bool isVertical);
qreal getSize(bool isVertical);
qreal getChildrensTotalFlex(bool isVertical);
void layout(bool addSpacing, float _scale, std::vector<DropRect> &dropRects,
std::vector<ResizeRect> &resizeRects);
bool isOrContainsNode(Node *_node)
{
if (this == _node) {
return true;
}
return std::any_of(
this->children.begin(), this->children.end(),
[_node](std::unique_ptr<Node> &n) { return n->isOrContainsNode(_node); });
}
Node *findNodeContainingSplit(Split *_split)
{
if (this->type == Type::_Split && this->split == _split) {
return this;
}
for (std::unique_ptr<Node> &node : this->children) {
Node *a = node->findNodeContainingSplit(_split);
if (a != nullptr) {
return a;
}
}
return nullptr;
}
void insertSplitRelative(Split *_split, Direction _direction)
{
if (this->parent == nullptr) {
switch (this->type) {
case Node::EmptyRoot: {
this->setSplit(_split);
} break;
case Node::_Split: {
this->nestSplitIntoCollection(_split, _direction);
} break;
case Node::HorizontalContainer: {
if (toContainerType(_direction) == Node::HorizontalContainer) {
assert(false);
} else {
this->nestSplitIntoCollection(_split, _direction);
}
} break;
case Node::VerticalContainer: {
if (toContainerType(_direction) == Node::VerticalContainer) {
assert(false);
} else {
this->nestSplitIntoCollection(_split, _direction);
}
} break;
}
return;
}
// parent != nullptr
if (parent->type == toContainerType(_direction)) {
// hell yeah we'll just insert it next to outselves
this->_insertNextToThis(_split, _direction);
} else {
this->nestSplitIntoCollection(_split, _direction);
}
}
void nestSplitIntoCollection(Split *_split, Direction _direction)
{
// we'll need to nest outselves
// move all our data into a new node
Node *clone = new Node();
clone->type = this->type;
clone->children = std::move(this->children);
for (std::unique_ptr<Node> &node : clone->children) {
node->parent = clone;
}
clone->split = this->split;
clone->parent = this;
// add the node to our children and change our type
this->children.push_back(std::unique_ptr<Node>(clone));
this->type = toContainerType(_direction);
this->split = nullptr;
clone->_insertNextToThis(_split, _direction);
}
void _insertNextToThis(Split *_split, Direction _direction)
{
auto &siblings = this->parent->children;
qreal width = this->parent->geometry.width() / siblings.size();
qreal height = this->parent->geometry.height() / siblings.size();
if (siblings.size() == 1) {
this->geometry = QRect(0, 0, width, height);
}
auto it = std::find_if(siblings.begin(), siblings.end(),
[this](auto &node) { return this == node.get(); });
assert(it != siblings.end());
if (_direction == Direction::Right || _direction == Direction::Below) {
it++;
}
Node *node = new Node(_split, this->parent);
node->geometry = QRectF(0, 0, width, height);
siblings.insert(it, std::unique_ptr<Node>(node));
}
void setSplit(Split *_split)
{
assert(this->split == nullptr);
assert(this->children.size() == 0);
this->split = _split;
this->type = Type::_Split;
}
Position releaseSplit()
{
assert(this->type == Type::_Split);
if (parent == nullptr) {
this->type = Type::EmptyRoot;
this->split = nullptr;
Position pos;
pos.relativeNode = nullptr;
pos.direction = Direction::Right;
return pos;
} else {
auto &siblings = this->parent->children;
auto it = std::find_if(begin(siblings), end(siblings),
[this](auto &node) { return this == node.get(); });
assert(it != siblings.end());
Position position;
if (siblings.size() == 2) {
// delete this and move split to parent
position.relativeNode = this->parent;
if (this->parent->type == Type::VerticalContainer) {
position.direction =
siblings.begin() == it ? Direction::Above : Direction::Below;
} else {
position.direction =
siblings.begin() == it ? Direction::Left : Direction::Right;
}
Node *_parent = this->parent;
siblings.erase(it);
std::unique_ptr<Node> &sibling = siblings.front();
_parent->type = sibling->type;
_parent->split = sibling->split;
std::vector<std::unique_ptr<Node>> nodes = std::move(sibling->children);
for (auto &node : nodes) {
node->parent = _parent;
}
_parent->children = std::move(nodes);
} else {
if (this == siblings.back().get()) {
position.direction = this->parent->type == Type::VerticalContainer
? Direction::Below
: Direction::Right;
siblings.erase(it);
position.relativeNode = siblings.back().get();
} else {
position.relativeNode = (it + 1)->get();
position.direction = this->parent->type == Type::VerticalContainer
? Direction::Above
: Direction::Left;
siblings.erase(it);
}
}
return position;
}
}
void layout(bool addSpacing, float _scale, std::vector<DropRect> &dropRects)
{
switch (this->type) {
case Node::_Split: {
QRect rect = this->geometry.toRect();
this->split->setGeometry(rect.marginsRemoved(QMargins(1, 1, 1, 1)));
} break;
case Node::VerticalContainer: {
qreal totalHeight =
std::accumulate(this->children.begin(), this->children.end(), 0,
[](qreal val, std::unique_ptr<Node> &node) {
return val + node->geometry.height();
});
qreal childX = this->geometry.left();
qreal childWidth = this->geometry.width();
if (addSpacing) {
qreal offset = std::min<qreal>(this->geometry.width() * 0.1, _scale * 24);
dropRects.emplace_back(
QRect(childX, this->geometry.top(), offset, this->geometry.height()),
Position(this, Direction::Left));
dropRects.emplace_back(
QRect(childX + this->geometry.width() - offset, this->geometry.top(),
offset, this->geometry.height()),
Position(this, Direction::Right));
childX += offset;
childWidth -= offset * 2;
}
qreal scaleFactor = this->geometry.height() / totalHeight;
qreal y = this->geometry.top();
for (std::unique_ptr<Node> &child : this->children) {
child->geometry =
QRectF(childX, y, childWidth, child->geometry.height() * scaleFactor);
child->layout(addSpacing, _scale, dropRects);
y += child->geometry.height();
}
} break;
case Node::HorizontalContainer: {
qreal totalWidth =
std::accumulate(this->children.begin(), this->children.end(), 0,
[](qreal val, std::unique_ptr<Node> &node) {
return val + node->geometry.width();
});
qreal childY = this->geometry.top();
qreal childHeight = this->geometry.height();
if (addSpacing) {
qreal offset = std::min<qreal>(this->geometry.height() * 0.1, _scale * 24);
dropRects.emplace_back(
QRect(this->geometry.left(), childY, this->geometry.width(), offset),
Position(this, Direction::Above));
dropRects.emplace_back(
QRect(this->geometry.left(), childY + this->geometry.height() - offset,
this->geometry.width(), offset),
Position(this, Direction::Below));
childY += offset;
childHeight -= offset * 2;
}
qreal scaleFactor = this->geometry.width() / totalWidth;
qreal x = this->geometry.left();
for (std::unique_ptr<Node> &child : this->children) {
child->geometry =
QRectF(x, childY, child->geometry.width() * scaleFactor, childHeight);
child->layout(addSpacing, _scale, dropRects);
x += child->geometry.width();
}
} break;
};
}
static Type toContainerType(Direction _dir)
{
return _dir == Direction::Left || _dir == Direction::Right ? Type::HorizontalContainer
: Type::VerticalContainer;
}
static Type toContainerType(Direction _dir);
friend class SplitContainer;
};
private:
class DropOverlay : public QWidget
{
public:
DropOverlay(SplitContainer *_parent = nullptr)
: QWidget(_parent)
, parent(_parent)
, mouseOverPoint(-10000, -10000)
{
this->setMouseTracking(true);
this->setAcceptDrops(true);
}
DropOverlay(SplitContainer *_parent = nullptr);
void setRects(std::vector<SplitContainer::DropRect> _rects)
{
this->rects = std::move(_rects);
}
void setRects(std::vector<SplitContainer::DropRect> _rects);
pajlada::Signals::NoArgSignal dragEnded;
protected:
void paintEvent(QPaintEvent *event) override
{
QPainter painter(this);
// painter.fillRect(this->rect(), QColor("#334"));
bool foundMover = false;
for (DropRect &rect : this->rects) {
if (!foundMover && rect.rect.contains(this->mouseOverPoint)) {
painter.setBrush(getApp()->themes->splits.dropPreview);
painter.setPen(getApp()->themes->splits.dropPreviewBorder);
foundMover = true;
} else {
painter.setBrush(QColor(0, 0, 0, 0));
painter.setPen(QColor(0, 0, 0, 0));
// painter.setPen(getApp()->themes->splits.dropPreviewBorder);
}
painter.drawRect(rect.rect);
}
}
void mouseMoveEvent(QMouseEvent *event)
{
this->mouseOverPoint = event->pos();
}
void leaveEvent(QEvent *event)
{
this->mouseOverPoint = QPoint(-10000, -10000);
}
void dragEnterEvent(QDragEnterEvent *event)
{
event->acceptProposedAction();
}
void dragMoveEvent(QDragMoveEvent *event)
{
event->acceptProposedAction();
this->mouseOverPoint = event->pos();
this->update();
}
void dragLeaveEvent(QDragLeaveEvent *event)
{
this->mouseOverPoint = QPoint(-10000, -10000);
this->close();
this->dragEnded.invoke();
}
void dropEvent(QDropEvent *event)
{
Position *position = nullptr;
for (DropRect &rect : this->rects) {
if (rect.rect.contains(this->mouseOverPoint)) {
position = &rect.position;
break;
}
}
if (position != nullptr) {
this->parent->insertSplit(SplitContainer::draggingSplit, *position);
event->acceptProposedAction();
}
this->mouseOverPoint = QPoint(-10000, -10000);
this->close();
this->dragEnded.invoke();
}
void paintEvent(QPaintEvent *event) override;
void dragEnterEvent(QDragEnterEvent *event);
void dragMoveEvent(QDragMoveEvent *event);
void dragLeaveEvent(QDragLeaveEvent *event);
void dropEvent(QDropEvent *event);
private:
std::vector<DropRect> rects;
@@ -468,6 +144,27 @@ public:
SplitContainer *parent;
};
class ResizeHandle : public QWidget
{
public:
SplitContainer *parent;
Node *node;
void setVertical(bool isVertical);
ResizeHandle(SplitContainer *_parent = nullptr);
void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
friend class SplitContainer;
private:
bool vertical;
bool isMouseDown = false;
};
public:
SplitContainer(Notebook *parent, NotebookTab *_tab);
void appendNewSplit(bool openChannelNameDialog);
@@ -478,6 +175,8 @@ public:
Position releaseSplit(Split *split);
Position deleteSplit(Split *split);
void decodeFromJson(QJsonObject &obj);
int getSplitCount()
{
return 0;
@@ -491,30 +190,22 @@ public:
void refreshTabTitle();
NotebookTab *getTab() const;
const Node *getBaseNode()
Node *getBaseNode()
{
return &this->baseNode;
}
static bool isDraggingSplit;
static Split *draggingSplit;
// static Position dragOriginalPosition;
protected:
// bool eventFilter(QObject *object, QEvent *event) override;
void paintEvent(QPaintEvent *event) override;
// void showEvent(QShowEvent *event) override;
// void enterEvent(QEvent *event) override;
void leaveEvent(QEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void dragEnterEvent(QDragEnterEvent *event) override;
void dragMoveEvent(QDragMoveEvent *event) override;
void dragLeaveEvent(QDragLeaveEvent *event) override;
void dropEvent(QDropEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
@@ -534,6 +225,7 @@ private:
std::vector<DropRegion> dropRegions;
NotebookPageDropPreview dropPreview;
DropOverlay overlay;
std::vector<std::unique_ptr<ResizeHandle>> resizeHandles;
QPoint mouseOverPoint;
void layout();
@@ -545,19 +237,7 @@ private:
bool isDragging = false;
// struct {
// QVBoxLayout parentLayout;
// QHBoxLayout hbox;
// } ui;
// std::vector<Split *> splits;
// void setPreviewRect(QPoint mousePos);
// std::pair<int, int> getChatPosition(const Split *chatWidget);
// Split *createChatWidget();
void decodeNodeRecusively(QJsonObject &obj, Node *node);
};
} // namespace widgets