-
-
Notifications
You must be signed in to change notification settings - Fork 465
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Split up Window Layout loading into a loading and application stage (#…
…1964) * Split up Window Layout loading into a loading and application stage Previously, we were creating UI elements at while we were reading the window-layout.json file. We now read the window-layout.json file fully first, which results in a WindowLayout struct which is built up of a list of windows with a list of tabs with a root node which contains containers and splits. This WindowLayout can then be applied. This will enable PRs like #1940 to start Chatterino with Window Layouts that aren't defined in a json file. This commit has deprecated loading of v1 window layouts (we're now on v2). If a v1 window layout is there, it will just be ignored and Chatterino will boot up as if it did not have a window layout at all, and on save that old window layout will be gone. * Fix compile error for mac
- Loading branch information
Showing
8 changed files
with
484 additions
and
191 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
#include "common/WindowDescriptors.hpp" | ||
|
||
#include "widgets/Window.hpp" | ||
|
||
namespace chatterino { | ||
|
||
namespace { | ||
|
||
QJsonArray loadWindowArray(const QString &settingsPath) | ||
{ | ||
QFile file(settingsPath); | ||
file.open(QIODevice::ReadOnly); | ||
QByteArray data = file.readAll(); | ||
QJsonDocument document = QJsonDocument::fromJson(data); | ||
QJsonArray windows_arr = document.object().value("windows").toArray(); | ||
return windows_arr; | ||
} | ||
|
||
template <typename T> | ||
T loadNodes(const QJsonObject &obj) | ||
{ | ||
static_assert("loadNodes must be called with the SplitNodeDescriptor " | ||
"or ContainerNodeDescriptor type"); | ||
} | ||
|
||
template <> | ||
SplitNodeDescriptor loadNodes(const QJsonObject &root) | ||
{ | ||
SplitNodeDescriptor descriptor; | ||
|
||
descriptor.flexH_ = root.value("flexh").toDouble(1.0); | ||
descriptor.flexV_ = root.value("flexv").toDouble(1.0); | ||
|
||
auto data = root.value("data").toObject(); | ||
|
||
SplitDescriptor::loadFromJSON(descriptor, root, data); | ||
|
||
return descriptor; | ||
} | ||
|
||
template <> | ||
ContainerNodeDescriptor loadNodes(const QJsonObject &root) | ||
{ | ||
ContainerNodeDescriptor descriptor; | ||
|
||
descriptor.flexH_ = root.value("flexh").toDouble(1.0); | ||
descriptor.flexV_ = root.value("flexv").toDouble(1.0); | ||
|
||
descriptor.vertical_ = root.value("type").toString() == "vertical"; | ||
|
||
for (QJsonValue _val : root.value("items").toArray()) | ||
{ | ||
auto _obj = _val.toObject(); | ||
|
||
auto _type = _obj.value("type"); | ||
if (_type == "split") | ||
{ | ||
descriptor.items_.emplace_back( | ||
loadNodes<SplitNodeDescriptor>(_obj)); | ||
} | ||
else | ||
{ | ||
descriptor.items_.emplace_back( | ||
loadNodes<ContainerNodeDescriptor>(_obj)); | ||
} | ||
} | ||
|
||
return descriptor; | ||
} | ||
|
||
} // namespace | ||
|
||
void SplitDescriptor::loadFromJSON(SplitDescriptor &descriptor, | ||
const QJsonObject &root, | ||
const QJsonObject &data) | ||
{ | ||
descriptor.type_ = data.value("type").toString(); | ||
descriptor.server_ = data.value("server").toInt(-1); | ||
if (data.contains("channel")) | ||
{ | ||
descriptor.channelName_ = data.value("channel").toString(); | ||
} | ||
else | ||
{ | ||
descriptor.channelName_ = data.value("name").toString(); | ||
} | ||
} | ||
|
||
WindowLayout WindowLayout::loadFromFile(const QString &path) | ||
{ | ||
WindowLayout layout; | ||
|
||
bool hasSetAMainWindow = false; | ||
|
||
// "deserialize" | ||
for (const QJsonValue &window_val : loadWindowArray(path)) | ||
{ | ||
QJsonObject window_obj = window_val.toObject(); | ||
|
||
WindowDescriptor window; | ||
|
||
// Load window type | ||
QString type_val = window_obj.value("type").toString(); | ||
auto type = type_val == "main" ? WindowType::Main : WindowType::Popup; | ||
|
||
if (type == WindowType::Main) | ||
{ | ||
if (hasSetAMainWindow) | ||
{ | ||
qDebug() | ||
<< "Window Layout file contains more than one Main window " | ||
"- demoting to Popup type"; | ||
type = WindowType::Popup; | ||
} | ||
hasSetAMainWindow = true; | ||
} | ||
|
||
window.type_ = type; | ||
|
||
// Load window state | ||
if (window_obj.value("state") == "minimized") | ||
{ | ||
window.state_ = WindowDescriptor::State::Minimized; | ||
} | ||
else if (window_obj.value("state") == "maximized") | ||
{ | ||
window.state_ = WindowDescriptor::State::Maximized; | ||
} | ||
|
||
// Load window geometry | ||
{ | ||
int x = window_obj.value("x").toInt(-1); | ||
int y = window_obj.value("y").toInt(-1); | ||
int width = window_obj.value("width").toInt(-1); | ||
int height = window_obj.value("height").toInt(-1); | ||
|
||
window.geometry_ = QRect(x, y, width, height); | ||
} | ||
|
||
bool hasSetASelectedTab = false; | ||
|
||
// Load window tabs | ||
QJsonArray tabs = window_obj.value("tabs").toArray(); | ||
for (QJsonValue tab_val : tabs) | ||
{ | ||
TabDescriptor tab; | ||
|
||
QJsonObject tab_obj = tab_val.toObject(); | ||
|
||
// Load tab custom title | ||
QJsonValue title_val = tab_obj.value("title"); | ||
if (title_val.isString()) | ||
{ | ||
tab.customTitle_ = title_val.toString(); | ||
} | ||
|
||
// Load tab selected state | ||
tab.selected_ = tab_obj.value("selected").toBool(false); | ||
|
||
if (tab.selected_) | ||
{ | ||
if (hasSetASelectedTab) | ||
{ | ||
qDebug() << "Window contains more than one selected tab - " | ||
"demoting to unselected"; | ||
tab.selected_ = false; | ||
} | ||
hasSetASelectedTab = true; | ||
} | ||
|
||
// Load tab "highlightsEnabled" state | ||
tab.highlightsEnabled_ = | ||
tab_obj.value("highlightsEnabled").toBool(true); | ||
|
||
QJsonObject splitRoot = tab_obj.value("splits2").toObject(); | ||
|
||
// Load tab splits | ||
if (!splitRoot.isEmpty()) | ||
{ | ||
// root type | ||
auto nodeType = splitRoot.value("type").toString(); | ||
if (nodeType == "split") | ||
{ | ||
tab.rootNode_ = loadNodes<SplitNodeDescriptor>(splitRoot); | ||
} | ||
else if (nodeType == "horizontal" || nodeType == "vertical") | ||
{ | ||
tab.rootNode_ = | ||
loadNodes<ContainerNodeDescriptor>(splitRoot); | ||
} | ||
} | ||
|
||
window.tabs_.emplace_back(std::move(tab)); | ||
} | ||
|
||
// Load emote popup position | ||
QJsonObject emote_popup_obj = window_obj.value("emotePopup").toObject(); | ||
layout.emotePopupPos_ = QPoint(emote_popup_obj.value("x").toInt(), | ||
emote_popup_obj.value("y").toInt()); | ||
|
||
layout.windows_.emplace_back(std::move(window)); | ||
} | ||
|
||
return layout; | ||
} | ||
|
||
} // namespace chatterino |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
#pragma once | ||
|
||
#include <QString> | ||
|
||
#include <optional> | ||
#include <variant> | ||
|
||
namespace chatterino { | ||
|
||
/** | ||
* A WindowLayout contains one or more windows. | ||
* Only one of those windows can be the main window | ||
* | ||
* Each window contains a list of tabs. | ||
* Only one of those tabs can be marked as selected. | ||
* | ||
* Each tab contains a root node. | ||
* The root node is either a: | ||
* - Split Node (for single-split tabs), or | ||
* - Container Node (for multi-split tabs). | ||
* This container node would then contain a list of nodes on its own, which could be split nodes or further container nodes | ||
**/ | ||
|
||
// from widgets/Window.hpp | ||
enum class WindowType; | ||
|
||
struct SplitDescriptor { | ||
// twitch or mentions or watching or whispers or irc | ||
QString type_; | ||
|
||
// Twitch Channel name or IRC channel name | ||
QString channelName_; | ||
|
||
// IRC server | ||
int server_{-1}; | ||
|
||
// Whether "Moderation Mode" (the sword icon) is enabled in this split or not | ||
bool moderationMode_{false}; | ||
|
||
static void loadFromJSON(SplitDescriptor &descriptor, | ||
const QJsonObject &root, const QJsonObject &data); | ||
}; | ||
|
||
struct SplitNodeDescriptor : SplitDescriptor { | ||
qreal flexH_ = 1; | ||
qreal flexV_ = 1; | ||
}; | ||
|
||
struct ContainerNodeDescriptor; | ||
|
||
using NodeDescriptor = | ||
std::variant<ContainerNodeDescriptor, SplitNodeDescriptor>; | ||
|
||
struct ContainerNodeDescriptor { | ||
qreal flexH_ = 1; | ||
qreal flexV_ = 1; | ||
|
||
bool vertical_ = false; | ||
|
||
std::vector<NodeDescriptor> items_; | ||
}; | ||
|
||
struct TabDescriptor { | ||
QString customTitle_; | ||
bool selected_{false}; | ||
bool highlightsEnabled_{true}; | ||
|
||
std::optional<NodeDescriptor> rootNode_; | ||
}; | ||
|
||
struct WindowDescriptor { | ||
enum class State { | ||
None, | ||
Minimized, | ||
Maximized, | ||
}; | ||
|
||
WindowType type_; | ||
State state_ = State::None; | ||
|
||
QRect geometry_; | ||
|
||
std::vector<TabDescriptor> tabs_; | ||
}; | ||
|
||
class WindowLayout | ||
{ | ||
public: | ||
static WindowLayout loadFromFile(const QString &path); | ||
|
||
// A complete window layout has a single emote popup position that is shared among all windows | ||
QPoint emotePopupPos_; | ||
|
||
std::vector<WindowDescriptor> windows_; | ||
}; | ||
|
||
} // namespace chatterino |
Oops, something went wrong.