mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
added split resizing and splitting
This commit is contained in:
parent
28fb877020
commit
5b26cdaa07
9 changed files with 856 additions and 629 deletions
|
@ -10,6 +10,7 @@
|
|||
#include "widgets/accountswitchpopupwidget.hpp"
|
||||
#include "widgets/settingsdialog.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
|
@ -20,6 +21,9 @@
|
|||
namespace chatterino {
|
||||
namespace singletons {
|
||||
|
||||
using SplitNode = widgets::SplitContainer::Node;
|
||||
using SplitDirection = widgets::SplitContainer::Direction;
|
||||
|
||||
void WindowManager::showSettingsDialog()
|
||||
{
|
||||
QTimer::singleShot(80, [] { widgets::SettingsDialog::showDialog(); });
|
||||
|
@ -201,15 +205,24 @@ void WindowManager::initialize()
|
|||
}
|
||||
|
||||
// load splits
|
||||
QJsonObject splitRoot = tab_obj.value("splits2").toObject();
|
||||
|
||||
if (!splitRoot.isEmpty()) {
|
||||
tab->decodeFromJson(splitRoot);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// fallback load splits (old)
|
||||
int colNr = 0;
|
||||
for (QJsonValue column_val : tab_obj.value("splits").toArray()) {
|
||||
for (QJsonValue split_val : column_val.toArray()) {
|
||||
widgets::Split *split = new widgets::Split(tab);
|
||||
|
||||
QJsonObject split_obj = split_val.toObject();
|
||||
split->setChannel(this->decodeChannel(split_obj));
|
||||
split->setChannel(decodeChannel(split_obj));
|
||||
|
||||
// tab->addToLayout(split, std::make_pair(colNr, 10000000));
|
||||
tab->appendSplit(split);
|
||||
}
|
||||
colNr++;
|
||||
}
|
||||
|
@ -270,23 +283,11 @@ void WindowManager::save()
|
|||
}
|
||||
|
||||
// splits
|
||||
QJsonArray columns_arr;
|
||||
// std::vector<std::vector<widgets::Split *>> columns = tab->getColumns();
|
||||
QJsonObject splits;
|
||||
|
||||
// for (std::vector<widgets::Split *> &cells : columns) {
|
||||
// QJsonArray cells_arr;
|
||||
this->encodeNodeRecusively(tab->getBaseNode(), splits);
|
||||
|
||||
// for (widgets::Split *cell : cells) {
|
||||
// QJsonObject cell_obj;
|
||||
|
||||
// this->encodeChannel(cell->getIndirectChannel(), cell_obj);
|
||||
|
||||
// cells_arr.append(cell_obj);
|
||||
// }
|
||||
// columns_arr.append(cells_arr);
|
||||
// }
|
||||
|
||||
tab_obj.insert("splits", columns_arr);
|
||||
tab_obj.insert("splits2", splits);
|
||||
tabs_arr.append(tab_obj);
|
||||
}
|
||||
|
||||
|
@ -302,10 +303,46 @@ void WindowManager::save()
|
|||
QString settingsPath = app->paths->settingsFolderPath + SETTINGS_FILENAME;
|
||||
QFile file(settingsPath);
|
||||
file.open(QIODevice::WriteOnly | QIODevice::Truncate);
|
||||
file.write(document.toJson());
|
||||
|
||||
QJsonDocument::JsonFormat format =
|
||||
#ifdef _DEBUG
|
||||
QJsonDocument::JsonFormat::Compact
|
||||
#else
|
||||
(QJsonDocument::JsonFormat)0
|
||||
#endif
|
||||
;
|
||||
|
||||
file.write(document.toJson(format));
|
||||
file.flush();
|
||||
}
|
||||
|
||||
void WindowManager::encodeNodeRecusively(SplitNode *node, QJsonObject &obj)
|
||||
{
|
||||
switch (node->getType()) {
|
||||
case SplitNode::_Split: {
|
||||
obj.insert("type", "split");
|
||||
QJsonObject split;
|
||||
encodeChannel(node->getSplit()->getIndirectChannel(), split);
|
||||
obj.insert("data", split);
|
||||
obj.insert("flexh", node->getHorizontalFlex());
|
||||
obj.insert("flexv", node->getVerticalFlex());
|
||||
} break;
|
||||
case SplitNode::HorizontalContainer:
|
||||
case SplitNode::VerticalContainer: {
|
||||
obj.insert("type", node->getType() == SplitNode::HorizontalContainer ? "horizontal"
|
||||
: "vertical");
|
||||
|
||||
QJsonArray items_arr;
|
||||
for (const std::unique_ptr<SplitNode> &n : node->getChildren()) {
|
||||
QJsonObject subObj;
|
||||
this->encodeNodeRecusively(n.get(), subObj);
|
||||
items_arr.append(subObj);
|
||||
}
|
||||
obj.insert("items", items_arr);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void WindowManager::encodeChannel(IndirectChannel channel, QJsonObject &obj)
|
||||
{
|
||||
util::assertInGuiThread();
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
#include "widgets/window.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
namespace widgets {
|
||||
class SplitContainer::Node;
|
||||
}
|
||||
namespace singletons {
|
||||
|
||||
class WindowManager
|
||||
|
@ -42,8 +45,13 @@ private:
|
|||
widgets::Window *mainWindow = nullptr;
|
||||
widgets::Window *selectedWindow = nullptr;
|
||||
|
||||
void encodeChannel(IndirectChannel channel, QJsonObject &obj);
|
||||
IndirectChannel decodeChannel(const QJsonObject &obj);
|
||||
void encodeNodeRecusively(widgets::SplitContainer::Node *node, QJsonObject &obj);
|
||||
void decodeNodeRecusively(widgets::SplitContainer *container,
|
||||
widgets::SplitContainer::Node *node, QJsonObject &obj, bool vertical);
|
||||
|
||||
public:
|
||||
static void encodeChannel(IndirectChannel channel, QJsonObject &obj);
|
||||
static IndirectChannel decodeChannel(const QJsonObject &obj);
|
||||
};
|
||||
|
||||
} // namespace singletons
|
||||
|
|
|
@ -27,7 +27,6 @@ BaseWidget::~BaseWidget()
|
|||
|
||||
float BaseWidget::getScale() const
|
||||
{
|
||||
// return 1.f;
|
||||
BaseWidget *baseWidget = dynamic_cast<BaseWidget *>(this->window());
|
||||
|
||||
if (baseWidget == nullptr) {
|
||||
|
|
|
@ -227,9 +227,47 @@ void SplitHeader::paintEvent(QPaintEvent *)
|
|||
|
||||
void SplitHeader::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
this->dragging = true;
|
||||
|
||||
this->dragStart = event->pos();
|
||||
}
|
||||
|
||||
this->doubleClicked = false;
|
||||
}
|
||||
|
||||
void SplitHeader::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
if (this->dragging && event->button() == Qt::LeftButton) {
|
||||
QPoint pos = event->globalPos();
|
||||
|
||||
if (!showingHelpTooltip) {
|
||||
this->showingHelpTooltip = true;
|
||||
|
||||
QTimer::singleShot(400, this, [this, pos] {
|
||||
if (this->doubleClicked) {
|
||||
this->doubleClicked = false;
|
||||
this->showingHelpTooltip = false;
|
||||
return;
|
||||
}
|
||||
|
||||
TooltipWidget *widget = new TooltipWidget();
|
||||
|
||||
widget->setText("Double click or press <ctrl+r> to change the channel.\nClick and "
|
||||
"drag to move the split.");
|
||||
widget->setAttribute(Qt::WA_DeleteOnClose);
|
||||
widget->move(pos);
|
||||
widget->show();
|
||||
|
||||
QTimer::singleShot(3000, widget, [this, widget] {
|
||||
widget->close();
|
||||
this->showingHelpTooltip = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this->dragging = false;
|
||||
}
|
||||
|
||||
void SplitHeader::mouseMoveEvent(QMouseEvent *event)
|
||||
|
@ -242,27 +280,27 @@ void SplitHeader::mouseMoveEvent(QMouseEvent *event)
|
|||
}
|
||||
|
||||
if (this->dragging) {
|
||||
if (std::abs(this->dragStart.x() - event->pos().x()) > 12 ||
|
||||
std::abs(this->dragStart.y() - event->pos().y()) > 12) {
|
||||
if (std::abs(this->dragStart.x() - event->pos().x()) > (int)(12 * this->getScale()) ||
|
||||
std::abs(this->dragStart.y() - event->pos().y()) > (int)(12 * this->getScale())) {
|
||||
this->split->drag();
|
||||
this->dragging = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SplitHeader::leaveEvent(QEvent *event)
|
||||
{
|
||||
TooltipWidget::getInstance()->hide();
|
||||
BaseWidget::leaveEvent(event);
|
||||
}
|
||||
|
||||
void SplitHeader::mouseDoubleClickEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
this->split->doChangeChannel();
|
||||
}
|
||||
this->doubleClicked = true;
|
||||
}
|
||||
|
||||
void SplitHeader::leaveEvent(QEvent *event)
|
||||
{
|
||||
TooltipWidget::getInstance()->hide();
|
||||
BaseWidget::leaveEvent(event);
|
||||
}
|
||||
void SplitHeader::rightButtonClicked()
|
||||
{
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ protected:
|
|||
|
||||
virtual void paintEvent(QPaintEvent *) override;
|
||||
virtual void mousePressEvent(QMouseEvent *event) override;
|
||||
virtual void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
virtual void mouseMoveEvent(QMouseEvent *event) override;
|
||||
virtual void leaveEvent(QEvent *event) override;
|
||||
virtual void mouseDoubleClickEvent(QMouseEvent *event) override;
|
||||
|
@ -50,6 +51,8 @@ private:
|
|||
|
||||
QPoint dragStart;
|
||||
bool dragging = false;
|
||||
bool doubleClicked = false;
|
||||
bool showingHelpTooltip = false;
|
||||
|
||||
pajlada::Signals::Connection onlineStatusChangedConnection;
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "application.hpp"
|
||||
#include "common.hpp"
|
||||
#include "singletons/thememanager.hpp"
|
||||
#include "singletons/windowmanager.hpp"
|
||||
#include "util/helpers.hpp"
|
||||
#include "util/layoutcreator.hpp"
|
||||
#include "widgets/helper/notebooktab.hpp"
|
||||
|
@ -11,6 +12,8 @@
|
|||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QHBoxLayout>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QMimeData>
|
||||
#include <QObject>
|
||||
#include <QPainter>
|
||||
|
@ -25,7 +28,6 @@ namespace widgets {
|
|||
|
||||
bool SplitContainer::isDraggingSplit = false;
|
||||
Split *SplitContainer::draggingSplit = nullptr;
|
||||
// SplitContainer::Position SplitContainer::dragOriginalPosition;
|
||||
|
||||
SplitContainer::SplitContainer(Notebook *parent, NotebookTab *_tab)
|
||||
: BaseWidget(parent)
|
||||
|
@ -38,9 +40,19 @@ SplitContainer::SplitContainer(Notebook *parent, NotebookTab *_tab)
|
|||
|
||||
this->refreshTabTitle();
|
||||
|
||||
this->managedConnect(Split::modifierStatusChanged, [this](auto) {
|
||||
// fourtf: maybe optimize
|
||||
this->managedConnect(Split::modifierStatusChanged, [this](auto modifiers) {
|
||||
this->layout();
|
||||
|
||||
if (modifiers == Qt::AltModifier) {
|
||||
for (std::unique_ptr<ResizeHandle> &handle : this->resizeHandles) {
|
||||
handle->show();
|
||||
handle->raise();
|
||||
}
|
||||
} else {
|
||||
for (std::unique_ptr<ResizeHandle> &handle : this->resizeHandles) {
|
||||
handle->hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this->setCursor(Qt::PointingHandCursor);
|
||||
|
@ -83,7 +95,7 @@ void SplitContainer::appendSplit(Split *split)
|
|||
|
||||
void SplitContainer::insertSplit(Split *split, const Position &position)
|
||||
{
|
||||
this->insertSplit(split, position.direction, position.relativeNode);
|
||||
this->insertSplit(split, position.direction, (Node *)position.relativeNode);
|
||||
}
|
||||
|
||||
void SplitContainer::insertSplit(Split *split, Direction direction, Split *relativeTo)
|
||||
|
@ -117,7 +129,7 @@ void SplitContainer::insertSplit(Split *split, Direction direction, Node *relati
|
|||
split->giveFocus(Qt::MouseFocusReason);
|
||||
this->splits.push_back(split);
|
||||
|
||||
// this->setAcceptDrops(false);
|
||||
this->refreshTabTitle();
|
||||
|
||||
this->layout();
|
||||
}
|
||||
|
@ -135,9 +147,7 @@ SplitContainer::Position SplitContainer::releaseSplit(Split *split)
|
|||
this->splits.front()->giveFocus(Qt::MouseFocusReason);
|
||||
}
|
||||
|
||||
// if (this->splits.empty()) {
|
||||
// this->setAcceptDrops(true);
|
||||
// }
|
||||
this->refreshTabTitle();
|
||||
|
||||
return position;
|
||||
}
|
||||
|
@ -155,13 +165,10 @@ void SplitContainer::layout()
|
|||
this->baseNode.geometry = this->rect();
|
||||
|
||||
std::vector<DropRect> _dropRects;
|
||||
std::vector<ResizeRect> _resizeRects;
|
||||
this->baseNode.layout(
|
||||
Split::modifierStatus == (Qt::AltModifier | Qt::ControlModifier) || this->isDragging,
|
||||
this->getScale(), _dropRects);
|
||||
_dropRects.clear();
|
||||
this->baseNode.layout(
|
||||
Split::modifierStatus == (Qt::AltModifier | Qt::ControlModifier) || this->isDragging,
|
||||
this->getScale(), _dropRects);
|
||||
this->getScale(), _dropRects, _resizeRects);
|
||||
|
||||
this->dropRects = _dropRects;
|
||||
|
||||
|
@ -190,10 +197,37 @@ void SplitContainer::layout()
|
|||
}
|
||||
|
||||
this->overlay.setRects(std::move(_dropRects));
|
||||
|
||||
// handle resizeHandles
|
||||
if (this->resizeHandles.size() < _resizeRects.size()) {
|
||||
while (this->resizeHandles.size() < _resizeRects.size()) {
|
||||
this->resizeHandles.push_back(std::make_unique<ResizeHandle>(this));
|
||||
}
|
||||
} else if (this->resizeHandles.size() > _resizeRects.size()) {
|
||||
this->resizeHandles.resize(_resizeRects.size());
|
||||
}
|
||||
|
||||
{
|
||||
int i = 0;
|
||||
for (ResizeRect &resizeRect : _resizeRects) {
|
||||
ResizeHandle *handle = this->resizeHandles[i].get();
|
||||
handle->setGeometry(resizeRect.rect);
|
||||
handle->setVertical(resizeRect.vertical);
|
||||
handle->node = resizeRect.node;
|
||||
|
||||
if (Split::modifierStatus == Qt::AltModifier) {
|
||||
handle->show();
|
||||
handle->raise();
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// redraw
|
||||
this->update();
|
||||
}
|
||||
|
||||
/// EVENTS
|
||||
void SplitContainer::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
BaseWidget::resizeEvent(event);
|
||||
|
@ -229,14 +263,14 @@ void SplitContainer::paintEvent(QPaintEvent *)
|
|||
|
||||
painter.setPen(this->themeManager->splits.header.text);
|
||||
|
||||
QString text = "Click to add a <split>";
|
||||
QString text = "Click to add a split";
|
||||
|
||||
Notebook *notebook = dynamic_cast<Notebook *>(this->parentWidget());
|
||||
|
||||
if (notebook != nullptr) {
|
||||
if (notebook->tabCount() > 1) {
|
||||
text += "\n\ntip: you can drag a <split> while holding <Alt>";
|
||||
text += "\nor add another one by pressing <Ctrl+T>";
|
||||
text += "\n\nTip: After adding a split you can hold <Alt> to move it or split it "
|
||||
"further.";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -269,110 +303,21 @@ void SplitContainer::paintEvent(QPaintEvent *)
|
|||
|
||||
void SplitContainer::dragEnterEvent(QDragEnterEvent *event)
|
||||
{
|
||||
// if (!event->mimeData()->hasFormat("chatterino/split"))
|
||||
// return;
|
||||
if (!event->mimeData()->hasFormat("chatterino/split"))
|
||||
return;
|
||||
|
||||
// if (!SplitContainer::isDraggingSplit) {
|
||||
// return;
|
||||
// }
|
||||
if (!SplitContainer::isDraggingSplit) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->isDragging = true;
|
||||
this->layout();
|
||||
|
||||
// if (this->splits.empty()) {
|
||||
// event->acceptProposedAction();
|
||||
// }
|
||||
|
||||
this->overlay.setGeometry(this->rect());
|
||||
this->overlay.show();
|
||||
this->overlay.raise();
|
||||
}
|
||||
|
||||
void SplitContainer::dragMoveEvent(QDragMoveEvent *event)
|
||||
{
|
||||
// if (this->splits.empty()) {
|
||||
// event->acceptProposedAction();
|
||||
// }
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
void SplitContainer::dropEvent(QDropEvent *event)
|
||||
{
|
||||
// if (this->splits.empty()) {
|
||||
// this->insertSplit(SplitContainer::draggingSplit, Direction::Above);
|
||||
// event->acceptProposedAction();
|
||||
// }
|
||||
|
||||
// this->isDragging = false;
|
||||
// this->layout();
|
||||
}
|
||||
|
||||
// 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<QVBoxLayout *>(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<Split *>(w);
|
||||
// chatWidget->giveFocus(Qt::OtherFocusReason);
|
||||
// }
|
||||
//}
|
||||
|
||||
// void SplitContainer::enterEvent(QEvent *event)
|
||||
//{
|
||||
// if (this->ui.hbox.count() == 0) {
|
||||
// this->setCursor(QCursor(Qt::PointingHandCursor));
|
||||
// } else {
|
||||
// this->setCursor(QCursor(Qt::ArrowCursor));
|
||||
// }
|
||||
//}
|
||||
|
||||
void SplitContainer::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
this->mouseOverPoint = event->pos();
|
||||
|
@ -385,75 +330,6 @@ void SplitContainer::leaveEvent(QEvent *event)
|
|||
this->update();
|
||||
}
|
||||
|
||||
// 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<QFocusEvent *>(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<int, int> 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<int, int> 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);
|
||||
|
@ -462,30 +338,610 @@ void SplitContainer::refreshTabTitle()
|
|||
return;
|
||||
}
|
||||
|
||||
this->tab->setTitle("default title");
|
||||
QString newTitle = "";
|
||||
bool first = true;
|
||||
|
||||
// QString newTitle = "";
|
||||
// bool first = true;
|
||||
for (const auto &chatWidget : this->splits) {
|
||||
auto channelName = chatWidget->getChannel()->name;
|
||||
if (channelName.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// for (const auto &chatWidget : this->splits) {
|
||||
// auto channelName = chatWidget->getChannel()->name;
|
||||
// if (channelName.isEmpty()) {
|
||||
// continue;
|
||||
// }
|
||||
if (!first) {
|
||||
newTitle += ", ";
|
||||
}
|
||||
newTitle += channelName;
|
||||
|
||||
// if (!first) {
|
||||
// newTitle += ", ";
|
||||
// }
|
||||
// newTitle += channelName;
|
||||
first = false;
|
||||
}
|
||||
|
||||
// first = false;
|
||||
// }
|
||||
if (newTitle.isEmpty()) {
|
||||
newTitle = "empty";
|
||||
}
|
||||
|
||||
// if (newTitle.isEmpty()) {
|
||||
// newTitle = "empty";
|
||||
// }
|
||||
this->tab->setTitle(newTitle);
|
||||
}
|
||||
|
||||
// this->tab->setTitle(newTitle);
|
||||
void SplitContainer::decodeFromJson(QJsonObject &obj)
|
||||
{
|
||||
assert(this->baseNode.type == Node::EmptyRoot);
|
||||
|
||||
this->decodeNodeRecusively(obj, &this->baseNode);
|
||||
}
|
||||
|
||||
void SplitContainer::decodeNodeRecusively(QJsonObject &obj, Node *node)
|
||||
{
|
||||
QString type = obj.value("type").toString();
|
||||
|
||||
if (type == "split") {
|
||||
auto *split = new Split(this);
|
||||
split->setChannel(singletons::WindowManager::decodeChannel(obj.value("data").toObject()));
|
||||
|
||||
this->appendSplit(split);
|
||||
} else if (type == "horizontal" || type == "vertical") {
|
||||
bool vertical = type == "vertical";
|
||||
|
||||
Direction direction = vertical ? Direction::Below : Direction::Right;
|
||||
|
||||
node->type = vertical ? Node::VerticalContainer : Node::HorizontalContainer;
|
||||
|
||||
for (QJsonValue _val : obj.value("items").toArray()) {
|
||||
auto _obj = _val.toObject();
|
||||
|
||||
auto _type = _obj.value("type");
|
||||
if (_type == "split") {
|
||||
auto *split = new Split(this);
|
||||
split->setChannel(
|
||||
singletons::WindowManager::decodeChannel(_obj.value("data").toObject()));
|
||||
|
||||
this->insertSplit(split, direction, node);
|
||||
|
||||
this->baseNode.findNodeContainingSplit(split)->flexH =
|
||||
_obj.value("flexh").toDouble(1.0);
|
||||
this->baseNode.findNodeContainingSplit(split)->flexV =
|
||||
_obj.value("flexv").toDouble(1.0);
|
||||
} else {
|
||||
Node *_node = new Node();
|
||||
_node->parent = node;
|
||||
node->children.emplace_back(_node);
|
||||
this->decodeNodeRecusively(_obj, _node);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (node->getChildren().size() < 2) {
|
||||
auto *split = new Split(this);
|
||||
split->setChannel(
|
||||
singletons::WindowManager::decodeChannel(obj.value("data").toObject()));
|
||||
|
||||
this->insertSplit(split, direction, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Node
|
||||
//
|
||||
|
||||
SplitContainer::Node::Type SplitContainer::Node::getType()
|
||||
{
|
||||
return this->type;
|
||||
}
|
||||
Split *SplitContainer::Node::getSplit()
|
||||
{
|
||||
return this->split;
|
||||
}
|
||||
|
||||
SplitContainer::Node *SplitContainer::Node::getParent()
|
||||
{
|
||||
return this->parent;
|
||||
}
|
||||
|
||||
qreal SplitContainer::Node::getHorizontalFlex()
|
||||
{
|
||||
return this->flexH;
|
||||
}
|
||||
|
||||
qreal SplitContainer::Node::getVerticalFlex()
|
||||
{
|
||||
return this->flexV;
|
||||
}
|
||||
|
||||
const std::vector<std::unique_ptr<SplitContainer::Node>> &SplitContainer::Node::getChildren()
|
||||
{
|
||||
return this->children;
|
||||
}
|
||||
|
||||
SplitContainer::Node::Node()
|
||||
: type(SplitContainer::Node::Type::EmptyRoot)
|
||||
, split(nullptr)
|
||||
, parent(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
SplitContainer::Node::Node(Split *_split, Node *_parent)
|
||||
: type(Type::_Split)
|
||||
, split(_split)
|
||||
, parent(_parent)
|
||||
{
|
||||
}
|
||||
|
||||
bool SplitContainer::Node::isOrContainsNode(SplitContainer::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); });
|
||||
}
|
||||
|
||||
SplitContainer::Node *SplitContainer::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 SplitContainer::Node::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: {
|
||||
this->nestSplitIntoCollection(_split, _direction);
|
||||
} break;
|
||||
case Node::VerticalContainer: {
|
||||
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 SplitContainer::Node::nestSplitIntoCollection(Split *_split, Direction _direction)
|
||||
{
|
||||
if (toContainerType(_direction) == this->type) {
|
||||
this->children.emplace_back(new Node(_split, this));
|
||||
} else {
|
||||
// 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 SplitContainer::Node::_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 SplitContainer::Node::setSplit(Split *_split)
|
||||
{
|
||||
assert(this->split == nullptr);
|
||||
assert(this->children.size() == 0);
|
||||
|
||||
this->split = _split;
|
||||
this->type = Type::_Split;
|
||||
}
|
||||
|
||||
SplitContainer::Position SplitContainer::Node::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;
|
||||
}
|
||||
}
|
||||
|
||||
qreal SplitContainer::Node::getFlex(bool isVertical)
|
||||
{
|
||||
return isVertical ? this->flexV : this->flexH;
|
||||
}
|
||||
|
||||
qreal SplitContainer::Node::getSize(bool isVertical)
|
||||
{
|
||||
return isVertical ? this->geometry.height() : this->geometry.width();
|
||||
}
|
||||
|
||||
qreal SplitContainer::Node::getChildrensTotalFlex(bool isVertical)
|
||||
{
|
||||
return std::accumulate(
|
||||
this->children.begin(), this->children.end(), (qreal)0,
|
||||
[=](qreal val, std::unique_ptr<Node> &node) { return val + node->getFlex(isVertical); });
|
||||
}
|
||||
|
||||
void SplitContainer::Node::layout(bool addSpacing, float _scale, std::vector<DropRect> &dropRects,
|
||||
std::vector<ResizeRect> &resizeRects)
|
||||
{
|
||||
for (std::unique_ptr<Node> &node : this->children) {
|
||||
if (node->flexH <= 0)
|
||||
node->flexH = 0;
|
||||
if (node->flexV <= 0)
|
||||
node->flexV = 0;
|
||||
}
|
||||
|
||||
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:
|
||||
case Node::HorizontalContainer: {
|
||||
bool isVertical = this->type == Node::VerticalContainer;
|
||||
|
||||
// vars
|
||||
qreal minSize = 48 * _scale;
|
||||
|
||||
qreal totalFlex = this->getChildrensTotalFlex(isVertical);
|
||||
qreal totalSize = std::accumulate(
|
||||
this->children.begin(), this->children.end(), (qreal)0,
|
||||
[=](int val, std::unique_ptr<Node> &node) {
|
||||
return val + std::max<qreal>(this->getSize(isVertical) / totalFlex *
|
||||
node->getFlex(isVertical),
|
||||
minSize);
|
||||
});
|
||||
|
||||
qreal sizeMultiplier = this->getSize(isVertical) / totalSize;
|
||||
QRectF childRect = this->geometry;
|
||||
|
||||
// add spacing if reqested
|
||||
if (addSpacing) {
|
||||
qreal offset = std::min<qreal>(this->getSize(!isVertical) * 0.1, _scale * 24);
|
||||
|
||||
// droprect left / above
|
||||
dropRects.emplace_back(
|
||||
QRect(this->geometry.left(), this->geometry.top(),
|
||||
isVertical ? offset : this->geometry.width(),
|
||||
isVertical ? this->geometry.height() : offset),
|
||||
Position(this, isVertical ? Direction::Left : Direction::Above));
|
||||
|
||||
// droprect right / below
|
||||
if (isVertical) {
|
||||
dropRects.emplace_back(
|
||||
QRect(this->geometry.right() - offset, this->geometry.top(), offset,
|
||||
this->geometry.height()),
|
||||
Position(this, Direction::Right));
|
||||
} else {
|
||||
dropRects.emplace_back(
|
||||
QRect(this->geometry.left(), this->geometry.bottom() - offset,
|
||||
this->geometry.width(), offset),
|
||||
Position(this, Direction::Below));
|
||||
}
|
||||
|
||||
// shrink childRect
|
||||
if (isVertical) {
|
||||
childRect.setLeft(childRect.left() + offset);
|
||||
childRect.setRight(childRect.right() - offset);
|
||||
} else {
|
||||
childRect.setTop(childRect.top() + offset);
|
||||
childRect.setBottom(childRect.bottom() - offset);
|
||||
}
|
||||
}
|
||||
|
||||
// iterate children
|
||||
qreal pos = isVertical ? childRect.top() : childRect.left();
|
||||
for (std::unique_ptr<Node> &child : this->children) {
|
||||
// set rect
|
||||
QRectF rect = childRect;
|
||||
if (isVertical) {
|
||||
rect.setTop(pos);
|
||||
rect.setHeight(
|
||||
std::max<qreal>(this->geometry.height() / totalFlex * child->flexV,
|
||||
minSize) *
|
||||
sizeMultiplier);
|
||||
} else {
|
||||
rect.setLeft(pos);
|
||||
rect.setWidth(std::max<qreal>(this->geometry.width() / totalFlex * child->flexH,
|
||||
minSize) *
|
||||
sizeMultiplier);
|
||||
}
|
||||
|
||||
child->geometry = rect;
|
||||
child->layout(addSpacing, _scale, dropRects, resizeRects);
|
||||
|
||||
pos += child->getSize(isVertical);
|
||||
|
||||
// add resize rect
|
||||
if (child != this->children.front()) {
|
||||
QRect r = isVertical ? QRect(this->geometry.left(), child->geometry.top() - 4,
|
||||
this->geometry.width(), 8)
|
||||
: QRect(child->geometry.left() - 4, this->geometry.top(),
|
||||
8, this->geometry.height());
|
||||
resizeRects.push_back(ResizeRect(r, child.get(), isVertical));
|
||||
}
|
||||
|
||||
// normalize flex
|
||||
if (isVertical) {
|
||||
child->flexV = child->flexV / totalFlex * this->children.size();
|
||||
child->flexH = 1;
|
||||
} else {
|
||||
child->flexH = child->flexH / totalFlex * this->children.size();
|
||||
child->flexV = 1;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
};
|
||||
}
|
||||
|
||||
SplitContainer::Node::Type SplitContainer::Node::toContainerType(Direction _dir)
|
||||
{
|
||||
return _dir == Direction::Left || _dir == Direction::Right ? Type::HorizontalContainer
|
||||
: Type::VerticalContainer;
|
||||
}
|
||||
|
||||
//
|
||||
// DropOverlay
|
||||
//
|
||||
|
||||
SplitContainer::DropOverlay::DropOverlay(SplitContainer *_parent)
|
||||
: QWidget(_parent)
|
||||
, parent(_parent)
|
||||
, mouseOverPoint(-10000, -10000)
|
||||
{
|
||||
this->setMouseTracking(true);
|
||||
this->setAcceptDrops(true);
|
||||
}
|
||||
|
||||
void SplitContainer::DropOverlay::setRects(std::vector<SplitContainer::DropRect> _rects)
|
||||
{
|
||||
this->rects = std::move(_rects);
|
||||
}
|
||||
|
||||
// pajlada::Signals::NoArgSignal dragEnded;
|
||||
|
||||
void SplitContainer::DropOverlay::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
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 SplitContainer::DropOverlay::dragEnterEvent(QDragEnterEvent *event)
|
||||
{
|
||||
event->acceptProposedAction();
|
||||
}
|
||||
|
||||
void SplitContainer::DropOverlay::dragMoveEvent(QDragMoveEvent *event)
|
||||
{
|
||||
event->acceptProposedAction();
|
||||
|
||||
this->mouseOverPoint = event->pos();
|
||||
this->update();
|
||||
}
|
||||
|
||||
void SplitContainer::DropOverlay::dragLeaveEvent(QDragLeaveEvent *event)
|
||||
{
|
||||
this->mouseOverPoint = QPoint(-10000, -10000);
|
||||
this->close();
|
||||
this->dragEnded.invoke();
|
||||
}
|
||||
|
||||
void SplitContainer::DropOverlay::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();
|
||||
}
|
||||
|
||||
//
|
||||
// ResizeHandle
|
||||
//
|
||||
|
||||
void SplitContainer::ResizeHandle::setVertical(bool isVertical)
|
||||
{
|
||||
this->setCursor(isVertical ? Qt::SplitVCursor : Qt::SplitHCursor);
|
||||
this->vertical = isVertical;
|
||||
}
|
||||
|
||||
SplitContainer::ResizeHandle::ResizeHandle(SplitContainer *_parent)
|
||||
: QWidget(_parent)
|
||||
, parent(_parent)
|
||||
{
|
||||
this->setMouseTracking(true);
|
||||
}
|
||||
|
||||
void SplitContainer::ResizeHandle::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
QPainter painter(this);
|
||||
|
||||
painter.fillRect(this->rect(), "#999");
|
||||
}
|
||||
|
||||
void SplitContainer::ResizeHandle::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
this->isMouseDown = true;
|
||||
}
|
||||
|
||||
void SplitContainer::ResizeHandle::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
this->isMouseDown = false;
|
||||
}
|
||||
|
||||
void SplitContainer::ResizeHandle::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if (!this->isMouseDown) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(node != nullptr);
|
||||
assert(node->parent != nullptr);
|
||||
|
||||
auto &siblings = node->parent->getChildren();
|
||||
auto it =
|
||||
std::find_if(siblings.begin(), siblings.end(),
|
||||
[this](const std::unique_ptr<Node> &n) { return n.get() == this->node; });
|
||||
|
||||
assert(it != siblings.end());
|
||||
Node *before = siblings[it - siblings.begin() - 1].get();
|
||||
|
||||
QPoint topLeft = this->parent->mapToGlobal(before->geometry.topLeft().toPoint());
|
||||
QPoint bottomRight = this->parent->mapToGlobal(this->node->geometry.bottomRight().toPoint());
|
||||
|
||||
int globalX = topLeft.x() > event->globalX()
|
||||
? topLeft.x()
|
||||
: (bottomRight.x() < event->globalX() ? bottomRight.x() : event->globalX());
|
||||
int globalY = topLeft.y() > event->globalY()
|
||||
? topLeft.y()
|
||||
: (bottomRight.y() < event->globalY() ? bottomRight.y() : event->globalY());
|
||||
|
||||
QPoint mousePoint(globalX, globalY);
|
||||
|
||||
if (this->vertical) {
|
||||
qreal totalFlexV = this->node->flexV + before->flexV;
|
||||
before->flexV =
|
||||
totalFlexV * (mousePoint.y() - topLeft.y()) / (bottomRight.y() - topLeft.y());
|
||||
this->node->flexV = totalFlexV - before->flexV;
|
||||
|
||||
this->parent->layout();
|
||||
|
||||
// move handle
|
||||
this->move(this->x(), (int)before->geometry.bottom() - 4);
|
||||
} else {
|
||||
qreal totalFlexH = this->node->flexH + before->flexH;
|
||||
before->flexH =
|
||||
totalFlexH * (mousePoint.x() - topLeft.x()) / (bottomRight.x() - topLeft.x());
|
||||
this->node->flexH = totalFlexH - before->flexH;
|
||||
|
||||
this->parent->layout();
|
||||
|
||||
// move handle
|
||||
this->move((int)before->geometry.right() - 4, this->y());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace widgets
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -43,6 +43,11 @@ TooltipWidget::~TooltipWidget()
|
|||
this->fontChangedConnection.disconnect();
|
||||
}
|
||||
|
||||
void TooltipWidget::themeRefreshEvent()
|
||||
{
|
||||
this->setStyleSheet("color: #fff; background: #000");
|
||||
}
|
||||
|
||||
void TooltipWidget::scaleChangedEvent(float)
|
||||
{
|
||||
this->updateFont();
|
||||
|
|
|
@ -31,6 +31,7 @@ public:
|
|||
protected:
|
||||
void changeEvent(QEvent *) override;
|
||||
void leaveEvent(QEvent *) override;
|
||||
void themeRefreshEvent() override;
|
||||
void scaleChangedEvent(float) override;
|
||||
|
||||
private:
|
||||
|
|
Loading…
Reference in a new issue