lxgui
gui_addon_registry.cpp
1 #include "lxgui/gui_addon_registry.hpp"
2 
3 #include "lxgui/gui_event_emitter.hpp"
4 #include "lxgui/gui_localizer.hpp"
5 #include "lxgui/gui_out.hpp"
6 #include "lxgui/utils_file_system.hpp"
7 #include "lxgui/utils_range.hpp"
8 #include "lxgui/utils_std.hpp"
9 #include "lxgui/utils_string.hpp"
10 
11 #include <fstream>
12 #include <lxgui/extern_sol2_state.hpp>
13 
14 namespace {
15 // This should be incremented for each non-backward compatible change
16 // to the GUI API, or when new elements are added to the API.
17 const char* lxgui_ui_version = "0001";
18 } // namespace
19 
20 namespace lxgui::gui {
21 
23  sol::state& lua, localizer& loc, event_emitter& emitter, root& r, virtual_root& vr) :
24  lua_(lua), localizer_(loc), event_emitter_(emitter), root_(r), virtual_root_(vr) {}
25 
26 void addon_registry::load_addon_toc_(
27  const std::string& addon_name, const std::string& addon_directory) {
28  auto& addons = addon_list_[addon_directory];
29  if (addons.find(addon_name) != addons.end())
30  return;
31 
32  addon a;
33  a.enabled = true;
34  a.main_directory = utils::cut(addon_directory, "/").back();
35  a.directory = addon_directory + "/" + addon_name;
36 
37  std::string toc_file = a.directory + "/" + addon_name + ".toc";
38  std::ifstream file(toc_file);
39  if (!file.is_open())
40  return;
41 
42  std::string line;
43  while (std::getline(file, line)) {
44  utils::replace(line, "\r", "");
45  if (line.empty())
46  continue;
47 
48  std::string_view line_view = line;
49 
50  if (line_view.size() >= 2 && line_view[0] == '#' && line_view[1] == '#') {
51  line_view = line_view.substr(2);
52  line_view = utils::trim(line_view, ' ');
53  auto args = utils::cut_first(line_view, ":");
54  if (!args.first.empty() && !args.second.empty()) {
55  std::string_view key = utils::trim(args.first, ' ');
56  std::string_view value = utils::trim(args.second, ' ');
57 
58  if (key == "Interface") {
59  a.ui_version = value;
60 
61  if (a.ui_version == lxgui_ui_version)
62  a.enabled = true;
63  else {
64  gui::out << gui::warning << "gui::manager: "
65  << "Wrong UI version for \"" << addon_name
66  << "\" (got: " << a.ui_version
67  << ", expected: " << lxgui_ui_version << "). AddOn disabled."
68  << std::endl;
69  a.enabled = false;
70  }
71  } else if (key == "Title")
72  a.name = value;
73  else if (key == "Version")
74  a.version = value;
75  else if (key == "Author")
76  a.author = value;
77  else if (key == "SavedVariables") {
78  for (auto var : utils::cut(value, ",")) {
79  var = utils::trim(var, ' ');
80  if (!utils::has_no_content(var))
81  a.saved_variable_list.push_back(std::string{var});
82  }
83  }
84  }
85  } else {
86  line_view = utils::trim(line_view, ' ');
87  if (!utils::has_no_content(line_view))
88  a.file_list.push_back(a.directory + "/" + std::string{line_view});
89  }
90  }
91 
92  if (a.name.empty())
93  gui::out << gui::error << "gui::manager: Missing addon name in " << toc_file << "."
94  << std::endl;
95  else
96  addons[addon_name] = a;
97 }
98 
99 void addon_registry::load_addon_files_(const addon& a) {
100  localizer_.load_translations(a.directory);
101 
102  current_addon_ = &a;
103  for (const auto& file : a.file_list) {
104  const std::string extension = utils::get_file_extension(file);
105  if (extension == ".lua") {
106  auto result = lua_.do_file(file);
107  if (!result.valid()) {
108  std::string err = result.get<sol::error>().what();
109  gui::out << gui::error << err << std::endl;
110  event_emitter_.fire_event("LUA_ERROR", {err});
111  }
112  } else {
113  this->parse_layout_file_(file, a);
114  }
115  }
116 
117  std::string saved_variables_file =
118  "saves/interface/" + a.main_directory + "/" + a.name + ".lua";
119 
120  if (utils::file_exists(saved_variables_file)) {
121  auto result = lua_.do_file(saved_variables_file);
122  if (!result.valid()) {
123  std::string err = result.get<sol::error>().what();
124  gui::out << gui::error << err << std::endl;
125  event_emitter_.fire_event("LUA_ERROR", {err});
126  }
127  }
128 
129  event_emitter_.fire_event("ADDON_LOADED", {a.name});
130 }
131 
132 void addon_registry::load_addon_directory(const std::string& directory) {
133  for (const auto& sub_dir : utils::get_directory_list(directory))
134  this->load_addon_toc_(sub_dir, directory);
135 
136  std::vector<addon*> core_addon_stack;
137  std::vector<addon*> addon_stack;
138  bool core = false;
139 
140  auto& addons = addon_list_[directory];
141 
142  std::ifstream file(directory + "/addons.txt");
143  if (file.is_open()) {
144  std::string line;
145  while (std::getline(file, line)) {
146  utils::replace(line, "\r", "");
147  if (line.empty())
148  continue;
149 
150  std::string_view line_view = line;
151 
152  if (line_view[0] == '#') {
153  line_view = line_view.substr(1);
154  line_view = utils::trim(line_view, ' ');
155  core = line_view == "Core";
156  } else {
157  auto args = utils::cut_first(line_view, ":");
158  if (!args.first.empty() && !args.second.empty()) {
159  std::string_view key = utils::trim(args.first, ' ');
160  std::string_view value = utils::trim(args.second, ' ');
161  auto iter = addons.find(std::string{key});
162  if (iter != addons.end()) {
163  if (core)
164  core_addon_stack.push_back(&iter->second);
165  else
166  addon_stack.push_back(&iter->second);
167 
168  iter->second.enabled = value == "1";
169  }
170  }
171  }
172  }
173  file.close();
174  }
175 
176  for (auto* a : core_addon_stack) {
177  if (a->enabled)
178  this->load_addon_files_(*a);
179  }
180 
181  for (auto* a : addon_stack) {
182  if (a->enabled)
183  this->load_addon_files_(*a);
184  }
185 
186  current_addon_ = nullptr;
187 }
188 
189 std::string serialize(const std::string& tab, const sol::object& value) noexcept {
190  if (value.is<double>()) {
191  return utils::to_string(value.as<double>());
192  } else if (value.is<int>()) {
193  return utils::to_string(value.as<int>());
194  } else if (value.is<std::string>()) {
195  return "\"" + utils::to_string(value.as<std::string>()) + "\"";
196  } else if (value.is<sol::table>()) {
197  std::string result;
198  result += "{";
199 
200  std::string content;
201  sol::table table = value.as<sol::table>();
202  for (const auto& key_value : table) {
203  content += tab + " [" + serialize("", key_value.first) +
204  "] = " + serialize(tab + " ", key_value.second) + ",\n";
205  }
206 
207  if (!content.empty())
208  result += "\n" + content + tab;
209 
210  result += "}";
211  return result;
212  }
213 
214  return "nil";
215 }
216 
217 std::string serialize_global(sol::state& lua, const std::string& variable) noexcept {
218  sol::object value = lua.globals()[variable];
219  return serialize("", value);
220 }
221 
223  for (const auto& directory : addon_list_) {
224  for (const auto& a : utils::range::value(directory.second))
225  save_variables_(a);
226  }
227 }
228 
229 void addon_registry::save_variables_(const addon& a) const noexcept {
230  if (!a.saved_variable_list.empty()) {
231  if (!utils::make_directory("saves/interface/" + a.main_directory)) {
233  << "gui::addon_registry: unable to create directory 'saves/interface/"
234  << a.main_directory << "'" << std::endl;
235  return;
236  }
237 
238  std::ofstream file("saves/interface/" + a.main_directory + "/" + a.name + ".lua");
239  for (const auto& variable : a.saved_variable_list) {
240  std::string serialized = serialize_global(lua_, variable);
241  if (!serialized.empty())
242  file << serialized << "\n";
243  }
244  }
245 }
246 
248  return current_addon_;
249 }
250 
252  current_addon_ = a;
253 }
254 
255 } // namespace lxgui::gui
const addon * get_current_addon()
Returns the addon that is being parsed.
void save_variables() const
Save Lua variables registred for saving for all addons.
void set_current_addon(const addon *a)
Sets the current addon.
void load_addon_directory(const std::string &directory)
Parse all addons inside a directory.
addon_registry(sol::state &lua, localizer &loc, event_emitter &emitter, root &r, virtual_root &vr)
Constructor.
Generates events and keep tracks of registered callbacks.
void fire_event(const std::string &event_name, event_data data=event_data{})
Emmit a new event.
Utility class to translate strings for display in GUI.
void load_translations(const std::string &folder_path)
Loads new translations from a folder, selecting the language automatically.
Root of the UI object hierarchy.
Definition: gui_root.hpp:39
Root of the virtual UI object hierarchy.
std::string serialize_global(sol::state &lua, const std::string &variable) noexcept
std::ostream out
std::string serialize(const std::string &tab, const sol::object &value) noexcept
const std::string warning
Definition: gui_out.cpp:6
const std::string error
Definition: gui_out.cpp:7
range_impl::value_range< T > value(T &container)
Expose the value rather than the (key,value) pair.
bool make_directory(const std::string &path)
string_vector get_directory_list(const std::string &rel_path)
std::string get_file_extension(const std::string &file)
bool file_exists(const std::string &file)
A piece of the user interface.
Definition: gui_addon.hpp:12
std::string directory
Definition: gui_addon.hpp:21
std::string version
Definition: gui_addon.hpp:14
std::string author
Definition: gui_addon.hpp:16
std::string ui_version
Definition: gui_addon.hpp:15
std::string main_directory
Definition: gui_addon.hpp:20
std::vector< std::string > saved_variable_list
Definition: gui_addon.hpp:24
std::string name
Definition: gui_addon.hpp:13
std::vector< std::string > file_list
Definition: gui_addon.hpp:23