main.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  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) 2018 Michael R. P. Ragazzon
  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 <RmlUi/Core.h>
  29. #include <RmlUi/Core/StreamMemory.h>
  30. #include <RmlUi/Core/TransformPrimitive.h>
  31. #include <RmlUi/Debugger.h>
  32. #include <RmlUi_Backend.h>
  33. #include <Shell.h>
  34. static const Rml::String sandbox_default_rcss = R"(
  35. body { top: 0; left: 0; right: 0; bottom: 0; overflow: hidden auto; }
  36. scrollbarvertical { width: 15px; }
  37. scrollbarvertical slidertrack { background: #eee; }
  38. scrollbarvertical slidertrack:active { background: #ddd; }
  39. scrollbarvertical sliderbar { width: 15px; min-height: 30px; background: #aaa; }
  40. scrollbarvertical sliderbar:hover { background: #888; }
  41. scrollbarvertical sliderbar:active { background: #666; }
  42. scrollbarhorizontal { height: 15px; }
  43. scrollbarhorizontal slidertrack { background: #eee; }
  44. scrollbarhorizontal slidertrack:active { background: #ddd; }
  45. scrollbarhorizontal sliderbar { height: 15px; min-width: 30px; background: #aaa; }
  46. scrollbarhorizontal sliderbar:hover { background: #888; }
  47. scrollbarhorizontal sliderbar:active { background: #666; }
  48. )";
  49. class DemoWindow : public Rml::EventListener {
  50. public:
  51. DemoWindow(const Rml::String& title, Rml::Context* context)
  52. {
  53. using namespace Rml;
  54. document = context->LoadDocument("basic/demo/data/demo.rml");
  55. if (document)
  56. {
  57. document->GetElementById("title")->SetInnerRML(title);
  58. // Add sandbox default text.
  59. if (auto source = rmlui_dynamic_cast<Rml::ElementFormControl*>(document->GetElementById("sandbox_rml_source")))
  60. {
  61. auto value = source->GetValue();
  62. value += "<p>Write your RML here</p>\n\n<!-- <img src=\"assets/high_scores_alien_1.tga\"/> -->";
  63. source->SetValue(value);
  64. }
  65. // Prepare sandbox document.
  66. if (auto target = document->GetElementById("sandbox_target"))
  67. {
  68. iframe = context->CreateDocument();
  69. auto iframe_ptr = iframe->GetParentNode()->RemoveChild(iframe);
  70. target->AppendChild(std::move(iframe_ptr));
  71. iframe->SetProperty(PropertyId::Position, Property(Style::Position::Absolute));
  72. iframe->SetProperty(PropertyId::Display, Property(Style::Display::Block));
  73. iframe->SetInnerRML("<p>Rendered output goes here.</p>");
  74. // Load basic RML style sheet
  75. Rml::String style_sheet_content;
  76. {
  77. // Load file into string
  78. auto file_interface = Rml::GetFileInterface();
  79. Rml::FileHandle handle = file_interface->Open("assets/rml.rcss");
  80. size_t length = file_interface->Length(handle);
  81. style_sheet_content.resize(length);
  82. file_interface->Read((void*)style_sheet_content.data(), length, handle);
  83. file_interface->Close(handle);
  84. style_sheet_content += sandbox_default_rcss;
  85. }
  86. Rml::StreamMemory stream((Rml::byte*)style_sheet_content.data(), style_sheet_content.size());
  87. stream.SetSourceURL("sandbox://default_rcss");
  88. rml_basic_style_sheet = MakeShared<Rml::StyleSheetContainer>();
  89. rml_basic_style_sheet->LoadStyleSheetContainer(&stream);
  90. }
  91. // Add sandbox style sheet text.
  92. if (auto source = rmlui_dynamic_cast<Rml::ElementFormControl*>(document->GetElementById("sandbox_rcss_source")))
  93. {
  94. Rml::String value = "/* Write your RCSS here */\n\n/* body { color: #fea; background: #224; }\nimg { image-color: red; } */";
  95. source->SetValue(value);
  96. SetSandboxStylesheet(value);
  97. }
  98. gauge = document->GetElementById("gauge");
  99. progress_horizontal = document->GetElementById("progress_horizontal");
  100. document->Show();
  101. }
  102. }
  103. void Update()
  104. {
  105. if (iframe)
  106. {
  107. iframe->UpdateDocument();
  108. }
  109. if (submitting && gauge && progress_horizontal)
  110. {
  111. using namespace Rml;
  112. constexpr float progressbars_time = 2.f;
  113. const float progress = Math::Min(float(GetSystemInterface()->GetElapsedTime() - submitting_start_time) / progressbars_time, 2.f);
  114. float value_gauge = 1.0f;
  115. float value_horizontal = 0.0f;
  116. if (progress < 1.0f)
  117. value_gauge = 0.5f - 0.5f * Math::Cos(Math::RMLUI_PI * progress);
  118. else
  119. value_horizontal = 0.5f - 0.5f * Math::Cos(Math::RMLUI_PI * (progress - 1.0f));
  120. progress_horizontal->SetAttribute("value", value_horizontal);
  121. const float value_begin = 0.09f;
  122. const float value_end = 1.f - value_begin;
  123. float value_mapped = value_begin + value_gauge * (value_end - value_begin);
  124. gauge->SetAttribute("value", value_mapped);
  125. auto value_gauge_str = CreateString(10, "%d %%", Math::RoundToInteger(value_gauge * 100.f));
  126. auto value_horizontal_str = CreateString(10, "%d %%", Math::RoundToInteger(value_horizontal * 100.f));
  127. if (auto el_value = document->GetElementById("gauge_value"))
  128. el_value->SetInnerRML(value_gauge_str);
  129. if (auto el_value = document->GetElementById("progress_value"))
  130. el_value->SetInnerRML(value_horizontal_str);
  131. String label = "Placing tubes";
  132. size_t num_dots = (size_t(progress * 10.f) % 4);
  133. if (progress > 1.0f)
  134. label += "... Placed! Assembling message";
  135. if (progress < 2.0f)
  136. label += String(num_dots, '.');
  137. else
  138. label += "... Done!";
  139. if (auto el_label = document->GetElementById("progress_label"))
  140. el_label->SetInnerRML(label);
  141. if (progress >= 2.0f)
  142. {
  143. submitting = false;
  144. if (auto el_output = document->GetElementById("form_output"))
  145. el_output->SetInnerRML(submit_message);
  146. }
  147. document->GetContext()->RequestNextUpdate(.0);
  148. }
  149. }
  150. void Shutdown()
  151. {
  152. if (document)
  153. {
  154. document->Close();
  155. document = nullptr;
  156. }
  157. }
  158. void ProcessEvent(Rml::Event& event) override
  159. {
  160. using namespace Rml;
  161. switch (event.GetId())
  162. {
  163. case EventId::Keydown:
  164. {
  165. Rml::Input::KeyIdentifier key_identifier = (Rml::Input::KeyIdentifier)event.GetParameter<int>("key_identifier", 0);
  166. if (key_identifier == Rml::Input::KI_ESCAPE)
  167. Backend::RequestExit();
  168. }
  169. break;
  170. default: break;
  171. }
  172. }
  173. Rml::ElementDocument* GetDocument() { return document; }
  174. void SubmitForm(Rml::String in_submit_message)
  175. {
  176. submitting = true;
  177. submitting_start_time = Rml::GetSystemInterface()->GetElapsedTime();
  178. submit_message = in_submit_message;
  179. if (auto el_output = document->GetElementById("form_output"))
  180. el_output->SetInnerRML("");
  181. if (auto el_progress = document->GetElementById("submit_progress"))
  182. el_progress->SetProperty("display", "block");
  183. }
  184. void SetSandboxStylesheet(const Rml::String& string)
  185. {
  186. if (iframe && rml_basic_style_sheet)
  187. {
  188. auto style = Rml::MakeShared<Rml::StyleSheetContainer>();
  189. Rml::StreamMemory stream((const Rml::byte*)string.data(), string.size());
  190. stream.SetSourceURL("sandbox://rcss");
  191. style->LoadStyleSheetContainer(&stream);
  192. style = rml_basic_style_sheet->CombineStyleSheetContainer(*style);
  193. iframe->SetStyleSheetContainer(style);
  194. }
  195. }
  196. void SetSandboxBody(const Rml::String& string)
  197. {
  198. if (iframe)
  199. {
  200. iframe->SetInnerRML(string);
  201. }
  202. }
  203. private:
  204. Rml::ElementDocument* document = nullptr;
  205. Rml::ElementDocument* iframe = nullptr;
  206. Rml::Element *gauge = nullptr, *progress_horizontal = nullptr;
  207. Rml::SharedPtr<Rml::StyleSheetContainer> rml_basic_style_sheet;
  208. bool submitting = false;
  209. double submitting_start_time = 0;
  210. Rml::String submit_message;
  211. };
  212. Rml::UniquePtr<DemoWindow> demo_window;
  213. struct TweeningParameters {
  214. Rml::Tween::Type type = Rml::Tween::Linear;
  215. Rml::Tween::Direction direction = Rml::Tween::Out;
  216. float duration = 0.5f;
  217. } tweening_parameters;
  218. class DemoEventListener : public Rml::EventListener {
  219. public:
  220. DemoEventListener(const Rml::String& value, Rml::Element* element) : value(value), element(element) {}
  221. void ProcessEvent(Rml::Event& event) override
  222. {
  223. using namespace Rml;
  224. if (value == "exit")
  225. {
  226. // Test replacing the current element.
  227. // Need to be careful with regard to lifetime issues. The event's current element will be destroyed, so we cannot
  228. // use it after SetInnerRml(). The library should handle this case safely internally when propagating the event further.
  229. Element* parent = element->GetParentNode();
  230. parent->SetInnerRML("<button onclick='confirm_exit' onblur='cancel_exit' onmouseout='cancel_exit'>Are you sure?</button>");
  231. if (Element* child = parent->GetChild(0))
  232. child->Focus();
  233. }
  234. else if (value == "confirm_exit")
  235. {
  236. Backend::RequestExit();
  237. }
  238. else if (value == "cancel_exit")
  239. {
  240. if (Element* parent = element->GetParentNode())
  241. parent->SetInnerRML("<button id='exit' onclick='exit'>Exit</button>");
  242. }
  243. else if (value == "change_color")
  244. {
  245. Colourb color((byte)Math::RandomInteger(255), (byte)Math::RandomInteger(255), (byte)Math::RandomInteger(255));
  246. element->Animate("image-color", Property(color, Unit::COLOUR), tweening_parameters.duration,
  247. Tween(tweening_parameters.type, tweening_parameters.direction));
  248. event.StopPropagation();
  249. }
  250. else if (value == "move_child")
  251. {
  252. Vector2f mouse_pos(event.GetParameter("mouse_x", 0.0f), event.GetParameter("mouse_y", 0.0f));
  253. if (Element* child = element->GetFirstChild())
  254. {
  255. Vector2f new_pos =
  256. mouse_pos - element->GetAbsoluteOffset() - Vector2f(0.35f * child->GetClientWidth(), 0.9f * child->GetClientHeight());
  257. Property destination = Transform::MakeProperty({Transforms::Translate2D(new_pos.x, new_pos.y)});
  258. if (tweening_parameters.duration <= 0)
  259. child->SetProperty(PropertyId::Transform, destination);
  260. else
  261. child->Animate("transform", destination, tweening_parameters.duration,
  262. Tween(tweening_parameters.type, tweening_parameters.direction));
  263. }
  264. }
  265. else if (value == "tween_function")
  266. {
  267. static const SmallUnorderedMap<String, Tween::Type> tweening_functions = {{"back", Tween::Back}, {"bounce", Tween::Bounce},
  268. {"circular", Tween::Circular}, {"cubic", Tween::Cubic}, {"elastic", Tween::Elastic}, {"exponential", Tween::Exponential},
  269. {"linear", Tween::Linear}, {"quadratic", Tween::Quadratic}, {"quartic", Tween::Quartic}, {"quintic", Tween::Quintic},
  270. {"sine", Tween::Sine}};
  271. String value = event.GetParameter("value", String());
  272. auto it = tweening_functions.find(value);
  273. if (it != tweening_functions.end())
  274. tweening_parameters.type = it->second;
  275. else
  276. {
  277. RMLUI_ERROR;
  278. }
  279. }
  280. else if (value == "tween_direction")
  281. {
  282. String value = event.GetParameter("value", String());
  283. if (value == "in")
  284. tweening_parameters.direction = Tween::In;
  285. else if (value == "out")
  286. tweening_parameters.direction = Tween::Out;
  287. else if (value == "in-out")
  288. tweening_parameters.direction = Tween::InOut;
  289. else
  290. {
  291. RMLUI_ERROR;
  292. }
  293. }
  294. else if (value == "tween_duration")
  295. {
  296. float value = (float)std::atof(rmlui_static_cast<Rml::ElementFormControl*>(element)->GetValue().c_str());
  297. tweening_parameters.duration = value;
  298. if (auto el_duration = element->GetElementById("duration"))
  299. el_duration->SetInnerRML(CreateString(20, "%2.2f", value));
  300. }
  301. else if (value == "rating")
  302. {
  303. auto el_rating = element->GetElementById("rating");
  304. auto el_rating_emoji = element->GetElementById("rating_emoji");
  305. if (el_rating && el_rating_emoji)
  306. {
  307. enum { Sad, Mediocre, Exciting, Celebrate, Champion, CountEmojis };
  308. static const Rml::String emojis[CountEmojis] = {(const char*)u8"😢", (const char*)u8"😐", (const char*)u8"😮", (const char*)u8"😎",
  309. (const char*)u8"🏆"};
  310. int value = event.GetParameter("value", 50);
  311. Rml::String emoji;
  312. if (value <= 0)
  313. emoji = emojis[Sad];
  314. else if (value < 50)
  315. emoji = emojis[Mediocre];
  316. else if (value < 75)
  317. emoji = emojis[Exciting];
  318. else if (value < 100)
  319. emoji = emojis[Celebrate];
  320. else
  321. emoji = emojis[Champion];
  322. el_rating->SetInnerRML(Rml::CreateString(30, "%d%%", value));
  323. el_rating_emoji->SetInnerRML(emoji);
  324. }
  325. }
  326. else if (value == "submit_form")
  327. {
  328. const auto& p = event.GetParameters();
  329. Rml::String output = "<p>";
  330. for (auto& entry : p)
  331. {
  332. auto value = Rml::StringUtilities::EncodeRml(entry.second.Get<Rml::String>());
  333. if (entry.first == "message")
  334. value = "<br/>" + value;
  335. output += "<strong>" + entry.first + "</strong>: " + value + "<br/>";
  336. }
  337. output += "</p>";
  338. demo_window->SubmitForm(output);
  339. }
  340. else if (value == "set_sandbox_body")
  341. {
  342. if (auto source = rmlui_dynamic_cast<Rml::ElementFormControl*>(element->GetElementById("sandbox_rml_source")))
  343. {
  344. auto value = source->GetValue();
  345. demo_window->SetSandboxBody(value);
  346. }
  347. }
  348. else if (value == "set_sandbox_style")
  349. {
  350. if (auto source = rmlui_dynamic_cast<Rml::ElementFormControl*>(element->GetElementById("sandbox_rcss_source")))
  351. {
  352. auto value = source->GetValue();
  353. demo_window->SetSandboxStylesheet(value);
  354. }
  355. }
  356. }
  357. void OnDetach(Rml::Element* /*element*/) override { delete this; }
  358. private:
  359. Rml::String value;
  360. Rml::Element* element;
  361. };
  362. class DemoEventListenerInstancer : public Rml::EventListenerInstancer {
  363. public:
  364. Rml::EventListener* InstanceEventListener(const Rml::String& value, Rml::Element* element) override
  365. {
  366. return new DemoEventListener(value, element);
  367. }
  368. };
  369. #if defined RMLUI_PLATFORM_WIN32
  370. #include <RmlUi_Include_Windows.h>
  371. int APIENTRY WinMain(HINSTANCE /*instance_handle*/, HINSTANCE /*previous_instance_handle*/, char* /*command_line*/, int /*command_show*/)
  372. #else
  373. int main(int /*argc*/, char** /*argv*/)
  374. #endif
  375. {
  376. const int width = 1600;
  377. const int height = 890;
  378. // Initializes the shell which provides common functionality used by the included samples.
  379. if (!Shell::Initialize())
  380. return -1;
  381. // Constructs the system and render interfaces, creates a window, and attaches the renderer.
  382. if (!Backend::Initialize("Demo Sample", width, height, true))
  383. {
  384. Shell::Shutdown();
  385. return -1;
  386. }
  387. // Install the custom interfaces constructed by the backend before initializing RmlUi.
  388. Rml::SetSystemInterface(Backend::GetSystemInterface());
  389. Rml::SetRenderInterface(Backend::GetRenderInterface());
  390. // RmlUi initialisation.
  391. Rml::Initialise();
  392. // Create the main RmlUi context.
  393. Rml::Context* context = Rml::CreateContext("main", Rml::Vector2i(width, height));
  394. if (!context)
  395. {
  396. Rml::Shutdown();
  397. Backend::Shutdown();
  398. Shell::Shutdown();
  399. return -1;
  400. }
  401. Rml::Debugger::Initialise(context);
  402. DemoEventListenerInstancer event_listener_instancer;
  403. Rml::Factory::RegisterEventListenerInstancer(&event_listener_instancer);
  404. Shell::LoadFonts();
  405. demo_window = Rml::MakeUnique<DemoWindow>("Demo sample", context);
  406. demo_window->GetDocument()->AddEventListener(Rml::EventId::Keydown, demo_window.get());
  407. demo_window->GetDocument()->AddEventListener(Rml::EventId::Keyup, demo_window.get());
  408. demo_window->GetDocument()->AddEventListener(Rml::EventId::Animationend, demo_window.get());
  409. bool running = true;
  410. while (running)
  411. {
  412. demo_window->Update();
  413. running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, true);
  414. context->Update();
  415. Backend::BeginFrame();
  416. context->Render();
  417. Backend::PresentFrame();
  418. }
  419. demo_window->Shutdown();
  420. // Shutdown RmlUi.
  421. Rml::Shutdown();
  422. Backend::Shutdown();
  423. Shell::Shutdown();
  424. demo_window.reset();
  425. return 0;
  426. }