1#include "GuiContainer.h"
2#include "GuiExtension.h"
4#include "Renderer/Tasks/RenderTask.h"
5#include "Renderer/RenderTexture.h"
6#include "Renderer/RenderResources.h"
7#include "Renderer/InspectorGui/InspectorGuiRenderer.h"
9#include "Resources/TextureManager.h"
10#include "Resources/ResourceStore.h"
12#include "Systems/Core/ScriptSystem.h"
14#include "Scripting/ScriptingManager.h"
15#include "Scripting/ScriptingEngine.h"
19#include "Foundation/HashSequence.h"
20#include "Foundation/Logging/Logger.h"
21#include "Foundation/ComponentModel/Entity.h"
26#define IMGUI_DEFINE_MATH_OPERATORS
28#include "imgui_internal.h"
39 std::shared_ptr<litehtml::element> findElement(
const std::shared_ptr<litehtml::element> & e,
const StringView &
id)
41 auto a = e->get_attr(
"id");
42 if (a &&
id == a)
return e;
44 auto numChildren = e->get_children_count();
46 for (
size_t i = 0; i < numChildren; ++i) {
47 auto child = e->get_child(i);
48 auto found = findElement(child,
id);
50 if (found)
return found;
56 std::string findFont(
const StringView & family,
int )
60 return family.to_string();
62 return "/usr/share/fonts/truetype/freefont/FreeSans.ttf";
64 if (family ==
"default") {
65 return "C:/Windows/Fonts/times.ttf";
67 else if (family ==
"sans-serif" || family ==
"arial") {
68 return "C:/Windows/Fonts/arial.ttf";
71 return family.to_string();
73 return "C:/Windows/Fonts/times.ttf";
79 return { color.red / 255.0f, color.green / 255.0f, color.blue / 255.0f, color.alpha / 255.0f };
82 ImVec2 toImVec2(
int x,
int y)
84 return ImVec2(
static_cast<float>(x),
static_cast<float>(y));
89litehtml::uint_ptr Cogs::Core::GuiContainer::create_font(
const litehtml::tchar_t * faceName,
int size,
int weight, litehtml::font_style ,
unsigned int ,
litehtml::font_metrics * fm)
91 const size_t code =
hashSequence(faceName, size, weight);
93 auto imRenderer = inspectorGuiRenderer->getImguiRenderer();
97 auto it = imRenderer->fontRegistry.fonts.find(code);
98 if (it != imRenderer->fontRegistry.fonts.end()) {
102 font = &imRenderer->fontRegistry.fonts[code];
103 font->load(renderContext->context, findFont(faceName, weight),
static_cast<float>(size),
nullptr);
106 fm->height =
static_cast<int>(font->font[0]->FontSize);
107 fm->descent =
static_cast<int>(font->font[0]->Descent);
108 fm->ascent =
static_cast<int>(font->font[0]->Ascent);
110 return reinterpret_cast<litehtml::uint_ptr
>(font);
113void Cogs::Core::GuiContainer::delete_font(litehtml::uint_ptr )
118int Cogs::Core::GuiContainer::text_width(
const litehtml::tchar_t * text, litehtml::uint_ptr hFont)
120 auto font = (GuiFont *)hFont;
122 StringView view{ text };
124 auto size = font->font[0]->CalcTextSizeA(font->font[0]->FontSize, 100000, 0, view.begin(), view.end());
126 return static_cast<int>(size.x);
131 auto font = (GuiFont *)hFont;
133 auto col = toImVec4(color);
135 ImGui::PushFont(font->font[0]);
137 ImGui::SetCursorPos(toImVec2(pos.left(), pos.top() - (
int)font->font[0]->FontSize / 2));
139 ImGui::PushStyleColor(ImGuiCol_Text, col);
140 ImGui::Text(
"%s", text);
141 ImGui::PopStyleColor();
146int Cogs::Core::GuiContainer::pt_to_px(
int pt)
151int Cogs::Core::GuiContainer::get_default_font_size()
const
156const litehtml::tchar_t * Cogs::Core::GuiContainer::get_default_font_name()
const
165void Cogs::Core::GuiContainer::load_image(
const litehtml::tchar_t * src,
const litehtml::tchar_t * ,
bool )
167 auto name = StringView(src);
168 auto code = name.hash();
170 auto it = guiTextures.find(code);
172 if (it != guiTextures.end())
return;
174 if (name[0] ==
'$') {
175 guiTextures[code] = renderContext->context->textureManager->getTexture(name);
177 guiTextures[code] = renderContext->context->textureManager->loadTexture(name, NoResourceId,
TextureLoadFlags::None);
181void Cogs::Core::GuiContainer::get_image_size(
const litehtml::tchar_t * src,
const litehtml::tchar_t * ,
litehtml::size & sz)
183 auto name = StringView(src);
185 auto it = guiTextures.find(name.hash());
187 if (it == guiTextures.end())
return;
189 auto & texture = it->second;
191 if (!texture)
return;
193 sz.height = texture->description.height;
194 sz.width = texture->description.width;
203 return radius.bottom_left_x != 0;
208 int width = borders.left.width;
210 if (borders.right.width != width ||
211 borders.bottom.width != width ||
212 borders.top.width != width) {
213 LOG_WARNING(logger,
"Multiple border widths not supported.");
216 return static_cast<float>(width);
219 int calculateSegments(
float radius)
221 return radius > 1.0f ? 16 : -1;
226 auto imguiDrawList = ImGui::GetWindowDrawList();
228 const float halfPi = glm::half_pi<float>();
229 const float halfWidth = width * 0.5f;
231 const float r0 = glm::max(0.0f, radius.top_left_x - halfWidth);
232 const float r1 = glm::max(0.0f, radius.top_right_x - halfWidth);
233 const float r2 = glm::max(0.0f, radius.bottom_right_x - halfWidth);
234 const float r3 = glm::max(0.0f, radius.bottom_left_x - halfWidth);
236 imguiDrawList->PathArcTo(ImVec2(a.x + r0, a.y + r0), r0, 2 * halfPi, 3 * halfPi, calculateSegments(r0));
237 imguiDrawList->PathArcTo(ImVec2(b.x - r1, a.y + r1), r1, 3 * halfPi, 4 * halfPi, calculateSegments(r1));
238 imguiDrawList->PathArcTo(ImVec2(b.x - r2, b.y - r2), r2, 0, halfPi, calculateSegments(r2));
239 imguiDrawList->PathArcTo(ImVec2(a.x + r3, b.y - r3), r3, halfPi, 2 * halfPi, calculateSegments(r3));
246 ImVec2 p0 = toImVec2(bg.origin_box.left(), bg.origin_box.top());
247 ImVec2 p1 = toImVec2(bg.origin_box.right(), bg.origin_box.bottom());
249 ImVec4 color = toImVec4(bg.color);
251 auto imguiDrawList = ImGui::GetWindowDrawList();
253 const float borderWidth =
static_cast<float>(bg.origin_box.x - bg.border_box.x);
254 const float radius =
static_cast<float>(bg.border_radius.bottom_left_x) - borderWidth;
256 const auto u32Color = ImGui::ColorConvertFloat4ToU32(color);
258 if (bg.image.empty()) {
259 if (hasRadius(bg.border_radius)) {
260 createRoundedPath(p0, p1, bg.border_radius, borderWidth * 2);
261 imguiDrawList->PathFillConvex(u32Color);
263 imguiDrawList->AddRectFilled(p0, p1, u32Color);
266 auto name = StringView(bg.image);
267 auto & texture = guiTextures[name.hash()];
268 auto renderTexture = renderContext->resources->getRenderTexture(texture);
271 const auto texId = ImTextureID(renderTexture->textureHandle.handle);
274 imguiDrawList->PushTextureID(texId);
275 createRoundedPath(p0, p1, bg.border_radius, borderWidth * 2);
276 imguiDrawList->PathFillConvex(u32Color);
277 imguiDrawList->PopTextureID();
279 imguiDrawList->AddImage(texId, p0, p1, ImVec2(), ImVec2(1, 1), u32Color);
282 imguiDrawList->AddRectFilled(p0, p1, u32Color, radius);
291 if (borders.left.style == litehtml::border_style_none)
return;
293 if (borders.left.style != litehtml::border_style_solid) {
294 LOG_WARNING(logger,
"Only solid border style supported.");
297 auto width = getBorderWidth(borders);
299 if (width == 0)
return;
301 ImVec4 color = toImVec4(borders.left.color);
303 const float halfWidth = width / 2;
304 const float offset = 0.5f;
306 const auto a = ImVec2((
float)pos.left() + halfWidth + offset, (
float)pos.top() + halfWidth + offset);
307 const auto b = ImVec2((
float)pos.right() - halfWidth - offset, (
float)pos.bottom() - halfWidth - offset);
309 auto imguiDrawList = ImGui::GetWindowDrawList();
311 if (!hasRadius(borders.radius)) {
312 imguiDrawList->PathRect(a, b);
314 createRoundedPath(a, b, borders.radius, width);
317 imguiDrawList->PathStroke(ImGui::ColorConvertFloat4ToU32(color),
true, width);
320void Cogs::Core::GuiContainer::on_load(
const std::shared_ptr<litehtml::document> & document)
323 auto attribute = document->root()->get_child(2)->get_attr(
"onload");
326 auto scriptContext = context->scriptSystem->getScriptContext(entity->getComponent<ScriptComponent>(), ScriptFlags::JavaScript);
327 scriptContext->eval(attribute);
335 ScriptObject * createElement(
const ScriptContextHandle & scriptContext,
litehtml::document * document,
const litehtml::element::ptr & found)
337 if (found->get_userdata()) {
338 auto storedElement = (ScriptObject *)found->get_userdata();
342 if (storedElement->generation == found->get_userdata_generation()) {
343 return storedElement;
347 auto htmlElement = scriptContext->createObject(ScriptValueType::Object, (
void *)
"HTMLElement");
349 scriptContext->addProperty(htmlElement,
"innerHTML", ScriptValueType::String,
350 [=](ScriptObject *,
const ScriptArgs & ) -> ScriptObject *
353 found->get_text(text);
354 return scriptContext->createObject(ScriptValueType::String, (
void *)text.c_str());
356 [=](ScriptObject *,
const ScriptArgs & args) -> ScriptObject *
358 found->set_inner_html(args[0].stringValue.c_str());
359 found->get_document()->container()->invalidate_layout(found->get_document().get());
363 scriptContext->addProperty(htmlElement,
"className", ScriptValueType::String,
364 [=](ScriptObject *,
const ScriptArgs & ) -> ScriptObject *
366 return scriptContext->createObject(ScriptValueType::String, (
void *)found->get_attr(
"class"));
368 [=](ScriptObject *,
const ScriptArgs & args) -> ScriptObject *
370 found->set_attr(
"class", args[0].stringValue.c_str());
371 found->apply_stylesheet(document->get_styles());
372 found->parse_styles();
377 auto styles = scriptContext->createObjectProxy(
378 [=](ScriptObject * ,
const StringView & key) -> ScriptObject *
380 auto style = found->get_style_property(key.data(),
false);
382 return style ? scriptContext->createObject(ScriptValueType::String, (
void *)style) :
nullptr;
384 [=](ScriptObject * ,
const StringView & key,
const ScriptArg & value) ->
bool
387 style.add_property(key.data(), value.stringValue.data(),
nullptr,
true);
389 found->add_style(style);
391 found->parse_styles();
393 found->get_document()->container()->invalidate_layout(found->get_document().get());
398 scriptContext->setProperty(htmlElement,
"styles", styles);
400 auto attributes = scriptContext->createObjectProxy(
401 [=](ScriptObject * ,
const StringView & key) -> ScriptObject *
403 return scriptContext->createObject(ScriptValueType::String, (
void *)found->get_attr(key.data()));
405 [=](ScriptObject * ,
const StringView & key,
const ScriptArg & value) ->
bool
407 found->set_attr(key.data(), value.stringValue.data());
411 scriptContext->setProperty(htmlElement,
"attributes", attributes);
413 scriptContext->setProperty(htmlElement,
"id", found->get_attr(
"id"));
415 found->set_userdata(htmlElement, htmlElement->generation);
420 void createDocument(
const ScriptContextHandle & scriptContext,
litehtml::document * document,
const litehtml::element::ptr & )
422 auto doc = scriptContext->createObject(ScriptValueType::Object);
423 scriptContext->setProperty(
nullptr,
"document", doc);
424 scriptContext->addFunction(doc,
"getElementById", ScriptValueType::Object, { ScriptValueType::String },
425 [&, document, scriptContext](ScriptObject *,
const ScriptArgs & args) -> ScriptObject *
427 auto root = document->root();
428 auto found = findElement(root, args[0].stringValue);
430 if (!found)
return nullptr;
432 return createElement(scriptContext, document, found);
436 void dispatchMouseEvent(ScriptContextHandle scriptContext,
const litehtml::element::ptr & el,
const StringView & eventName,
const StringView & attributeName,
int x,
int y)
438 auto attribute = el->get_attr(attributeName.data());
441 scriptContext->eval(attribute);
445 if (!el->get_userdata())
return;
447 auto elementObject = createElement(scriptContext, el->get_document().get(), el);
448 auto eventObject = scriptContext->createObject(ScriptValueType::Object, (
void *)
"MouseEvent");
450 scriptContext->setProperty(
nullptr,
"_event", eventObject);
452 auto clientCoords = el->get_placement();
455 scriptContext->setProperty(eventObject,
"button", 0.0);
456 scriptContext->setProperty(eventObject,
"target", elementObject);
457 scriptContext->setProperty(eventObject,
"type", eventName);
458 scriptContext->setProperty(eventObject,
"screenX", x);
459 scriptContext->setProperty(eventObject,
"screenY", y);
460 scriptContext->setProperty(eventObject,
"clientX", x - clientCoords.x);
461 scriptContext->setProperty(eventObject,
"clientY", y - clientCoords.y);
463 scriptContext->callFunction(elementObject,
"_dispatchEvent", eventObject);
465 scriptContext->setProperty(
nullptr,
"_event",
nullptr);
468 ScriptContextHandle getScriptContext(Context * context,
Entity * entity)
470 return context->scriptSystem->getScriptContext(entity->
getComponent<ScriptComponent>(), ScriptFlags::JavaScript);
475void Cogs::Core::GuiContainer::on_mouse_enter(
const litehtml::element::ptr & el)
477 dispatchMouseEvent(getScriptContext(context, entity), el,
"mouseover",
"onmouseover", 0, 0);
480void Cogs::Core::GuiContainer::on_mouse_move(
const litehtml::element::ptr & el,
int x,
int y)
482 dispatchMouseEvent(getScriptContext(context, entity), el,
"mousemove",
"onmousemove", x, y);
485void Cogs::Core::GuiContainer::on_mouse_leave(
const litehtml::element::ptr & el)
487 dispatchMouseEvent(getScriptContext(context, entity), el,
"mouseout",
"onmouseout", 0, 0);
490void Cogs::Core::GuiContainer::on_click(
const litehtml::element::ptr & el)
492 dispatchMouseEvent(getScriptContext(context, entity), el,
"click",
"onclick", 0, 0);
495void Cogs::Core::GuiContainer::import_css(litehtml::tstring & text,
const litehtml::tstring & url, litehtml::tstring & )
500void Cogs::Core::GuiContainer::on_anchor_click(
const litehtml::tchar_t * url,
const litehtml::element::ptr & el)
502 if (std::strstr(url,
"cogs:") == url) {
505 message->writeString(url + 5);
506 sendMessage(message);
509 auto clicked = el->get_attr(
"onclick");
512 auto scriptContext = context->scriptSystem->getScriptContext(entity->getComponent<ScriptComponent>(), ScriptFlags::JavaScript);
514 scriptContext->eval(std::string(clicked) +
"();");
521 client.width = (int)size.x;
522 client.height = (
int)size.y;
527std::shared_ptr<litehtml::element> Cogs::Core::GuiContainer::create_element(
const litehtml::tchar_t * ,
const litehtml::string_map & ,
const std::shared_ptr<litehtml::document>& )
534 media.width = (int)size.x;
535 media.height = (
int)size.y;
536 media.device_width = (int)size.x;
537 media.device_height = (
int)size.y;
538 media.type = litehtml::media_type_screen;
539 media.resolution = 96;
542void Cogs::Core::GuiContainer::execute_script(
litehtml::document * document,
const litehtml::element::ptr el)
548 if (scriptElement->get_text()) {
549 auto scriptContext = context->scriptSystem->getScriptContext(entity->getComponent<ScriptComponent>(), ScriptFlags::JavaScript);
551 createDocument(scriptContext, document, el);
553 auto source = scriptElement->get_attr(
"src",
nullptr);
555 auto content = context->resourceStore->getResourceContentString(source);
557 scriptContext->addScript(content);
559 scriptContext->addScript(scriptElement->get_text());
569 auto entity = (
Entity *)document->get_userdata();
572 auto guiComponent = entity->getComponent<GuiComponent>();
573 auto * guiSystem =
static_cast<GuiSystem*
>(context->getExtensionSystem(
GuiSystem::getTypeId()));
576 auto & data = guiSystem->getData(guiComponent);
578 data.invalidated =
true;
583void Cogs::Core::GuiContainer::initialize(RenderTaskContext * renderContext, InspectorGuiRenderer * guiRenderer)
585 this->renderContext = renderContext;
586 context = renderContext->context;
587 this->inspectorGuiRenderer = guiRenderer;
590void Cogs::Core::GuiContainer::pushState(ComponentModel::Entity * entity, glm::vec2 size)
592 assert(renderContext && inspectorGuiRenderer && context &&
"Gui container not ready.");
595 this->entity = entity;
597 this->scriptComponent = entity->getComponentHandle<ScriptComponent>();
600void Cogs::Core::GuiContainer::popState()
606void Cogs::Core::GuiContainer::clear()
Container for components, providing composition of dynamic entities.
T * getComponent() const
Get a pointer to the first component implementing the given type in the entity.
static Reflection::TypeId getTypeId()
Get the type id of the component type used by the system.
Log implementation class.
static Ptr allocate(uint32_t id)
Retrieves an existing message from the pool, or creates a new one if the pool is empty.
static constexpr size_t NoPosition
No position.
@ NoCachedContent
Never use cached data.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Contains all Cogs related functionality.
constexpr size_t hashSequence(const T &t, const U &u)
Hash the last two items in a sequence of objects.
static ComponentHandle Empty()
Returns an empty, invalid handle. Will evaluate to false if tested against using operator bool().