From c1a3764f446ee47ac5c02f6885f5ee88c66cabdd Mon Sep 17 00:00:00 2001 From: fourtf Date: Thu, 10 May 2018 19:50:31 +0200 Subject: [PATCH] added basic new layout --- src/singletons/windowmanager.cpp | 22 +- src/widgets/helper/notebookbutton.cpp | 6 +- src/widgets/helper/notebooktab.cpp | 2 +- src/widgets/helper/splitheader.cpp | 14 +- src/widgets/helper/splitinput.cpp | 49 +- src/widgets/helper/splitoverlay.cpp | 12 + src/widgets/helper/splitoverlay.hpp | 3 +- src/widgets/notebook.cpp | 20 +- src/widgets/split.cpp | 98 ++- src/widgets/split.hpp | 31 +- src/widgets/splitcontainer.cpp | 962 +++++++++++++++----------- src/widgets/splitcontainer.hpp | 465 ++++++++++++- 12 files changed, 1101 insertions(+), 583 deletions(-) diff --git a/src/singletons/windowmanager.cpp b/src/singletons/windowmanager.cpp index fa9499371..ec0778950 100644 --- a/src/singletons/windowmanager.cpp +++ b/src/singletons/windowmanager.cpp @@ -209,7 +209,7 @@ void WindowManager::initialize() QJsonObject split_obj = split_val.toObject(); split->setChannel(this->decodeChannel(split_obj)); - tab->addToLayout(split, std::make_pair(colNr, 10000000)); + // tab->addToLayout(split, std::make_pair(colNr, 10000000)); } colNr++; } @@ -271,20 +271,20 @@ void WindowManager::save() // splits QJsonArray columns_arr; - std::vector> columns = tab->getColumns(); + // std::vector> columns = tab->getColumns(); - for (std::vector &cells : columns) { - QJsonArray cells_arr; + // for (std::vector &cells : columns) { + // QJsonArray cells_arr; - for (widgets::Split *cell : cells) { - QJsonObject cell_obj; + // for (widgets::Split *cell : cells) { + // QJsonObject cell_obj; - this->encodeChannel(cell->getIndirectChannel(), cell_obj); + // this->encodeChannel(cell->getIndirectChannel(), cell_obj); - cells_arr.append(cell_obj); - } - columns_arr.append(cells_arr); - } + // cells_arr.append(cell_obj); + // } + // columns_arr.append(cells_arr); + // } tab_obj.insert("splits", columns_arr); tabs_arr.append(tab_obj); diff --git a/src/widgets/helper/notebookbutton.cpp b/src/widgets/helper/notebookbutton.cpp index 48d977164..a99fcc1ec 100644 --- a/src/widgets/helper/notebookbutton.cpp +++ b/src/widgets/helper/notebookbutton.cpp @@ -139,10 +139,10 @@ void NotebookButton::dropEvent(QDropEvent *event) Notebook *notebook = dynamic_cast(this->parentWidget()); if (notebook != nuuls) { - SplitContainer *tab = notebook->addNewPage(); + SplitContainer *page = notebook->addNewPage(); - SplitContainer::draggingSplit->setParent(tab); - tab->addToLayout(SplitContainer::draggingSplit); + SplitContainer::draggingSplit->setParent(page); + page->appendSplit(SplitContainer::draggingSplit); } } } diff --git a/src/widgets/helper/notebooktab.cpp b/src/widgets/helper/notebooktab.cpp index 20774bfd1..8ff070645 100644 --- a/src/widgets/helper/notebooktab.cpp +++ b/src/widgets/helper/notebooktab.cpp @@ -429,7 +429,7 @@ NotebookTab::NotebookTab(Notebook *_notebook) QString newTitle = d.getText(); if (newTitle.isEmpty()) { this->useDefaultTitle = true; - this->page->refreshTitle(); + this->page->refreshTabTitle(); } else { this->useDefaultTitle = false; this->setTitle(newTitle); diff --git a/src/widgets/helper/splitheader.cpp b/src/widgets/helper/splitheader.cpp index 42fef1c01..2b278dea0 100644 --- a/src/widgets/helper/splitheader.cpp +++ b/src/widgets/helper/splitheader.cpp @@ -241,13 +241,13 @@ void SplitHeader::mouseMoveEvent(QMouseEvent *event) tooltipWidget->show(); } - if (this->dragging) { - if (std::abs(this->dragStart.x() - event->pos().x()) > 12 || - std::abs(this->dragStart.y() - event->pos().y()) > 12) { - this->split->drag(); - this->dragging = false; - } - } + // if (this->dragging) { + // if (std::abs(this->dragStart.x() - event->pos().x()) > 12 || + // std::abs(this->dragStart.y() - event->pos().y()) > 12) { + // this->split->drag(); + // this->dragging = false; + // } + // } } void SplitHeader::leaveEvent(QEvent *event) diff --git a/src/widgets/helper/splitinput.cpp b/src/widgets/helper/splitinput.cpp index 7d595acdd..2a086df64 100644 --- a/src/widgets/helper/splitinput.cpp +++ b/src/widgets/helper/splitinput.cpp @@ -163,15 +163,16 @@ void SplitInput::installKeyPressedEvent() return; } if (event->modifiers() == Qt::AltModifier) { - SplitContainer *page = - static_cast(this->chatWidget->parentWidget()); + // SplitContainer *page = + // static_cast(this->chatWidget->parentWidget()); - int reqX = page->currentX; - int reqY = page->lastRequestedY[reqX] - 1; + // page->requestFocus(); + // int reqX = page->currentX; + // int reqY = page->lastRequestedY[reqX] - 1; - qDebug() << "Alt+Down to" << reqX << "/" << reqY; + // qDebug() << "Alt+Down to" << reqX << "/" << reqY; - page->requestFocus(reqX, reqY); + // page->requestFocus(reqX, reqY); } else { if (this->prevMsg.size() && this->prevIndex) { if (this->prevIndex == (this->prevMsg.size())) { @@ -191,15 +192,15 @@ void SplitInput::installKeyPressedEvent() return; } if (event->modifiers() == Qt::AltModifier) { - SplitContainer *page = - static_cast(this->chatWidget->parentWidget()); + // SplitContainer *page = + // static_cast(this->chatWidget->parentWidget()); - int reqX = page->currentX; - int reqY = page->lastRequestedY[reqX] + 1; + // int reqX = page->currentX; + // int reqY = page->lastRequestedY[reqX] + 1; - qDebug() << "Alt+Down to" << reqX << "/" << reqY; + // qDebug() << "Alt+Down to" << reqX << "/" << reqY; - page->requestFocus(reqX, reqY); + // page->requestFocus(reqX, reqY); } else { if (this->prevIndex != (this->prevMsg.size() - 1) && this->prevIndex != this->prevMsg.size()) { @@ -216,27 +217,27 @@ void SplitInput::installKeyPressedEvent() } } else if (event->key() == Qt::Key_Left) { if (event->modifiers() == Qt::AltModifier) { - SplitContainer *page = - static_cast(this->chatWidget->parentWidget()); + // SplitContainer *page = + // static_cast(this->chatWidget->parentWidget()); - int reqX = page->currentX - 1; - int reqY = page->lastRequestedY[reqX]; + // int reqX = page->currentX - 1; + // int reqY = page->lastRequestedY[reqX]; - qDebug() << "Alt+Left to" << reqX << "/" << reqY; + // qDebug() << "Alt+Left to" << reqX << "/" << reqY; - page->requestFocus(reqX, reqY); + // page->requestFocus(reqX, reqY); } } else if (event->key() == Qt::Key_Right) { if (event->modifiers() == Qt::AltModifier) { - SplitContainer *page = - static_cast(this->chatWidget->parentWidget()); + // SplitContainer *page = + // static_cast(this->chatWidget->parentWidget()); - int reqX = page->currentX + 1; - int reqY = page->lastRequestedY[reqX]; + // int reqX = page->currentX + 1; + // int reqY = page->lastRequestedY[reqX]; - qDebug() << "Alt+Right to" << reqX << "/" << reqY; + // qDebug() << "Alt+Right to" << reqX << "/" << reqY; - page->requestFocus(reqX, reqY); + // page->requestFocus(reqX, reqY); } } else if (event->key() == Qt::Key_Tab) { if (event->modifiers() == Qt::ControlModifier) { diff --git a/src/widgets/helper/splitoverlay.cpp b/src/widgets/helper/splitoverlay.cpp index 0943111a6..fd6f23240 100644 --- a/src/widgets/helper/splitoverlay.cpp +++ b/src/widgets/helper/splitoverlay.cpp @@ -9,6 +9,7 @@ #include "application.hpp" #include "singletons/resourcemanager.hpp" #include "widgets/split.hpp" +#include "widgets/splitcontainer.hpp" namespace chatterino { namespace widgets { @@ -146,6 +147,17 @@ bool SplitOverlay::ButtonEventFilter::eventFilter(QObject *watched, QEvent *even this->parent->split->drag(); } return true; + } else { + SplitContainer *container = this->parent->split->getContainer(); + + if (container != nullptr) { + auto *_split = new Split(container); + container->insertSplit( + _split, + (SplitContainer::Direction)(this->hoveredElement + SplitContainer::Left - + SplitLeft), + this->parent->split); + } } } break; } diff --git a/src/widgets/helper/splitoverlay.hpp b/src/widgets/helper/splitoverlay.hpp index 31b7a4b42..de24dac6c 100644 --- a/src/widgets/helper/splitoverlay.hpp +++ b/src/widgets/helper/splitoverlay.hpp @@ -17,7 +17,8 @@ protected: void paintEvent(QPaintEvent *event) override; private: - enum HoveredElement { None, SplitMove, SplitLeft, SplitRight, SplitUp, SplitDown }; + // fourtf: !!! preserve the order of left, up, right and down + enum HoveredElement { None, SplitMove, SplitLeft, SplitUp, SplitRight, SplitDown }; HoveredElement hoveredElement = None; Split *split; diff --git a/src/widgets/notebook.cpp b/src/widgets/notebook.cpp index 06e21b798..8ac53af8d 100644 --- a/src/widgets/notebook.cpp +++ b/src/widgets/notebook.cpp @@ -391,13 +391,13 @@ Notebook::Notebook(Window *parent, bool _showButtons) // Window-wide hotkeys // CTRL+T: Create new split in selected notebook page - CreateWindowShortcut(this, "CTRL+T", [this]() { - if (this->selectedPage == nullptr) { - return; - } + // CreateWindowShortcut(this, "CTRL+T", [this]() { + // if (this->selectedPage == nullptr) { + // return; + // } - this->selectedPage->addChat(true); - }); + // this->selectedPage->addChat(true); + // }); } SplitContainer *Notebook::addNewPage(bool select) @@ -420,7 +420,7 @@ SplitContainer *Notebook::addNewPage(bool select) void Notebook::removePage(SplitContainer *page) { - if (page->splitCount() > 0 && closeConfirmDialog.exec() != QMessageBox::Yes) { + if (page->getSplitCount() > 0 && closeConfirmDialog.exec() != QMessageBox::Yes) { return; } @@ -484,7 +484,8 @@ void Notebook::select(SplitContainer *page) if (this->selectedPage != nullptr) { this->selectedPage->setHidden(true); this->selectedPage->getTab()->setSelected(false); - for (auto split : this->selectedPage->getSplits()) { + + for (Split *split : this->selectedPage->getSplits()) { split->updateLastReadMessage(); } } @@ -663,8 +664,7 @@ void Notebook::settingsButtonClicked() void Notebook::usersButtonClicked() { auto app = getApp(); - app->windows->showAccountSelectPopup( - this->mapToGlobal(this->userButton.rect().bottomRight())); + app->windows->showAccountSelectPopup(this->mapToGlobal(this->userButton.rect().bottomRight())); } void Notebook::addPageButtonClicked() diff --git a/src/widgets/split.cpp b/src/widgets/split.cpp index 441a9a08d..4d8681a05 100644 --- a/src/widgets/split.cpp +++ b/src/widgets/split.cpp @@ -135,6 +135,8 @@ Split::Split(QWidget *parent) // this->overlay->hide(); // } }); + + this->setAcceptDrops(true); } Split::~Split() @@ -144,6 +146,16 @@ Split::~Split() this->indirectChannelChangedConnection.disconnect(); } +ChannelView &Split::getChannelView() +{ + return this->view; +} + +SplitContainer *Split::getContainer() +{ + return this->container; +} + bool Split::isInContainer() const { return this->container != nullptr; @@ -185,30 +197,6 @@ void Split::setChannel(IndirectChannel newChannel) this->channelChanged.invoke(); } -void Split::setFlexSizeX(double x) -{ - // this->flexSizeX = x; - // this->parentPage->updateFlexValues(); -} - -double Split::getFlexSizeX() -{ - // return this->flexSizeX; - return 1; -} - -void Split::setFlexSizeY(double y) -{ - // this->flexSizeY = y; - // this->parentPage.updateFlexValues(); -} - -double Split::getFlexSizeY() -{ - // return this->flexSizeY; - return 1; -} - void Split::setModerationMode(bool value) { if (value != this->moderationMode) { @@ -236,7 +224,7 @@ void Split::showChangeChannelPopup(const char *dialogTitle, bool empty, if (dialog->hasSeletedChannel()) { this->setChannel(dialog->getSelectedChannel()); if (this->isInContainer()) { - this->container->refreshTitle(); + this->container->refreshTabTitle(); } } @@ -284,9 +272,9 @@ void Split::mouseMoveEvent(QMouseEvent *event) void Split::mousePressEvent(QMouseEvent *event) { - if (event->buttons() == Qt::LeftButton && event->modifiers() & Qt::AltModifier) { - this->drag(); - } + // if (event->buttons() == Qt::LeftButton && event->modifiers() & Qt::AltModifier) { + // this->drag(); + // } } void Split::keyPressEvent(QKeyEvent *event) @@ -338,19 +326,40 @@ void Split::handleModifiers(QEvent *event, Qt::KeyboardModifiers modifiers) } } +void Split::dragEnterEvent(QDragEnterEvent *event) +{ + event->acceptProposedAction(); + this->isDragging = true; + QTimer::singleShot(1, [this] { this->overlay->show(); }); +} + +void Split::dragLeaveEvent(QDragLeaveEvent *event) +{ + this->overlay->hide(); + this->isDragging = false; +} + +void Split::dragMoveEvent(QDragMoveEvent *event) +{ + event->acceptProposedAction(); +} + +void Split::dropEvent(QDropEvent *event) +{ +} + /// Slots void Split::doAddSplit() { if (this->container) { - this->container->addChat(true); + this->container->appendNewSplit(true); } } void Split::doCloseSplit() { if (this->container) { - this->container->removeFromLayout(this); - deleteLater(); + this->container->deleteSplit(this); } } @@ -373,7 +382,7 @@ void Split::doPopup() new Split(static_cast(window.getNotebook().getOrAddSelectedPage())); split->setChannel(this->getIndirectChannel()); - window.getNotebook().getOrAddSelectedPage()->addToLayout(split); + window.getNotebook().getOrAddSelectedPage()->appendSplit(split); window.show(); } @@ -536,26 +545,6 @@ static Iter select_randomly(Iter start, Iter end) return select_randomly(start, end, gen); } -void Split::doIncFlexX() -{ - this->setFlexSizeX(this->getFlexSizeX() * 1.2); -} - -void Split::doDecFlexX() -{ - this->setFlexSizeX(this->getFlexSizeX() * (1 / 1.2)); -} - -void Split::doIncFlexY() -{ - this->setFlexSizeY(this->getFlexSizeY() * 1.2); -} - -void Split::doDecFlexY() -{ - this->setFlexSizeY(this->getFlexSizeY() * (1 / 1.2)); -} - void Split::drag() { auto container = dynamic_cast(this->parentWidget()); @@ -564,7 +553,7 @@ void Split::drag() SplitContainer::isDraggingSplit = true; SplitContainer::draggingSplit = this; - auto originalLocation = container->removeFromLayout(this); + auto originalLocation = container->releaseSplit(this); QDrag *drag = new QDrag(this); QMimeData *mimeData = new QMimeData; @@ -576,7 +565,8 @@ void Split::drag() Qt::DropAction dropAction = drag->exec(Qt::MoveAction); if (dropAction == Qt::IgnoreAction) { - container->addToLayout(this, originalLocation); + container->insertSplit(this, + originalLocation); // SplitContainer::dragOriginalPosition); } SplitContainer::isDraggingSplit = false; diff --git a/src/widgets/split.hpp b/src/widgets/split.hpp index e184cefc9..450623d0b 100644 --- a/src/widgets/split.hpp +++ b/src/widgets/split.hpp @@ -39,9 +39,6 @@ class Split : public BaseWidget, pajlada::Signals::SignalHolder Q_OBJECT - static pajlada::Signals::Signal altPressedStatusChanged; - static bool altPressesStatus; - public: explicit Split(SplitContainer *parent); explicit Split(QWidget *parent); @@ -50,20 +47,13 @@ public: pajlada::Signals::NoArgSignal channelChanged; - ChannelView &getChannelView() - { - return this->view; - } + ChannelView &getChannelView(); + SplitContainer *getContainer(); IndirectChannel getIndirectChannel(); ChannelPtr getChannel(); void setChannel(IndirectChannel newChannel); - void setFlexSizeX(double x); - double getFlexSizeX(); - void setFlexSizeY(double y); - double getFlexSizeY(); - void setModerationMode(bool value); bool getModerationMode() const; @@ -79,6 +69,9 @@ public: bool isInContainer() const; + static pajlada::Signals::Signal altPressedStatusChanged; + static bool altPressesStatus; + protected: void paintEvent(QPaintEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; @@ -89,6 +82,11 @@ protected: void enterEvent(QEvent *event) override; void leaveEvent(QEvent *event) override; + void dragEnterEvent(QDragEnterEvent *event) override; + void dragLeaveEvent(QDragLeaveEvent *event) override; + void dragMoveEvent(QDragMoveEvent *event) override; + void dropEvent(QDropEvent *event) override; + private: SplitContainer *container; IndirectChannel channel; @@ -99,12 +97,10 @@ private: SplitInput input; SplitOverlay *overlay; - double flexSizeX = 1; - double flexSizeY = 1; - bool moderationMode = false; bool isMouseOver = false; + bool isDragging = false; pajlada::Signals::Connection channelIDChangedConnection; pajlada::Signals::Connection usermodeChangedConnection; @@ -151,11 +147,6 @@ public slots: // Open viewer list of the channel void doOpenViewerList(); - - void doIncFlexX(); - void doDecFlexX(); - void doIncFlexY(); - void doDecFlexY(); }; } // namespace widgets diff --git a/src/widgets/splitcontainer.cpp b/src/widgets/splitcontainer.cpp index 578e0ad69..0cf7f2c99 100644 --- a/src/widgets/splitcontainer.cpp +++ b/src/widgets/splitcontainer.cpp @@ -24,7 +24,7 @@ namespace widgets { bool SplitContainer::isDraggingSplit = false; Split *SplitContainer::draggingSplit = nullptr; -std::pair SplitContainer::dropPosition = std::pair(-1, -1); +// SplitContainer::Position SplitContainer::dragOriginalPosition; SplitContainer::SplitContainer(Notebook *parent, NotebookTab *_tab) : BaseWidget(parent) @@ -33,182 +33,23 @@ SplitContainer::SplitContainer(Notebook *parent, NotebookTab *_tab) { this->tab->page = this; - this->setLayout(&this->ui.parentLayout); + // this->setLayout(&this->ui.parentLayout); + + // this->setHidden(true); + // this->setAcceptDrops(true); + + // this->ui.parentLayout.addSpacing(1); + // this->ui.parentLayout.addLayout(&this->ui.hbox); + // this->ui.parentLayout.setMargin(0); + + // this->ui.hbox.setSpacing(1); + // this->ui.hbox.setMargin(0); + + this->refreshTabTitle(); + + this->managedConnect(Split::altPressedStatusChanged, [this](auto) { this->layout(); }); - this->setHidden(true); this->setAcceptDrops(true); - - this->ui.parentLayout.addSpacing(1); - this->ui.parentLayout.addLayout(&this->ui.hbox); - this->ui.parentLayout.setMargin(0); - - this->ui.hbox.setSpacing(1); - this->ui.hbox.setMargin(0); - - this->refreshTitle(); -} - -void SplitContainer::updateFlexValues() -{ - for (int i = 0; i < this->ui.hbox.count(); i++) { - QVBoxLayout *vbox = (QVBoxLayout *)ui.hbox.itemAt(i)->layout(); - - if (vbox->count() != 0) { - ui.hbox.setStretch(i, (int)(1000 * ((Split *)vbox->itemAt(0))->getFlexSizeX())); - } - } -} - -int SplitContainer::splitCount() const -{ - return this->splits.size(); -} - -std::pair SplitContainer::removeFromLayout(Split *widget) -{ - widget->getChannelView().tabHighlightRequested.disconnectAll(); - - // remove reference to chat widget from chatWidgets vector - auto it = std::find(std::begin(this->splits), std::end(this->splits), widget); - if (it != std::end(this->splits)) { - this->splits.erase(it); - - this->refreshTitle(); - } - - Split *neighbouringSplit = nullptr; - - // Position the split was found at - int positionX = -1, positionY = -1; - - bool removed = false; - - QVBoxLayout *layoutToRemove = nullptr; - - // Find widget in box, remove it, return its position - for (int i = 0; i < this->ui.hbox.count(); ++i) { - auto vbox = static_cast(this->ui.hbox.itemAt(i)); - - auto vboxCount = vbox->count(); - - for (int j = 0; j < vboxCount; ++j) { - if (vbox->itemAt(j)->widget() != widget) { - neighbouringSplit = dynamic_cast(vbox->itemAt(j)->widget()); - - if (removed && neighbouringSplit != nullptr) { - // The widget we searched for has been found, and we have a split to switch - // focus to - break; - } - - continue; - } - - removed = true; - positionX = i; - - // Remove split from box - widget->setParent(nullptr); - - if (vbox->count() == 0) { - // The split was the last item remaining in the vbox - // Remove the vbox once all iteration is done - layoutToRemove = vbox; - positionY = -1; - break; - } - - // Don't break here yet, we want to keep iterating this vbox if possible to find the - // closest still-alive neighbour that we can switch focus to - positionY = j; - - --j; - --vboxCount; - } - - if (removed && neighbouringSplit != nullptr) { - // The widget we searched for has been found, and we have a split to switch focus to - break; - } - } - - if (removed) { - if (layoutToRemove != nullptr) { - // The split we removed was the last split in its box. Remove the box - // We delay the removing of the box so we can keep iterating over hbox safely - this->ui.hbox.removeItem(layoutToRemove); - delete layoutToRemove; - } - - if (neighbouringSplit != nullptr) { - // We found a neighbour split we can switch focus to - neighbouringSplit->giveFocus(Qt::MouseFocusReason); - } - } - - return std::make_pair(positionX, positionY); -} - -void SplitContainer::addToLayout(Split *widget, std::pair position) -{ - this->splits.push_back(widget); - widget->getChannelView().tabHighlightRequested.connect( - [this](HighlightState state) { this->tab->setHighlightState(state); }); - - this->refreshTitle(); - - widget->giveFocus(Qt::MouseFocusReason); - - // add vbox at the end - if (position.first < 0 || position.first >= this->ui.hbox.count()) { - auto vbox = new QVBoxLayout(); - vbox->addWidget(widget); - - this->ui.hbox.addLayout(vbox, 1); - - this->refreshCurrentFocusCoordinates(); - return; - } - - // insert vbox - if (position.second == -1) { - auto vbox = new QVBoxLayout(); - vbox->addWidget(widget); - - this->ui.hbox.insertLayout(position.first, vbox, 1); - this->refreshCurrentFocusCoordinates(); - return; - } - - // add to existing vbox - auto vbox = static_cast(this->ui.hbox.itemAt(position.first)); - - vbox->insertWidget(std::max(0, std::min(vbox->count(), position.second)), widget); - - this->refreshCurrentFocusCoordinates(); -} - -const std::vector &SplitContainer::getSplits() const -{ - return this->splits; -} - -std::vector> SplitContainer::getColumns() const -{ - std::vector> columns; - - for (int i = 0; i < this->ui.hbox.count(); i++) { - std::vector cells; - - QLayout *vbox = this->ui.hbox.itemAt(i)->layout(); - for (int j = 0; j < vbox->count(); j++) { - cells.push_back(dynamic_cast(vbox->itemAt(j)->widget())); - } - - columns.push_back(cells); - } - - return columns; } NotebookTab *SplitContainer::getTab() const @@ -216,241 +57,126 @@ NotebookTab *SplitContainer::getTab() const return this->tab; } -void SplitContainer::addChat(bool openChannelNameDialog) +void SplitContainer::appendNewSplit(bool openChannelNameDialog) { - Split *w = this->createChatWidget(); - this->addToLayout(w, std::pair(-1, -1)); + Split *split = new Split(this); + this->appendSplit(split); if (openChannelNameDialog) { - w->showChangeChannelPopup("Open channel name", true, [=](bool ok) { + split->showChangeChannelPopup("Open channel name", true, [=](bool ok) { if (!ok) { - this->removeFromLayout(w); - delete w; + this->deleteSplit(split); } }); } } -void SplitContainer::refreshCurrentFocusCoordinates(bool alsoSetLastRequested) +void SplitContainer::appendSplit(Split *split) { - int setX = -1; - int setY = -1; - bool doBreak = false; - for (int x = 0; x < this->ui.hbox.count(); ++x) { - QLayoutItem *item = this->ui.hbox.itemAt(x); - if (item->isEmpty()) { - setX = x; - break; - } - QVBoxLayout *vbox = static_cast(item->layout()); - - for (int y = 0; y < vbox->count(); ++y) { - QLayoutItem *innerItem = vbox->itemAt(y); - - if (innerItem->isEmpty()) { - setX = x; - setY = y; - doBreak = true; - break; - } - - QWidget *w = innerItem->widget(); - if (w) { - Split *chatWidget = static_cast(w); - if (chatWidget->hasFocus()) { - setX = x; - setY = y; - doBreak = true; - break; - } - } - } - - if (doBreak) { - break; - } - } - - if (setX != -1) { - this->currentX = setX; - - if (setY != -1) { - this->currentY = setY; - - if (alsoSetLastRequested) { - this->lastRequestedY[setX] = setY; - } - } - } + this->insertSplit(split, Direction::Right); } -void SplitContainer::requestFocus(int requestedX, int requestedY) +void SplitContainer::insertSplit(Split *split, const Position &position) { - // XXX: Perhaps if we request an Y coordinate out of bounds, we shuold set all previously set - // requestedYs to 0 (if -1 is requested) or that x-coordinates vbox count (if requestedY >= - // currentvbox.count() is requested) - if (requestedX < 0 || requestedX >= this->ui.hbox.count()) { - return; - } - - QLayoutItem *item = this->ui.hbox.itemAt(requestedX); - QWidget *xW = item->widget(); - if (item->isEmpty()) { - qDebug() << "Requested hbox item " << requestedX << "is empty"; - if (xW) { - qDebug() << "but xW is not null"; - // TODO: figure out what to do here - } - return; - } - - QVBoxLayout *vbox = static_cast(item->layout()); - - if (requestedY < 0) { - requestedY = 0; - } else if (requestedY >= vbox->count()) { - requestedY = vbox->count() - 1; - } - - this->lastRequestedY[requestedX] = requestedY; - - QLayoutItem *innerItem = vbox->itemAt(requestedY); - - if (innerItem->isEmpty()) { - qDebug() << "Requested vbox item " << requestedY << "is empty"; - return; - } - - QWidget *w = innerItem->widget(); - if (w) { - Split *chatWidget = static_cast(w); - chatWidget->giveFocus(Qt::OtherFocusReason); - } + this->insertSplit(split, position.direction, position.relativeNode); } -void SplitContainer::enterEvent(QEvent *) +void SplitContainer::insertSplit(Split *split, Direction direction, Split *relativeTo) { - if (this->ui.hbox.count() == 0) { - this->setCursor(QCursor(Qt::PointingHandCursor)); + Node *node = this->baseNode.findNodeContainingSplit(relativeTo); + assert(node != nullptr); + + this->insertSplit(split, direction, node); +} + +void SplitContainer::insertSplit(Split *split, Direction direction, Node *relativeTo) +{ + if (relativeTo == nullptr) { + if (this->baseNode.type == Node::EmptyRoot) { + this->baseNode.setSplit(split); + } else if (this->baseNode.type == Node::_Split) { + this->baseNode.nestSplitIntoCollection(split, direction); + } else { + this->baseNode.insertSplitRelative(split, direction); + } } else { - this->setCursor(QCursor(Qt::ArrowCursor)); + assert(this->baseNode.isOrContainsNode(relativeTo)); + + relativeTo->insertSplitRelative(split, direction); } + + split->setParent(this); + split->show(); + this->splits.push_back(split); + + this->layout(); } -void SplitContainer::leaveEvent(QEvent *) +SplitContainer::Position SplitContainer::releaseSplit(Split *split) { + Node *node = this->baseNode.findNodeContainingSplit(split); + assert(node != nullptr); + + this->splits.erase(std::find(this->splits.begin(), this->splits.end(), split)); + split->setParent(nullptr); + Position position = node->releaseSplit(); + this->layout(); + return position; +} + +SplitContainer::Position SplitContainer::deleteSplit(Split *split) +{ + assert(split != nullptr); + + split->deleteLater(); + return releaseSplit(split); +} + +void SplitContainer::layout() +{ + this->baseNode.geometry = this->rect(); + + std::vector _dropRects; + this->baseNode.layout(Split::altPressesStatus | this->isDragging, _dropRects); + _dropRects.clear(); + this->baseNode.layout(Split::altPressesStatus | this->isDragging, _dropRects); + + this->dropRects = std::move(_dropRects); + this->update(); +} + +/// EVENTS +void SplitContainer::resizeEvent(QResizeEvent *event) +{ + BaseWidget::resizeEvent(event); + + this->layout(); } void SplitContainer::mouseReleaseEvent(QMouseEvent *event) { - if (this->ui.hbox.count() == 0 && event->button() == Qt::LeftButton) { - // "Add Chat" was clicked - this->addChat(true); + if (event->button() == Qt::LeftButton) { + if (this->splits.size() == 0) { + // "Add Chat" was clicked + this->appendNewSplit(true); - this->setCursor(QCursor(Qt::ArrowCursor)); - } -} - -void SplitContainer::dragEnterEvent(QDragEnterEvent *event) -{ - if (!event->mimeData()->hasFormat("chatterino/split")) - return; - - if (!isDraggingSplit) { - return; - } - - this->dropRegions.clear(); - - if (this->ui.hbox.count() == 0) { - this->dropRegions.push_back(DropRegion(rect(), std::pair(-1, -1))); - } else { - for (int i = 0; i < this->ui.hbox.count() + 1; ++i) { - this->dropRegions.push_back( - DropRegion(QRect(((i * 4 - 1) * width() / this->ui.hbox.count()) / 4, 0, - width() / this->ui.hbox.count() / 2 + 1, height() + 1), - std::pair(i, -1))); - } - - for (int i = 0; i < this->ui.hbox.count(); ++i) { - auto vbox = static_cast(this->ui.hbox.itemAt(i)); - - for (int j = 0; j < vbox->count() + 1; ++j) { - this->dropRegions.push_back(DropRegion( - QRect(i * width() / this->ui.hbox.count(), - ((j * 2 - 1) * height() / vbox->count()) / 2, - width() / this->ui.hbox.count() + 1, height() / vbox->count() + 1), - - std::pair(i, j))); + this->setCursor(QCursor(Qt::ArrowCursor)); + } else { + auto it = + std::find_if(this->dropRects.begin(), this->dropRects.end(), + [event](DropRect &rect) { return rect.rect.contains(event->pos()); }); + if (it != this->dropRects.end()) { + this->insertSplit(new Split(this), it->position); } } } - - setPreviewRect(event->pos()); - - event->acceptProposedAction(); -} - -void SplitContainer::dragMoveEvent(QDragMoveEvent *event) -{ - setPreviewRect(event->pos()); -} - -void SplitContainer::setPreviewRect(QPoint mousePos) -{ - for (DropRegion region : this->dropRegions) { - if (region.rect.contains(mousePos)) { - this->dropPreview.setBounds(region.rect); - - if (!this->dropPreview.isVisible()) { - this->dropPreview.setGeometry(this->rect()); - this->dropPreview.show(); - this->dropPreview.raise(); - } - - dropPosition = region.position; - - return; - } - } - - this->dropPreview.hide(); -} - -void SplitContainer::dragLeaveEvent(QDragLeaveEvent *event) -{ - this->dropPreview.hide(); -} - -void SplitContainer::dropEvent(QDropEvent *event) -{ - if (isDraggingSplit) { - event->acceptProposedAction(); - - SplitContainer::draggingSplit->setParent(this); - - addToLayout(SplitContainer::draggingSplit, dropPosition); - } - - this->dropPreview.hide(); -} - -bool SplitContainer::eventFilter(QObject *object, QEvent *event) -{ - if (event->type() == QEvent::FocusIn) { - QFocusEvent *focusEvent = static_cast(event); - - this->refreshCurrentFocusCoordinates((focusEvent->reason() == Qt::MouseFocusReason)); - } - - return false; } void SplitContainer::paintEvent(QPaintEvent *) { QPainter painter(this); - if (this->ui.hbox.count() == 0) { + if (this->splits.size() == 0) { painter.fillRect(rect(), this->themeManager->splits.background); painter.setPen(this->themeManager->splits.header.text); @@ -471,6 +197,13 @@ void SplitContainer::paintEvent(QPaintEvent *) painter.fillRect(rect(), this->themeManager->splits.messageSeperator); } + for (DropRect &dropRect : this->dropRects) { + painter.setPen("#774"); + painter.setBrush(QBrush("#553")); + + painter.drawRect(dropRect.rect.marginsRemoved(QMargins(2, 2, 2, 2))); + } + QBrush accentColor = (QApplication::activeWindow() == this->window() ? this->themeManager->tabs.selected.backgrounds.regular : this->themeManager->tabs.selected.backgrounds.unfocused); @@ -478,69 +211,470 @@ void SplitContainer::paintEvent(QPaintEvent *) painter.fillRect(0, 0, width(), 1, accentColor); } -void SplitContainer::showEvent(QShowEvent *event) +void SplitContainer::dragEnterEvent(QDragEnterEvent *event) { - // Whenever this notebook page is shown, give focus to the last focused chat widget - // If this is the first time this notebook page is shown, it will give focus to the top-left - // chat widget - this->requestFocus(this->currentX, this->currentY); -} + if (!event->mimeData()->hasFormat("chatterino/split")) + return; -static std::pair getWidgetPositionInLayout(QLayout *layout, const Split *chatWidget) -{ - for (int i = 0; i < layout->count(); ++i) { - printf("xD\n"); + if (!SplitContainer::isDraggingSplit) { + return; } - return std::make_pair(-1, -1); + this->isDragging = true; + this->layout(); + + event->acceptProposedAction(); + + // this->dropRegions.clear(); + + // if (this->ui.hbox.count() == 0) { + // this->dropRegions.push_back(DropRegion(rect(), std::pair(-1, -1))); + // } else { + // for (int i = 0; i < this->ui.hbox.count() + 1; ++i) { + // this->dropRegions.push_back( + // DropRegion(QRect(((i * 4 - 1) * width() / this->ui.hbox.count()) / 4, 0, + // width() / this->ui.hbox.count() / 2 + 1, height() + 1), + // std::pair(i, -1))); + // } + + // for (int i = 0; i < this->ui.hbox.count(); ++i) { + // auto vbox = static_cast(this->ui.hbox.itemAt(i)); + + // for (int j = 0; j < vbox->count() + 1; ++j) { + // this->dropRegions.push_back(DropRegion( + // QRect(i * width() / this->ui.hbox.count(), + // ((j * 2 - 1) * height() / vbox->count()) / 2, + // width() / this->ui.hbox.count() + 1, height() / vbox->count() + 1), + + // std::pair(i, j))); + // } + // } + // } + + // setPreviewRect(event->pos()); + + // event->acceptProposedAction(); } -std::pair SplitContainer::getChatPosition(const Split *chatWidget) +void SplitContainer::dragMoveEvent(QDragMoveEvent *event) { - auto layout = this->ui.hbox.layout(); + // setPreviewRect(event->pos()); - if (layout == nullptr) { - return std::make_pair(-1, -1); + for (auto &dropRect : this->dropRects) { + if (dropRect.rect.contains(event->pos())) { + event->acceptProposedAction(); + return; + } + } + event->setAccepted(false); +} + +void SplitContainer::dragLeaveEvent(QDragLeaveEvent *event) +{ + this->isDragging = false; + this->layout(); + + // this->dropPreview.hide(); +} + +void SplitContainer::dropEvent(QDropEvent *event) +{ + // if (isDraggingSplit) { + // event->acceptProposedAction(); + + // SplitContainer::draggingSplit->setParent(this); + + // addToLayout(SplitContainer::draggingSplit, dropPosition); + // } + + // this->dropPreview.hide(); + + for (auto &dropRect : this->dropRects) { + if (dropRect.rect.contains(event->pos())) { + this->insertSplit(SplitContainer::draggingSplit, dropRect.position); + event->acceptProposedAction(); + break; + } } - return getWidgetPositionInLayout(layout, chatWidget); + this->isDragging = false; + this->layout(); } -Split *SplitContainer::createChatWidget() -{ - auto split = new Split(this); +// void SplitContainer::updateFlexValues() +//{ +// for (int i = 0; i < this->ui.hbox.count(); i++) { +// QVBoxLayout *vbox = (QVBoxLayout *)ui.hbox.itemAt(i)->layout(); - return split; -} +// if (vbox->count() != 0) { +// ui.hbox.setStretch(i, (int)(1000 * ((Split *)vbox->itemAt(0))->getFlexSizeX())); +// } +// } +//} -void SplitContainer::refreshTitle() +// int SplitContainer::splitCount() const +//{ +// return this->splits.size(); +//} + +// std::pair SplitContainer::removeFromLayout(Split *widget) +//{ +// widget->getChannelView().tabHighlightRequested.disconnectAll(); + +// // remove reference to chat widget from chatWidgets vector +// auto it = std::find(std::begin(this->splits), std::end(this->splits), widget); +// if (it != std::end(this->splits)) { +// this->splits.erase(it); + +// this->refreshTitle(); +// } + +// Split *neighbouringSplit = nullptr; + +// // Position the split was found at +// int positionX = -1, positionY = -1; + +// bool removed = false; + +// QVBoxLayout *layoutToRemove = nullptr; + +// // Find widget in box, remove it, return its position +// for (int i = 0; i < this->ui.hbox.count(); ++i) { +// auto vbox = static_cast(this->ui.hbox.itemAt(i)); + +// auto vboxCount = vbox->count(); + +// for (int j = 0; j < vboxCount; ++j) { +// if (vbox->itemAt(j)->widget() != widget) { +// neighbouringSplit = dynamic_cast(vbox->itemAt(j)->widget()); + +// if (removed && neighbouringSplit != nullptr) { +// // The widget we searched for has been found, and we have a split to switch +// // focus to +// break; +// } + +// continue; +// } + +// removed = true; +// positionX = i; + +// // Remove split from box +// widget->setParent(nullptr); + +// if (vbox->count() == 0) { +// // The split was the last item remaining in the vbox +// // Remove the vbox once all iteration is done +// layoutToRemove = vbox; +// positionY = -1; +// break; +// } + +// // Don't break here yet, we want to keep iterating this vbox if possible to find the +// // closest still-alive neighbour that we can switch focus to +// positionY = j; + +// --j; +// --vboxCount; +// } + +// if (removed && neighbouringSplit != nullptr) { +// // The widget we searched for has been found, and we have a split to switch focus to +// break; +// } +// } + +// if (removed) { +// if (layoutToRemove != nullptr) { +// // The split we removed was the last split in its box. Remove the box +// // We delay the removing of the box so we can keep iterating over hbox safely +// this->ui.hbox.removeItem(layoutToRemove); +// delete layoutToRemove; +// } + +// if (neighbouringSplit != nullptr) { +// // We found a neighbour split we can switch focus to +// neighbouringSplit->giveFocus(Qt::MouseFocusReason); +// } +// } + +// return std::make_pair(positionX, positionY); +//} + +// void SplitContainer::addToLayout(Split *widget, std::pair position) +//{ +// this->splits.push_back(widget); +// widget->getChannelView().tabHighlightRequested.connect( +// [this](HighlightState state) { this->tab->setHighlightState(state); }); + +// this->refreshTitle(); + +// widget->giveFocus(Qt::MouseFocusReason); + +// // add vbox at the end +// if (position.first < 0 || position.first >= this->ui.hbox.count()) { +// auto vbox = new QVBoxLayout(); +// vbox->addWidget(widget); + +// this->ui.hbox.addLayout(vbox, 1); + +// this->refreshCurrentFocusCoordinates(); +// return; +// } + +// // insert vbox +// if (position.second == -1) { +// auto vbox = new QVBoxLayout(); +// vbox->addWidget(widget); + +// this->ui.hbox.insertLayout(position.first, vbox, 1); +// this->refreshCurrentFocusCoordinates(); +// return; +// } + +// // add to existing vbox +// auto vbox = static_cast(this->ui.hbox.itemAt(position.first)); + +// vbox->insertWidget(std::max(0, std::min(vbox->count(), position.second)), widget); + +// this->refreshCurrentFocusCoordinates(); +//} + +// const std::vector &SplitContainer::getSplits() const +//{ +// return this->splits; +//} + +// std::vector> SplitContainer::getColumns() const +//{ +// std::vector> columns; + +// for (int i = 0; i < this->ui.hbox.count(); i++) { +// std::vector cells; + +// QLayout *vbox = this->ui.hbox.itemAt(i)->layout(); +// for (int j = 0; j < vbox->count(); j++) { +// cells.push_back(dynamic_cast(vbox->itemAt(j)->widget())); +// } + +// columns.push_back(cells); +// } + +// return columns; +//} + +// void SplitContainer::refreshCurrentFocusCoordinates(bool alsoSetLastRequested) +//{ +// int setX = -1; +// int setY = -1; +// bool doBreak = false; +// for (int x = 0; x < this->ui.hbox.count(); ++x) { +// QLayoutItem *item = this->ui.hbox.itemAt(x); +// if (item->isEmpty()) { +// setX = x; +// break; +// } +// QVBoxLayout *vbox = static_cast(item->layout()); + +// for (int y = 0; y < vbox->count(); ++y) { +// QLayoutItem *innerItem = vbox->itemAt(y); + +// if (innerItem->isEmpty()) { +// setX = x; +// setY = y; +// doBreak = true; +// break; +// } + +// QWidget *w = innerItem->widget(); +// if (w) { +// Split *chatWidget = static_cast(w); +// if (chatWidget->hasFocus()) { +// setX = x; +// setY = y; +// doBreak = true; +// break; +// } +// } +// } + +// if (doBreak) { +// break; +// } +// } + +// if (setX != -1) { +// this->currentX = setX; + +// if (setY != -1) { +// this->currentY = setY; + +// if (alsoSetLastRequested) { +// this->lastRequestedY[setX] = setY; +// } +// } +// } +//} + +// void SplitContainer::requestFocus(int requestedX, int requestedY) +//{ +// // XXX: Perhaps if we request an Y coordinate out of bounds, we shuold set all previously set +// // requestedYs to 0 (if -1 is requested) or that x-coordinates vbox count (if requestedY >= +// // currentvbox.count() is requested) +// if (requestedX < 0 || requestedX >= this->ui.hbox.count()) { +// return; +// } + +// QLayoutItem *item = this->ui.hbox.itemAt(requestedX); +// QWidget *xW = item->widget(); +// if (item->isEmpty()) { +// qDebug() << "Requested hbox item " << requestedX << "is empty"; +// if (xW) { +// qDebug() << "but xW is not null"; +// // TODO: figure out what to do here +// } +// return; +// } + +// QVBoxLayout *vbox = static_cast(item->layout()); + +// if (requestedY < 0) { +// requestedY = 0; +// } else if (requestedY >= vbox->count()) { +// requestedY = vbox->count() - 1; +// } + +// this->lastRequestedY[requestedX] = requestedY; + +// QLayoutItem *innerItem = vbox->itemAt(requestedY); + +// if (innerItem->isEmpty()) { +// qDebug() << "Requested vbox item " << requestedY << "is empty"; +// return; +// } + +// QWidget *w = innerItem->widget(); +// if (w) { +// Split *chatWidget = static_cast(w); +// chatWidget->giveFocus(Qt::OtherFocusReason); +// } +//} + +// void SplitContainer::enterEvent(QEvent *) +//{ +// if (this->ui.hbox.count() == 0) { +// this->setCursor(QCursor(Qt::PointingHandCursor)); +// } else { +// this->setCursor(QCursor(Qt::ArrowCursor)); +// } +//} + +// void SplitContainer::leaveEvent(QEvent *) +//{ +//} + +// void SplitContainer::setPreviewRect(QPoint mousePos) +//{ +// for (DropRegion region : this->dropRegions) { +// if (region.rect.contains(mousePos)) { +// this->dropPreview.setBounds(region.rect); + +// if (!this->dropPreview.isVisible()) { +// this->dropPreview.setGeometry(this->rect()); +// this->dropPreview.show(); +// this->dropPreview.raise(); +// } + +// dropPosition = region.position; + +// return; +// } +// } + +// this->dropPreview.hide(); +//} + +// bool SplitContainer::eventFilter(QObject *object, QEvent *event) +//{ +// if (event->type() == QEvent::FocusIn) { +// QFocusEvent *focusEvent = static_cast(event); + +// this->refreshCurrentFocusCoordinates((focusEvent->reason() == Qt::MouseFocusReason)); +// } + +// return false; +//} + +// void SplitContainer::showEvent(QShowEvent *event) +//{ +// // Whenever this notebook page is shown, give focus to the last focused chat widget +// // If this is the first time this notebook page is shown, it will give focus to the +// top-left +// // chat widget +// this->requestFocus(this->currentX, this->currentY); +//} + +// static std::pair getWidgetPositionInLayout(QLayout *layout, const Split +// *chatWidget) +//{ +// for (int i = 0; i < layout->count(); ++i) { +// printf("xD\n"); +// } + +// return std::make_pair(-1, -1); +//} + +// std::pair SplitContainer::getChatPosition(const Split *chatWidget) +//{ +// auto layout = this->ui.hbox.layout(); + +// if (layout == nullptr) { +// return std::make_pair(-1, -1); +// } + +// return getWidgetPositionInLayout(layout, chatWidget); +//} + +// Split *SplitContainer::createChatWidget() +//{ +// auto split = new Split(this); + +// return split; +//} + +void SplitContainer::refreshTabTitle() { + assert(this->tab != nullptr); + if (!this->tab->useDefaultTitle) { return; } - QString newTitle = ""; - bool first = true; + this->tab->setTitle("default title"); - for (const auto &chatWidget : this->splits) { - auto channelName = chatWidget->getChannel()->name; - if (channelName.isEmpty()) { - continue; - } + // QString newTitle = ""; + // bool first = true; - if (!first) { - newTitle += ", "; - } - newTitle += channelName; + // for (const auto &chatWidget : this->splits) { + // auto channelName = chatWidget->getChannel()->name; + // if (channelName.isEmpty()) { + // continue; + // } - first = false; - } + // if (!first) { + // newTitle += ", "; + // } + // newTitle += channelName; - if (newTitle.isEmpty()) { - newTitle = "empty"; - } + // first = false; + // } - this->tab->setTitle(newTitle); + // if (newTitle.isEmpty()) { + // newTitle = "empty"; + // } + + // this->tab->setTitle(newTitle); } } // namespace widgets diff --git a/src/widgets/splitcontainer.hpp b/src/widgets/splitcontainer.hpp index e2cef4d87..1ed3253e9 100644 --- a/src/widgets/splitcontainer.hpp +++ b/src/widgets/splitcontainer.hpp @@ -12,47 +12,433 @@ #include #include +#include +#include +#include + +#include + namespace chatterino { namespace widgets { -class SplitContainer : public BaseWidget +class SplitContainer : public BaseWidget, pajlada::Signals::SignalHolder { Q_OBJECT public: + // 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> &getChildren() + { + return this->children; + } + + private: + Type type; + Split *split; + Node *parent; + QRectF geometry; + std::vector> 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 &n) { return n->isOrContainsNode(_node); }); + } + + Node *findNodeContainingSplit(Split *_split) + { + if (this->type == Type::_Split && this->split == _split) { + return this; + } + + for (std::unique_ptr &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 : 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(clone)); + this->type = toContainerType(_direction); + this->split = nullptr; + + clone->_insertNextToThis(_split, _direction); + } + + void _insertNextToThis(Split *_split, Direction _direction) + { + auto &siblings = this->parent->children; + + qreal size = + // std::accumulate(this->parent->children.begin(), + // this->parent->children.end(), 0, + // [_direction](qreal val, Node *node) { + // if (toContainerType(_direction) == + // Type::VerticalContainer) { + // return val + node->geometry.height(); + // } else { + // return val + node->geometry.width(); + // } + // }); + + this->parent->geometry.width() / siblings.size(); + + if (siblings.size() == 1) { + this->geometry = QRect(0, 0, size, size); + } + + 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, size, size); + siblings.insert(it, std::unique_ptr(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 &sibling = siblings.front(); + _parent->type = sibling->type; + _parent->split = sibling->split; + std::vector> 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, std::vector &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) { + return val + node->geometry.height(); + }); + + qreal childX = this->geometry.left(); + qreal childWidth = this->geometry.width(); + + if (addSpacing) { + qreal offset = this->geometry.width() * 0.1; + 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 &child : this->children) { + child->geometry = + QRectF(childX, y, childWidth, child->geometry.height() * scaleFactor); + + child->layout(addSpacing, 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) { + return val + node->geometry.width(); + }); + + qreal childY = this->geometry.top(); + qreal childHeight = this->geometry.height(); + + if (addSpacing) { + qreal offset = this->geometry.height() * 0.1; + 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 += this->geometry.height() * 0.1; + childHeight -= this->geometry.height() * 0.2; + } + + qreal scaleFactor = this->geometry.width() / totalWidth; + + qreal x = this->geometry.left(); + for (std::unique_ptr &child : this->children) { + child->geometry = + QRectF(x, childY, child->geometry.width() * scaleFactor, childHeight); + + child->layout(addSpacing, dropRects); + x += child->geometry.width(); + } + } break; + }; + } + + static Type toContainerType(Direction _dir) + { + return _dir == Direction::Left || _dir == Direction::Right ? Type::HorizontalContainer + : Type::VerticalContainer; + } + + friend class SplitContainer; + }; + SplitContainer(Notebook *parent, NotebookTab *_tab); - std::pair removeFromLayout(Split *widget); - void addToLayout(Split *widget, std::pair position = std::pair(-1, -1)); + 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 getSplits() const + { + return this->splits; + } + + void refreshTabTitle(); - const std::vector &getSplits() const; - std::vector> getColumns() const; NotebookTab *getTab() const; + const Node *getBaseNode() + { + return &this->baseNode; + } - void addChat(bool openChannelNameDialog = false); + // std::pair removeFromLayout(Split *widget); + // void addToLayout(Split *widget, std::pair position = std::pair(-1, + // -1)); + + // const std::vector &getSplits() const; + // std::vector> getColumns() const; + + // void addChat(bool openChannelNameDialog = false); + + // static std::pair dropPosition; + + // int currentX = 0; + // int currentY = 0; + // std::map lastRequestedY; + + // void refreshCurrentFocusCoordinates(bool alsoSetLastRequested = false); + // void requestFocus(int x, int y); + + // void updateFlexValues(); + // int splitCount() const; + + // void loadSplits(); + + // void save(); static bool isDraggingSplit; static Split *draggingSplit; - static std::pair dropPosition; - - int currentX = 0; - int currentY = 0; - std::map lastRequestedY; - - void refreshCurrentFocusCoordinates(bool alsoSetLastRequested = false); - void requestFocus(int x, int y); - - void updateFlexValues(); - int splitCount() const; + // static Position dragOriginalPosition; protected: - bool eventFilter(QObject *object, QEvent *event) override; + // bool eventFilter(QObject *object, QEvent *event) override; void paintEvent(QPaintEvent *event) override; - void showEvent(QShowEvent *event) override; + // void showEvent(QShowEvent *event) override; - void enterEvent(QEvent *event) override; - void leaveEvent(QEvent *event) override; + // void enterEvent(QEvent *event) override; + // void leaveEvent(QEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void dragEnterEvent(QDragEnterEvent *event) override; @@ -60,6 +446,8 @@ protected: void dragLeaveEvent(QDragLeaveEvent *event) override; void dropEvent(QDropEvent *event) override; + void resizeEvent(QResizeEvent *event) override; + private: struct DropRegion { QRect rect; @@ -72,31 +460,32 @@ private: } }; - NotebookTab *tab; - - struct { - QVBoxLayout parentLayout; - - QHBoxLayout hbox; - } ui; - - std::vector splits; + std::vector dropRects; std::vector dropRegions; - NotebookPageDropPreview dropPreview; - void setPreviewRect(QPoint mousePos); + void layout(); - std::pair getChatPosition(const Split *chatWidget); + Node baseNode; - Split *createChatWidget(); + NotebookTab *tab; + std::vector splits; -public: - void refreshTitle(); + bool isDragging = false; - void loadSplits(); + // struct { + // QVBoxLayout parentLayout; - void save(); + // QHBoxLayout hbox; + // } ui; + + // std::vector splits; + + // void setPreviewRect(QPoint mousePos); + + // std::pair getChatPosition(const Split *chatWidget); + + // Split *createChatWidget(); }; } // namespace widgets