Browse Source

Refactor demo sample

Move main classes into separate files, and some cleanup.
Michael Ragazzon 1 year ago
parent
commit
e8d77acb4d

+ 4 - 0
Samples/basic/demo/CMakeLists.txt

@@ -3,6 +3,10 @@ set(TARGET_NAME "${RMLUI_SAMPLE_PREFIX}${SAMPLE_NAME}")
 
 add_executable(${TARGET_NAME} WIN32
 	src/main.cpp
+	src/DemoEventListener.cpp
+	src/DemoEventListener.h
+	src/DemoWindow.cpp
+	src/DemoWindow.h
 )
 
 set_common_target_options(${TARGET_NAME})

+ 213 - 0
Samples/basic/demo/src/DemoEventListener.cpp

@@ -0,0 +1,213 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2019-2024 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "DemoEventListener.h"
+#include "DemoWindow.h"
+#include <RmlUi/Core/Element.h>
+#include <RmlUi/Core/Elements/ElementFormControl.h>
+#include <RmlUi_Backend.h>
+
+DemoEventListener::DemoEventListener(const Rml::String& value, Rml::Element* element, DemoWindow* demo_window) :
+	value(value), element(element), demo_window(demo_window)
+{}
+
+void DemoEventListener::ProcessEvent(Rml::Event& event)
+{
+	using namespace Rml;
+
+	if (value == "exit")
+	{
+		// Test replacing the current element.
+		// Need to be careful with regard to lifetime issues. The event's current element will be destroyed, so we cannot
+		// use it after SetInnerRml(). The library should handle this case safely internally when propagating the event further.
+		Element* parent = element->GetParentNode();
+		parent->SetInnerRML("<button onclick='confirm_exit' onblur='cancel_exit' onmouseout='cancel_exit'>Are you sure?</button>");
+		if (Element* child = parent->GetChild(0))
+			child->Focus();
+	}
+	else if (value == "confirm_exit")
+	{
+		Backend::RequestExit();
+	}
+	else if (value == "cancel_exit")
+	{
+		if (Element* parent = element->GetParentNode())
+			parent->SetInnerRML("<button id='exit' onclick='exit'>Exit</button>");
+	}
+	else if (value == "change_color")
+	{
+		const TweeningParameters tweening_parameters = demo_window->GetTweeningParameters();
+		const Colourb color((byte)Math::RandomInteger(255), (byte)Math::RandomInteger(255), (byte)Math::RandomInteger(255));
+
+		element->Animate("image-color", Property(color, Unit::COLOUR), tweening_parameters.duration,
+			Tween(tweening_parameters.type, tweening_parameters.direction));
+
+		event.StopPropagation();
+	}
+	else if (value == "move_child")
+	{
+		const Vector2f mouse_pos = {event.GetParameter("mouse_x", 0.0f), event.GetParameter("mouse_y", 0.0f)};
+		if (Element* child = element->GetFirstChild())
+		{
+			Vector2f new_pos = mouse_pos - element->GetAbsoluteOffset() - Vector2f(0.35f * child->GetClientWidth(), 0.9f * child->GetClientHeight());
+			Property destination = Transform::MakeProperty({Transforms::Translate2D(new_pos.x, new_pos.y)});
+
+			const TweeningParameters tweening_parameters = demo_window->GetTweeningParameters();
+			if (tweening_parameters.duration <= 0)
+				child->SetProperty(PropertyId::Transform, destination);
+			else
+				child->Animate("transform", destination, tweening_parameters.duration,
+					Tween(tweening_parameters.type, tweening_parameters.direction));
+		}
+	}
+	else if (value == "tween_function")
+	{
+		static const SmallUnorderedMap<String, Tween::Type> tweening_functions = {
+			{"back", Tween::Back},
+			{"bounce", Tween::Bounce},
+			{"circular", Tween::Circular},
+			{"cubic", Tween::Cubic},
+			{"elastic", Tween::Elastic},
+			{"exponential", Tween::Exponential},
+			{"linear", Tween::Linear},
+			{"quadratic", Tween::Quadratic},
+			{"quartic", Tween::Quartic},
+			{"quintic", Tween::Quintic},
+			{"sine", Tween::Sine},
+		};
+
+		const String value = event.GetParameter("value", String());
+		auto it = tweening_functions.find(value);
+		if (it != tweening_functions.end())
+		{
+			TweeningParameters tweening_parameters = demo_window->GetTweeningParameters();
+			tweening_parameters.type = it->second;
+			demo_window->SetTweeningParameters(tweening_parameters);
+		}
+		else
+		{
+			RMLUI_ERROR;
+		}
+	}
+	else if (value == "tween_direction")
+	{
+		const String value = event.GetParameter("value", String());
+		TweeningParameters tweening_parameters = demo_window->GetTweeningParameters();
+		if (value == "in")
+			tweening_parameters.direction = Tween::In;
+		else if (value == "out")
+			tweening_parameters.direction = Tween::Out;
+		else if (value == "in-out")
+			tweening_parameters.direction = Tween::InOut;
+		else
+		{
+			RMLUI_ERROR;
+		}
+		demo_window->SetTweeningParameters(tweening_parameters);
+	}
+	else if (value == "tween_duration")
+	{
+		const float value = (float)std::atof(rmlui_static_cast<Rml::ElementFormControl*>(element)->GetValue().c_str());
+
+		TweeningParameters tweening_parameters = demo_window->GetTweeningParameters();
+		tweening_parameters.duration = value;
+		demo_window->SetTweeningParameters(tweening_parameters);
+
+		if (auto el_duration = element->GetElementById("duration"))
+			el_duration->SetInnerRML(CreateString(20, "%2.2f", value));
+	}
+	else if (value == "rating")
+	{
+		auto el_rating = element->GetElementById("rating");
+		auto el_rating_emoji = element->GetElementById("rating_emoji");
+		if (el_rating && el_rating_emoji)
+		{
+			enum { Sad, Mediocre, Exciting, Celebrate, Champion, CountEmojis };
+			static const Rml::String emojis[CountEmojis] = {(const char*)u8"😢", (const char*)u8"😐", (const char*)u8"😮", (const char*)u8"😎",
+				(const char*)u8"🏆"};
+			int value = event.GetParameter("value", 50);
+
+			Rml::String emoji;
+			if (value <= 0)
+				emoji = emojis[Sad];
+			else if (value < 50)
+				emoji = emojis[Mediocre];
+			else if (value < 75)
+				emoji = emojis[Exciting];
+			else if (value < 100)
+				emoji = emojis[Celebrate];
+			else
+				emoji = emojis[Champion];
+
+			el_rating->SetInnerRML(Rml::CreateString(30, "%d%%", value));
+			el_rating_emoji->SetInnerRML(emoji);
+		}
+	}
+	else if (value == "submit_form")
+	{
+		const auto& p = event.GetParameters();
+		Rml::String output = "<p>";
+		for (auto& entry : p)
+		{
+			auto value = Rml::StringUtilities::EncodeRml(entry.second.Get<Rml::String>());
+			if (entry.first == "message")
+				value = "<br/>" + value;
+			output += "<strong>" + entry.first + "</strong>: " + value + "<br/>";
+		}
+		output += "</p>";
+
+		demo_window->SubmitForm(output);
+	}
+	else if (value == "set_sandbox_body")
+	{
+		if (auto source = rmlui_dynamic_cast<Rml::ElementFormControl*>(element->GetElementById("sandbox_rml_source")))
+		{
+			auto value = source->GetValue();
+			demo_window->SetSandboxBody(value);
+		}
+	}
+	else if (value == "set_sandbox_style")
+	{
+		if (auto source = rmlui_dynamic_cast<Rml::ElementFormControl*>(element->GetElementById("sandbox_rcss_source")))
+		{
+			auto value = source->GetValue();
+			demo_window->SetSandboxStylesheet(value);
+		}
+	}
+}
+
+void DemoEventListener::OnDetach(Rml::Element* /*element*/)
+{
+	delete this;
+}
+
+DemoEventListenerInstancer::DemoEventListenerInstancer(DemoWindow* demo_window) : demo_window(demo_window) {}
+
+Rml::EventListener* DemoEventListenerInstancer::InstanceEventListener(const Rml::String& value, Rml::Element* element)
+{
+	return new DemoEventListener(value, element, demo_window);
+}

