Cogs.Core
GuiSystem.cpp
1#include "GuiSystem.h"
2#include "GuiContainer.h"
3#include "GuiDocument.h"
4#include "GuiRenderComponent.h"
5#include "GuiRenderSystem.h"
6
7#include "Context.h"
8#include "ViewContext.h"
9
10#include "Resources/ResourceStore.h"
11#include "Resources/Texture.h"
12
13#include "Renderer/Renderer.h"
14
15#include "Systems/Core/TransformSystem.h"
16#include "Systems/Core/CameraSystem.h"
17
18#include "Math/Projection.h"
19#include "Input/InputManager.h"
20
21#include "Foundation/ComponentModel/Entity.h"
22#include "Foundation/Logging/Logger.h"
23#include "Foundation/Platform/Mouse.h"
24
25namespace
26{
27 Cogs::Logging::Log logger = Cogs::Logging::getLogger("GuiSystem");
28}
29
30Cogs::Core::GuiData::~GuiData() {
31 if (htmlDocument) {
32 GuiContainer* container = static_cast<GuiContainer*>(htmlDocument->container());
33
34 htmlDocument = nullptr;
35 delete container;
36 }
37}
38
39Cogs::Core::GuiSystem::~GuiSystem() {
40 for (auto i = themes.begin(), e = themes.end(); i != e; ++i) {
41 delete i->second;
42 }
43}
44
46 Renderer* renderer = dynamic_cast<Renderer*>(context->renderer);
47
48 // Skip initialization until the renderer has been initialized.
49 if (!renderer->getRenderContext().device) return;
50
51 for (const auto & guiComponent : pool) {
52 auto& guiData = getData(&guiComponent);
53 auto guiDocument = guiComponent.document.resolve();
54
55 if (guiDocument && !guiData.htmlDocument && guiDocument->htmlContent.size()) {
56 GuiContainer* container = new GuiContainer();
57
58 container->initialize(&renderer->getRenderContext(), context->renderer->getEngineInspectorGuiRenderer());
59 container->pushState(guiComponent.getContainer(), context->renderer->getSize());
60
61 guiData.htmlDocument = litehtml::document::createFromString(guiDocument->htmlContent.c_str(), container, &guiDocument->htmlContext);
62
63 if (!guiData.htmlDocument) {
64 LOG_ERROR(logger, "Could not create GUI document from string.");
65 continue;
66 }
67
68 guiData.htmlDocument->set_userdata(guiComponent.getContainer());
69
70 container->on_load(guiData.htmlDocument);
71
72 guiData.invalidated = true;
73 }
74
75 if (!guiDocument) continue;
76 if (!guiData.htmlDocument) continue;
77
78 if (guiComponent.alwaysInvalidate) guiData.invalidated = true;
79
80 if (guiComponent.theme != guiData.theme) {
81 auto i = themes.find(guiComponent.theme);
82 litehtml::css* theme = nullptr;
83
84 if (i == themes.end()) {
85 std::string css = context->resourceStore->getResourceContentString(guiComponent.theme, ResourceStoreFlags::None);
86
87 theme = new litehtml::css();
88 theme->parse_stylesheet(css.c_str(), guiComponent.theme.c_str(), std::shared_ptr<litehtml::document>(), litehtml::media_query_list::ptr());
89 theme->sort_selectors();
90 themes[guiComponent.theme] = theme;
91 }
92 else {
93 theme = i->second;
94 }
95 guiData.htmlDocument->apply_styles(theme);
96 guiData.invalidated = true;
97 guiData.theme = guiComponent.theme;
98 }
99
100 updateInputs(guiComponent, guiData);
101
102 auto renderComponent = guiComponent.getComponent<GuiRenderComponent>();
103 auto & texture = renderComponent->target;
104
105 guiData.size = texture ? glm::vec2{ texture->description.width, texture->description.height } : context->renderer->getSize();
106
107 static_cast<GuiContainer*>(guiData.htmlDocument->container())->pushState(guiComponent.getContainer(), guiData.size);
108
109 float x = guiData.pointerX;
110 float y = guiData.pointerY;
111
112 if (guiData.pointerRelative) {
113 x *= guiData.size.x;
114 y *= guiData.size.y;
115 }
116
117 litehtml::position::vector pos;
118 if (guiData.pointerInside) {
119 guiData.htmlDocument->on_mouse_over((int)x, (int)y, 0, 0, pos);
120
121 if (guiData.pointerX != guiData.lastX || guiData.pointerY != guiData.lastY) {
122 guiData.htmlDocument->on_mouse_move((int)x, (int)y);
123
124 guiData.lastX = guiData.pointerX;
125 guiData.lastY = guiData.pointerY;
126 }
127
128 if (guiData.pointerDown) {
129 guiData.htmlDocument->on_lbutton_down((int)x, (int)y, 0, 0, pos);
130 } else if (guiData.pointerUp) {
131 guiData.htmlDocument->on_lbutton_up((int)x, (int)y, 0, 0, pos);
132 }
133 } else {
134 guiData.htmlDocument->on_mouse_leave(pos);
135 }
136
137 guiData.invalidated = guiData.invalidated || static_cast<GuiContainer*>(guiData.htmlDocument->container())->isInvalidated() || !renderComponent->target;
138 }
139}
140
141void Cogs::Core::GuiSystem::updateInputs(const GuiComponent & guiComponent, GuiData & data)
142{
143 auto & mouseState = context->getDefaultView()->refMouse().getState();
144 const bool pointerState = context->getDefaultView()->inputManager->getActionState(guiComponent.pointerAction);
145 float x = mouseState.position.x;
146 float y = mouseState.position.y;
147
148 data.pointerDown = data.pointerUp = false;
149 data.pointerInside = false;
150
151 if (pointerState != data.pointerState) {
152 data.pointerState = pointerState;
153 if (pointerState) {
154 data.pointerDown = true;
155 } else {
156 data.pointerUp = true;
157 }
158 }
159
160 auto pointerSource = guiComponent.pointerSource;
161 if (guiComponent.pointerSource == GuiPointerSource::Auto) {
162 auto guiRenderComponent = guiComponent.getComponent<GuiRenderComponent>();
163
164 if (!guiComponent.projector && !guiRenderComponent->target) {
165 pointerSource = GuiPointerSource::Mouse;
166 } else if (!guiComponent.projector && guiRenderComponent->target) {
167 pointerSource = GuiPointerSource::ProjectedMouse;
168 } else if (guiComponent.projector) {
169 pointerSource = GuiPointerSource::Projector;
170 }
171 }
172
173 auto transformComponent = guiComponent.getComponent<TransformComponent>();
174
175 if (!transformComponent) return;
176
177 auto worldTransform = context->transformSystem->getLocalToWorld(transformComponent);
178
179 glm::vec3 planeNormal = glm::vec3(worldTransform * glm::vec4(0, 0, 1, 0));
180 glm::vec3 planeOrigin = glm::vec3(worldTransform * glm::vec4(0, 0, 0, 1));
181
182 auto inverseWorldTransform = glm::inverse(worldTransform);
183
184 auto projectPointer = [&](float /*nX*/, float /*nY*/, const glm::vec3 & rayOrigin, const glm::vec3 & rayDirection) -> bool
185 {
186 glm::vec3 coords;
187 float t;
188 bool intersects = Cogs::Geometry::projectToPlane(
189 rayOrigin,
190 rayDirection,
191 planeOrigin,
192 planeNormal,
193 coords,
194 &t
195 );
196
197 if (intersects && t < 0.0f) {
198 auto inPlaneCoords = glm::vec3(inverseWorldTransform * glm::vec4(coords, 1));
199 data.pointerInside = true;
200
201 // Diff vector in plane
202 glm::vec3 ll = -glm::vec3(guiComponent.planeSize.x * 0.5f, guiComponent.planeSize.y * 0.5f, 0);
203 glm::vec3 diff = inPlaneCoords - ll;
204
205 diff.x *= (1.0f / guiComponent.planeSize.x);
206 diff.y *= (1.0f / guiComponent.planeSize.y);
207
208 data.pointerX = diff.x;
209 data.pointerY = 1.0f - diff.y;
210 data.pointerRelative = true;
211
212 return true;
213 }
214
215 return false;
216 };
217
218 auto projectViewPointer = [&](float nX, float nY, const glm::mat4 & invViewProj) -> bool
219 {
220 glm::vec4 hp = invViewProj * glm::vec4(nX, nY, -1.f, 1.f);
221 glm::vec3 rayOrigin = (1.f / hp.w) * glm::vec3(hp);
222
223 glm::vec4 hq = invViewProj * glm::vec4(nX, nY, 1.f, 1.f);
224 glm::vec3 rayDirection = -glm::normalize((1.f / hq.w) * glm::vec3(hq) - rayOrigin);
225
226 return projectPointer(nX, nY, rayOrigin, rayDirection);
227 };
228
229 switch (pointerSource)
230 {
231 case GuiPointerSource::Mouse:
232 {
233 data.pointerInside = true;
234 data.pointerX = x;
235 data.pointerY = y;
236 data.pointerRelative = false;
237 break;
238 }
239 case GuiPointerSource::ProjectedMouse:
240 {
241 auto & cameraData = context->cameraSystem->getData(context->cameraSystem->getMainCamera());
242 auto invViewProj = glm::inverse(cameraData.rawProjectionMatrix * cameraData.viewMatrix);
243
244 const auto size = context->renderer->getSize();
245
246 const float nx = ((2.0f * (x / size.x)) - 1.0f);
247 const float ny = -((2.0f * (y / size.y)) - 1.0f);
248
249 projectViewPointer(nx, ny, invViewProj);
250
251 break;
252 }
253 case GuiPointerSource::ProjectedCenter:
254 {
255 auto & cameraData = context->cameraSystem->getData(context->cameraSystem->getMainCamera());
256 auto invViewProj = glm::inverse(cameraData.rawProjectionMatrix * cameraData.viewMatrix);
257
258 projectViewPointer(0, 0, invViewProj);
259
260 break;
261 }
262 case GuiPointerSource::Projector:
263 {
264 const auto projectorTransform = guiComponent.projector->getComponent<TransformComponent>();
265 const auto projectorWorld = context->transformSystem->getLocalToWorld(projectorTransform);
266
267 auto rayOrigin = glm::vec3(projectorWorld * glm::vec4(0, 0, 0, 1));
268 auto rayDir = -glm::normalize(glm::vec3(projectorWorld * glm::vec4(0, 0, -1, 0)));
269
270 projectPointer(0, 0, rayOrigin, rayDir);
271
272 break;
273 }
274 default:
275 LOG_ERROR(logger, "Invalid pointer source.");
276 break;
277 }
278}
ComponentType * getComponent() const
Definition: Component.h:159
void update()
Updates the system state to that of the current frame.
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
class IRenderer * renderer
Renderer.
Definition: Context.h:228
std::unique_ptr< class ResourceStore > resourceStore
ResourceStore service instance.
Definition: Context.h:210
virtual glm::vec2 getSize() const =0
Get the output surface size of the renderer.
Core renderer system.
Definition: Renderer.h:28
Log implementation class.
Definition: LogManager.h:140
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:181