diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt
index ad4cd4758..c66106a30 100644
--- a/src/app/CMakeLists.txt
+++ b/src/app/CMakeLists.txt
@@ -655,6 +655,7 @@ target_sources(app-lib PRIVATE
ui/keyboard_shortcuts.cpp
ui/layer_frame_comboboxes.cpp
ui/layout.cpp
+ ui/layouts.cpp
ui/layout_selector.cpp
ui/main_menu_bar.cpp
ui/main_window.cpp
diff --git a/src/app/ui/layout.cpp b/src/app/ui/layout.cpp
index e0883a46f..cb3541687 100644
--- a/src/app/ui/layout.cpp
+++ b/src/app/ui/layout.cpp
@@ -28,7 +28,7 @@
namespace app {
-static void save_dock_layout(TiXmlElement& elem, const Dock* dock)
+static void save_dock_layout(TiXmlElement* elem, const Dock* dock)
{
for (const auto child : dock->children()) {
const int side = dock->whichSideChildIsDocked(child);
@@ -52,7 +52,7 @@ static void save_dock_layout(TiXmlElement& elem, const Dock* dock)
if (!sideStr.empty())
childElem.SetAttribute("side", sideStr);
- save_dock_layout(childElem, subdock);
+ save_dock_layout(&childElem, subdock);
}
else {
// Set the widget ID as the element name, e.g. ,
@@ -66,7 +66,7 @@ static void save_dock_layout(TiXmlElement& elem, const Dock* dock)
childElem.SetAttribute("height", size.h);
}
- elem.InsertEndChild(childElem);
+ elem->InsertEndChild(childElem);
}
}
@@ -136,40 +136,36 @@ static void load_dock_layout(const TiXmlElement* elem, Dock* dock)
}
}
-Layout::Layout(const std::string& name, const Dock* dock) : m_name(name)
+// static
+LayoutPtr Layout::MakeFromXmlElement(const TiXmlElement* layoutElem)
{
- XmlDocumentRef doc(new TiXmlDocument());
- TiXmlElement layoutsElem("layouts");
- {
- TiXmlElement layoutElem("layout");
- layoutElem.SetAttribute("name", name);
+ auto layout = std::make_shared();
+ if (auto name = layoutElem->Attribute("name"))
+ layout->m_name = name;
- save_dock_layout(layoutElem, dock);
+ layout->m_elem.reset(layoutElem->Clone()->ToElement());
+ return layout;
+}
- layoutsElem.InsertEndChild(layoutElem);
- }
+// static
+LayoutPtr Layout::MakeFromDock(const std::string& name, const Dock* dock)
+{
+ auto layout = std::make_shared();
+ layout->m_name = name;
- TiXmlDeclaration declaration("1.0", "utf-8", "");
- doc->InsertEndChild(declaration);
- doc->InsertEndChild(layoutsElem);
+ layout->m_elem = std::make_unique("layout");
+ layout->m_elem->SetAttribute("name", name);
+ save_dock_layout(layout->m_elem.get(), dock);
- std::stringstream s;
- s << *doc;
- m_data = s.str();
+ return layout;
}
bool Layout::loadLayout(Dock* dock) const
{
- XmlDocumentRef doc(new TiXmlDocument);
- doc->Parse(m_data.c_str(), 0, TIXML_DEFAULT_ENCODING);
- TiXmlHandle handle(doc.get());
-
- TiXmlElement* layoutElem = handle.FirstChild("layouts").FirstChild("layout").ToElement();
-
- if (!layoutElem)
+ if (!m_elem)
return false;
- TiXmlElement* elem = layoutElem->FirstChildElement();
+ TiXmlElement* elem = m_elem->FirstChildElement();
while (elem) {
load_dock_layout(elem, dock);
elem = elem->NextSiblingElement();
diff --git a/src/app/ui/layout.h b/src/app/ui/layout.h
index 8a53a3dfc..cd3844a8d 100644
--- a/src/app/ui/layout.h
+++ b/src/app/ui/layout.h
@@ -11,25 +11,29 @@
#include
#include
+class TiXmlElement;
+
namespace app {
class Dock;
-class Layout {
+class Layout;
+using LayoutPtr = std::shared_ptr;
+
+class Layout final {
public:
- Layout(const std::string& name, const Dock* dock);
+ static LayoutPtr MakeFromXmlElement(const TiXmlElement* layoutElem);
+ static LayoutPtr MakeFromDock(const std::string& name, const Dock* dock);
const std::string& name() const { return m_name; }
- const std::string& data() const { return m_data; }
+ const TiXmlElement* xmlElement() const { return m_elem.get(); }
bool loadLayout(Dock* dock) const;
private:
std::string m_name;
- std::string m_data;
+ std::unique_ptr m_elem;
};
-using LayoutPtr = std::shared_ptr;
-
} // namespace app
#endif
diff --git a/src/app/ui/layout_selector.cpp b/src/app/ui/layout_selector.cpp
index ed5736518..de57abed6 100644
--- a/src/app/ui/layout_selector.cpp
+++ b/src/app/ui/layout_selector.cpp
@@ -128,13 +128,13 @@ public:
case LayoutId::SAVE_LAYOUT: {
gen::NewLayout window;
window.name()->setText(
- fmt::format("{} ({})", window.name()->text(), m_selector->m_layouts.size()));
+ fmt::format("{} ({})", window.name()->text(), m_selector->m_layouts.size() + 1));
window.openWindowInForeground();
if (window.closer() == window.ok()) {
- auto layout = std::make_shared(window.name()->text(), win->customizableDock());
+ auto layout = Layout::MakeFromDock(window.name()->text(), win->customizableDock());
- m_selector->addLayout(std::move(layout));
+ m_selector->addLayout(layout);
}
break;
}
@@ -198,7 +198,7 @@ void LayoutSelector::addLayout(const LayoutPtr& layout)
auto item = m_comboBox.addItem(
new LayoutItem(this, LayoutItem::USER_DEFINED, layout->name(), layout));
- m_layouts.push_back(layout);
+ m_layouts.addLayout(layout);
m_comboBox.setSelectedItemIndex(item);
}
diff --git a/src/app/ui/layout_selector.h b/src/app/ui/layout_selector.h
index e9fedc980..6d91af9cb 100644
--- a/src/app/ui/layout_selector.h
+++ b/src/app/ui/layout_selector.h
@@ -11,6 +11,7 @@
#include "app/ui/dockable.h"
#include "app/ui/icon_button.h"
#include "app/ui/layout.h"
+#include "app/ui/layouts.h"
#include "ui/animated_widget.h"
#include "ui/box.h"
#include "ui/combobox.h"
@@ -61,7 +62,7 @@ private:
IconButton m_button;
gfx::Size m_startSize;
gfx::Size m_endSize;
- std::vector m_layouts;
+ Layouts m_layouts;
};
} // namespace app
diff --git a/src/app/ui/layouts.cpp b/src/app/ui/layouts.cpp
new file mode 100644
index 000000000..9089608a2
--- /dev/null
+++ b/src/app/ui/layouts.cpp
@@ -0,0 +1,79 @@
+// Aseprite
+// Copyright (c) 2022 Igara Studio S.A.
+//
+// This program is distributed under the terms of
+// the End-User License Agreement for Aseprite.
+
+#ifdef HAVE_CONFIG_H
+ #include "config.h"
+#endif
+
+#include "app/ui/layouts.h"
+
+#include "app/resource_finder.h"
+#include "app/xml_document.h"
+#include "app/xml_exception.h"
+#include "base/fs.h"
+
+#include
+
+namespace app {
+
+Layouts::Layouts()
+{
+ try {
+ std::string fn = m_userLayoutsFilename = UserLayoutsFilename();
+ if (base::is_file(fn))
+ load(fn);
+ }
+ catch (const std::exception& ex) {
+ LOG(ERROR, "LAY: Error loading user layouts: %s\n", ex.what());
+ }
+}
+
+Layouts::~Layouts()
+{
+ if (!m_userLayoutsFilename.empty())
+ save(m_userLayoutsFilename);
+}
+
+void Layouts::addLayout(const LayoutPtr& layout)
+{
+ m_layouts.push_back(layout);
+}
+
+void Layouts::load(const std::string& fn)
+{
+ XmlDocumentRef doc = app::open_xml(fn);
+ TiXmlHandle handle(doc.get());
+ TiXmlElement* layoutElem = handle.FirstChild("layouts").FirstChild("layout").ToElement();
+
+ while (layoutElem) {
+ m_layouts.push_back(Layout::MakeFromXmlElement(layoutElem));
+ layoutElem = layoutElem->NextSiblingElement();
+ }
+}
+
+void Layouts::save(const std::string& fn) const
+{
+ XmlDocumentRef doc(new TiXmlDocument());
+ TiXmlElement layoutsElem("layouts");
+
+ for (const auto& layout : m_layouts)
+ layoutsElem.InsertEndChild(*layout->xmlElement());
+
+ TiXmlDeclaration declaration("1.0", "utf-8", "");
+ doc->InsertEndChild(declaration);
+ doc->InsertEndChild(layoutsElem);
+ save_xml(doc, fn);
+}
+
+// static
+std::string Layouts::UserLayoutsFilename()
+{
+ ResourceFinder rf;
+ rf.includeUserDir("user.aseprite-layouts");
+ return rf.getFirstOrCreateDefault();
+}
+
+} // namespace app
diff --git a/src/app/ui/layouts.h b/src/app/ui/layouts.h
new file mode 100644
index 000000000..6fd5e8bfb
--- /dev/null
+++ b/src/app/ui/layouts.h
@@ -0,0 +1,44 @@
+// Aseprite
+// Copyright (c) 2022 Igara Studio S.A.
+//
+// This program is distributed under the terms of
+// the End-User License Agreement for Aseprite.
+
+#ifndef APP_UI_LAYOUTS_H_INCLUDED
+#define APP_UI_LAYOUTS_H_INCLUDED
+#pragma once
+
+#include "app/ui/layout.h"
+
+#include
+#include
+
+namespace app {
+
+class Layouts {
+public:
+ Layouts();
+ ~Layouts();
+
+ size_t size() const { return m_layouts.size(); }
+
+ void addLayout(const LayoutPtr& layout);
+
+ // To iterate layouts
+ using List = std::vector;
+ using iterator = List::iterator;
+ iterator begin() { return m_layouts.begin(); }
+ iterator end() { return m_layouts.end(); }
+
+private:
+ void load(const std::string& fn);
+ void save(const std::string& fn) const;
+ static std::string UserLayoutsFilename();
+
+ List m_layouts;
+ std::string m_userLayoutsFilename;
+};
+
+} // namespace app
+
+#endif