3#include "GuiContainer.h"
4#include "GuiExtension.h"
6#include "Renderer/Tasks/RenderTask.h"
7#include "Renderer/RenderTexture.h"
8#include "Renderer/RenderResources.h"
9#include "Renderer/InspectorGui/InspectorGuiRenderer.h"
11#include "Resources/TextureManager.h"
12#include "Resources/ResourceStore.h"
14#include "Systems/Core/ScriptSystem.h"
16#include "Scripting/ScriptingManager.h"
17#include "Scripting/ScriptingEngine.h"
21#include "Foundation/HashSequence.h"
22#include "Foundation/Logging/Logger.h"
23#include "Foundation/ComponentModel/Entity.h"
28#define IMGUI_DEFINE_MATH_OPERATORS
30#include "imgui_internal.h"
41 std::shared_ptr<litehtml::element> findElement(
const std::shared_ptr<litehtml::element> & e,
const StringView &
id)
43 auto a = e->get_attr(
"id");
44 if (a &&
id == a)
return e;
46 auto numChildren = e->get_children_count();
48 for (
size_t i = 0; i < numChildren; ++i) {
49 auto child = e->get_child(i);
50 auto found = findElement(child,
id);
52 if (found)
return found;
58 std::string findFont(
const StringView & family,
int )
62 return family.to_string();
64 return "/usr/share/fonts/truetype/freefont/FreeSans.ttf";
66 if (family ==
"default") {
67 return "C:/Windows/Fonts/times.ttf";
69 else if (family ==
"sans-serif" || family ==
"arial") {
70 return "C:/Windows/Fonts/arial.ttf";
73 return family.to_string();
75 return "C:/Windows/Fonts/times.ttf";
81 return { color.red / 255.0f, color.green / 255.0f, color.blue / 255.0f, color.alpha / 255.0f };
84 ImVec2 toImVec2(
int x,
int y)
86 return ImVec2(
static_cast<float>(x),
static_cast<float>(y));
91litehtml::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)
93 const size_t code =
hashSequence(faceName, size, weight);
95 auto imRenderer = inspectorGuiRenderer->getImguiRenderer();
99 auto it = imRenderer->fontRegistry.fonts.find(code);
100 if (it != imRenderer->fontRegistry.fonts.end()) {
104 font = &imRenderer->fontRegistry.fonts[code];
105 font->load(renderContext->context, findFont(faceName, weight),
static_cast<float>(size),
nullptr);
108 fm->height =
static_cast<int>(font->font[0]->FontSize);
109 fm->descent =
static_cast<int>(font->font[0]->Descent);
110 fm->ascent =
static_cast<int>(font->font[0]->Ascent);
112 return reinterpret_cast<litehtml::uint_ptr
>(font);
115void Cogs::Core::GuiContainer::delete_font(litehtml::uint_ptr )
120int Cogs::Core::GuiContainer::text_width(
const litehtml::tchar_t * text, litehtml::uint_ptr hFont)
122 auto font = (GuiFont *)hFont;
124 StringView view{ text };
126 auto size = font->font[0]->CalcTextSizeA(font->font[0]->FontSize, 100000, 0, view.begin(), view.end());
128 return static_cast<int>(size.x);
133 auto font = (GuiFont *)hFont;
135 auto col = toImVec4(color);
137 ImGui::PushFont(font->font[0]);
139 ImGui::SetCursorPos(toImVec2(pos.left(), pos.top() - (
int)font->font[0]->FontSize / 2));
141 ImGui::PushStyleColor(ImGuiCol_Text, col);
142 ImGui::Text(
"%s", text);
143 ImGui::PopStyleColor();
148int Cogs::Core::GuiContainer::pt_to_px(
int pt)
153int Cogs::Core::GuiContainer::get_default_font_size()
const
158const litehtml::tchar_t * Cogs::Core::GuiContainer::get_default_font_name()
const
167void Cogs::Core::GuiContainer::load_image(
const litehtml::tchar_t * src,
const litehtml::tchar_t * ,
bool )
169 auto name = StringView(src);
170 auto code = name.hash();
172 auto it = guiTextures.find(code);
174 if (it != guiTextures.end())
return;
176 if (name[0] ==
'$') {
177 guiTextures[code] = renderContext->context->textureManager->getTexture(name);
179 guiTextures[code] = renderContext->context->textureManager->loadTexture(name, NoResourceId,
TextureLoadFlags::None);
183void Cogs::Core::GuiContainer::get_image_size(
const litehtml::tchar_t * src,
const litehtml::tchar_t * ,
litehtml::size & sz)
185 auto name = StringView(src);
187 auto it = guiTextures.find(name.hash());
189 if (it == guiTextures.end())
return;
191 auto & texture = it->second;
193 if (!texture)
return;
195 sz.height = texture->description.height;
196 sz.width = texture->description.width;
205 return radius.bottom_left_x != 0;
210 int width = borders.left.width;
212 if (borders.right.width != width ||
213 borders.bottom.width != width ||
214 borders.top.width != width) {
215 LOG_WARNING(logger,
"Multiple border widths not supported.");
218 return static_cast<float>(width);
221 int calculateSegments(
float radius)
223 return radius > 1.0f ? 16 : -1;
228 auto imguiDrawList = ImGui::GetWindowDrawList();
230 constexpr float halfPi = 0.5f * std::numbers::pi_v<float>;
231 float halfWidth = width * 0.5f;
233 const float r0 = glm::max(0.0f, radius.top_left_x - halfWidth);
234 const float r1 = glm::max(0.0f, radius.top_right_x - halfWidth);
235 const float r2 = glm::max(0.0f, radius.bottom_right_x - halfWidth);
236 const float r3 = glm::max(0.0f, radius.bottom_left_x - halfWidth);
238 imguiDrawList->PathArcTo(ImVec2(a.x + r0, a.y + r0), r0, 2 * halfPi, 3 * halfPi, calculateSegments(r0));
239 imguiDrawList->PathArcTo(ImVec2(b.x - r1, a.y + r1), r1, 3 * halfPi, 4 * halfPi, calculateSegments(r1));
240 imguiDrawList->PathArcTo(ImVec2(b.x - r2, b.y - r2), r2, 0, halfPi, calculateSegments(r2));
241 imguiDrawList->PathArcTo(ImVec2(a.x + r3, b.y - r3), r3, halfPi, 2 * halfPi, calculateSegments(r3));
248 ImVec2 p0 = toImVec2(bg.origin_box.left(), bg.origin_box.top());
249 ImVec2 p1 = toImVec2(bg.origin_box.right(), bg.origin_box.bottom());
251 ImVec4 color = toImVec4(bg.color);
253 auto imguiDrawList = ImGui::GetWindowDrawList();
255 const float borderWidth =
static_cast<float>(bg.origin_box.x - bg.border_box.x);
256 const float radius =
static_cast<float>(bg.border_radius.bottom_left_x) - borderWidth;
258 const auto u32Color = ImGui::ColorConvertFloat4ToU32(color);
260 if (bg.image.empty()) {
261 if (hasRadius(bg.border_radius)) {
262 createRoundedPath(p0, p1, bg.border_radius, borderWidth * 2);
263 imguiDrawList->PathFillConvex(u32Color);
265 imguiDrawList->AddRectFilled(p0, p1, u32Color);
268 auto name = StringView(bg.image);
269 auto & texture = guiTextures[name.hash()];
270 auto renderTexture = renderContext->resources->getRenderTexture(texture);
273 const auto texId = ImTextureID(renderTexture->textureHandle.handle);
276 imguiDrawList->PushTextureID(texId);
277 createRoundedPath(p0, p1, bg.border_radius, borderWidth * 2);
278 imguiDrawList->PathFillConvex(u32Color);
279 imguiDrawList->PopTextureID();
281 imguiDrawList->AddImage(texId, p0, p1, ImVec2(), ImVec2(1, 1), u32Color);
284 imguiDrawList->AddRectFilled(p0, p1, u32Color, radius);
293 if (borders.left.style == litehtml::border_style_none)
return;
295 if (borders.left.style != litehtml::border_style_solid) {
296 LOG_WARNING(logger,
"Only solid border style supported.");
299 auto width = getBorderWidth(borders);
301 if (width == 0)
return;
303 ImVec4 color = toImVec4(borders.left.color);
305 const float halfWidth = width / 2;
306 const float offset = 0.5f;
308 const auto a = ImVec2((
float)pos.left() + halfWidth + offset, (
float)pos.top() + halfWidth + offset);
309 const auto b = ImVec2((
float)pos.right() - halfWidth - offset, (
float)pos.bottom() - halfWidth - offset);
311 auto imguiDrawList = ImGui::GetWindowDrawList();
313 if (!hasRadius(borders.radius)) {
314 imguiDrawList->PathRect(a, b);
316 createRoundedPath(a, b, borders.radius, width);
319 imguiDrawList->PathStroke(ImGui::ColorConvertFloat4ToU32(color),
true, width);
322void Cogs::Core::GuiContainer::on_load(
const std::shared_ptr<litehtml::document> & document)
325 auto attribute = document->root()->get_child(2)->get_attr(
"onload");
328 auto scriptContext = context->scriptSystem->getScriptContext(entity->getComponent<ScriptComponent>(), ScriptFlags::JavaScript);
329 scriptContext->eval(attribute);
337 ScriptObject * createElement(
const ScriptContextHandle & scriptContext,
litehtml::document * document,
const litehtml::element::ptr & found)
339 if (found->get_userdata()) {
340 auto storedElement = (ScriptObject *)found->get_userdata();
344 if (storedElement->generation == found->get_userdata_generation()) {
345 return storedElement;
349 auto htmlElement = scriptContext->createObject(ScriptValueType::Object, (
void *)
"HTMLElement");
351 scriptContext->addProperty(htmlElement,
"innerHTML", ScriptValueType::String,
352 [=](ScriptObject *,
const ScriptArgs & ) -> ScriptObject *
355 found->get_text(text);
356 return scriptContext->createObject(ScriptValueType::String, (
void *)text.c_str());
358 [=](ScriptObject *,
const ScriptArgs & args) -> ScriptObject *
360 found->set_inner_html(args[0].stringValue.c_str());
361 found->get_document()->container()->invalidate_layout(found->get_document().get());
365 scriptContext->addProperty(htmlElement,
"className", ScriptValueType::String,
366 [=](ScriptObject *,
const ScriptArgs & ) -> ScriptObject *
368 return scriptContext->createObject(ScriptValueType::String, (
void *)found->get_attr(
"class"));
370 [=](ScriptObject *,
const ScriptArgs & args) -> ScriptObject *
372 found->set_attr(
"class", args[0].stringValue.c_str());
373 found->apply_stylesheet(document->get_styles());
374 found->parse_styles();
379 auto styles = scriptContext->createObjectProxy(
380 [=](ScriptObject * ,
const StringView & key) -> ScriptObject *
382 auto style = found->get_style_property(key.data(),
false);
384 return style ? scriptContext->createObject(ScriptValueType::String, (
void *)style) :
nullptr;
386 [=](ScriptObject * ,
const StringView & key,
const ScriptArg & value) ->
bool
389 style.add_property(key.data(), value.stringValue.data(),
nullptr,
true);
391 found->add_style(style);
393 found->parse_styles();
395 found->get_document()->container()->invalidate_layout(found->get_document().get());
400 scriptContext->setProperty(htmlElement,
"styles", styles);
402 auto attributes = scriptContext->createObjectProxy(
403 [=](ScriptObject * ,
const StringView & key) -> ScriptObject *
405 return scriptContext->createObject(ScriptValueType::String, (
void *)found->get_attr(key.data()));
407 [=](ScriptObject * ,
const StringView & key,
const ScriptArg & value) ->
bool
409 found->set_attr(key.data(), value.stringValue.data());
413 scriptContext->setProperty(htmlElement,
"attributes", attributes);
415 scriptContext->setProperty(htmlElement,
"id", found->get_attr(
"id"));
417 found->set_userdata(htmlElement, htmlElement->generation);
422 void createDocument(
const ScriptContextHandle & scriptContext,
litehtml::document * document,
const litehtml::element::ptr & )
424 auto doc = scriptContext->createObject(ScriptValueType::Object);
425 scriptContext->setProperty(
nullptr,
"document", doc);
426 scriptContext->addFunction(doc,
"getElementById", ScriptValueType::Object, { ScriptValueType::String },
427 [&, document, scriptContext](ScriptObject *,
const ScriptArgs & args) -> ScriptObject *
429 auto root = document->root();
430 auto found = findElement(root, args[0].stringValue);
432 if (!found)
return nullptr;
434 return createElement(scriptContext, document, found);
438 void dispatchMouseEvent(ScriptContextHandle scriptContext,
const litehtml::element::ptr & el,
const StringView & eventName,
const StringView & attributeName,
int x,
int y)
440 auto attribute = el->get_attr(attributeName.data());
443 scriptContext->eval(attribute);
447 if (!el->get_userdata())
return;
449 auto elementObject = createElement(scriptContext, el->get_document().get(), el);
450 auto eventObject = scriptContext->createObject(ScriptValueType::Object, (
void *)
"MouseEvent");
452 scriptContext->setProperty(
nullptr,
"_event", eventObject);
454 auto clientCoords = el->get_placement();
457 scriptContext->setProperty(eventObject,
"button", 0.0);
458 scriptContext->setProperty(eventObject,
"target", elementObject);
459 scriptContext->setProperty(eventObject,
"type", eventName);
460 scriptContext->setProperty(eventObject,
"screenX", x);
461 scriptContext->setProperty(eventObject,
"screenY", y);
462 scriptContext->setProperty(eventObject,
"clientX", x - clientCoords.x);
463 scriptContext->setProperty(eventObject,
"clientY", y - clientCoords.y);
465 scriptContext->callFunction(elementObject,
"_dispatchEvent", eventObject);
467 scriptContext->setProperty(
nullptr,
"_event",
nullptr);
470 ScriptContextHandle getScriptContext(Context * context,
Entity * entity)
472 return context->scriptSystem->getScriptContext(entity->
getComponent<ScriptComponent>(), ScriptFlags::JavaScript);
477void Cogs::Core::GuiContainer::on_mouse_enter(
const litehtml::element::ptr & el)
479 dispatchMouseEvent(getScriptContext(context, entity), el,
"mouseover",
"onmouseover", 0, 0);
482void Cogs::Core::GuiContainer::on_mouse_move(
const litehtml::element::ptr & el,
int x,
int y)
484 dispatchMouseEvent(getScriptContext(context, entity), el,
"mousemove",
"onmousemove", x, y);
487void Cogs::Core::GuiContainer::on_mouse_leave(
const litehtml::element::ptr & el)
489 dispatchMouseEvent(getScriptContext(context, entity), el,
"mouseout",
"onmouseout", 0, 0);
492void Cogs::Core::GuiContainer::on_click(
const litehtml::element::ptr & el)
494 dispatchMouseEvent(getScriptContext(context, entity), el,
"click",
"onclick", 0, 0);
497void Cogs::Core::GuiContainer::import_css(litehtml::tstring & text,
const litehtml::tstring & url, litehtml::tstring & )
502void Cogs::Core::GuiContainer::on_anchor_click(
const litehtml::tchar_t * url,
const litehtml::element::ptr & el)
504 if (std::strstr(url,
"cogs:") == url) {
508 auto clicked = el->get_attr(
"onclick");
511 auto scriptContext = context->scriptSystem->getScriptContext(entity->getComponent<ScriptComponent>(), ScriptFlags::JavaScript);
513 scriptContext->eval(std::string(clicked) +
"();");
520 client.width = (int)size.x;
521 client.height = (
int)size.y;
526std::shared_ptr<litehtml::element> Cogs::Core::GuiContainer::create_element(
const litehtml::tchar_t * ,
const litehtml::string_map & ,
const std::shared_ptr<litehtml::document>& )
533 media.width = (int)size.x;
534 media.height = (
int)size.y;
535 media.device_width = (int)size.x;
536 media.device_height = (
int)size.y;
537 media.type = litehtml::media_type_screen;
538 media.resolution = 96;
541void Cogs::Core::GuiContainer::execute_script(
litehtml::document * document,
const litehtml::element::ptr el)
547 if (scriptElement->get_text()) {
548 auto scriptContext = context->scriptSystem->getScriptContext(entity->getComponent<ScriptComponent>(), ScriptFlags::JavaScript);
550 createDocument(scriptContext, document, el);
552 auto source = scriptElement->get_attr(
"src",
nullptr);
554 auto content = context->resourceStore->getResourceContentString(source);
556 scriptContext->addScript(content);
558 scriptContext->addScript(scriptElement->get_text());
568 auto entity = (
Entity *)document->get_userdata();
571 auto guiComponent = entity->getComponent<GuiComponent>();
572 auto * guiSystem =
static_cast<GuiSystem*
>(context->getExtensionSystem(
GuiSystem::getTypeId()));
575 auto & data = guiSystem->getData(guiComponent);
577 data.invalidated =
true;
582void Cogs::Core::GuiContainer::initialize(RenderTaskContext * renderContext, InspectorGuiRenderer * guiRenderer)
584 this->renderContext = renderContext;
585 context = renderContext->context;
586 this->inspectorGuiRenderer = guiRenderer;
589void Cogs::Core::GuiContainer::pushState(ComponentModel::Entity * entity, glm::vec2 size)
591 assert(renderContext && inspectorGuiRenderer && context &&
"Gui container not ready.");
594 this->entity = entity;
596 this->scriptComponent = entity->getComponentHandle<ScriptComponent>();
599void Cogs::Core::GuiContainer::popState()
605void 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 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().