DataBinding.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612
  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-2023 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 "../Common/TestsShell.h"
  29. #include <RmlUi/Core/Context.h>
  30. #include <RmlUi/Core/DataModelHandle.h>
  31. #include <RmlUi/Core/Element.h>
  32. #include <RmlUi/Core/ElementDocument.h>
  33. #include <cmath>
  34. #include <doctest.h>
  35. using namespace Rml;
  36. namespace {
  37. static const String document_rml = R"(
  38. <rml>
  39. <head>
  40. <title>Test</title>
  41. <link type="text/template" href="/assets/window.rml"/>
  42. <style>
  43. body.window
  44. {
  45. left: 50px;
  46. right: 50px;
  47. top: 30px;
  48. bottom: 30px;
  49. max-width: none;
  50. max-height: none;
  51. }
  52. div#content
  53. {
  54. text-align: left;
  55. padding: 50px;
  56. box-sizing: border-box;
  57. }
  58. </style>
  59. </head>
  60. <body template="window">
  61. <div data-model="basics">
  62. <input type="text" data-value="i0"/>
  63. <h1>Globals</h1>
  64. <p>{{ i0 }}</p>
  65. <p>{{ i1 }}</p>
  66. <p>{{ i2 }}</p>
  67. <p>{{ i3 }}</p>
  68. <p>{{ s0 }}</p>
  69. <p>{{ s1 }}</p>
  70. <p>{{ s2.val }}</p>
  71. <p>{{ s3.val }}</p>
  72. <p>{{ s4.val }}</p>
  73. <p>{{ s5.val }}</p>
  74. <p>{{ simple }}</p>
  75. <p>{{ scoped }}</p>
  76. <h1>Basic</h1>
  77. <p>{{ basic.a }}</p>
  78. <p>{{ basic.b }}</p>
  79. <p>{{ basic.c }}</p>
  80. <p>{{ basic.d }}</p>
  81. <p>{{ basic.e }}</p>
  82. <p>{{ basic.f }}</p>
  83. <p id="simple" data-event-click="basic.simple = 2">{{ basic.simple }}</p>
  84. <p>{{ basic.scoped }}</p>
  85. <h1>Wrapped</h1>
  86. <p>{{ wrapped.a.val }}</p>
  87. <p>{{ wrapped.b.val }}</p>
  88. <p>{{ wrapped.c.val }}</p>
  89. <p>{{ wrapped.d.val }}</p>
  90. <p>{{ wrapped.e.val }}</p>
  91. <h1>Pointed</h1>
  92. <p>{{ pointed.a.val }}</p>
  93. <p>{{ pointed.b.val }}</p>
  94. <p>{{ pointed.c.val }}</p>
  95. <h1>Arrays</h1>
  96. <p><span data-for="arrays.a">{{ it }} </span></p>
  97. <p><span data-for="arrays.b">{{ it }} </span></p>
  98. <p><span data-for="arrays.c">{{ it.val }} </span></p>
  99. <p><span data-for="arrays.d">{{ it.val }} </span></p>
  100. <p><span data-for="arrays.e">{{ it.val }} </span></p>
  101. </div>
  102. </body>
  103. </rml>
  104. )";
  105. static const String inside_string_rml = R"(
  106. <rml>
  107. <head>
  108. <title>Test</title>
  109. <link type="text/template" href="/assets/window.rml"/>
  110. <style>
  111. body.window
  112. {
  113. left: 50px;
  114. right: 50px;
  115. top: 30px;
  116. bottom: 30px;
  117. max-width: -1px;
  118. max-height: -1px;
  119. }
  120. </style>
  121. </head>
  122. <body template="window">
  123. <div data-model="basics">
  124. <p>{{ i0 }}</p>
  125. <p>{{ 'i0' }}</p>
  126. <p>{{ 'i{}23' }}</p>
  127. <p>before {{ 'i{{test}}23' }} test</p>
  128. <p>a {{ 'i' }} b {{ 'j' }} c</p>
  129. <p>{{i0}}</p>
  130. <p>{{ 'i{}' }}</p>
  131. </div>
  132. </body>
  133. </rml>
  134. )";
  135. static const String aliasing_rml = R"(
  136. <rml>
  137. <head>
  138. <title>Test</title>
  139. <link type="text/rcss" href="/assets/rml.rcss"/>
  140. <link type="text/rcss" href="/assets/invader.rcss"/>
  141. <link type="text/template" href="/../Tests/Data/UnitTests/data-title.rml"/>
  142. <style>
  143. body {
  144. width: 600px;
  145. height: 400px;
  146. background: #ccc;
  147. color: #333;
  148. }
  149. .title-wrapper { border: 1dp red; }
  150. .icon { width: 64dp; height: 64dp; display: inline-block; }
  151. .icon[icon="a"] { decorator: image("/assets/high_scores_alien_1.tga"); }
  152. .icon[icon="b"] { decorator: image("/assets/high_scores_alien_2.tga"); }
  153. </style>
  154. </head>
  155. <body data-model="basics">
  156. <p>{{ i0 }}</p>
  157. <p data-alias-differentname="i0">{{ differentname }}</p>
  158. <div data-alias-title="s0" data-alias-icon="wrapped.a.val" id="w1">
  159. <template src="data-title"/>
  160. </div>
  161. <div data-alias-title="s1" data-alias-icon="wrapped.b.val" id="w2">
  162. <template src="data-title"/>
  163. </div>
  164. </body>
  165. </rml>
  166. )";
  167. static const String dynamic_rml = R"(
  168. <rml>
  169. <head>
  170. <title>Test</title>
  171. <link type="text/rcss" href="/assets/rml.rcss"/>
  172. <link type="text/rcss" href="/assets/invader.rcss"/>
  173. <link type="text/template" href="/../Tests/Data/UnitTests/data-title.rml"/>
  174. </head>
  175. <body data-model="basics">
  176. <p>{{ arrays.a[0] }}</p>
  177. <p>{{ arrays.a[i0] }}</p>
  178. <p>{{ arrays.b[i1] }}</p>
  179. <p>{{ arrays.c[arrays.b[i1] - 19].val }}</p>
  180. <p>{{ arrays.c[sqrt(arrays.b[i1] - 12) - 1].val }}</p>
  181. </body>
  182. </rml>
  183. )";
  184. struct StringWrap {
  185. StringWrap(String val = "wrap_default") : val(val) {}
  186. String val;
  187. };
  188. enum SimpleEnum { Simple_Zero = 0, Simple_One, Simple_Two };
  189. enum class ScopedEnum : uint64_t { Zero = 0, One, Two };
  190. struct Globals {
  191. int i0 = 0;
  192. int* i1 = new int(1);
  193. UniquePtr<int> i2 = MakeUnique<int>(2);
  194. SharedPtr<int> i3 = MakeShared<int>(3);
  195. SimpleEnum simple = Simple_One;
  196. ScopedEnum scoped = ScopedEnum::One;
  197. String s0 = "s0";
  198. String* s1 = new String("s1");
  199. StringWrap s2 = StringWrap("s2");
  200. StringWrap* s3 = new StringWrap("s3");
  201. UniquePtr<StringWrap> s4 = MakeUnique<StringWrap>("s4");
  202. SharedPtr<StringWrap> s5 = MakeShared<StringWrap>("s5");
  203. // Invalid
  204. const int x0 = 100; // Invalid: const variable
  205. const int* x1 = new int(101); // Invalid: const pointer
  206. UniquePtr<const int> x2 = MakeUnique<int>(102); // Invalid: const pointer
  207. const StringWrap* x3 = new StringWrap("x2"); // Invalid: const pointer
  208. UniquePtr<const StringWrap> x4 = MakeUnique<StringWrap>("x3"); // Invalid: const pointer
  209. } globals;
  210. struct Basic {
  211. int a = 1;
  212. int* b = new int(2);
  213. SimpleEnum simple = Simple_One;
  214. ScopedEnum scoped = ScopedEnum::One;
  215. int GetC()
  216. {
  217. static int v = 5;
  218. return v;
  219. }
  220. int& GetD()
  221. {
  222. static int v = 5;
  223. return v;
  224. }
  225. int* GetE()
  226. {
  227. static int v = 6;
  228. return &v;
  229. }
  230. UniquePtr<int> GetF() { return MakeUnique<int>(7); }
  231. // Invalid: const member
  232. const int x0 = 2;
  233. // Invalid: const pointer
  234. const int* x1 = new int(3);
  235. // Invalid: const qualified member function
  236. int GetX2() const { return 4; }
  237. // Invalid: const reference return
  238. const int& GetX3()
  239. {
  240. static int g = 7;
  241. return g;
  242. }
  243. // Invalid: const pointer return
  244. const int* GetX4()
  245. {
  246. static int h = 8;
  247. return &h;
  248. }
  249. // Invalid: Illegal signature
  250. int GetX5(int) { return 9; }
  251. };
  252. struct Wrapped {
  253. StringWrap a = {"a"};
  254. StringWrap* b = new StringWrap("b");
  255. UniquePtr<StringWrap> c = MakeUnique<StringWrap>("c");
  256. StringWrap& GetD()
  257. {
  258. static StringWrap v = {"e"};
  259. return v;
  260. }
  261. StringWrap* GetE()
  262. {
  263. static StringWrap v = {"f"};
  264. return &v;
  265. }
  266. // Invalid: const pointer
  267. const StringWrap* x0 = new StringWrap("x0");
  268. // Invalid (run-time): Returning non-scalar variable by value.
  269. StringWrap GetX1() { return {"x1"}; }
  270. // Invalid (run-time): Returning non-scalar variable by value.
  271. UniquePtr<StringWrap> GetX2() { return MakeUnique<StringWrap>("x2"); }
  272. };
  273. using StringWrapPtr = UniquePtr<StringWrap>;
  274. struct Pointed {
  275. StringWrapPtr a = MakeUnique<StringWrap>("a");
  276. StringWrapPtr& GetB()
  277. {
  278. static StringWrapPtr v = MakeUnique<StringWrap>("b");
  279. return v;
  280. }
  281. StringWrapPtr* GetC()
  282. {
  283. static StringWrapPtr v = MakeUnique<StringWrap>("c");
  284. return &v;
  285. }
  286. // Invalid: We disallow recursive pointer types (pointer to pointer)
  287. StringWrapPtr* x0 = new StringWrapPtr(new StringWrap("x0"));
  288. // Invalid (run-time error): Only scalar data members can be returned by value
  289. StringWrapPtr GetX1() { return MakeUnique<StringWrap>("x1"); }
  290. };
  291. struct Arrays {
  292. Vector<int> a = {10, 11, 12};
  293. Vector<int*> b = {new int(20), new int(21), new int(22)};
  294. Vector<StringWrap> c = {StringWrap("c1"), StringWrap("c2"), StringWrap("c3")};
  295. Vector<StringWrap*> d = {new StringWrap("d1"), new StringWrap("d2"), new StringWrap("d3")};
  296. Vector<StringWrapPtr> e;
  297. // Invalid: const pointer
  298. Vector<const int*> x0 = {new int(30), new int(31), new int(32)};
  299. // Invalid: const pointer
  300. Vector<UniquePtr<const StringWrap>> x1;
  301. Arrays()
  302. {
  303. e.emplace_back(MakeUnique<StringWrap>("e1"));
  304. e.emplace_back(MakeUnique<StringWrap>("e2"));
  305. e.emplace_back(MakeUnique<StringWrap>("e3"));
  306. x1.emplace_back(MakeUnique<StringWrap>("x1_1"));
  307. x1.emplace_back(MakeUnique<StringWrap>("x1_2"));
  308. x1.emplace_back(MakeUnique<StringWrap>("x1_3"));
  309. }
  310. };
  311. DataModelHandle model_handle;
  312. bool InitializeDataBindings(Context* context)
  313. {
  314. Rml::DataModelConstructor constructor = context->CreateDataModel("basics");
  315. if (!constructor)
  316. return false;
  317. constructor.RegisterTransformFunc("sqrt", [](const VariantList& params) {
  318. if (params.empty())
  319. return Variant();
  320. return Variant(std::sqrt(params[0].Get<int>()));
  321. });
  322. if (auto handle = constructor.RegisterStruct<StringWrap>())
  323. {
  324. handle.RegisterMember("val", &StringWrap::val);
  325. }
  326. {
  327. // Globals
  328. constructor.Bind("i0", &globals.i0);
  329. constructor.Bind("i1", &globals.i1);
  330. constructor.Bind("i2", &globals.i2);
  331. constructor.Bind("i3", &globals.i3);
  332. constructor.Bind("s0", &globals.s0);
  333. constructor.Bind("s1", &globals.s1);
  334. constructor.Bind("s2", &globals.s2);
  335. constructor.Bind("s3", &globals.s3);
  336. constructor.Bind("s4", &globals.s4);
  337. constructor.Bind("s5", &globals.s5);
  338. constructor.Bind("simple", &globals.simple);
  339. constructor.Bind("scoped", &globals.scoped);
  340. // Invalid: Each of the following should give a compile-time failure.
  341. // constructor.Bind("x0", &globals.x0);
  342. // constructor.Bind("x1", &globals.x1);
  343. // constructor.Bind("x2", &globals.x2);
  344. // constructor.Bind("x3", &globals.x3);
  345. // constructor.Bind("x4", &globals.x4);
  346. }
  347. if (auto handle = constructor.RegisterStruct<Basic>())
  348. {
  349. handle.RegisterMember("a", &Basic::a);
  350. handle.RegisterMember("b", &Basic::b);
  351. handle.RegisterMember("c", &Basic::GetC);
  352. handle.RegisterMember("d", &Basic::GetD);
  353. handle.RegisterMember("e", &Basic::GetE);
  354. handle.RegisterMember("f", &Basic::GetF);
  355. handle.RegisterMember("simple", &Basic::simple);
  356. handle.RegisterMember("scoped", &Basic::scoped);
  357. // handle.RegisterMember("x0", &Basic::x0);
  358. // handle.RegisterMember("x1", &Basic::x1);
  359. // handle.RegisterMember("x2", &Basic::GetX2);
  360. // handle.RegisterMember("x3", &Basic::GetX3);
  361. // handle.RegisterMember("x4", &Basic::GetX4);
  362. // handle.RegisterMember("x5", &Basic::GetX5);
  363. }
  364. constructor.Bind("basic", new Basic);
  365. if (auto handle = constructor.RegisterStruct<Wrapped>())
  366. {
  367. handle.RegisterMember("a", &Wrapped::a);
  368. handle.RegisterMember("b", &Wrapped::b);
  369. handle.RegisterMember("c", &Wrapped::c);
  370. handle.RegisterMember("d", &Wrapped::GetD);
  371. handle.RegisterMember("e", &Wrapped::GetE);
  372. // handle.RegisterMember("x0", &Wrapped::x0);
  373. // handle.RegisterMember("x1", &Wrapped::GetX1);
  374. // handle.RegisterMember("x2", &Wrapped::GetX2);
  375. }
  376. constructor.Bind("wrapped", new Wrapped);
  377. if (auto handle = constructor.RegisterStruct<Pointed>())
  378. {
  379. handle.RegisterMember("a", &Pointed::a);
  380. handle.RegisterMember("b", &Pointed::GetB);
  381. handle.RegisterMember("c", &Pointed::GetC);
  382. // handle.RegisterMember("x0", &Pointed::x0);
  383. // handle.RegisterMember("x1", &Pointed::GetX1);
  384. }
  385. constructor.Bind("pointed", new Pointed);
  386. constructor.RegisterArray<decltype(Arrays::a)>();
  387. constructor.RegisterArray<decltype(Arrays::b)>();
  388. constructor.RegisterArray<decltype(Arrays::c)>();
  389. constructor.RegisterArray<decltype(Arrays::d)>();
  390. constructor.RegisterArray<decltype(Arrays::e)>();
  391. // constructor.RegisterArray<decltype(Arrays::x0)>();
  392. // constructor.RegisterArray<decltype(Arrays::x1)>();
  393. if (auto handle = constructor.RegisterStruct<Arrays>())
  394. {
  395. handle.RegisterMember("a", &Arrays::a);
  396. handle.RegisterMember("b", &Arrays::b);
  397. handle.RegisterMember("c", &Arrays::c);
  398. handle.RegisterMember("d", &Arrays::d);
  399. handle.RegisterMember("e", &Arrays::e);
  400. // handle.RegisterMember("x0", &Arrays::x0);
  401. // handle.RegisterMember("x1", &Arrays::x1);
  402. }
  403. constructor.Bind("arrays", new Arrays);
  404. model_handle = constructor.GetModelHandle();
  405. return true;
  406. }
  407. } // Anonymous namespace
  408. TEST_CASE("data_binding")
  409. {
  410. Context* context = TestsShell::GetContext();
  411. REQUIRE(context);
  412. REQUIRE(InitializeDataBindings(context));
  413. ElementDocument* document = context->LoadDocumentFromMemory(document_rml);
  414. REQUIRE(document);
  415. document->Show();
  416. TestsShell::RenderLoop();
  417. document->Close();
  418. TestsShell::ShutdownShell();
  419. }
  420. TEST_CASE("data_binding.inside_string")
  421. {
  422. Context* context = TestsShell::GetContext();
  423. REQUIRE(context);
  424. REQUIRE(InitializeDataBindings(context));
  425. ElementDocument* document = context->LoadDocumentFromMemory(inside_string_rml);
  426. REQUIRE(document);
  427. document->Show();
  428. TestsShell::RenderLoop();
  429. CHECK(document->QuerySelector("p:nth-child(4)")->GetInnerRML() == "before i{{test}}23 test");
  430. CHECK(document->QuerySelector("p:nth-child(5)")->GetInnerRML() == "a i b j c");
  431. document->Close();
  432. TestsShell::ShutdownShell();
  433. }
  434. TEST_CASE("data_binding.aliasing")
  435. {
  436. Context* context = TestsShell::GetContext();
  437. REQUIRE(context);
  438. REQUIRE(InitializeDataBindings(context));
  439. ElementDocument* document = context->LoadDocumentFromMemory(aliasing_rml);
  440. REQUIRE(document);
  441. document->Show();
  442. TestsShell::RenderLoop();
  443. CHECK(document->QuerySelector("p:nth-child(1)")->GetInnerRML() == document->QuerySelector("p:nth-child(2)")->GetInnerRML());
  444. CHECK(document->QuerySelector("#w1 .title")->GetInnerRML() == "s0");
  445. CHECK(document->QuerySelector("#w1 .icon")->GetAttribute("icon", String()) == "a");
  446. CHECK(document->QuerySelector("#w2 .title")->GetInnerRML() == "s1");
  447. CHECK(document->QuerySelector("#w2 .icon")->GetAttribute("icon", String()) == "b");
  448. document->Close();
  449. TestsShell::ShutdownShell();
  450. }
  451. TEST_CASE("data_binding.dynamic_variables")
  452. {
  453. Context* context = TestsShell::GetContext();
  454. REQUIRE(context);
  455. REQUIRE(InitializeDataBindings(context));
  456. ElementDocument* document = context->LoadDocumentFromMemory(dynamic_rml);
  457. REQUIRE(document);
  458. document->Show();
  459. TestsShell::RenderLoop();
  460. CHECK(document->QuerySelector("p:nth-child(1)")->GetInnerRML() == "10");
  461. CHECK(document->QuerySelector("p:nth-child(2)")->GetInnerRML() == "10");
  462. CHECK(document->QuerySelector("p:nth-child(3)")->GetInnerRML() == "21");
  463. CHECK(document->QuerySelector("p:nth-child(4)")->GetInnerRML() == "c3");
  464. CHECK(document->QuerySelector("p:nth-child(5)")->GetInnerRML() == "c3");
  465. *globals.i1 = 0;
  466. context->GetDataModel("basics").GetModelHandle().DirtyVariable("i1");
  467. TestsShell::RenderLoop();
  468. CHECK(document->QuerySelector("p:nth-child(3)")->GetInnerRML() == "20");
  469. CHECK(document->QuerySelector("p:nth-child(4)")->GetInnerRML() == "c2");
  470. document->Close();
  471. *globals.i1 = 1;
  472. TestsShell::ShutdownShell();
  473. }
  474. static const String set_enum_rml = R"(
  475. <rml>
  476. <head>
  477. <title>Test</title>
  478. <link type="text/template" href="/assets/window.rml"/>
  479. <style>
  480. body.window {
  481. width: 500px;
  482. height: 400px;
  483. }
  484. </style>
  485. </head>
  486. <body template="window">
  487. <div data-model="basics">
  488. <p id="simple" data-event-click="simple = 2">{{ simple }}</p>
  489. </div>
  490. </body>
  491. </rml>
  492. )";
  493. TEST_CASE("data_binding.set_enum")
  494. {
  495. Context* context = TestsShell::GetContext();
  496. REQUIRE(context);
  497. globals.simple = Simple_One;
  498. REQUIRE(InitializeDataBindings(context));
  499. ElementDocument* document = context->LoadDocumentFromMemory(set_enum_rml);
  500. REQUIRE(document);
  501. document->Show();
  502. TestsShell::RenderLoop();
  503. Element* element = document->GetElementById("simple");
  504. CHECK(element->GetInnerRML() == "1");
  505. element->DispatchEvent(EventId::Click, Dictionary());
  506. TestsShell::RenderLoop();
  507. CHECK(globals.simple == Simple_Two);
  508. CHECK(element->GetInnerRML() == "2");
  509. document->Close();
  510. TestsShell::ShutdownShell();
  511. }