+ 61 - 0
Samples/basic/demo/src/DemoEventListener.h

@@ -0,0 +1,61 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2019-2024 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef DEMOEVENTLISTENER_H
+#define DEMOEVENTLISTENER_H
+
+#include <RmlUi/Core/Element.h>
+#include <RmlUi/Core/EventListener.h>
+#include <RmlUi/Core/EventListenerInstancer.h>
+
+class DemoWindow;
+
+class DemoEventListener : public Rml::EventListener {
+public:
+	DemoEventListener(const Rml::String& value, Rml::Element* element, DemoWindow* demo_window);
+
+	void ProcessEvent(Rml::Event& event) override;
+
+	void OnDetach(Rml::Element* element) override;
+
+private:
+	Rml::String value;
+	Rml::Element* element;
+	DemoWindow* demo_window;
+};
+
+class DemoEventListenerInstancer : public Rml::EventListenerInstancer {
+public:
+	DemoEventListenerInstancer(DemoWindow* demo_window);
+
+	Rml::EventListener* InstanceEventListener(const Rml::String& value, Rml::Element* element) override;
+
+private:
+	DemoWindow* demo_window;
+};
+
+#endif

+ 247 - 0
Samples/basic/demo/src/DemoWindow.cpp

@@ -0,0 +1,247 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2019-2024 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "DemoWindow.h"
+#include "RmlUi/Core/StreamMemory.h"
+#include <RmlUi/Core/Context.h>
+#include <RmlUi/Core/ElementDocument.h>
+#include <RmlUi/Core/Elements/ElementFormControl.h>
+#include <RmlUi/Core/FileInterface.h>
+#include <RmlUi/Core/StyleSheetContainer.h>
+#include <RmlUi_Backend.h>
+
+static const Rml::String g_sandbox_default_rcss = R"(
+body { top: 0; left: 0; right: 0; bottom: 0; overflow: hidden auto; }
+scrollbarvertical { width: 15px; }
+scrollbarvertical slidertrack { background: #eee; }
+scrollbarvertical slidertrack:active { background: #ddd; }
+scrollbarvertical sliderbar { width: 15px; min-height: 30px; background: #aaa; }
+scrollbarvertical sliderbar:hover { background: #888; }
+scrollbarvertical sliderbar:active { background: #666; }
+scrollbarhorizontal { height: 15px; }
+scrollbarhorizontal slidertrack { background: #eee; }
+scrollbarhorizontal slidertrack:active { background: #ddd; }
+scrollbarhorizontal sliderbar { height: 15px; min-width: 30px; background: #aaa; }
+scrollbarhorizontal sliderbar:hover { background: #888; }
+scrollbarhorizontal sliderbar:active { background: #666; }
+)";
+
+bool DemoWindow::Initialize(const Rml::String& title, Rml::Context* context)
+{
+	using namespace Rml;
+
+	document = context->LoadDocument("basic/demo/data/demo.rml");
+	if (!document)
+		return false;
+
+	document->GetElementById("title")->SetInnerRML(title);
+
+	// Add sandbox default text.
+	if (auto source = rmlui_dynamic_cast<Rml::ElementFormControl*>(document->GetElementById("sandbox_rml_source")))
+	{
+		auto value = source->GetValue();
+		value += "<p>Write your RML here</p>\n\n<!-- <img src=\"assets/high_scores_alien_1.tga\"/> -->";
+		source->SetValue(value);
+	}
+
+	// Prepare sandbox document.
+	if (auto target = document->GetElementById("sandbox_target"))
+	{
+		iframe = context->CreateDocument();
+		auto iframe_ptr = iframe->GetParentNode()->RemoveChild(iframe);
+		target->AppendChild(std::move(iframe_ptr));
+		iframe->SetProperty(PropertyId::Position, Property(Style::Position::Absolute));
+		iframe->SetProperty(PropertyId::Display, Property(Style::Display::Block));
+		iframe->SetInnerRML("<p>Rendered output goes here.</p>");
+
+		// Load basic RML style sheet
+		Rml::String style_sheet_content;
+		{
+			// Load file into string
+			auto file_interface = Rml::GetFileInterface();
+			Rml::FileHandle handle = file_interface->Open("assets/rml.rcss");
+
+			size_t length = file_interface->Length(handle);
+			style_sheet_content.resize(length);
+			file_interface->Read((void*)style_sheet_content.data(), length, handle);
+			file_interface->Close(handle);
+
+			style_sheet_content += g_sandbox_default_rcss;
+		}
+
+		Rml::StreamMemory stream((Rml::byte*)style_sheet_content.data(), style_sheet_content.size());
+		stream.SetSourceURL("sandbox://default_rcss");
+
+		rml_basic_style_sheet = MakeShared<Rml::StyleSheetContainer>();
+		rml_basic_style_sheet->LoadStyleSheetContainer(&stream);
+	}
+
+	// Add sandbox style sheet text.
+	if (auto source = rmlui_dynamic_cast<Rml::ElementFormControl*>(document->GetElementById("sandbox_rcss_source")))
+	{
+		Rml::String value = "/* Write your RCSS here */\n\n/* body { color: #fea; background: #224; }\nimg { image-color: red; } */";
+		source->SetValue(value);
+		SetSandboxStylesheet(value);
+	}
+
+	gauge = document->GetElementById("gauge");
+	progress_horizontal = document->GetElementById("progress_horizontal");
+
+	document->Show();
+
+	return true;
+}
+
+void DemoWindow::Shutdown()
+{
+	if (document)
+	{
+		document->Close();
+		document = nullptr;
+	}
+}
+
+void DemoWindow::Update()
+{
+	if (iframe)
+		iframe->UpdateDocument();
+
+	if (submitting && gauge && progress_horizontal)
+	{
+		using namespace Rml;
+		constexpr float progressbars_time = 2.f;
+		const float progress = Math::Min(float(GetSystemInterface()->GetElapsedTime() - submitting_start_time) / progressbars_time, 2.f);
+
+		float value_gauge = 1.0f;
+		float value_horizontal = 0.0f;
+		if (progress < 1.0f)
+			value_gauge = 0.5f - 0.5f * Math::Cos(Math::RMLUI_PI * progress);
+		else
+			value_horizontal = 0.5f - 0.5f * Math::Cos(Math::RMLUI_PI * (progress - 1.0f));
+
+		progress_horizontal->SetAttribute("value", value_horizontal);
+
+		const float value_begin = 0.09f;
+		const float value_end = 1.f - value_begin;
+		float value_mapped = value_begin + value_gauge * (value_end - value_begin);
+		gauge->SetAttribute("value", value_mapped);
+
+		auto value_gauge_str = CreateString(10, "%d %%", Math::RoundToInteger(value_gauge * 100.f));
+		auto value_horizontal_str = CreateString(10, "%d %%", Math::RoundToInteger(value_horizontal * 100.f));
+
+		if (auto el_value = document->GetElementById("gauge_value"))
+			el_value->SetInnerRML(value_gauge_str);
+		if (auto el_value = document->GetElementById("progress_value"))
+			el_value->SetInnerRML(value_horizontal_str);
+
+		String label = "Placing tubes";
+		size_t num_dots = (size_t(progress * 10.f) % 4);
+		if (progress > 1.0f)
+			label += "... Placed! Assembling message";
+		if (progress < 2.0f)
+			label += String(num_dots, '.');
+		else
+			label += "... Done!";
+
+		if (auto el_label = document->GetElementById("progress_label"))
+			el_label->SetInnerRML(label);
+
+		if (progress >= 2.0f)
+		{
+			submitting = false;
+			if (auto el_output = document->GetElementById("form_output"))
+				el_output->SetInnerRML(submit_message);
+		}
+
+		document->GetContext()->RequestNextUpdate(.0);
+	}
+}
+
+void DemoWindow::ProcessEvent(Rml::Event& event)
+{
+	using namespace Rml;
+
+	switch (event.GetId())
+	{
+	case EventId::Keydown:
+	{
+		Rml::Input::KeyIdentifier key_identifier = (Rml::Input::KeyIdentifier)event.GetParameter<int>("key_identifier", 0);
+
+		if (key_identifier == Rml::Input::KI_ESCAPE)
+			Backend::RequestExit();
+	}
+	break;
+
+	default: break;
+	}
+}
+
+Rml::ElementDocument* DemoWindow::GetDocument()
+{
+	return document;
+}
+
+void DemoWindow::SubmitForm(Rml::String in_submit_message)
+{
+	submitting = true;
+	submitting_start_time = Rml::GetSystemInterface()->GetElapsedTime();
+	submit_message = in_submit_message;
+	if (auto el_output = document->GetElementById("form_output"))
+		el_output->SetInnerRML("");
+	if (auto el_progress = document->GetElementById("submit_progress"))
+		el_progress->SetProperty("display", "block");
+}
+
+void DemoWindow::SetSandboxStylesheet(const Rml::String& string)
+{
+	if (iframe && rml_basic_style_sheet)
+	{
+		auto style = Rml::MakeShared<Rml::StyleSheetContainer>();
+		Rml::StreamMemory stream((const Rml::byte*)string.data(), string.size());
+		stream.SetSourceURL("sandbox://rcss");
+
+		style->LoadStyleSheetContainer(&stream);
+		style = rml_basic_style_sheet->CombineStyleSheetContainer(*style);
+		iframe->SetStyleSheetContainer(style);
+	}
+}
+
+void DemoWindow::SetSandboxBody(const Rml::String& string)
+{
+	if (iframe)
+		iframe->SetInnerRML(string);
+}
+
+TweeningParameters DemoWindow::GetTweeningParameters() const
+{
+	return tweening_parameters;
+}
+
+void DemoWindow::SetTweeningParameters(TweeningParameters in_tweening_parameters)
+{
+	tweening_parameters = in_tweening_parameters;
+}

+ 72 - 0
Samples/basic/demo/src/DemoWindow.h

@@ -0,0 +1,72 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2019-2024 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef DEMOWINDOW_H
+#define DEMOWINDOW_H
+
+#include <RmlUi/Core/Element.h>
+#include <RmlUi/Core/EventListener.h>
+
+struct TweeningParameters {
+	Rml::Tween::Type type = Rml::Tween::Linear;
+	Rml::Tween::Direction direction = Rml::Tween::Out;
+	float duration = 0.5f;
+};
+
+class DemoWindow : public Rml::EventListener {
+public:
+	bool Initialize(const Rml::String& title, Rml::Context* context);
+	void Shutdown();
+
+	void Update();
+
+	void ProcessEvent(Rml::Event& event) override;
+
+	Rml::ElementDocument* GetDocument();
+
+	void SubmitForm(Rml::String in_submit_message);
+	void SetSandboxStylesheet(const Rml::String& string);
+	void SetSandboxBody(const Rml::String& string);
+
+	TweeningParameters GetTweeningParameters() const;
+	void SetTweeningParameters(TweeningParameters tweening_parameters);
+
+private:
+	Rml::ElementDocument* document = nullptr;
+	Rml::ElementDocument* iframe = nullptr;
+	Rml::Element* gauge = nullptr;
+	Rml::Element* progress_horizontal = nullptr;
+	Rml::SharedPtr<Rml::StyleSheetContainer> rml_basic_style_sheet;
+
+	bool submitting = false;
+	double submitting_start_time = 0;
+	Rml::String submit_message;
+
+	TweeningParameters tweening_parameters;
+};
+
+#endif

+ 21 - 395
Samples/basic/demo/src/main.cpp

@@ -26,394 +26,14 @@
  *
  */
 
-#include <RmlUi/Core.h>
-#include <RmlUi/Core/StreamMemory.h>
-#include <RmlUi/Core/TransformPrimitive.h>
+#include "DemoEventListener.h"
+#include "DemoWindow.h"
+#include <RmlUi/Core/ElementDocument.h>
+#include <RmlUi/Core/Factory.h>
 #include <RmlUi/Debugger.h>
 #include <RmlUi_Backend.h>
 #include <Shell.h>
 
-static const Rml::String sandbox_default_rcss = R"(
-body { top: 0; left: 0; right: 0; bottom: 0; overflow: hidden auto; }
-scrollbarvertical { width: 15px; }
-scrollbarvertical slidertrack { background: #eee; }
-scrollbarvertical slidertrack:active { background: #ddd; }
-scrollbarvertical sliderbar { width: 15px; min-height: 30px; background: #aaa; }
-scrollbarvertical sliderbar:hover { background: #888; }
-scrollbarvertical sliderbar:active { background: #666; }
-scrollbarhorizontal { height: 15px; }
-scrollbarhorizontal slidertrack { background: #eee; }
-scrollbarhorizontal slidertrack:active { background: #ddd; }
-scrollbarhorizontal sliderbar { height: 15px; min-width: 30px; background: #aaa; }
-scrollbarhorizontal sliderbar:hover { background: #888; }
-scrollbarhorizontal sliderbar:active { background: #666; }
-)";
-
-class DemoWindow : public Rml::EventListener {
-public:
-	DemoWindow(const Rml::String& title, Rml::Context* context)
-	{
-		using namespace Rml;
-		document = context->LoadDocument("basic/demo/data/demo.rml");
-		if (document)
-		{
-			document->GetElementById("title")->SetInnerRML(title);
-
-			// Add sandbox default text.
-			if (auto source = rmlui_dynamic_cast<Rml::ElementFormControl*>(document->GetElementById("sandbox_rml_source")))
-			{
-				auto value = source->GetValue();
-				value += "<p>Write your RML here</p>\n\n<!-- <img src=\"assets/high_scores_alien_1.tga\"/> -->";
-				source->SetValue(value);
-			}
-
-			// Prepare sandbox document.
-			if (auto target = document->GetElementById("sandbox_target"))
-			{
-				iframe = context->CreateDocument();
-				auto iframe_ptr = iframe->GetParentNode()->RemoveChild(iframe);
-				target->AppendChild(std::move(iframe_ptr));
-				iframe->SetProperty(PropertyId::Position, Property(Style::Position::Absolute));
-				iframe->SetProperty(PropertyId::Display, Property(Style::Display::Block));
-				iframe->SetInnerRML("<p>Rendered output goes here.</p>");
-
-				// Load basic RML style sheet
-				Rml::String style_sheet_content;
-				{
-					// Load file into string
-					auto file_interface = Rml::GetFileInterface();
-					Rml::FileHandle handle = file_interface->Open("assets/rml.rcss");
-
-					size_t length = file_interface->Length(handle);
-					style_sheet_content.resize(length);
-					file_interface->Read((void*)style_sheet_content.data(), length, handle);
-					file_interface->Close(handle);
-
-					style_sheet_content += sandbox_default_rcss;
-				}
-
-				Rml::StreamMemory stream((Rml::byte*)style_sheet_content.data(), style_sheet_content.size());
-				stream.SetSourceURL("sandbox://default_rcss");
-
-				rml_basic_style_sheet = MakeShared<Rml::StyleSheetContainer>();
-				rml_basic_style_sheet->LoadStyleSheetContainer(&stream);
-			}
-
-			// Add sandbox style sheet text.
-			if (auto source = rmlui_dynamic_cast<Rml::ElementFormControl*>(document->GetElementById("sandbox_rcss_source")))
-			{
-				Rml::String value = "/* Write your RCSS here */\n\n/* body { color: #fea; background: #224; }\nimg { image-color: red; } */";
-				source->SetValue(value);
-				SetSandboxStylesheet(value);
-			}
-
-			gauge = document->GetElementById("gauge");
-			progress_horizontal = document->GetElementById("progress_horizontal");
-
-			document->Show();
-		}
-	}
-
-	void Update()
-	{
-		if (iframe)
-		{
-			iframe->UpdateDocument();
-		}
-		if (submitting && gauge && progress_horizontal)
-		{
-			using namespace Rml;
-			constexpr float progressbars_time = 2.f;
-			const float progress = Math::Min(float(GetSystemInterface()->GetElapsedTime() - submitting_start_time) / progressbars_time, 2.f);
-
-			float value_gauge = 1.0f;
-			float value_horizontal = 0.0f;
-			if (progress < 1.0f)
-				value_gauge = 0.5f - 0.5f * Math::Cos(Math::RMLUI_PI * progress);
-			else
-				value_horizontal = 0.5f - 0.5f * Math::Cos(Math::RMLUI_PI * (progress - 1.0f));
-
-			progress_horizontal->SetAttribute("value", value_horizontal);
-
-			const float value_begin = 0.09f;
-			const float value_end = 1.f - value_begin;
-			float value_mapped = value_begin + value_gauge * (value_end - value_begin);
-			gauge->SetAttribute("value", value_mapped);
-
-			auto value_gauge_str = CreateString(10, "%d %%", Math::RoundToInteger(value_gauge * 100.f));
-			auto value_horizontal_str = CreateString(10, "%d %%", Math::RoundToInteger(value_horizontal * 100.f));
-
-			if (auto el_value = document->GetElementById("gauge_value"))
-				el_value->SetInnerRML(value_gauge_str);
-			if (auto el_value = document->GetElementById("progress_value"))
-				el_value->SetInnerRML(value_horizontal_str);
-
-			String label = "Placing tubes";
-			size_t num_dots = (size_t(progress * 10.f) % 4);
-			if (progress > 1.0f)
-				label += "... Placed! Assembling message";
-			if (progress < 2.0f)
-				label += String(num_dots, '.');
-			else
-				label += "... Done!";
-
-			if (auto el_label = document->GetElementById("progress_label"))
-				el_label->SetInnerRML(label);
-
-			if (progress >= 2.0f)
-			{
-				submitting = false;
-				if (auto el_output = document->GetElementById("form_output"))
-					el_output->SetInnerRML(submit_message);
-			}
-
-			document->GetContext()->RequestNextUpdate(.0);
-		}
-	}
-
-	void Shutdown()
-	{
-		if (document)
-		{
-			document->Close();
-			document = nullptr;
-		}
-	}
-
-	void ProcessEvent(Rml::Event& event) override
-	{
-		using namespace Rml;
-
-		switch (event.GetId())
-		{
-		case EventId::Keydown:
-		{
-			Rml::Input::KeyIdentifier key_identifier = (Rml::Input::KeyIdentifier)event.GetParameter<int>("key_identifier", 0);
-
-			if (key_identifier == Rml::Input::KI_ESCAPE)
-				Backend::RequestExit();
-		}
-		break;
-
-		default: break;
-		}
-	}
-
-	Rml::ElementDocument* GetDocument() { return document; }
-
-	void SubmitForm(Rml::String in_submit_message)
-	{
-		submitting = true;
-		submitting_start_time = Rml::GetSystemInterface()->GetElapsedTime();
-		submit_message = in_submit_message;
-		if (auto el_output = document->GetElementById("form_output"))
-			el_output->SetInnerRML("");
-		if (auto el_progress = document->GetElementById("submit_progress"))
-			el_progress->SetProperty("display", "block");
-	}
-
-	void SetSandboxStylesheet(const Rml::String& string)
-	{
-		if (iframe && rml_basic_style_sheet)
-		{
-			auto style = Rml::MakeShared<Rml::StyleSheetContainer>();
-			Rml::StreamMemory stream((const Rml::byte*)string.data(), string.size());
-			stream.SetSourceURL("sandbox://rcss");
-
-			style->LoadStyleSheetContainer(&stream);
-			style = rml_basic_style_sheet->CombineStyleSheetContainer(*style);
-			iframe->SetStyleSheetContainer(style);
-		}
-	}
-
-	void SetSandboxBody(const Rml::String& string)
-	{
-		if (iframe)
-		{
-			iframe->SetInnerRML(string);
-		}
-	}
-
-private:
-	Rml::ElementDocument* document = nullptr;
-	Rml::ElementDocument* iframe = nullptr;
-	Rml::Element *gauge = nullptr, *progress_horizontal = nullptr;
-	Rml::SharedPtr<Rml::StyleSheetContainer> rml_basic_style_sheet;
-
-	bool submitting = false;
-	double submitting_start_time = 0;
-	Rml::String submit_message;
-};
-
-Rml::UniquePtr<DemoWindow> demo_window;
-
-struct TweeningParameters {
-	Rml::Tween::Type type = Rml::Tween::Linear;
-	Rml::Tween::Direction direction = Rml::Tween::Out;
-	float duration = 0.5f;
-} tweening_parameters;
-
-class DemoEventListener : public Rml::EventListener {
-public:
-	DemoEventListener(const Rml::String& value, Rml::Element* element) : value(value), element(element) {}
-
-	void ProcessEvent(Rml::Event& event) override
-	{
-		using namespace Rml;
-
-		if (value == "exit")
-		{
-			// Test replacing the current element.
-			// Need to be careful with regard to lifetime issues. The event's current element will be destroyed, so we cannot
-			// use it after SetInnerRml(). The library should handle this case safely internally when propagating the event further.
-			Element* parent = element->GetParentNode();
-			parent->SetInnerRML("<button onclick='confirm_exit' onblur='cancel_exit' onmouseout='cancel_exit'>Are you sure?</button>");
-			if (Element* child = parent->GetChild(0))
-				child->Focus();
-		}
-		else if (value == "confirm_exit")
-		{
-			Backend::RequestExit();
-		}
-		else if (value == "cancel_exit")
-		{
-			if (Element* parent = element->GetParentNode())
-				parent->SetInnerRML("<button id='exit' onclick='exit'>Exit</button>");
-		}
-		else if (value == "change_color")
-		{
-			Colourb color((byte)Math::RandomInteger(255), (byte)Math::RandomInteger(255), (byte)Math::RandomInteger(255));
-			element->Animate("image-color", Property(color, Unit::COLOUR), tweening_parameters.duration,
-				Tween(tweening_parameters.type, tweening_parameters.direction));
-			event.StopPropagation();
-		}
-		else if (value == "move_child")
-		{
-			Vector2f mouse_pos(event.GetParameter("mouse_x", 0.0f), event.GetParameter("mouse_y", 0.0f));
-			if (Element* child = element->GetFirstChild())
-			{
-				Vector2f new_pos =
-					mouse_pos - element->GetAbsoluteOffset() - Vector2f(0.35f * child->GetClientWidth(), 0.9f * child->GetClientHeight());
-				Property destination = Transform::MakeProperty({Transforms::Translate2D(new_pos.x, new_pos.y)});
-				if (tweening_parameters.duration <= 0)
-					child->SetProperty(PropertyId::Transform, destination);
-				else
-					child->Animate("transform", destination, tweening_parameters.duration,
-						Tween(tweening_parameters.type, tweening_parameters.direction));
-			}
-		}
-		else if (value == "tween_function")
-		{
-			static const SmallUnorderedMap<String, Tween::Type> tweening_functions = {{"back", Tween::Back}, {"bounce", Tween::Bounce},
-				{"circular", Tween::Circular}, {"cubic", Tween::Cubic}, {"elastic", Tween::Elastic}, {"exponential", Tween::Exponential},
-				{"linear", Tween::Linear}, {"quadratic", Tween::Quadratic}, {"quartic", Tween::Quartic}, {"quintic", Tween::Quintic},
-				{"sine", Tween::Sine}};
-
-			String value = event.GetParameter("value", String());
-			auto it = tweening_functions.find(value);
-			if (it != tweening_functions.end())
-				tweening_parameters.type = it->second;
-			else
-			{
-				RMLUI_ERROR;
-			}
-		}
-		else if (value == "tween_direction")
-		{
-			String value = event.GetParameter("value", String());
-			if (value == "in")
-				tweening_parameters.direction = Tween::In;
-			else if (value == "out")
-				tweening_parameters.direction = Tween::Out;
-			else if (value == "in-out")
-				tweening_parameters.direction = Tween::InOut;
-			else
-			{
-				RMLUI_ERROR;
-			}
-		}
-		else if (value == "tween_duration")
-		{
-			float value = (float)std::atof(rmlui_static_cast<Rml::ElementFormControl*>(element)->GetValue().c_str());
-			tweening_parameters.duration = value;
-			if (auto el_duration = element->GetElementById("duration"))
-				el_duration->SetInnerRML(CreateString(20, "%2.2f", value));
-		}
-		else if (value == "rating")
-		{
-			auto el_rating = element->GetElementById("rating");
-			auto el_rating_emoji = element->GetElementById("rating_emoji");
-			if (el_rating && el_rating_emoji)
-			{
-				enum { Sad, Mediocre, Exciting, Celebrate, Champion, CountEmojis };
-				static const Rml::String emojis[CountEmojis] = {(const char*)u8"😢", (const char*)u8"😐", (const char*)u8"😮", (const char*)u8"😎",
-					(const char*)u8"🏆"};
-				int value = event.GetParameter("value", 50);
-
-				Rml::String emoji;
-				if (value <= 0)
-					emoji = emojis[Sad];
-				else if (value < 50)
-					emoji = emojis[Mediocre];
-				else if (value < 75)
-					emoji = emojis[Exciting];
-				else if (value < 100)
-					emoji = emojis[Celebrate];
-				else
-					emoji = emojis[Champion];
-
-				el_rating->SetInnerRML(Rml::CreateString(30, "%d%%", value));
-				el_rating_emoji->SetInnerRML(emoji);
-			}
-		}
-		else if (value == "submit_form")
-		{
-			const auto& p = event.GetParameters();
-			Rml::String output = "<p>";
-			for (auto& entry : p)
-			{
-				auto value = Rml::StringUtilities::EncodeRml(entry.second.Get<Rml::String>());
-				if (entry.first == "message")
-					value = "<br/>" + value;
-				output += "<strong>" + entry.first + "</strong>: " + value + "<br/>";
-			}
-			output += "</p>";
-
-			demo_window->SubmitForm(output);
-		}
-		else if (value == "set_sandbox_body")
-		{
-			if (auto source = rmlui_dynamic_cast<Rml::ElementFormControl*>(element->GetElementById("sandbox_rml_source")))
-			{
-				auto value = source->GetValue();
-				demo_window->SetSandboxBody(value);
-			}
-		}
-		else if (value == "set_sandbox_style")
-		{
-			if (auto source = rmlui_dynamic_cast<Rml::ElementFormControl*>(element->GetElementById("sandbox_rcss_source")))
-			{
-				auto value = source->GetValue();
-				demo_window->SetSandboxStylesheet(value);
-			}
-		}
-	}
-
-	void OnDetach(Rml::Element* /*element*/) override { delete this; }
-
-private:
-	Rml::String value;
-	Rml::Element* element;
-};
-
-class DemoEventListenerInstancer : public Rml::EventListenerInstancer {
-public:
-	Rml::EventListener* InstanceEventListener(const Rml::String& value, Rml::Element* element) override
-	{
-		return new DemoEventListener(value, element);
-	}
-};
-
 #if defined RMLUI_PLATFORM_WIN32
 	#include <RmlUi_Include_Windows.h>
 int APIENTRY WinMain(HINSTANCE /*instance_handle*/, HINSTANCE /*previous_instance_handle*/, char* /*command_line*/, int /*command_show*/)
@@ -454,20 +74,29 @@ int main(int /*argc*/, char** /*argv*/)
 
 	Rml::Debugger::Initialise(context);
 
-	DemoEventListenerInstancer event_listener_instancer;
+	Shell::LoadFonts();
+
+	DemoWindow demo_window;
+
+	DemoEventListenerInstancer event_listener_instancer{&demo_window};
 	Rml::Factory::RegisterEventListenerInstancer(&event_listener_instancer);
 
-	Shell::LoadFonts();
+	if (!demo_window.Initialize("Demo sample", context))
+	{
+		Rml::Shutdown();
+		Backend::Shutdown();
+		Shell::Shutdown();
+		return -1;
+	}
 
-	demo_window = Rml::MakeUnique<DemoWindow>("Demo sample", context);
-	demo_window->GetDocument()->AddEventListener(Rml::EventId::Keydown, demo_window.get());
-	demo_window->GetDocument()->AddEventListener(Rml::EventId::Keyup, demo_window.get());
-	demo_window->GetDocument()->AddEventListener(Rml::EventId::Animationend, demo_window.get());
+	demo_window.GetDocument()->AddEventListener(Rml::EventId::Keydown, &demo_window);
+	demo_window.GetDocument()->AddEventListener(Rml::EventId::Keyup, &demo_window);
+	demo_window.GetDocument()->AddEventListener(Rml::EventId::Animationend, &demo_window);
 
 	bool running = true;
 	while (running)
 	{
-		demo_window->Update();
+		demo_window.Update();
 
 		running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, true);
 		context->Update();
@@ -477,15 +106,12 @@ int main(int /*argc*/, char** /*argv*/)
 		Backend::PresentFrame();
 	}
 
-	demo_window->Shutdown();
+	demo_window.Shutdown();
 
-	// Shutdown RmlUi.
 	Rml::Shutdown();
 
 	Backend::Shutdown();
 	Shell::Shutdown();
 
-	demo_window.reset();
-
 	return 0;
 }