Files
chatterino2/src/widgets/splits/SplitContainer.hpp
nerix 1043f9f803 Remove Unnecessary Includes in Headers (#4275)
* refactor: remove unnecessary includes in headers

* fix: formatting

* chore: changelog

* fix: scrollbar

* fix: suggestions and old appbase remains

* fix: suggestion

* fix: missing Qt forward declarations

* fix: another qt include

* fix: includes for precompiled-headers=off

* Add missing `<memory>` includes

* Add missing `#pragma once`

* Fix tests

Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
2022-12-31 14:41:01 +00:00

281 lines
7.7 KiB
C++

#pragma once
#include "common/WindowDescriptors.hpp"
#include "widgets/BaseWidget.hpp"
#include <pajlada/signals/signal.hpp>
#include <pajlada/signals/signalholder.hpp>
#include <QDragEnterEvent>
#include <QRect>
#include <QWidget>
#include <algorithm>
#include <optional>
#include <unordered_map>
#include <variant>
#include <vector>
class QJsonObject;
namespace chatterino {
class Split;
class NotebookTab;
class Notebook;
//
// 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 final : public BaseWidget
{
Q_OBJECT
public:
struct Node;
// fourtf: !!! preserve the order of left, up, right and down
// It's important to preserve since we cast from an int to this enum, so 0 = left, 1 = above etc
enum Direction { Left, Above, Right, Below };
struct Position final {
private:
Position() = default;
Position(Node *relativeNode, Direction direcion)
: relativeNode_(relativeNode)
, direction_(direcion)
{
}
Node *relativeNode_{nullptr};
Direction direction_{Direction::Right};
friend struct Node;
friend class SplitContainer;
};
private:
struct DropRect final {
QRect rect;
Position position;
DropRect(const QRect &_rect, const Position &_position)
: rect(_rect)
, position(_position)
{
}
};
struct ResizeRect final {
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 class Type {
EmptyRoot,
Split,
VerticalContainer,
HorizontalContainer,
};
Type getType() const;
Split *getSplit() const;
Node *getParent() const;
qreal getHorizontalFlex() const;
qreal getVerticalFlex() const;
const std::vector<std::unique_ptr<Node>> &getChildren();
private:
Node();
Node(Split *_split, Node *_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);
// Clamps the flex values ensuring they're never below 0
void clamp();
static Type toContainerType(Direction _dir);
Type type_;
Split *split_;
Node *preferedFocusTarget_;
Node *parent_;
QRectF geometry_;
qreal flexH_ = 1;
qreal flexV_ = 1;
std::vector<std::unique_ptr<Node>> children_;
friend class SplitContainer;
};
private:
class DropOverlay final : public QWidget
{
public:
DropOverlay(SplitContainer *_parent = nullptr);
void setRects(std::vector<SplitContainer::DropRect> _rects);
pajlada::Signals::NoArgSignal dragEnded;
protected:
void paintEvent(QPaintEvent *event) override;
void dragEnterEvent(QDragEnterEvent *event) override;
void dragMoveEvent(QDragMoveEvent *event) override;
void dragLeaveEvent(QDragLeaveEvent *event) override;
void dropEvent(QDropEvent *event) override;
private:
std::vector<DropRect> rects_;
QPoint mouseOverPoint_;
SplitContainer *parent_;
};
class ResizeHandle final : 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;
void mouseDoubleClickEvent(QMouseEvent *event) override;
friend class SplitContainer;
private:
void resetFlex();
bool vertical_;
bool isMouseDown_ = false;
};
public:
SplitContainer(Notebook *parent);
Split *appendNewSplit(bool openChannelNameDialog);
struct InsertOptions {
/// Position must be set alone, as if it's set it will override direction & relativeNode with its underlying values
std::optional<Position> position{};
/// Will be used to figure out the relative node, so relative node or position must not be set if using this
Split *relativeSplit{nullptr};
Node *relativeNode{nullptr};
std::optional<Direction> direction{};
};
// Insert split into the base node of this container
// Default values for each field must be specified due to these bugs:
// - https://bugs.llvm.org/show_bug.cgi?id=36684
// - https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96645
void insertSplit(Split *split, InsertOptions &&options = InsertOptions{
.position = std::nullopt,
.relativeSplit = nullptr,
.relativeNode = nullptr,
.direction = std::nullopt,
});
// Returns a pointer to the selected split
Split *getSelectedSplit() const;
Position releaseSplit(Split *split);
Position deleteSplit(Split *split);
void selectNextSplit(Direction direction);
void setSelected(Split *split);
std::vector<Split *> getSplits() const;
void refreshTab();
NotebookTab *getTab() const;
Node *getBaseNode();
void setTab(NotebookTab *tab);
void hideResizeHandles();
void resetMouseStatus();
void applyFromDescriptor(const NodeDescriptor &rootNode);
void popup();
protected:
void paintEvent(QPaintEvent *event) override;
void focusInEvent(QFocusEvent *event) override;
void leaveEvent(QEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void dragEnterEvent(QDragEnterEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
private:
void applyFromDescriptorRecursively(const NodeDescriptor &rootNode,
Node *baseNode);
void layout();
void selectSplitRecursive(Node *node, Direction direction);
void focusSplitRecursive(Node *node);
void setPreferedTargetRecursive(Node *node);
void addSplit(Split *split);
Split *getTopRightSplit(Node &node);
void refreshTabTitle();
void refreshTabLiveStatus();
std::vector<DropRect> dropRects_;
DropOverlay overlay_;
std::vector<std::unique_ptr<ResizeHandle>> resizeHandles_;
QPoint mouseOverPoint_;
Node baseNode_;
Split *selected_{};
Split *topRight_{};
bool disableLayouting_{};
NotebookTab *tab_;
std::vector<Split *> splits_;
std::unordered_map<Split *, pajlada::Signals::SignalHolder>
connectionsPerSplit_;
pajlada::Signals::SignalHolder signalHolder_;
// Specifies whether the user is currently dragging something over this container
bool isDragging_ = false;
};
} // namespace chatterino