DataView.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. /*
  2. * This source file is part of RmlUi, the HTML/CSS Interface Middleware
  3. *
  4. * For the latest information, see http://github.com/mikke89/RmlUi
  5. *
  6. * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
  7. * Copyright (c) 2019 The RmlUi Team, and contributors
  8. *
  9. * Permission is hereby granted, free of charge, to any person obtaining a copy
  10. * of this software and associated documentation files (the "Software"), to deal
  11. * in the Software without restriction, including without limitation the rights
  12. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. * copies of the Software, and to permit persons to whom the Software is
  14. * furnished to do so, subject to the following conditions:
  15. *
  16. * The above copyright notice and this permission notice shall be included in
  17. * all copies or substantial portions of the Software.
  18. *
  19. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. * THE SOFTWARE.
  26. *
  27. */
  28. #include "precompiled.h"
  29. #include "../../Include/RmlUi/Core/DataView.h"
  30. #include "../../Include/RmlUi/Core/DataModel.h"
  31. namespace Rml {
  32. namespace Core {
  33. DataView::~DataView() {}
  34. Element* DataView::GetElement() const
  35. {
  36. Element* result = attached_element.get();
  37. if (!result)
  38. Log::Message(Log::LT_WARNING, "Could not retrieve element in view, was it destroyed?");
  39. return result;
  40. }
  41. DataView::DataView(Element* element) : attached_element(element->GetObserverPtr()), element_depth(0) {
  42. if (element)
  43. {
  44. for (Element* parent = element->GetParentNode(); parent; parent = parent->GetParentNode())
  45. element_depth += 1;
  46. }
  47. }
  48. DataViewText::DataViewText(const DataModel& model, ElementText* in_parent_element, const String& in_text, const size_t index_begin_search) : DataView(in_parent_element)
  49. {
  50. text.reserve(in_text.size());
  51. bool success = true;
  52. size_t previous_close_brackets = 0;
  53. size_t begin_brackets = index_begin_search;
  54. while ((begin_brackets = in_text.find("{{", begin_brackets)) != String::npos)
  55. {
  56. text.insert(text.end(), in_text.begin() + previous_close_brackets, in_text.begin() + begin_brackets);
  57. const size_t begin_name = begin_brackets + 2;
  58. const size_t end_name = in_text.find("}}", begin_name);
  59. if (end_name == String::npos)
  60. {
  61. success = false;
  62. break;
  63. }
  64. DataEntry entry;
  65. entry.index = text.size();
  66. String address_str = StringUtilities::StripWhitespace(StringView(in_text.data() + begin_name, in_text.data() + end_name));
  67. entry.variable_address = model.ResolveAddress(address_str, in_parent_element);
  68. data_entries.push_back(std::move(entry));
  69. previous_close_brackets = end_name + 2;
  70. begin_brackets = previous_close_brackets;
  71. }
  72. if (data_entries.empty())
  73. success = false;
  74. if (success && previous_close_brackets < in_text.size())
  75. text.insert(text.end(), in_text.begin() + previous_close_brackets, in_text.end());
  76. if (success)
  77. {
  78. Update(model);
  79. }
  80. else
  81. {
  82. text.clear();
  83. data_entries.clear();
  84. InvalidateView();
  85. }
  86. }
  87. bool DataViewText::Update(const DataModel& model)
  88. {
  89. bool entries_modified = false;
  90. for (DataEntry& entry : data_entries)
  91. {
  92. String value;
  93. bool result = model.GetValue(entry.variable_address, value);
  94. if (result && entry.value != value)
  95. {
  96. entry.value = value;
  97. entries_modified = true;
  98. }
  99. }
  100. if (entries_modified)
  101. {
  102. if (Element* element = GetElement())
  103. {
  104. RMLUI_ASSERTMSG(rmlui_dynamic_cast<ElementText*>(element), "Somehow the element type was changed from ElementText since construction of the view. Should not be possible?");
  105. if(auto text_element = static_cast<ElementText*>(element))
  106. {
  107. String new_text = BuildText();
  108. text_element->SetText(new_text);
  109. }
  110. }
  111. else
  112. {
  113. Log::Message(Log::LT_WARNING, "Could not update data view text, element no longer valid. Was it destroyed?");
  114. }
  115. }
  116. return entries_modified;
  117. }
  118. String DataViewText::BuildText() const
  119. {
  120. size_t reserve_size = text.size();
  121. for (const DataEntry& entry : data_entries)
  122. reserve_size += entry.value.size();
  123. String result;
  124. result.reserve(reserve_size);
  125. size_t previous_index = 0;
  126. for (const DataEntry& entry : data_entries)
  127. {
  128. result += text.substr(previous_index, entry.index - previous_index);
  129. result += entry.value;
  130. previous_index = entry.index;
  131. }
  132. if (previous_index < text.size())
  133. result += text.substr(previous_index);
  134. return result;
  135. }
  136. DataViewAttribute::DataViewAttribute(const DataModel& model, Element* element, Element* parent, const String& binding_name, const String& attribute_name)
  137. : DataView(element), attribute_name(attribute_name)
  138. {
  139. variable_address = model.ResolveAddress(binding_name, parent);
  140. if (attribute_name.empty())
  141. InvalidateView();
  142. else
  143. Update(model);
  144. }
  145. bool DataViewAttribute::Update(const DataModel& model)
  146. {
  147. bool result = false;
  148. String value;
  149. Element* element = GetElement();
  150. if (model.GetValue(variable_address, value) && element)
  151. {
  152. Variant* attribute = element->GetAttribute(attribute_name);
  153. if (!attribute || (attribute && attribute->Get<String>() != value))
  154. {
  155. element->SetAttribute(attribute_name, value);
  156. result = true;
  157. }
  158. }
  159. return result;
  160. }
  161. DataViewStyle::DataViewStyle(const DataModel& model, Element* element, Element* parent, const String& binding_name, const String& property_name)
  162. : DataView(element), property_name(property_name)
  163. {
  164. variable_address = model.ResolveAddress(binding_name, parent);
  165. if (variable_address.empty() || property_name.empty())
  166. InvalidateView();
  167. else
  168. Update(model);
  169. }
  170. bool DataViewStyle::Update(const DataModel& model)
  171. {
  172. bool result = false;
  173. String value;
  174. Element* element = GetElement();
  175. if (model.GetValue(variable_address, value) && element)
  176. {
  177. const Property* p = element->GetLocalProperty(property_name);
  178. if (!p || p->Get<String>() != value)
  179. {
  180. element->SetProperty(property_name, value);
  181. result = true;
  182. }
  183. }
  184. return result;
  185. }
  186. DataViewIf::DataViewIf(const DataModel& model, Element* element, Element* parent, const String& binding_name) : DataView(element)
  187. {
  188. variable_address = model.ResolveAddress(binding_name, element);
  189. if (variable_address.empty())
  190. InvalidateView();
  191. else
  192. Update(model);
  193. }
  194. bool DataViewIf::Update(const DataModel& model)
  195. {
  196. bool result = false;
  197. bool value = false;
  198. Element* element = GetElement();
  199. if (model.GetValue(variable_address, value) && element)
  200. {
  201. bool is_visible = (element->GetLocalStyleProperties().count(PropertyId::Display) == 0);
  202. if(is_visible != value)
  203. {
  204. if (value)
  205. element->RemoveProperty(PropertyId::Display);
  206. else
  207. element->SetProperty(PropertyId::Display, Property(Style::Display::None));
  208. result = true;
  209. }
  210. }
  211. return result;
  212. }
  213. DataViewFor::DataViewFor(const DataModel& model, Element* element, const String& in_binding_name, const String& in_rml_content)
  214. : DataView(element), rml_contents(in_rml_content)
  215. {
  216. StringList binding_list;
  217. StringUtilities::ExpandString(binding_list, in_binding_name, ':');
  218. if (binding_list.empty() || binding_list.size() > 2 || binding_list.front().empty() || binding_list.back().empty())
  219. {
  220. Log::Message(Log::LT_WARNING, "Invalid syntax in data-for '%s'", in_binding_name.c_str());
  221. InvalidateView();
  222. return;
  223. }
  224. if (binding_list.size() == 2)
  225. alias_name = binding_list.front();
  226. else
  227. alias_name = "it";
  228. const String& binding_name = binding_list.back();
  229. variable_address = model.ResolveAddress(binding_name, element);
  230. if (variable_address.empty())
  231. {
  232. InvalidateView();
  233. return;
  234. }
  235. attributes = element->GetAttributes();
  236. attributes.erase("data-for");
  237. element->SetProperty(PropertyId::Display, Property(Style::Display::None));
  238. Update(model);
  239. }
  240. bool DataViewFor::Update(const DataModel& model)
  241. {
  242. Variable variable = model.GetVariable(variable_address);
  243. if (!variable)
  244. return false;
  245. bool result = false;
  246. const int size = variable.Size();
  247. const int num_elements = (int)elements.size();
  248. Element* element = GetElement();
  249. for (int i = 0; i < Math::Max(size, num_elements); i++)
  250. {
  251. if (i >= num_elements)
  252. {
  253. ElementPtr new_element_ptr = Factory::InstanceElement(nullptr, element->GetTagName(), element->GetTagName(), attributes);
  254. new_element_ptr->SetDataModel((DataModel*)(&model));
  255. Element* new_element = element->GetParentNode()->InsertBefore(std::move(new_element_ptr), element);
  256. elements.push_back(new_element);
  257. Address replacement_address;
  258. replacement_address.reserve(variable_address.size() + 1);
  259. replacement_address = variable_address;
  260. replacement_address.push_back(AddressEntry(i));
  261. model.InsertAlias(new_element, alias_name, replacement_address);
  262. elements[i]->SetInnerRML(rml_contents);
  263. RMLUI_ASSERT(i < (int)elements.size());
  264. }
  265. if (i >= size)
  266. {
  267. model.EraseAliases(elements[i]);
  268. elements[i]->GetParentNode()->RemoveChild(elements[i]).reset();
  269. elements[i] = nullptr;
  270. }
  271. }
  272. if (num_elements > size)
  273. elements.resize(size);
  274. return result;
  275. }
  276. DataViews::DataViews()
  277. {}
  278. DataViews::~DataViews()
  279. {}
  280. void DataViews::Add(UniquePtr<DataView> view) {
  281. views_to_add.push_back(std::move(view));
  282. }
  283. void DataViews::OnElementRemove(Element* element)
  284. {
  285. for (auto it = views.begin(); it != views.end();)
  286. {
  287. auto& view = *it;
  288. if (view && view->GetElement() == element)
  289. {
  290. views_to_remove.push_back(std::move(view));
  291. it = views.erase(it);
  292. }
  293. else
  294. ++it;
  295. }
  296. }
  297. bool DataViews::Update(const DataModel & model, const SmallUnorderedSet< String >& dirty_variables)
  298. {
  299. bool result = false;
  300. std::vector<DataView*> dirty_views;
  301. if(!views_to_add.empty())
  302. {
  303. views.reserve(views.size() + views_to_add.size());
  304. for (auto&& view : views_to_add)
  305. {
  306. dirty_views.push_back(view.get());
  307. for(const String& variable_name : view->GetVariableNameList())
  308. name_view_map.emplace(variable_name, view.get());
  309. views.push_back(std::move(view));
  310. }
  311. views_to_add.clear();
  312. }
  313. for(const String& variable_name : dirty_variables)
  314. {
  315. auto pair = name_view_map.equal_range(variable_name);
  316. for (auto it = pair.first; it != pair.second; ++it)
  317. dirty_views.push_back(it->second);
  318. }
  319. // Remove duplicate entries
  320. std::sort(dirty_views.begin(), dirty_views.end());
  321. auto it_remove = std::unique(dirty_views.begin(), dirty_views.end());
  322. dirty_views.erase(it_remove, dirty_views.end());
  323. // Sort by the element's depth in the document tree so that any structural changes due to a changed variable are reflected in the element's children.
  324. // Eg. the 'data-for' view will remove children if any of its data variable array size is reduced.
  325. std::sort(dirty_views.begin(), dirty_views.end(), [](auto&& left, auto&& right) { return left->GetElementDepth() < right->GetElementDepth(); });
  326. // Todo: Newly created views won't actually be updated until next Update loop.
  327. for (DataView* view : dirty_views)
  328. {
  329. RMLUI_ASSERT(view);
  330. if (!view)
  331. continue;
  332. if (view->IsValid())
  333. result |= view->Update(model);
  334. }
  335. // Destroy views marked for destruction
  336. // @performance: Horrible...
  337. if (!views_to_remove.empty())
  338. {
  339. for (const auto& view : views_to_remove)
  340. {
  341. for (auto it = name_view_map.begin(); it != name_view_map.end(); )
  342. {
  343. if (it->second == view.get())
  344. it = name_view_map.erase(it);
  345. else
  346. ++it;
  347. }
  348. }
  349. views_to_remove.clear();
  350. }
  351. return result;
  352. }
  353. }
  354. }