main.cpp 17 KB

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