#pragma once #include "common/WindowDescriptors.hpp" #include "widgets/BaseWidget.hpp" #include #include #include #include #include #include #include #include #include #include #include #include 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 enum Direction { Left, Above, Right, Below }; struct Position final { private: Position() = default; Position(Node *relativeNode, Direction direcion) : relativeNode_(relativeNode) , direction_(direcion) { } Node *relativeNode_; Direction direction_; 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 Type { EmptyRoot, _Split, VerticalContainer, HorizontalContainer }; Type getType(); Split *getSplit(); Node *getParent(); qreal getHorizontalFlex(); qreal getVerticalFlex(); const std::vector> &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 &dropRects_, std::vector &resizeRects); static Type toContainerType(Direction _dir); Type type_; Split *split_; Node *preferedFocusTarget_; Node *parent_; QRectF geometry_; qreal flexH_ = 1; qreal flexV_ = 1; std::vector> children_; friend class SplitContainer; }; private: class DropOverlay final : public QWidget { public: DropOverlay(SplitContainer *_parent = nullptr); void setRects(std::vector _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 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); 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); Split *getSelectedSplit() const; Position releaseSplit(Split *split); Position deleteSplit(Split *split); void selectNextSplit(Direction direction); void setSelected(Split *selected_); int getSplitCount(); const std::vector getSplits() const; void refreshTab(); NotebookTab *getTab() const; Node *getBaseNode(); void setTab(NotebookTab *tab_); void hideResizeHandles(); void resetMouseStatus(); static bool isDraggingSplit; static Split *draggingSplit; 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 *node); 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(); struct DropRegion { QRect rect; std::pair position; DropRegion(QRect rect, std::pair position) { this->rect = rect; this->position = position; } }; std::vector dropRects_; std::vector dropRegions_; DropOverlay overlay_; std::vector> resizeHandles_; QPoint mouseOverPoint_; Node baseNode_; Split *selected_{}; Split *topRight_{}; bool disableLayouting_{}; NotebookTab *tab_; std::vector splits_; std::unordered_map connectionsPerSplit_; pajlada::Signals::SignalHolder signalHolder_; bool isDragging_ = false; }; } // namespace chatterino