1// Aseprite Code Generator
2// Copyright (c) 2021 Igara Studio S.A.
3// Copyright (c) 2014-2018 David Capello
4//
5// This file is released under the terms of the MIT license.
6// Read LICENSE.txt for more information.
7
8#include "gen/ui_class.h"
9
10#include "base/exception.h"
11#include "base/file_handle.h"
12#include "base/fs.h"
13#include "base/string.h"
14#include "gen/common.h"
15
16#include <iostream>
17#include <set>
18#include <vector>
19
20typedef std::vector<TiXmlElement*> XmlElements;
21
22namespace {
23
24struct Item {
25 std::string xmlid;
26 std::string cppid;
27 std::string type;
28 std::string incl;
29 const Item& typeIncl(const char* type,
30 const char* incl) {
31 this->type = type;
32 this->incl = incl;
33 return *this;
34 }
35};
36
37}
38
39static TiXmlElement* find_element_by_id(TiXmlElement* elem, const std::string& thisId)
40{
41 const char* id = elem->Attribute("id");
42 if (id && id == thisId)
43 return elem;
44
45 TiXmlElement* child = elem->FirstChildElement();
46 while (child) {
47 TiXmlElement* match = find_element_by_id(child, thisId);
48 if (match)
49 return match;
50
51 child = child->NextSiblingElement();
52 }
53
54 return NULL;
55}
56
57static void collect_widgets_with_ids(TiXmlElement* elem, XmlElements& widgets)
58{
59 TiXmlElement* child = elem->FirstChildElement();
60 while (child) {
61 const char* id = child->Attribute("id");
62 if (id)
63 widgets.push_back(child);
64 collect_widgets_with_ids(child, widgets);
65 child = child->NextSiblingElement();
66 }
67}
68
69static Item convert_to_item(TiXmlElement* elem)
70{
71 static std::string parent;
72 const std::string name = elem->Value();
73 if (name != "item")
74 parent = name;
75
76 Item item;
77 if (elem->Attribute("id")) {
78 item.xmlid = elem->Attribute("id");
79 item.cppid = convert_xmlid_to_cppid(item.xmlid, false);
80 }
81
82 if (name == "box")
83 return item.typeIncl("ui::Box",
84 "ui/box.h");
85 if (name == "button")
86 return item.typeIncl("ui::Button",
87 "ui/button.h");
88 if (name == "buttonset")
89 return item.typeIncl("app::ButtonSet",
90 "app/ui/button_set.h");
91 if (name == "check") {
92 if (elem->Attribute("pref") != nullptr)
93 return item.typeIncl("BoolPrefWidget<ui::CheckBox>",
94 "app/ui/pref_widget.h");
95 else
96 return item.typeIncl("ui::CheckBox",
97 "ui/button.h");
98 }
99 if (name == "colorpicker")
100 return item.typeIncl("app::ColorButton",
101 "app/ui/color_button.h");
102 if (name == "combobox")
103 return item.typeIncl("ui::ComboBox",
104 "ui/combobox.h");
105 if (name == "dropdownbutton")
106 return item.typeIncl("app::DropDownButton",
107 "app/ui/drop_down_button.h");
108 if (name == "entry")
109 return item.typeIncl("ui::Entry",
110 "ui/entry.h");
111 if (name == "expr")
112 return item.typeIncl("app::ExprEntry",
113 "app/ui/expr_entry.h");
114 if (name == "grid")
115 return item.typeIncl("ui::Grid",
116 "ui/grid.h");
117 if (name == "hbox")
118 return item.typeIncl("ui::HBox",
119 "ui/box.h");
120 if (name == "image")
121 return item.typeIncl("ui::ImageView",
122 "ui/image_view.h");
123 if (name == "item" &&
124 parent == "buttonset")
125 return item.typeIncl("app::ButtonSet::Item",
126 "app/ui/button_set.h");
127 if (name == "label")
128 return item.typeIncl("ui::Label",
129 "ui/label.h");
130 if (name == "link")
131 return item.typeIncl("ui::LinkLabel",
132 "ui/link_label.h");
133 if (name == "listbox")
134 return item.typeIncl("ui::ListBox",
135 "ui/listbox.h");
136 if (name == "listitem")
137 return item.typeIncl("ui::ListItem",
138 "ui/listitem.h");
139 if (name == "panel")
140 return item.typeIncl("ui::Panel",
141 "ui/panel.h");
142 if (name == "popupwindow")
143 return item.typeIncl("ui::PopupWindow",
144 "ui/popup_window.h");
145 if (name == "radio")
146 return item.typeIncl("ui::RadioButton",
147 "ui/button.h");
148 if (name == "search")
149 return item.typeIncl("app::SearchEntry",
150 "app/ui/search_entry.h");
151 if (name == "separator")
152 return item.typeIncl("ui::Separator",
153 "ui/separator.h");
154 if (name == "slider")
155 return item.typeIncl("ui::Slider",
156 "ui/slider.h");
157 if (name == "splitter")
158 return item.typeIncl("ui::Splitter",
159 "ui/splitter.h");
160 if (name == "textbox")
161 return item.typeIncl("ui::TextBox",
162 "ui/textbox.h");
163 if (name == "tipwindow")
164 return item.typeIncl("ui::TipWindow",
165 "ui/tooltips.h");
166 if (name == "vbox")
167 return item.typeIncl("ui::VBox",
168 "ui/box.h");
169 if (name == "view")
170 return item.typeIncl("ui::View",
171 "ui/view.h");
172 if (name == "window")
173 return item.typeIncl("ui::Window",
174 "ui/window.h");
175
176 throw base::Exception("Unknown widget name: " + name);
177}
178
179void gen_ui_class(TiXmlDocument* doc,
180 const std::string& inputFn,
181 const std::string& widgetId)
182{
183 std::cout
184 << "// Don't modify, generated file from " << inputFn << "\n"
185 << "\n";
186
187 TiXmlHandle handle(doc);
188 TiXmlElement* elem = handle.FirstChild("gui").ToElement();
189 elem = find_element_by_id(elem, widgetId);
190 if (!elem) {
191 std::cout << "#error Widget not found: " << widgetId << "\n";
192 return;
193 }
194
195 std::vector<Item> items;
196 {
197 XmlElements xmlWidgets;
198 collect_widgets_with_ids(elem, xmlWidgets);
199 for (TiXmlElement* elem : xmlWidgets) {
200 const char* id = elem->Attribute("id");
201 if (!id)
202 continue;
203 items.push_back(convert_to_item(elem));
204 }
205 }
206
207 std::string className = convert_xmlid_to_cppid(widgetId, true);
208 std::string fnUpper = base::string_to_upper(base::get_file_title(inputFn));
209 Item mainWidget = convert_to_item(elem);
210
211 std::set<std::string> headerFiles;
212 headerFiles.insert("app/find_widget.h");
213 headerFiles.insert("app/load_widget.h");
214 headerFiles.insert(mainWidget.incl);
215 for (const Item& item : items)
216 headerFiles.insert(item.incl);
217
218 std::cout
219 << "#ifndef GENERATED_" << fnUpper << "_H_INCLUDED\n"
220 << "#define GENERATED_" << fnUpper << "_H_INCLUDED\n"
221 << "#pragma once\n"
222 << "\n";
223 for (const auto& incl : headerFiles)
224 std::cout << "#include \"" << incl << "\"\n";
225 std::cout
226 << "\n"
227 << "namespace app {\n"
228 << "namespace gen {\n"
229 << "\n"
230 << " class " << className << " : public " << mainWidget.type << " {\n"
231 << " public:\n"
232 << " " << className << "()";
233
234 // Special ctor for base class
235 if (mainWidget.type == "ui::Window") {
236 const char* desktop = elem->Attribute("desktop");
237 if (desktop && std::string(desktop) == "true")
238 std::cout
239 << " : ui::Window(ui::Window::DesktopWindow)";
240 else
241 std::cout
242 << " : ui::Window(ui::Window::WithTitleBar)";
243 }
244
245 std::cout
246 << " {\n"
247 << " app::load_widget(\"" << base::get_file_name(inputFn) << "\", \"" << widgetId << "\", this);\n"
248 << " app::finder(this)\n";
249
250 for (const Item& item : items) {
251 std::cout
252 << " >> \"" << item.xmlid << "\" >> m_" << item.cppid << "\n";
253 }
254
255 std::cout
256 << " ;\n"
257 << " }\n"
258 << "\n";
259
260 for (const Item& item : items) {
261 std::cout
262 << " " << item.type << "* " << item.cppid << "() const { return m_" << item.cppid << "; }\n";
263 }
264
265 std::cout
266 << "\n"
267 << " private:\n";
268
269 for (const Item& item : items) {
270 std::cout
271 << " " << item.type << "* m_" << item.cppid << ";\n";
272 }
273
274 std::cout
275 << " };\n"
276 << "\n"
277 << "} // namespace gen\n"
278 << "} // namespace app\n"
279 << "\n"
280 << "#endif\n";
281}
282