mirror-chatterino2/src/widgets/splitcontainer.hpp

565 lines
18 KiB
C++
Raw Normal View History

#pragma once
2016-12-29 17:31:07 +01:00
#include "widgets/basewidget.hpp"
2017-11-12 17:21:50 +01:00
#include "widgets/helper/droppreview.hpp"
#include "widgets/helper/notebooktab.hpp"
#include "widgets/split.hpp"
2016-12-30 12:20:26 +01:00
2017-01-18 04:52:47 +01:00
#include <QDragEnterEvent>
#include <QHBoxLayout>
#include <QRect>
#include <QVBoxLayout>
#include <QVector>
#include <QWidget>
2018-05-10 19:50:31 +02:00
#include <algorithm>
#include <functional>
#include <vector>
2018-05-10 23:58:07 +02:00
#include <pajlada/signals/signal.hpp>
2018-05-10 19:50:31 +02:00
#include <pajlada/signals/signalholder.hpp>
2018-05-10 23:58:07 +02:00
// remove
#include "application.hpp"
#include "singletons/thememanager.hpp"
2017-01-18 21:30:23 +01:00
namespace chatterino {
namespace widgets {
2018-05-10 19:50:31 +02:00
class SplitContainer : public BaseWidget, pajlada::Signals::SignalHolder
2016-12-29 17:31:07 +01:00
{
2017-01-11 18:52:09 +01:00
Q_OBJECT
2016-12-29 17:31:07 +01:00
public:
2018-05-10 19:50:31 +02:00
// fourtf: !!! preserve the order of left, up, right and down
enum Direction { Left, Above, Right, Below };
struct Node;
struct Position {
private:
Position() = default;
Position(Node *_relativeNode, Direction _direcion)
: relativeNode(_relativeNode)
, direction(_direcion)
{
}
Node *relativeNode;
Direction direction;
friend struct Node;
friend class SplitContainer;
};
struct DropRect {
QRect rect;
Position position;
DropRect(const QRect &_rect, const Position &_position)
: rect(_rect)
, position(_position)
{
}
};
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;
}
private:
Type type;
Split *split;
Node *parent;
QRectF geometry;
std::vector<std::unique_ptr<Node>> children;
Node()
: type(Type::EmptyRoot)
, split(nullptr)
, parent(nullptr)
{
}
Node(Split *_split, Node *_parent)
: type(Type::_Split)
, split(_split)
, parent(_parent)
{
}
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;
2018-05-11 13:55:10 +02:00
qreal width = this->parent->geometry.width() / siblings.size();
qreal height = this->parent->geometry.height() / siblings.size();
2018-05-10 19:50:31 +02:00
if (siblings.size() == 1) {
2018-05-11 13:55:10 +02:00
this->geometry = QRect(0, 0, width, height);
2018-05-10 19:50:31 +02:00
}
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);
2018-05-11 13:55:10 +02:00
node->geometry = QRectF(0, 0, width, height);
2018-05-10 19:50:31 +02:00
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;
}
}
2018-05-10 23:58:07 +02:00
void layout(bool addSpacing, float _scale, std::vector<DropRect> &dropRects)
2018-05-10 19:50:31 +02:00
{
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) {
2018-05-10 23:58:07 +02:00
qreal offset = std::min<qreal>(this->geometry.width() * 0.1, _scale * 24);
2018-05-10 19:50:31 +02:00
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);
2018-05-10 23:58:07 +02:00
child->layout(addSpacing, _scale, dropRects);
2018-05-10 19:50:31 +02:00
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) {
2018-05-10 23:58:07 +02:00
qreal offset = std::min<qreal>(this->geometry.height() * 0.1, _scale * 24);
2018-05-10 19:50:31 +02:00
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));
2018-05-10 23:58:07 +02:00
childY += offset;
childHeight -= offset * 2;
2018-05-10 19:50:31 +02:00
}
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);
2018-05-10 23:58:07 +02:00
child->layout(addSpacing, _scale, dropRects);
2018-05-10 19:50:31 +02:00
x += child->geometry.width();
}
} break;
};
}
static Type toContainerType(Direction _dir)
{
return _dir == Direction::Left || _dir == Direction::Right ? Type::HorizontalContainer
: Type::VerticalContainer;
}
friend class SplitContainer;
};
2018-05-10 23:58:07 +02:00
class DropOverlay : public QWidget
{
public:
DropOverlay(SplitContainer *_parent = nullptr)
: QWidget(_parent)
, parent(_parent)
, mouseOverPoint(-10000, -10000)
{
this->setMouseTracking(true);
this->setAcceptDrops(true);
}
void setRects(std::vector<SplitContainer::DropRect> _rects)
{
this->rects = std::move(_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();
}
private:
std::vector<DropRect> rects;
QPoint mouseOverPoint;
SplitContainer *parent;
};
SplitContainer(Notebook *parent, NotebookTab *_tab);
2018-05-10 19:50:31 +02:00
void appendNewSplit(bool openChannelNameDialog);
void appendSplit(Split *split);
void insertSplit(Split *split, const Position &position);
void insertSplit(Split *split, Direction direction, Split *relativeTo);
void insertSplit(Split *split, Direction direction, Node *relativeTo = nullptr);
Position releaseSplit(Split *split);
Position deleteSplit(Split *split);
int getSplitCount()
{
return 0;
}
const std::vector<Split *> getSplits() const
{
return this->splits;
}
void refreshTabTitle();
2017-01-01 02:30:42 +01:00
2017-04-12 17:46:44 +02:00
NotebookTab *getTab() const;
2018-05-10 19:50:31 +02:00
const Node *getBaseNode()
{
return &this->baseNode;
}
2018-05-10 19:50:31 +02:00
static bool isDraggingSplit;
static Split *draggingSplit;
// static Position dragOriginalPosition;
2017-11-12 17:21:50 +01:00
2016-12-30 18:00:25 +01:00
protected:
2018-05-10 19:50:31 +02:00
// bool eventFilter(QObject *object, QEvent *event) override;
2018-04-07 12:53:10 +02:00
void paintEvent(QPaintEvent *event) override;
2017-01-01 02:30:42 +01:00
2018-05-10 19:50:31 +02:00
// void showEvent(QShowEvent *event) override;
2018-05-10 19:50:31 +02:00
// void enterEvent(QEvent *event) override;
2018-05-10 23:58:07 +02:00
void leaveEvent(QEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
2018-04-07 12:53:10 +02:00
void mouseReleaseEvent(QMouseEvent *event) override;
2018-04-07 12:53:10 +02:00
void dragEnterEvent(QDragEnterEvent *event) override;
void dragMoveEvent(QDragMoveEvent *event) override;
void dragLeaveEvent(QDragLeaveEvent *event) override;
void dropEvent(QDropEvent *event) override;
2017-01-01 02:30:42 +01:00
2018-05-10 19:50:31 +02:00
void resizeEvent(QResizeEvent *event) override;
2017-04-12 17:46:44 +02:00
private:
2017-01-11 18:52:09 +01:00
struct DropRegion {
2017-01-01 02:30:42 +01:00
QRect rect;
std::pair<int, int> position;
DropRegion(QRect rect, std::pair<int, int> position)
{
this->rect = rect;
this->position = position;
}
};
2018-05-10 19:50:31 +02:00
std::vector<DropRect> dropRects;
std::vector<DropRegion> dropRegions;
NotebookPageDropPreview dropPreview;
2018-05-10 23:58:07 +02:00
DropOverlay overlay;
QPoint mouseOverPoint;
2018-05-10 19:50:31 +02:00
void layout();
2017-01-01 02:30:42 +01:00
2018-05-10 19:50:31 +02:00
Node baseNode;
2017-04-12 17:46:44 +02:00
2018-05-10 19:50:31 +02:00
NotebookTab *tab;
std::vector<Split *> splits;
2018-05-10 19:50:31 +02:00
bool isDragging = false;
2017-01-01 02:30:42 +01:00
2018-05-10 19:50:31 +02:00
// struct {
// QVBoxLayout parentLayout;
2018-05-10 19:50:31 +02:00
// QHBoxLayout hbox;
// } ui;
2017-01-29 12:26:22 +01:00
2018-05-10 19:50:31 +02:00
// std::vector<Split *> splits;
2018-05-10 19:50:31 +02:00
// void setPreviewRect(QPoint mousePos);
2018-05-10 19:50:31 +02:00
// std::pair<int, int> getChatPosition(const Split *chatWidget);
2018-05-10 19:50:31 +02:00
// Split *createChatWidget();
2016-12-29 17:31:07 +01:00
};
2017-04-14 17:52:22 +02:00
} // namespace widgets
} // namespace chatterino