refactor(window-layout): Build descriptor for serializing (#7092)

It's useful for other components to get a split descriptor from a split
(e.g. chatterino-embed). To avoid differences between the descriptor and
the JSON output (e.g. adding a field to the split data), this makes the
descriptor mandatory. Instead of going `SplitContainer* -> JSON`, we do
`SplitContainer* -> NodeDescriptor -> JSON`. In a followup, I'm going to
add `*Descriptor::toJSON`.

Reviewed-by: pajlada <rasmus.karlsson@pajlada.com>
This commit is contained in:
Nerixyz
2026-07-26 10:34:56 +00:00
committed by GitHub
parent 8f84483330
commit 45f98a71b0
5 changed files with 94 additions and 108 deletions
+44 -71
View File
@@ -16,6 +16,7 @@
#include "util/CombinePath.hpp"
#include "util/FilesystemHelpers.hpp"
#include "util/SignalListener.hpp"
#include "util/Variant.hpp"
#include "widgets/AccountSwitchPopup.hpp"
#include "widgets/dialogs/SettingsDialog.hpp"
#include "widgets/FramelessEmbedWindow.hpp"
@@ -38,6 +39,8 @@
#include <chrono>
#include <optional>
using namespace Qt::Literals;
namespace {
std::optional<bool> &shouldMoveOutOfBoundsWindow()
@@ -676,92 +679,62 @@ void WindowManager::encodeTab(SplitContainer *tab, bool isSelected,
// splits
QJsonObject splits;
WindowManager::encodeNodeRecursively(tab->getBaseNode(), splits);
WindowManager::encodeNodeRecursively(tab->buildDescriptor(), splits);
obj.insert("splits2", splits);
}
void WindowManager::encodeNodeRecursively(SplitNode *node, QJsonObject &obj)
void WindowManager::encodeNodeRecursively(const NodeDescriptor &descriptor,
QJsonObject &obj)
{
switch (node->getType())
{
case SplitNode::Type::Split: {
obj.insert("type", "split");
obj.insert("moderationMode", node->getSplit()->getModerationMode());
std::visit(variant::Overloaded{
[&](const SplitNodeDescriptor &split) {
obj.insert("type", "split");
obj.insert("flexh", split.flexH_);
obj.insert("flexv", split.flexV_);
QJsonObject split;
WindowManager::encodeChannel(node->getSplit()->getIndirectChannel(),
split);
obj.insert("data", split);
obj.insert("moderationMode", split.moderationMode_);
QJsonArray filters;
WindowManager::encodeFilters(node->getSplit(), filters);
obj.insert("filters", filters);
QJsonObject data{{"type"_L1, split.type_}};
if (!split.channelName_.isEmpty())
{
data.insert("name"_L1, split.channelName_);
}
obj.insert("data", data);
auto spellOverride = node->getSplit()->checkSpellingOverride();
if (spellOverride)
{
obj["checkSpelling"] = *spellOverride;
}
}
break;
case SplitNode::Type::HorizontalContainer:
case SplitNode::Type::VerticalContainer: {
obj.insert("type",
node->getType() == SplitNode::Type::HorizontalContainer
? "horizontal"
: "vertical");
QJsonArray filters;
WindowManager::encodeFilters(split.filters_, filters);
obj.insert("filters", filters);
QJsonArray itemsArr;
for (const auto &n : node->getChildren())
{
QJsonObject subObj;
WindowManager::encodeNodeRecursively(n.get(), subObj);
itemsArr.append(subObj);
}
obj.insert("items", itemsArr);
}
break;
if (split.spellCheckOverride.has_value())
{
obj["checkSpelling"] = *split.spellCheckOverride;
}
},
[&](const ContainerNodeDescriptor &container) {
obj.insert("type", container.vertical_ ? "vertical"
: "horizontal");
obj.insert("flexh", container.flexH_);
obj.insert("flexv", container.flexV_);
default:
break;
}
obj.insert("flexh", node->getHorizontalFlex());
obj.insert("flexv", node->getVerticalFlex());
QJsonArray itemsArr;
for (const auto &n : container.items_)
{
QJsonObject subObj;
WindowManager::encodeNodeRecursively(n, subObj);
itemsArr.append(subObj);
}
obj.insert("items", itemsArr);
},
},
descriptor);
}
void WindowManager::encodeChannel(IndirectChannel channel, QJsonObject &obj)
void WindowManager::encodeFilters(std::span<const QUuid> filters,
QJsonArray &arr)
{
assertInGuiThread();
obj.insert("type", qmagicenum::enumNameString(channel.getType()));
switch (channel.getType())
{
case Channel::Type::Twitch:
case Channel::Type::Misc:
obj.insert("name", channel.get()->getName());
break;
case Channel::Type::TwitchWhispers:
case Channel::Type::TwitchWatching:
case Channel::Type::TwitchMentions:
case Channel::Type::TwitchLive:
case Channel::Type::TwitchAutomod:
// FIXME: Remove these (#5703)
case Channel::Type::None:
case Channel::Type::Direct:
case Channel::Type::TwitchEnd:
break;
}
}
void WindowManager::encodeFilters(Split *split, QJsonArray &arr)
{
assertInGuiThread();
auto filters = split->getFilters();
for (const auto &f : filters)
{
arr.append(f.toString(QUuid::WithoutBraces));
+8 -3
View File
@@ -26,7 +26,13 @@ class Window;
class ChannelView;
class IndirectChannel;
class Split;
struct SplitDescriptor;
struct SplitNodeDescriptor;
struct ContainerNodeDescriptor;
using NodeDescriptor =
std::variant<ContainerNodeDescriptor, SplitNodeDescriptor>;
class Channel;
using ChannelPtr = std::shared_ptr<Channel>;
struct Message;
@@ -63,8 +69,7 @@ public:
static void encodeTab(SplitContainer *tab, bool isSelected,
QJsonObject &obj);
static void encodeChannel(IndirectChannel channel, QJsonObject &obj);
static void encodeFilters(Split *split, QJsonArray &arr);
static void encodeFilters(std::span<const QUuid> filters, QJsonArray &arr);
void showSettingsDialog(
QWidget *parent,
@@ -162,7 +167,7 @@ public:
pajlada::Signals::Signal<const MessagePtr &> scrollToMessageSignal;
private:
static void encodeNodeRecursively(SplitContainer::Node *node,
static void encodeNodeRecursively(const NodeDescriptor &descriptor,
QJsonObject &obj);
// Load window layout from the window-layout.json file
+32
View File
@@ -1358,6 +1358,38 @@ void Split::setInputReply(const MessagePtr &reply)
this->input_->setReply(reply);
}
SplitDescriptor Split::buildDescriptor() const
{
SplitDescriptor descriptor;
descriptor.moderationMode_ = this->getModerationMode();
descriptor.filters_ = this->getFilters();
descriptor.spellCheckOverride = this->checkSpellingOverride();
auto chan = this->getChannel();
descriptor.type_ = qmagicenum::enumNameString(chan->getType());
switch (chan->getType())
{
case Channel::Type::Twitch:
case Channel::Type::Misc:
descriptor.channelName_ = chan->getName();
break;
case Channel::Type::TwitchWhispers:
case Channel::Type::TwitchWatching:
case Channel::Type::TwitchMentions:
case Channel::Type::TwitchLive:
case Channel::Type::TwitchAutomod:
// FIXME: Remove these (#5703)
case Channel::Type::None:
case Channel::Type::Direct:
case Channel::Type::TwitchEnd:
break;
}
return descriptor;
}
void Split::unpause()
{
this->view_->unpause(PauseReason::KeyboardModifier);
+4
View File
@@ -27,6 +27,8 @@ class PinnedMessageWidget;
class SelectChannelDialog;
class OverlayWindow;
struct SplitDescriptor;
// Each ChatWidget consists of three sub-elements that handle their own part of
// the chat widget: ChatWidgetHeader
// - Responsible for rendering which channel the ChatWidget is in, and the
@@ -86,6 +88,8 @@ public:
void setInputReply(const MessagePtr &reply);
SplitDescriptor buildDescriptor() const;
// This is called on window focus lost
void unpause();
+6 -34
View File
@@ -841,44 +841,14 @@ void SplitContainer::popup()
window.show();
}
QString channelTypeToString(Channel::Type value) noexcept
{
using Type = chatterino::Channel::Type;
switch (value)
{
default:
assert(false && "value cannot be serialized");
return "never";
case Type::Twitch:
return "twitch";
case Type::TwitchWhispers:
return "whispers";
case Type::TwitchWatching:
return "watching";
case Type::TwitchMentions:
return "mentions";
case Type::TwitchLive:
return "live";
case Type::TwitchAutomod:
return "automod";
case Type::Misc:
return "misc";
}
}
NodeDescriptor SplitContainer::buildDescriptorRecursively(
const Node *currentNode) const
{
if (currentNode->children_.empty())
if (currentNode->children_.empty() && currentNode->split_)
{
const auto channelType =
currentNode->split_->getIndirectChannel().getType();
SplitNodeDescriptor result;
result.type_ = channelTypeToString(channelType);
result.channelName_ = currentNode->split_->getChannel()->getName();
result.filters_ = currentNode->split_->getFilters();
SplitNodeDescriptor result(currentNode->split_->buildDescriptor());
result.flexH_ = currentNode->flexH_;
result.flexV_ = currentNode->flexV_;
return result;
}
@@ -890,6 +860,8 @@ NodeDescriptor SplitContainer::buildDescriptorRecursively(
descriptor.items_.push_back(
this->buildDescriptorRecursively(child.get()));
}
descriptor.flexH_ = currentNode->flexH_;
descriptor.flexV_ = currentNode->flexV_;
return descriptor;
}