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 if (guiComponent.listenerID) {
59 container->addListener(guiComponent.listenerID);
60 }
61 container->initialize(&renderer->getRenderContext(), context->renderer->getInspectorGuiRenderer());
62 container->pushState(guiComponent.getContainer(), context->renderer->getSize());
63
64 guiData.htmlDocument = litehtml::document::createFromString(guiDocument->htmlContent.c_str(), container, &guiDocument->htmlContext);
65
66 if (!guiData.htmlDocument) {
67 LOG_ERROR(logger, "Could not create GUI document from string.");
68 continue;
69 }
70
71 guiData.htmlDocument->set_userdata(guiComponent.getContainer());
72
73 container->on_load(guiData.htmlDocument);
74
75 guiData.invalidated = true;
76 }
77
78 if (!guiDocument) continue;
79 if (!guiData.htmlDocument) continue;
80
81 if (guiComponent.alwaysInvalidate) guiData.invalidated = true;
82
83 if (guiComponent.theme != guiData.theme) {
84 auto i = themes.find(guiComponent.theme);
85 litehtml::css* theme = nullptr;
86
87 if (i == themes.end()) {
88 std::string css = context->resourceStore->getResourceContentString(guiComponent.theme, ResourceStoreFlags::None);
89
90 theme = new litehtml::css();
91 theme->parse_stylesheet(css.c_str(), guiComponent.theme.c_str(), std::shared_ptr<litehtml::document>(), litehtml::media_query_list::ptr());
92 theme->sort_selectors();
93 themes[guiComponent.theme] = theme;
94 }
95 else {
96 theme = i->second;
97 }
98 guiData.htmlDocument->apply_styles(theme);
99 guiData.invalidated = true;
100 guiData.theme = guiComponent.theme;
101 }
102
103 updateInputs(guiComponent, guiData);
104
105 auto renderComponent = guiComponent.getComponent<GuiRenderComponent>();
106 auto & texture = renderComponent->target;
107
108 guiData.size = texture ? glm::vec2{ texture->description.width, texture->description.height } : context->renderer->getSize();
109
110 static_cast<GuiContainer*>(guiData.htmlDocument->container())->pushState(guiComponent.getContainer(), guiData.size);
111
112 float x = guiData.pointerX;
113 float y = guiData.pointerY;
114
115 if (guiData.pointerRelative) {
116 x *= guiData.size.x;
117 y *= guiData.size.y;
118 }
119
120 litehtml::position::vector pos;
121 if (guiData.pointerInside) {
122 guiData.htmlDocument->on_mouse_over((int)x, (int)y, 0, 0, pos);
123
124 if (guiData.pointerX != guiData.lastX || guiData.pointerY != guiData.lastY) {
125 guiData.htmlDocument->on_mouse_move((int)x, (int)y);
126
127 guiData.lastX = guiData.pointerX;
128 guiData.lastY = guiData.pointerY;
129 }
130
131 if (guiData.pointerDown) {
132 guiData.htmlDocument->on_lbutton_down((int)x, (int)y, 0, 0, pos);
133 } else if (guiData.pointerUp) {
134 guiData.htmlDocument->on_lbutton_up((int)x, (int)y, 0, 0, pos);
135 }
136 } else {
137 guiData.htmlDocument->on_mouse_leave(pos);
138 }
139
140 guiData.invalidated = guiData.invalidated || static_cast<GuiContainer*>(guiData.htmlDocument->container())->isInvalidated() || !renderComponent->target;
141 }
142}
143
144void Cogs::Core::GuiSystem::updateInputs(const GuiComponent & guiComponent, GuiData & data)
145{
146 auto & mouseState = context->getDefaultView()->refMouse().getState();
147 const bool pointerState = context->getDefaultView()->inputManager->getActionState(guiComponent.pointerAction);
148 int x = mouseState.position.x;
149 int y = mouseState.position.y;
150
151 data.pointerDown = data.pointerUp = false;
152 data.pointerInside = false;
153
154 if (pointerState != data.pointerState) {
155 data.pointerState = pointerState;
156 if (pointerState) {
157 data.pointerDown = true;
158 } else {
159 data.pointerUp = true;
160 }
161 }
162
163 auto pointerSource = guiComponent.pointerSource;
164 if (guiComponent.pointerSource == GuiPointerSource::Auto) {
165 auto guiRenderComponent = guiComponent.getComponent<GuiRenderComponent>();
166
167 if (!guiComponent.projector && !guiRenderComponent->target) {
168 pointerSource = GuiPointerSource::Mouse;
169 } else if (!guiComponent.projector && guiRenderComponent->target) {
170 pointerSource = GuiPointerSource::ProjectedMouse;
171 } else if (guiComponent.projector) {
172 pointerSource = GuiPointerSource::Projector;
173 }
174 }
175
176 auto transformComponent = guiComponent.getComponent<TransformComponent>();
177
178 if (!transformComponent) return;
179
180 auto worldTransform = context->transformSystem->getLocalToWorld(transformComponent);
181
182 glm::vec3 planeNormal = glm::vec3(worldTransform * glm::vec4(0, 0, 1, 0));
183 glm::vec3 planeOrigin = glm::vec3(worldTransform * glm::vec4(0, 0, 0, 1));
184
185 auto inverseWorldTransform = glm::inverse(worldTransform);
186
187 auto projectPointer = [&](float /*nX*/, float /*nY*/, const glm::vec3 & rayOrigin, const glm::vec3 & rayDirection) -> bool
188 {
189 glm::vec3 coords;
190 float t;
191 bool intersects = Cogs::Geometry::projectToPlane(
192 rayOrigin,
193 rayDirection,
194 planeOrigin,
195 planeNormal,
196 coords,
197 &t
198 );
199
200 if (intersects && t < 0.0f) {
201 auto inPlaneCoords = glm::vec3(inverseWorldTransform * glm::vec4(coords, 1));
202 data.pointerInside = true;
203
204 // Diff vector in plane
205 glm::vec3 ll = -glm::vec3(guiComponent.planeSize.x * 0.5f, guiComponent.planeSize.y * 0.5f, 0);
206 glm::vec3 diff = inPlaneCoords - ll;
207
208 diff.x *= (1.0f / guiComponent.planeSize.x);
209 diff.y *= (1.0f / guiComponent.planeSize.y);
210
211 data.pointerX = diff.x;
212 data.pointerY = 1.0f - diff.y;
213 data.pointerRelative = true;
214
215 return true;
216 }
217
218 return false;
219 };
220
221 auto projectViewPointer = [&](float nX, float nY, const glm::mat4 & invViewProj) -> bool
222 {
223 glm::vec4 hp = invViewProj * glm::vec4(nX, nY, -1.f, 1.f);
224 glm::vec3 rayOrigin = (1.f / hp.w) * glm::vec3(hp);
225
226 glm::vec4 hq = invViewProj * glm::vec4(nX, nY, 1.f, 1.f);
227 glm::vec3 rayDirection = -glm::normalize((1.f / hq.w) * glm::vec3(hq) - rayOrigin);
228
229 return projectPointer(nX, nY, rayOrigin, rayDirection);
230 };
231
232 switch (pointerSource)
233 {
234 case GuiPointerSource::Mouse:
235 {
236 data.pointerInside = true;
237 data.pointerX = (float)x;
238 data.pointerY = (float)y;
239 data.pointerRelative = false;
240 break;
241 }
242 case GuiPointerSource::ProjectedMouse:
243 {
244 auto & cameraData = context->cameraSystem->getData(context->cameraSystem->getMainCamera());
245 auto invViewProj = glm::inverse(cameraData.rawProjectionMatrix * cameraData.viewMatrix);
246
247 const auto size = context->renderer->getSize();
248
249 const float nx = ((2.0f * (static_cast<float>(x) / size.x)) - 1.0f);
250 const float ny = -((2.0f * (static_cast<float>(y) / size.y)) - 1.0f);
251
252 projectViewPointer(nx, ny, invViewProj);
253
254 break;
255 }
256 case GuiPointerSource::ProjectedCenter:
257 {
258 auto & cameraData = context->cameraSystem->getData(context->cameraSystem->getMainCamera());
259 auto invViewProj = glm::inverse(cameraData.rawProjectionMatrix * cameraData.viewMatrix);
260
261 projectViewPointer(0, 0, invViewProj);
262
263 break;
264 }
265 case GuiPointerSource::Projector:
266 {
267 const auto projectorTransform = guiComponent.projector->getComponent<TransformComponent>();
268 const auto projectorWorld = context->transformSystem->getLocalToWorld(projectorTransform);
269
270 auto rayOrigin = glm::vec3(projectorWorld * glm::vec4(0, 0, 0, 1));
271 auto rayDir = -glm::normalize(glm::vec3(projectorWorld * glm::vec4(0, 0, -1, 0)));
272
273 projectPointer(0, 0, rayOrigin, rayDir);
274
275 break;
276 }
277 default:
278 LOG_ERROR(logger, "Invalid pointer source.");
279 break;
280 }
281}
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:139
bool addListener(MessageHub *hub, bool bidirectional=false)
Adds the specified hub as a listener to this hub.
Definition: MessageHub.cpp:77
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:180