summaryrefslogtreecommitdiff
path: root/ui
diff options
context:
space:
mode:
Diffstat (limited to 'ui')
-rw-r--r--ui/applicationBase.cpp23
-rw-r--r--ui/applicationBase.h4
-rw-r--r--ui/builders/freeExtend.cpp30
-rw-r--r--ui/builders/freeExtend.h6
-rw-r--r--ui/builders/join.cpp18
-rw-r--r--ui/builders/join.h3
-rw-r--r--ui/builders/straight.cpp19
-rw-r--r--ui/builders/straight.h5
-rw-r--r--ui/editNetwork.cpp61
-rw-r--r--ui/editNetwork.h18
-rw-r--r--ui/gameMainSelector.cpp62
-rw-r--r--ui/gameMainSelector.h19
-rw-r--r--ui/gameMainWindow.cpp103
-rw-r--r--ui/gameMainWindow.h16
-rw-r--r--ui/icon.cpp9
-rw-r--r--ui/icon.h3
-rw-r--r--ui/iconButton.cpp57
-rw-r--r--ui/iconButton.h26
-rw-r--r--ui/imgui_extras.cpp33
-rw-r--r--ui/imgui_extras.h10
-rw-r--r--ui/imgui_wrap.h13
-rw-r--r--ui/mainApplication.cpp43
-rw-r--r--ui/mainApplication.h18
-rw-r--r--ui/mainWindow.cpp25
-rw-r--r--ui/mainWindow.h13
-rw-r--r--ui/manualCameraController.cpp8
-rw-r--r--ui/manualCameraController.h7
-rw-r--r--ui/modeHelper.h40
-rw-r--r--ui/queryTool.cpp42
-rw-r--r--ui/queryTool.h17
-rw-r--r--ui/svgIcon.cpp34
-rw-r--r--ui/svgIcon.h18
-rw-r--r--ui/text.cpp66
-rw-r--r--ui/text.h29
-rw-r--r--ui/toolbar.cpp35
-rw-r--r--ui/toolbar.h23
-rw-r--r--ui/uiComponent.cpp33
-rw-r--r--ui/uiComponent.h20
-rw-r--r--ui/uiComponentPlacer.cpp27
-rw-r--r--ui/uiComponentPlacer.h19
-rw-r--r--ui/window.cpp71
-rw-r--r--ui/window.h32
-rw-r--r--ui/windowContent.cpp30
-rw-r--r--ui/windowContent.h22
-rw-r--r--ui/worldOverlay.h3
45 files changed, 618 insertions, 595 deletions
diff --git a/ui/applicationBase.cpp b/ui/applicationBase.cpp
index 7764c58..961007b 100644
--- a/ui/applicationBase.cpp
+++ b/ui/applicationBase.cpp
@@ -1,9 +1,17 @@
#include "applicationBase.h"
+#include "imgui_wrap.h"
#include <SDL2/SDL.h>
#include <stdexcept>
ApplicationBase::ApplicationBase()
{
+ initSDL();
+ initImGUI();
+}
+
+void
+ApplicationBase::initSDL() const
+{
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0) {
throw std::runtime_error(SDL_GetError());
}
@@ -27,7 +35,22 @@ ApplicationBase::ApplicationBase()
setGlAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
}
+void
+ApplicationBase::initImGUI() const
+{
+ // Setup Dear ImGui context
+ IMGUI_CHECKVERSION();
+ ImGui::CreateContext();
+ ImGuiIO & io = ImGui::GetIO();
+ io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
+ io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
+ io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable docking
+ io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable viewports
+ io.IniFilename = nullptr; // Disable saving settings automagically
+}
+
ApplicationBase::~ApplicationBase()
{
SDL_Quit();
+ ImGui::DestroyContext();
}
diff --git a/ui/applicationBase.h b/ui/applicationBase.h
index 9d9b66b..082557d 100644
--- a/ui/applicationBase.h
+++ b/ui/applicationBase.h
@@ -9,4 +9,8 @@ public:
NO_COPY(ApplicationBase);
NO_MOVE(ApplicationBase);
+
+private:
+ void initSDL() const;
+ void initImGUI() const;
};
diff --git a/ui/builders/freeExtend.cpp b/ui/builders/freeExtend.cpp
index db127e6..aff7cd7 100644
--- a/ui/builders/freeExtend.cpp
+++ b/ui/builders/freeExtend.cpp
@@ -16,17 +16,17 @@ BuilderFreeExtend::move(
{
if (p1) {
if (const auto p = network->intersectRayNodes(ray)) {
- candidateLinks.objects = network->candidateJoins(*p1, p->pos);
+ candidateLinks = network->candidateJoins(*p1, p->pos);
}
else if (const auto p = geoData->intersectRay(ray)) {
- candidateLinks.objects = network->candidateExtend(*p1, p->first);
+ candidateLinks = network->candidateExtend(*p1, p->first);
}
else {
- candidateLinks.removeAll();
+ candidateLinks.clear();
}
}
else {
- candidateLinks.removeAll();
+ candidateLinks.clear();
}
}
@@ -38,11 +38,11 @@ BuilderFreeExtend::click(
case SDL_BUTTON_LEFT:
if (p1) {
if (const auto p = network->intersectRayNodes(ray)) {
- network->addJoins(*p1, p->pos);
+ createJoin(network, geoData, *p1, p->pos);
p1 = p->pos;
}
else if (const auto p = geoData->intersectRay(ray)) {
- network->addExtend(*p1, p->first);
+ createExtend(network, geoData, *p1, p->first);
p1 = p->first;
}
}
@@ -57,3 +57,21 @@ BuilderFreeExtend::click(
return;
}
}
+
+Link::CCollection
+BuilderFreeExtend::createJoin(
+ Network * network, const GeoData * geoData, GlobalPosition3D p1, GlobalPosition3D p2) const
+{
+ const auto links = network->addJoins(geoData, p1, p2);
+ setHeightsFor(network, links);
+ return links;
+}
+
+Link::CCollection
+BuilderFreeExtend::createExtend(
+ Network * network, const GeoData * geoData, GlobalPosition3D p1, GlobalPosition3D p2) const
+{
+ const auto links = network->addExtend(geoData, p1, p2);
+ setHeightsFor(network, links);
+ return links;
+}
diff --git a/ui/builders/freeExtend.h b/ui/builders/freeExtend.h
index 0d5f327..6f28493 100644
--- a/ui/builders/freeExtend.h
+++ b/ui/builders/freeExtend.h
@@ -5,11 +5,17 @@ class Network;
class GeoData;
class BuilderFreeExtend : public EditNetwork::Builder {
+private:
std::string hint() const override;
void click(Network * network, const GeoData * geoData, const SDL_MouseButtonEvent & e,
const Ray<GlobalPosition3D> & ray) override;
void move(Network * network, const GeoData * geoData, const SDL_MouseMotionEvent & e,
const Ray<GlobalPosition3D> & ray) override;
+public:
+ Link::CCollection createJoin(Network * network, const GeoData *, GlobalPosition3D, GlobalPosition3D) const;
+ Link::CCollection createExtend(Network * network, const GeoData *, GlobalPosition3D, GlobalPosition3D) const;
+
+private:
std::optional<GlobalPosition3D> p1;
};
diff --git a/ui/builders/join.cpp b/ui/builders/join.cpp
index 7474c5b..f6cbce5 100644
--- a/ui/builders/join.cpp
+++ b/ui/builders/join.cpp
@@ -15,25 +15,25 @@ BuilderJoin::move(Network * network, const GeoData *, const SDL_MouseMotionEvent
{
if (p1) {
if (const auto p = network->intersectRayNodes(ray)) {
- candidateLinks.objects = network->candidateJoins(p1->pos, p->pos);
+ candidateLinks = network->candidateJoins(p1->pos, p->pos);
}
else {
- candidateLinks.removeAll();
+ candidateLinks.clear();
}
}
}
void
BuilderJoin::click(
- Network * network, const GeoData *, const SDL_MouseButtonEvent & e, const Ray<GlobalPosition3D> & ray)
+ Network * network, const GeoData * geoData, const SDL_MouseButtonEvent & e, const Ray<GlobalPosition3D> & ray)
{
switch (e.button) {
case SDL_BUTTON_LEFT:
if (const auto p = network->intersectRayNodes(ray)) {
if (p1) {
- create(network, p1, p);
+ create(network, geoData, p1, p);
p1.reset();
- candidateLinks.removeAll();
+ candidateLinks.clear();
}
else {
p1 = p;
@@ -46,8 +46,10 @@ BuilderJoin::click(
}
}
-void
-BuilderJoin::create(Network * network, const Node::Ptr & p1, const Node::Ptr & p2) const
+Link::CCollection
+BuilderJoin::create(Network * network, const GeoData * geoData, const Node::Ptr & p1, const Node::Ptr & p2) const
{
- network->addJoins(p1->pos, p2->pos);
+ const auto links = network->addJoins(geoData, p1->pos, p2->pos);
+ setHeightsFor(network, links);
+ return links;
}
diff --git a/ui/builders/join.h b/ui/builders/join.h
index dd57895..326d23d 100644
--- a/ui/builders/join.h
+++ b/ui/builders/join.h
@@ -5,13 +5,14 @@ class Network;
class GeoData;
class BuilderJoin : public EditNetwork::Builder {
+private:
std::string hint() const override;
void click(Network * network, const GeoData * geoData, const SDL_MouseButtonEvent & e,
const Ray<GlobalPosition3D> & ray) override;
void move(Network * network, const GeoData * geoData, const SDL_MouseMotionEvent & e,
const Ray<GlobalPosition3D> & ray) override;
- void create(Network * network, const Node::Ptr & p1, const Node::Ptr & p2) const;
+ Link::CCollection create(Network * network, const GeoData *, const Node::Ptr & p1, const Node::Ptr & p2) const;
Node::Ptr p1;
};
diff --git a/ui/builders/straight.cpp b/ui/builders/straight.cpp
index 43f5ec8..e7d83b5 100644
--- a/ui/builders/straight.cpp
+++ b/ui/builders/straight.cpp
@@ -1,4 +1,5 @@
#include "straight.h"
+#include "stream_support.h"
#include <game/geoData.h>
std::string
@@ -16,10 +17,10 @@ BuilderStraight::move(
{
if (p1) {
if (const auto p = geoData->intersectRay(ray)) {
- candidateLinks.objects = network->candidateStraight(*p1, p->first);
+ candidateLinks = network->candidateStraight(*p1, p->first);
}
else {
- candidateLinks.removeAll();
+ candidateLinks.clear();
}
}
}
@@ -32,8 +33,8 @@ BuilderStraight::click(
case SDL_BUTTON_LEFT:
if (const auto p = geoData->intersectRay(ray)) {
if (p1) {
- create(network, *p1, p->first);
- candidateLinks.removeAll();
+ create(network, geoData, *p1, p->first);
+ candidateLinks.clear();
p1.reset();
}
else {
@@ -43,13 +44,15 @@ BuilderStraight::click(
return;
case SDL_BUTTON_MIDDLE:
p1.reset();
- candidateLinks.removeAll();
+ candidateLinks.clear();
return;
}
}
-void
-BuilderStraight::create(Network * network, GlobalPosition3D p1, GlobalPosition3D p2) const
+Link::CCollection
+BuilderStraight::create(Network * network, const GeoData * geoData, GlobalPosition3D p1, GlobalPosition3D p2) const
{
- network->addStraight(p1, p2);
+ const auto links = network->addStraight(geoData, p1, p2);
+ setHeightsFor(network, links);
+ return links;
}
diff --git a/ui/builders/straight.h b/ui/builders/straight.h
index 28eb66e..0a6f290 100644
--- a/ui/builders/straight.h
+++ b/ui/builders/straight.h
@@ -5,13 +5,16 @@ class Network;
class GeoData;
class BuilderStraight : public EditNetwork::Builder {
+private:
std::string hint() const override;
void click(Network * network, const GeoData * geoData, const SDL_MouseButtonEvent & e,
const Ray<GlobalPosition3D> & ray) override;
void move(Network * network, const GeoData * geoData, const SDL_MouseMotionEvent & e,
const Ray<GlobalPosition3D> & ray) override;
- void create(Network * network, GlobalPosition3D p1, GlobalPosition3D p2) const;
+public:
+ Link::CCollection create(Network * network, const GeoData *, GlobalPosition3D p1, GlobalPosition3D p2) const;
+private:
std::optional<GlobalPosition3D> p1;
};
diff --git a/ui/editNetwork.cpp b/ui/editNetwork.cpp
index ac2d93d..c900191 100644
--- a/ui/editNetwork.cpp
+++ b/ui/editNetwork.cpp
@@ -2,31 +2,21 @@
#include "builders/freeExtend.h"
#include "builders/join.h"
#include "builders/straight.h"
-#include "text.h"
+#include "imgui_wrap.h"
#include <game/gamestate.h>
-#include <game/geoData.h>
+#include <game/terrain.h>
#include <gfx/gl/sceneShader.h>
#include <gfx/models/texture.h>
-const std::filesystem::path fontpath {"/usr/share/fonts/hack/Hack-Regular.ttf"};
constexpr const glm::u8vec4 TRANSPARENT_BLUE {30, 50, 255, 200};
-EditNetwork::EditNetwork(Network * n) :
- network {n},
- builderToolbar {
- {"ui/icon/network.png", mode.toggle<BuilderStraight>()},
- {"ui/icon/network.png", mode.toggle<BuilderJoin>()},
- {"ui/icon/network.png", mode.toggle<BuilderFreeExtend>()},
- },
- blue {1, 1, &TRANSPARENT_BLUE}, font {fontpath, 15}
-{
-}
+EditNetwork::EditNetwork(Network * n) : network {n}, blue {1, 1, &TRANSPARENT_BLUE} { }
bool
EditNetwork::click(const SDL_MouseButtonEvent & e, const Ray<GlobalPosition3D> & ray)
{
if (builder && (e.button == SDL_BUTTON_LEFT || e.button == SDL_BUTTON_MIDDLE)) {
- builder->click(network, gameState->geoData.get(), e, ray);
+ builder->click(network, gameState->terrain.get(), e, ray);
return true;
}
return false;
@@ -36,38 +26,59 @@ bool
EditNetwork::move(const SDL_MouseMotionEvent & e, const Ray<GlobalPosition3D> & ray)
{
if (builder) {
- builder->move(network, gameState->geoData.get(), e, ray);
+ builder->move(network, gameState->terrain.get(), e, ray);
}
return false;
}
bool
-EditNetwork::handleInput(const SDL_Event & e, const UIComponent::Position & parentPos)
+EditNetwork::handleInput(const SDL_Event &)
{
- return builderToolbar.handleInput(e, parentPos);
+ return false;
}
void
-EditNetwork::render(const SceneShader & shader) const
+EditNetwork::render(const SceneShader & shader, const Frustum & frustum) const
{
if (builder) {
blue.bind();
shader.absolute.use();
- builder->render(shader);
+ builder->render(shader, frustum);
}
}
void
-EditNetwork::Builder::render(const SceneShader & shader) const
+EditNetwork::Builder::render(const SceneShader & shader, const Frustum & frustum) const
{
- candidateLinks.apply<const Renderable>(&Renderable::render, shader);
+ candidateLinks.apply<const Renderable>(&Renderable::render, shader, frustum);
}
void
-EditNetwork::render(const UIShader & shader, const UIComponent::Position & parentPos) const
+EditNetwork::Builder::setHeightsFor(Network * network, const Link::CCollection & links, GeoData::SetHeightsOpts opts)
{
- if (builder) {
- Text {builder->hint(), font, {{50, 10}, {0, 15}}, {1, 1, 0}}.render(shader, parentPos);
+ opts.surface = network->getBaseSurface();
+ const auto width = network->getBaseWidth();
+
+ for (const auto & link : links) {
+ gameState->terrain->setHeights(link->getBase(width), opts);
}
- builderToolbar.render(shader, parentPos);
+}
+
+void
+EditNetwork::render(bool & open)
+{
+ ImGui::SetNextWindowSize({-1, -1});
+ ImGui::Begin("Edit Network", &open);
+
+ auto builderChoice = [this]<typename Impl>(const char * name) {
+ if (ImGui::RadioButton(name, dynamic_cast<Impl *>(builder.get()))) {
+ builder = std::make_unique<Impl>();
+ }
+ };
+ builderChoice.operator()<BuilderStraight>("Straight");
+ builderChoice.operator()<BuilderJoin>("Join");
+ builderChoice.operator()<BuilderFreeExtend>("Free Extend");
+ ImGui::TextUnformatted(builder ? builder->hint().c_str() : "Select a build mode");
+
+ ImGui::End();
}
diff --git a/ui/editNetwork.h b/ui/editNetwork.h
index ec06fa7..4155534 100644
--- a/ui/editNetwork.h
+++ b/ui/editNetwork.h
@@ -1,8 +1,7 @@
#pragma once
+#include "game/geoData.h"
#include "gameMainSelector.h"
-#include "modeHelper.h"
-#include "toolbar.h"
#include "worldOverlay.h"
#include <game/gamestate.h>
#include <game/network/network.h>
@@ -16,33 +15,32 @@ public:
bool click(const SDL_MouseButtonEvent & e, const Ray<GlobalPosition3D> &) override;
bool move(const SDL_MouseMotionEvent & e, const Ray<GlobalPosition3D> &) override;
- bool handleInput(const SDL_Event & e, const UIComponent::Position &) override;
- void render(const SceneShader &) const override;
- void render(const UIShader & shader, const UIComponent::Position & pos) const override;
+ bool handleInput(const SDL_Event & e) override;
+ void render(const SceneShader &, const Frustum &) const override;
+ void render(bool & open) override;
using NetworkClickPos = std::variant<GlobalPosition3D, Node::Ptr>;
class Builder {
public:
virtual ~Builder() = default;
- virtual void render(const SceneShader & shader) const;
+ virtual void render(const SceneShader & shader, const Frustum &) const;
virtual std::string hint() const = 0;
virtual void click(Network *, const GeoData *, const SDL_MouseButtonEvent &, const Ray<GlobalPosition3D> &) = 0;
virtual void move(Network *, const GeoData *, const SDL_MouseMotionEvent &, const Ray<GlobalPosition3D> &) = 0;
+ static void setHeightsFor(Network *, const Link::CCollection &, GeoData::SetHeightsOpts = {});
+
using Ptr = std::unique_ptr<Builder>;
protected:
- Collection<const Link> candidateLinks;
+ SharedCollection<const Link> candidateLinks;
};
private:
Network * network;
Builder::Ptr builder;
- Mode<Builder::Ptr, ModeSecondClick::NoAction> mode {builder};
- Toolbar builderToolbar;
Texture blue;
- const Font font;
};
template<typename T> class EditNetworkOf : public EditNetwork {
diff --git a/ui/gameMainSelector.cpp b/ui/gameMainSelector.cpp
index 5bef48d..0c40abc 100644
--- a/ui/gameMainSelector.cpp
+++ b/ui/gameMainSelector.cpp
@@ -1,50 +1,44 @@
#include "gameMainSelector.h"
-#include "collection.h"
-#include "text.h"
#include "ui/uiComponent.h"
#include <SDL2/SDL.h>
#include <game/gamestate.h>
-#include <game/geoData.h>
#include <game/selectable.h>
+#include <game/terrain.h>
#include <game/worldobject.h> // IWYU pragma: keep
-#include <gfx/gl/camera.h>
-#include <optional>
+#include <gfx/camera.h>
#include <stream_support.h>
-#include <typeinfo>
-const std::filesystem::path fontpath {"/usr/share/fonts/hack/Hack-Regular.ttf"};
-
-GameMainSelector::GameMainSelector(const Camera * c, ScreenAbsCoord size) :
- UIComponent {{{}, size}}, camera {c}, font {fontpath, 15}
-{
-}
+GameMainSelector::GameMainSelector(const Camera * c) : camera {c} { }
constexpr ScreenAbsCoord TargetPos {5, 45};
void
-GameMainSelector::render(const UIShader & shader, const Position & parentPos) const
+GameMainSelector::render()
{
if (target) {
- target->render(shader, parentPos + position + TargetPos);
- }
- if (!clicked.empty()) {
- Text {clicked, font, {{50, 10}, {0, 15}}, {1, 1, 0}}.render(shader, parentPos);
+ bool open = true;
+ target->render(open);
+ if (!open) {
+ target.reset();
+ }
}
}
void
-GameMainSelector::render(const SceneShader & shader) const
+GameMainSelector::render(const SceneShader & shader, const Frustum & frustum) const
{
if (target) {
- target->render(shader);
+ target->render(shader, frustum);
}
}
bool
-GameMainSelector::handleInput(const SDL_Event & e, const Position & parentPos)
+GameMainSelector::handleInput(const SDL_Event & e)
{
- const auto getRay = [this](const auto & e) {
- const auto mouse = ScreenRelCoord {e.x, e.y} / position.size;
+ const auto getRay = [this, &window = e.window](const auto & e) {
+ glm::ivec2 size {};
+ SDL_GetWindowSizeInPixels(SDL_GetWindowFromID(window.windowID), &size.x, &size.y);
+ const auto mouse = ScreenRelCoord {e.x, e.y} / ScreenRelCoord {size};
return camera->unProject(mouse);
};
if (target) {
@@ -60,7 +54,7 @@ GameMainSelector::handleInput(const SDL_Event & e, const Position & parentPos)
}
break;
}
- return target->handleInput(e, parentPos + position + TargetPos);
+ return target->handleInput(e);
}
else {
switch (e.type) {
@@ -73,22 +67,8 @@ GameMainSelector::handleInput(const SDL_Event & e, const Position & parentPos)
}
void
-GameMainSelector::defaultClick(const Ray<GlobalPosition3D> & ray)
+GameMainSelector::defaultClick(const Ray<GlobalPosition3D> &)
{
- BaryPosition baryPos {};
- RelativeDistance distance {};
-
- if (const auto selected = gameState->world.applyOne<Selectable>(&Selectable::intersectRay, ray, baryPos, distance);
- selected != gameState->world.end()) {
- const auto & ref = *selected.base()->get();
- clicked = typeid(ref).name();
- }
- else if (const auto pos = gameState->geoData->intersectRay(ray)) {
- clicked = streamed_string(*pos);
- }
- else {
- clicked.clear();
- }
}
bool
@@ -104,17 +84,17 @@ GameMainSelector::Component::move(const SDL_MouseMotionEvent &, const Ray<Global
}
bool
-GameMainSelector::Component::handleInput(const SDL_Event &, const Position &)
+GameMainSelector::Component::handleInput(const SDL_Event &)
{
return false;
}
void
-GameMainSelector::Component::render(const UIShader &, const UIComponent::Position &) const
+GameMainSelector::Component::render(bool &)
{
}
void
-GameMainSelector::Component::render(const SceneShader &) const
+GameMainSelector::Component::render(const SceneShader &, const Frustum &) const
{
}
diff --git a/ui/gameMainSelector.h b/ui/gameMainSelector.h
index ccf0fa0..8c2be4b 100644
--- a/ui/gameMainSelector.h
+++ b/ui/gameMainSelector.h
@@ -2,16 +2,13 @@
#include "SDL_events.h"
#include "config/types.h"
-#include "font.h"
#include "uiComponent.h"
#include "worldOverlay.h"
#include <glm/glm.hpp>
#include <memory>
-#include <string>
class SceneShader;
template<typename> class Ray;
-class UIShader;
class Camera;
class GameMainSelector : public UIComponent, public WorldOverlay {
@@ -22,17 +19,17 @@ public:
virtual bool click(const SDL_MouseButtonEvent &, const Ray<GlobalPosition3D> &);
virtual bool move(const SDL_MouseMotionEvent &, const Ray<GlobalPosition3D> &);
- virtual bool handleInput(const SDL_Event &, const Position & pos);
- virtual void render(const UIShader & shader, const Position & pos) const;
- virtual void render(const SceneShader &) const;
+ virtual bool handleInput(const SDL_Event &);
+ virtual void render(bool & open);
+ virtual void render(const SceneShader &, const Frustum &) const;
};
- GameMainSelector(const Camera * c, ScreenAbsCoord size);
+ GameMainSelector(const Camera * c);
- void render(const UIShader & shader, const Position & pos) const override;
- void render(const SceneShader & shader) const override;
+ void render() override;
+ void render(const SceneShader & shader, const Frustum &) const override;
- bool handleInput(const SDL_Event & e, const Position &) override;
+ bool handleInput(const SDL_Event & e) override;
void defaultClick(const Ray<GlobalPosition3D> & ray);
@@ -40,6 +37,4 @@ public:
private:
const Camera * camera;
- const Font font;
- std::string clicked;
};
diff --git a/ui/gameMainWindow.cpp b/ui/gameMainWindow.cpp
index ccbcdba..dbbf8a7 100644
--- a/ui/gameMainWindow.cpp
+++ b/ui/gameMainWindow.cpp
@@ -1,38 +1,60 @@
#include "gameMainWindow.h"
#include "editNetwork.h"
#include "gameMainSelector.h"
-#include "gfx/camera_controller.h"
+#include "imgui_extras.h"
#include "manualCameraController.h"
-#include "modeHelper.h"
-#include "toolbar.h"
-#include "window.h"
+#include "queryTool.h"
+#include "svgIcon.h"
#include <SDL2/SDL.h>
#include <collection.h>
+#include <game/environment.h>
#include <game/gamestate.h>
#include <game/network/rail.h>
#include <game/worldobject.h> // IWYU pragma: keep
+#include <gfx/camera_controller.h>
#include <gfx/renderable.h>
#include <glad/gl.h>
#include <glm/glm.hpp>
#include <memory>
-class GameMainToolbar : Mode<decltype(GameMainSelector::target)>, public Toolbar {
+class GameMainToolbar : public UIComponent {
public:
- explicit GameMainToolbar(GameMainSelector * gms_) :
- Mode<decltype(GameMainSelector::target)> {gms_->target},
- Toolbar {
- {"ui/icon/network.png", toggle<EditNetworkOf<RailLinks>>()},
+ static constexpr auto TOOLBAR_HEIGHT = 54.F;
+ template<typename T> static constexpr T TOOLBAR_ICON_SIZE {32, 32};
+
+ explicit GameMainToolbar(GameMainSelector * gms) : gms {gms} { }
+
+ void
+ render() override
+ {
+ if (IltGui::BeginToolbar("bottomBar", ImGuiDir_Down, TOOLBAR_HEIGHT)) {
+ if (ImGui::ImageButton("Build rails", *buildRailsIcon, TOOLBAR_ICON_SIZE<ImVec2>)) {
+ gms->target = std::make_unique<EditNetworkOf<RailLinks>>();
+ }
+ if (ImGui::ImageButton("Query", *queryToolIcon, TOOLBAR_ICON_SIZE<ImVec2>)) {
+ gms->target = std::make_unique<QueryTool>();
+ }
+ IltGui::EndToolbar();
}
+ }
+
+ bool
+ handleInput(const SDL_Event &) override
{
+ return false;
}
+
+private:
+ SvgIcon buildRailsIcon {TOOLBAR_ICON_SIZE<ImageDimensions>, "ui/icon/rails.svg"};
+ SvgIcon queryToolIcon {TOOLBAR_ICON_SIZE<ImageDimensions>, "ui/icon/magnifier.svg"};
+ GameMainSelector * gms;
};
-GameMainWindow::GameMainWindow(size_t w, size_t h) :
- Window {w, h, "I Like Trains", SDL_WINDOW_OPENGL}, SceneRenderer {Window::size, 0}
+GameMainWindow::GameMainWindow(ScreenAbsCoord size) : SceneRenderer {size, 0}
{
uiComponents.create<ManualCameraController>(glm::vec2 {310'727'624, 494'018'810});
- auto gms = uiComponents.create<GameMainSelector>(&camera, ScreenAbsCoord {w, h});
- uiComponents.create<GameMainToolbar>(gms.get());
+ auto gms = uiComponents.create<GameMainSelector>(&camera);
+ uiComponents.create<GameMainToolbar>(gms);
}
void
@@ -41,45 +63,66 @@ GameMainWindow::tick(TickDuration)
uiComponents.apply<CameraController>(&CameraController::updateCamera, &camera);
}
+bool
+GameMainWindow::handleInput(const SDL_Event & event)
+{
+ switch (event.type) {
+ case SDL_WINDOWEVENT:
+ switch (event.window.event) {
+ case SDL_WINDOWEVENT_RESIZED:
+ SceneRenderer::resize({event.window.data1, event.window.data2});
+ break;
+ default:
+ break;
+ }
+ default:
+ break;
+ }
+
+ return WindowContent::handleInput(event);
+}
+
void
-GameMainWindow::render() const
+GameMainWindow::render()
{
SceneRenderer::render(*this);
- Window::render();
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glDisable(GL_DEPTH_TEST);
+ uiComponents.apply(&UIComponent::render);
}
void
-GameMainWindow::content(const SceneShader & shader) const
+GameMainWindow::content(const SceneShader & shader, const Frustum & frustum) const
{
- for (const auto & [id, asset] : gameState->assets) {
- if (const auto r = std::dynamic_pointer_cast<const Renderable>(asset)) {
- r->render(shader);
+ for (const auto & [assetId, asset] : gameState->assets) {
+ if (const auto renderable = asset.getAs<const Renderable>()) {
+ renderable->render(shader, frustum);
}
}
- gameState->world.apply<Renderable>(&Renderable::render, shader);
- uiComponents.apply<WorldOverlay>(&WorldOverlay::render, shader);
+ gameState->world.apply<const Renderable>(&Renderable::render, shader, frustum);
+ uiComponents.apply<WorldOverlay>(&WorldOverlay::render, shader, frustum);
}
void
-GameMainWindow::environment(const SceneShader & s, const SceneRenderer & r) const
+GameMainWindow::environment(const SceneShader &, const SceneRenderer & r) const
{
- // default for now
- SceneProvider::environment(s, r);
+ gameState->environment->render(r, *this);
}
void
GameMainWindow::lights(const SceneShader & shader) const
{
- gameState->world.apply<Renderable>(&Renderable::lights, shader);
+ gameState->world.apply<const Renderable>(&Renderable::lights, shader);
}
void
-GameMainWindow::shadows(const ShadowMapper & shadowMapper) const
+GameMainWindow::shadows(const ShadowMapper & shadowMapper, const Frustum & frustum) const
{
- for (const auto & [id, asset] : gameState->assets) {
- if (const auto r = std::dynamic_pointer_cast<const Renderable>(asset)) {
- r->shadows(shadowMapper);
+ for (const auto & [assetId, asset] : gameState->assets) {
+ if (const auto renderable = asset.getAs<const Renderable>()) {
+ renderable->shadows(shadowMapper, frustum);
}
}
- gameState->world.apply<Renderable>(&Renderable::shadows, shadowMapper);
+ gameState->world.apply<const Renderable>(&Renderable::shadows, shadowMapper, frustum);
}
diff --git a/ui/gameMainWindow.h b/ui/gameMainWindow.h
index a4e822e..71b6314 100644
--- a/ui/gameMainWindow.h
+++ b/ui/gameMainWindow.h
@@ -2,20 +2,20 @@
#include "chronology.h"
#include "gfx/gl/sceneRenderer.h"
-#include "window.h"
-#include <cstddef>
+#include "windowContent.h"
-class GameMainWindow : public Window, SceneRenderer, public SceneProvider {
+class GameMainWindow : public WindowContent, SceneRenderer, public SceneProvider {
public:
- GameMainWindow(size_t w, size_t h);
+ GameMainWindow(ScreenAbsCoord size);
void tick(TickDuration) override;
-
- void render() const override;
+ void render() override;
private:
- void content(const SceneShader &) const override;
+ bool handleInput(const SDL_Event &) override;
+
+ void content(const SceneShader &, const Frustum &) const override;
void environment(const SceneShader &, const SceneRenderer &) const override;
void lights(const SceneShader &) const override;
- void shadows(const ShadowMapper &) const override;
+ void shadows(const ShadowMapper &, const Frustum &) const override;
};
diff --git a/ui/icon.cpp b/ui/icon.cpp
index c3b5078..0bdc91a 100644
--- a/ui/icon.cpp
+++ b/ui/icon.cpp
@@ -22,9 +22,10 @@ Icon::Icon(const Image & tex) : size {tex.width, tex.height}
GL_RGBA, GL_UNSIGNED_BYTE, tex.data.data());
}
-void
-Icon::Bind() const
+ImTextureID
+Icon::operator*() const
{
- glActiveTexture(GL_TEXTURE0);
- glBindTexture(GL_TEXTURE_2D, m_texture);
+ static_assert(sizeof(m_texture) <= sizeof(ImTextureID));
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,performance-no-int-to-ptr) This is how ImGui works
+ return reinterpret_cast<ImTextureID>(*m_texture);
}
diff --git a/ui/icon.h b/ui/icon.h
index 76cd3ae..3d0788a 100644
--- a/ui/icon.h
+++ b/ui/icon.h
@@ -1,5 +1,6 @@
#pragma once
+#include "imgui_wrap.h"
#include <filesystem>
#include <glArrays.h>
#include <glm/glm.hpp>
@@ -11,8 +12,8 @@ public:
explicit Icon(const std::filesystem::path & fileName);
explicit Icon(const Image & image);
- void Bind() const;
const glm::vec2 size;
+ ImTextureID operator*() const;
private:
glTexture m_texture;
diff --git a/ui/iconButton.cpp b/ui/iconButton.cpp
deleted file mode 100644
index fe8c817..0000000
--- a/ui/iconButton.cpp
+++ /dev/null
@@ -1,57 +0,0 @@
-#include "iconButton.h"
-#include "glArrays.h"
-#include "ui/icon.h"
-#include "ui/uiComponent.h"
-#include <SDL2/SDL.h>
-#include <array>
-#include <filesystem>
-#include <functional>
-#include <glad/gl.h>
-#include <glm/gtc/type_ptr.hpp>
-#include <utility>
-
-IconButton::IconButton(const std::string & icon_, glm::vec2 position_, UIEvent click_) :
- UIComponent {{position_, ICON_SIZE}}, icon {icon_}, click {std::move(click_)}
-{
- glBindVertexArray(m_vertexArrayObject);
-
- glBindBuffer(GL_ARRAY_BUFFER, m_vertexArrayBuffer);
- glBufferData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(sizeof(glm::vec4) * 4), nullptr, GL_DYNAMIC_DRAW);
-
- glEnableVertexAttribArray(0);
- glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(glm::vec4), nullptr);
-
- glBindVertexArray(0);
-}
-
-void
-IconButton::render(const UIShader &, const Position & parentPos) const
-{
- icon.Bind();
- glBindVertexArray(m_vertexArrayObject);
- glBindBuffer(GL_ARRAY_BUFFER, m_vertexArrayBuffer);
- const auto abs = parentPos.origin + position.origin;
- const auto limit = abs + ICON_SIZE;
- std::array<glm::vec4, 4> vertices {{
- {abs.x, abs.y, 0, 0},
- {limit.x, abs.y, 1, 0},
- {limit.x, limit.y, 1, 1},
- {abs.x, limit.y, 0, 1},
- }};
- glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), glm::value_ptr(vertices.front()));
- glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
- glBindBuffer(GL_ARRAY_BUFFER, 0);
- glBindVertexArray(0);
-}
-
-bool
-IconButton::handleInput(const SDL_Event & e, const Position & parentPos)
-{
- const auto absPos = position + parentPos;
- if (absPos & e.button) {
- if (e.button.type == SDL_MOUSEBUTTONUP && e.button.button == SDL_BUTTON_LEFT) {
- click(e);
- }
- }
- return false;
-}
diff --git a/ui/iconButton.h b/ui/iconButton.h
deleted file mode 100644
index 0afe92d..0000000
--- a/ui/iconButton.h
+++ /dev/null
@@ -1,26 +0,0 @@
-#pragma once
-
-#include "icon.h"
-#include "uiComponent.h"
-#include <glArrays.h>
-#include <glm/glm.hpp>
-#include <string>
-
-class UIShader;
-union SDL_Event;
-
-static const constexpr glm::vec2 ICON_SIZE {32.F, 32.F};
-
-class IconButton : public UIComponent {
-public:
- IconButton(const std::string & icon, glm::vec2 position, UIEvent click);
-
- void render(const UIShader &, const Position & parentPos) const override;
-
- bool handleInput(const SDL_Event & e, const Position & parentPos) override;
-
- Icon icon;
- UIEvent click;
- glVertexArray m_vertexArrayObject;
- glBuffer m_vertexArrayBuffer;
-};
diff --git a/ui/imgui_extras.cpp b/ui/imgui_extras.cpp
new file mode 100644
index 0000000..1643f4f
--- /dev/null
+++ b/ui/imgui_extras.cpp
@@ -0,0 +1,33 @@
+#define IMGUI_INTERNAL
+#include "imgui_extras.h"
+
+namespace IltGui {
+ bool
+ BeginToolbar(const char * name, ImGuiDir dir, float axisSize, ImGuiWindowFlags windowFlags)
+ {
+ return BeginToolbar(name, ImGui::GetMainViewport(), dir, axisSize, windowFlags);
+ }
+
+ bool
+ BeginToolbar(
+ const char * name, ImGuiViewport * viewport, ImGuiDir dir, float axisSize, ImGuiWindowFlags windowFlags)
+ {
+ bool isOpen = ImGui::BeginViewportSideBar(name, viewport, dir, axisSize,
+ windowFlags | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings);
+ if (isOpen) {
+ if (dir == ImGuiDir_Up || dir == ImGuiDir_Down) {
+ ImGui::GetCurrentWindow()->DC.LayoutType = ImGuiLayoutType_Horizontal;
+ }
+ }
+ else {
+ ImGui::End();
+ }
+ return isOpen;
+ }
+
+ void
+ EndToolbar()
+ {
+ ImGui::End();
+ }
+}
diff --git a/ui/imgui_extras.h b/ui/imgui_extras.h
new file mode 100644
index 0000000..0babaa3
--- /dev/null
+++ b/ui/imgui_extras.h
@@ -0,0 +1,10 @@
+#include "imgui_wrap.h"
+
+namespace IltGui {
+ // NOLINTBEGIN(readability-identifier-naming)
+ bool BeginToolbar(const char * name, ImGuiViewport * viewport, ImGuiDir dir, float axisSize,
+ ImGuiWindowFlags windowFlags = 0);
+ bool BeginToolbar(const char * name, ImGuiDir dir, float axisSize, ImGuiWindowFlags windowFlags = 0);
+ void EndToolbar();
+ // NOLINTEND(readability-identifier-naming)
+}
diff --git a/ui/imgui_wrap.h b/ui/imgui_wrap.h
new file mode 100644
index 0000000..520d8b8
--- /dev/null
+++ b/ui/imgui_wrap.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wold-style-cast"
+#include "imgui.h" // IWYU pragma: export
+#ifdef IMGUI_INTERNAL
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wconversion"
+# pragma GCC diagnostic ignored "-Wsign-conversion"
+# include "imgui_internal.h" // IWYU pragma: export
+# pragma GCC diagnostic pop
+#endif
+#pragma GCC diagnostic pop
diff --git a/ui/mainApplication.cpp b/ui/mainApplication.cpp
new file mode 100644
index 0000000..6cb1037
--- /dev/null
+++ b/ui/mainApplication.cpp
@@ -0,0 +1,43 @@
+#include "mainApplication.h"
+#include "game/gamestate.h"
+#include "game/worldobject.h"
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wold-style-cast"
+#include "backends/imgui_impl_sdl2.h"
+#pragma GCC diagnostic pop
+
+void
+MainApplication::mainLoop()
+{
+ auto t_start = std::chrono::high_resolution_clock::now();
+ while (isRunning) {
+ processInputs();
+ const auto t_end = std::chrono::high_resolution_clock::now();
+ const auto t_passed = std::chrono::duration_cast<TickDuration>(t_end - t_start);
+
+ if (gameState) {
+ gameState->world.apply(&WorldObject::tick, t_passed);
+ }
+ windows.apply(&Window::tick, t_passed);
+ windows.apply(&Window::refresh);
+ ImGui::UpdatePlatformWindows();
+ ImGui::RenderPlatformWindowsDefault();
+
+ t_start = t_end;
+ }
+}
+
+void
+MainApplication::processInputs()
+{
+ for (SDL_Event e; SDL_PollEvent(&e);) {
+ if (e.type == SDL_QUIT) {
+ isRunning = false;
+ return;
+ }
+ ImGui_ImplSDL2_ProcessEvent(&e);
+ if (!ImGui::GetIO().WantCaptureMouse) {
+ windows.applyOne(&Window::handleInput, e);
+ }
+ }
+}
diff --git a/ui/mainApplication.h b/ui/mainApplication.h
new file mode 100644
index 0000000..1489587
--- /dev/null
+++ b/ui/mainApplication.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "applicationBase.h"
+#include "collection.h"
+#include "window.h"
+
+class MainApplication : public ApplicationBase {
+public:
+ using Windows = SharedCollection<Window>;
+ void mainLoop();
+
+protected:
+ Windows windows;
+
+private:
+ void processInputs();
+ bool isRunning {true};
+};
diff --git a/ui/mainWindow.cpp b/ui/mainWindow.cpp
new file mode 100644
index 0000000..57dabc0
--- /dev/null
+++ b/ui/mainWindow.cpp
@@ -0,0 +1,25 @@
+#include "mainWindow.h"
+#include <format>
+#include <stdexcept>
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wold-style-cast"
+#include "backends/imgui_impl_opengl3.h"
+#include "backends/imgui_impl_sdl2.h"
+#pragma GCC diagnostic pop
+
+MainWindow::MainWindow(ScreenAbsCoord size, const char * title, Uint32 flags) : Window {size, title, flags}
+{
+ if (const auto version = gladLoadGL(reinterpret_cast<GLADloadfunc>(SDL_GL_GetProcAddress)); version < 30003) {
+ throw std::runtime_error {std::format("Insufficient OpenGL version: {}", version)};
+ }
+
+ ImGui_ImplSDL2_InitForOpenGL(m_window, glContext.get());
+ ImGui_ImplOpenGL3_Init();
+}
+
+MainWindow::~MainWindow()
+{
+ ImGui_ImplOpenGL3_Shutdown();
+ ImGui_ImplSDL2_Shutdown();
+}
diff --git a/ui/mainWindow.h b/ui/mainWindow.h
new file mode 100644
index 0000000..d2de9b3
--- /dev/null
+++ b/ui/mainWindow.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include "window.h"
+#include <cstddef>
+
+class MainWindow : public Window {
+public:
+ MainWindow(ScreenAbsCoord size, const char * title, Uint32 flags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL);
+ ~MainWindow() override;
+
+ NO_MOVE(MainWindow);
+ NO_COPY(MainWindow);
+};
diff --git a/ui/manualCameraController.cpp b/ui/manualCameraController.cpp
index 065e1d8..553afc1 100644
--- a/ui/manualCameraController.cpp
+++ b/ui/manualCameraController.cpp
@@ -1,11 +1,11 @@
#include "manualCameraController.h"
#include <algorithm>
#include <cmath>
-#include <gfx/gl/camera.h>
+#include <gfx/camera.h>
#include <maths.h>
bool
-ManualCameraController::handleInput(const SDL_Event & e, const Position &)
+ManualCameraController::handleInput(const SDL_Event & e)
{
switch (e.type) {
case SDL_KEYDOWN:
@@ -72,13 +72,13 @@ ManualCameraController::handleInput(const SDL_Event & e, const Position &)
}
void
-ManualCameraController::render(const UIShader &, const Position &) const
+ManualCameraController::render()
{
}
void
ManualCameraController::updateCamera(Camera * camera) const
{
- const auto forward = glm::normalize(sincosf(direction) || -sin(pitch));
+ const auto forward = glm::normalize(sincos(direction) || -sin(pitch));
camera->setView((focus || 0) - (forward * 3.F * std::pow(dist, 1.3F)), forward);
}
diff --git a/ui/manualCameraController.h b/ui/manualCameraController.h
index 2f955e7..6501762 100644
--- a/ui/manualCameraController.h
+++ b/ui/manualCameraController.h
@@ -6,15 +6,14 @@
#include <glm/glm.hpp>
#include <maths.h>
-class UIShader;
class Camera;
class ManualCameraController : public CameraController, public UIComponent {
public:
- explicit ManualCameraController(GlobalPosition2D f) : UIComponent {{}}, focus {f} { }
+ explicit ManualCameraController(GlobalPosition2D f) : focus {f} { }
- bool handleInput(const SDL_Event & e, const Position &) override;
- void render(const UIShader &, const Position & parentPos) const override;
+ bool handleInput(const SDL_Event & e) override;
+ void render() override;
void updateCamera(Camera * camera) const override;
diff --git a/ui/modeHelper.h b/ui/modeHelper.h
deleted file mode 100644
index d20f2db..0000000
--- a/ui/modeHelper.h
+++ /dev/null
@@ -1,40 +0,0 @@
-#pragma once
-
-#include <memory>
-union SDL_Event;
-
-enum ModeSecondClick { Unset, Reset, NoAction };
-
-template<typename Target, ModeSecondClick msc = ModeSecondClick::Unset> class Mode {
-public:
- explicit Mode(Target & t) : target {t} { }
-
- Target & target;
-
- template<typename Mode, typename... Params>
- auto
- toggle(Params &&... params)
- {
- return [params..., this](const SDL_Event &) {
- toggleSetMode<Mode>(std::forward<Params>(params)...);
- };
- }
-
-private:
- template<typename Mode, typename... Params>
- void
- toggleSetMode(Params &&... params)
- {
- if (dynamic_cast<Mode *>(target.get())) {
- if constexpr (msc == ModeSecondClick::Unset) {
- target.reset();
- }
- if constexpr (msc == ModeSecondClick::Reset) {
- target = std::make_unique<Mode>(std::forward<Params>(params)...);
- }
- }
- else {
- target = std::make_unique<Mode>(std::forward<Params>(params)...);
- }
- }
-};
diff --git a/ui/queryTool.cpp b/ui/queryTool.cpp
new file mode 100644
index 0000000..549bd9e
--- /dev/null
+++ b/ui/queryTool.cpp
@@ -0,0 +1,42 @@
+#include "queryTool.h"
+#include "imgui_wrap.h"
+#include <game/gamestate.h>
+#include <game/selectable.h>
+#include <game/terrain.h>
+#include <game/worldobject.h>
+#include <ray.h>
+#include <stream_support.h>
+
+QueryTool::QueryTool() : clicked {"Click something for details"} { }
+
+bool
+QueryTool::click(const SDL_MouseButtonEvent & event, const Ray<GlobalPosition3D> & ray)
+{
+ if (event.button != SDL_BUTTON_LEFT) {
+ return false;
+ }
+ BaryPosition baryPos {};
+ RelativeDistance distance {};
+
+ if (const auto selected = gameState->world.applyOne<Selectable>(&Selectable::intersectRay, ray, baryPos, distance);
+ selected != gameState->world.end()) {
+ const auto & ref = *selected.base()->get();
+ clicked = typeid(ref).name();
+ }
+ else if (const auto pos = gameState->terrain->intersectRay(ray)) {
+ clicked = streamed_string(*pos);
+ }
+ else {
+ clicked.clear();
+ }
+ return true;
+}
+
+void
+QueryTool::render(bool & open)
+{
+ ImGui::SetNextWindowSize({-1, -1});
+ ImGui::Begin("Query Tool", &open);
+ ImGui::TextUnformatted(clicked.c_str());
+ ImGui::End();
+}
diff --git a/ui/queryTool.h b/ui/queryTool.h
new file mode 100644
index 0000000..74c5380
--- /dev/null
+++ b/ui/queryTool.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include "gameMainSelector.h"
+
+class QueryTool : public GameMainSelector::Component {
+public:
+ QueryTool();
+
+protected:
+ using GameMainSelector::Component::render;
+
+ bool click(const SDL_MouseButtonEvent &, const Ray<GlobalPosition3D> &) override;
+ void render(bool & open) override;
+
+private:
+ std::string clicked;
+};
diff --git a/ui/svgIcon.cpp b/ui/svgIcon.cpp
new file mode 100644
index 0000000..499d9cc
--- /dev/null
+++ b/ui/svgIcon.cpp
@@ -0,0 +1,34 @@
+#include "svgIcon.h"
+#include "gl_traits.h"
+#include <resource.h>
+
+SvgIcon::SvgIcon(ImageDimensions dim, const std::filesystem::path & path)
+{
+ const auto svgDoc = lunasvg::Document::loadFromFile(Resource::mapPath(path).native());
+ if (!svgDoc) {
+ throw std::runtime_error("Failed to load SVG from " + path.string());
+ }
+
+ auto bitmap = svgDoc->renderToBitmap(dim.x, dim.y);
+ if (bitmap.isNull()) {
+ throw std::runtime_error("Failed to render SVG " + path.string());
+ }
+ bitmap.convertToRGBA();
+
+ glBindTexture(GL_TEXTURE_2D, texture);
+
+ glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
+ glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
+
+ glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dim.x, dim.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, bitmap.data());
+}
+
+ImTextureID
+SvgIcon::operator*() const
+{
+ static_assert(sizeof(glTexture) <= sizeof(ImTextureID));
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,performance-no-int-to-ptr) This is how ImGui works
+ return reinterpret_cast<ImTextureID>(*texture);
+}
diff --git a/ui/svgIcon.h b/ui/svgIcon.h
new file mode 100644
index 0000000..106f97c
--- /dev/null
+++ b/ui/svgIcon.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "glArrays.h"
+#include "imgui_wrap.h"
+#include <config/types.h>
+#include <filesystem>
+#include <lunasvg.h>
+
+class SvgIcon {
+public:
+ SvgIcon(ImageDimensions, const std::filesystem::path &);
+
+ ImTextureID operator*() const;
+
+private:
+ friend class LoadFromFile; // Test case verifying size/content
+ glTexture texture;
+};
diff --git a/ui/text.cpp b/ui/text.cpp
deleted file mode 100644
index 5675061..0000000
--- a/ui/text.cpp
+++ /dev/null
@@ -1,66 +0,0 @@
-#include "text.h"
-#include "font.h"
-#include "gfx/gl/uiShader.h"
-#include "gfx/gl/vertexArrayObject.h"
-#include "uiComponent.h"
-#include <array>
-#include <collections.h>
-#include <glArrays.h>
-#include <glm/gtc/type_ptr.hpp>
-#include <maths.h>
-#include <numeric>
-#include <utility>
-
-Text::Text(std::string_view s, const Font & font, Position pos, glm::vec3 c) :
- UIComponent {pos}, colour {c}, font {font}
-{
- VertexArrayObject {vao}.addAttribs<Font::Quad::value_type>(quads.bufferName(), 0);
- operator=(s);
-}
-
-Text &
-Text::operator=(const std::string_view s)
-{
- auto tquads = font.render(s);
- models.resize(tquads.size());
- const auto glyphCount = std::accumulate(tquads.begin(), tquads.end(), size_t {}, [](auto && init, const auto & q) {
- return init += q.second.size();
- });
- quads.resize(glyphCount);
- GLint current = 0;
- auto model = models.begin();
- auto quad = quads.begin();
- for (const auto & [texture, fquads] : tquads) {
- model->first = texture;
- model->second = {fquads.size() * 4, current * 4};
- current += static_cast<GLint>(fquads.size());
- model++;
- quad = std::transform(fquads.begin(), fquads.end(), quad, [this](const Font::Quad & q) {
- return q * [this](const glm::vec4 & corner) {
- return corner + glm::vec4 {this->position.origin, 0, 0};
- };
- });
- }
- quads.unmap();
- return *this;
-}
-
-void
-Text::render(const UIShader & shader, const Position &) const
-{
- shader.text.use(colour);
- glActiveTexture(GL_TEXTURE0);
- glBindVertexArray(vao);
- for (const auto & m : models) {
- glBindTexture(GL_TEXTURE_2D, m.first);
- glDrawArrays(GL_QUADS, m.second.second, m.second.first);
- }
- glBindVertexArray(0);
- glBindTexture(GL_TEXTURE_2D, 0);
-}
-
-bool
-Text::handleInput(const SDL_Event &, const Position &)
-{
- return false;
-}
diff --git a/ui/text.h b/ui/text.h
deleted file mode 100644
index 31ed9a5..0000000
--- a/ui/text.h
+++ /dev/null
@@ -1,29 +0,0 @@
-#pragma once
-
-#include "font.h"
-#include "glContainer.h"
-#include "uiComponent.h"
-#include <glArrays.h>
-#include <glad/gl.h>
-#include <glm/glm.hpp>
-#include <string_view>
-
-class UIShader;
-union SDL_Event;
-
-class Text : public UIComponent {
-public:
- Text(std::string_view s, const Font &, Position, glm::vec3 colour);
-
- void render(const UIShader &, const Position & parentPos) const override;
- bool handleInput(const SDL_Event &, const Position & parentPos) override;
-
- Text & operator=(const std::string_view s);
-
-private:
- std::vector<std::pair<GLuint, std::pair<GLsizei, GLint>>> models;
- glContainer<Font::Quad> quads;
- glVertexArray vao;
- glm::vec3 colour;
- const Font & font;
-};
diff --git a/ui/toolbar.cpp b/ui/toolbar.cpp
deleted file mode 100644
index 31d87dc..0000000
--- a/ui/toolbar.cpp
+++ /dev/null
@@ -1,35 +0,0 @@
-#include "toolbar.h"
-#include "gfx/gl/uiShader.h"
-#include "ui/iconButton.h"
-#include "ui/uiComponent.h"
-#include "uiComponentPlacer.h"
-#include <SDL2/SDL.h>
-#include <glm/glm.hpp>
-
-Toolbar::Toolbar(const std::initializer_list<InitInfo> & initInfo) : UIComponent {{{}, {}}}
-{
- UIComponentPlacer placer {{10, 10}, 5, 1};
- for (const auto & ii : initInfo) {
- icons.create(ii.first, placer.next(ICON_SIZE), ii.second);
- }
- this->position.size = placer.getLimit();
-}
-
-void
-Toolbar::render(const UIShader & uiShader, const Position & parentPos) const
-{
- uiShader.icon.use();
- const auto absPos = this->position + parentPos;
- icons.apply(&UIComponent::render, uiShader, absPos);
-}
-
-bool
-Toolbar::handleInput(const SDL_Event & e, const Position & parentPos)
-{
- const auto absPos = this->position + parentPos;
- if (absPos & e.button) {
- icons.applyOne(&UIComponent::handleInput, e, absPos);
- return true;
- }
- return false;
-}
diff --git a/ui/toolbar.h b/ui/toolbar.h
deleted file mode 100644
index ea560f5..0000000
--- a/ui/toolbar.h
+++ /dev/null
@@ -1,23 +0,0 @@
-#pragma once
-
-#include "collection.h"
-#include "iconButton.h"
-#include "uiComponent.h"
-#include <initializer_list>
-#include <string>
-#include <utility>
-
-class UIShader;
-union SDL_Event;
-
-class Toolbar : public UIComponent {
-public:
- using InitInfo = std::pair<std::string, UIEvent>;
- explicit Toolbar(const std::initializer_list<InitInfo> & initInfo);
-
- void render(const UIShader & uiShader, const Position & parentPos) const override;
-
- bool handleInput(const SDL_Event & e, const Position & parentPos) override;
-
- Collection<IconButton, false> icons;
-};
diff --git a/ui/uiComponent.cpp b/ui/uiComponent.cpp
deleted file mode 100644
index aa4838d..0000000
--- a/ui/uiComponent.cpp
+++ /dev/null
@@ -1,33 +0,0 @@
-#include "uiComponent.h"
-#include <SDL2/SDL.h>
-
-UIComponent::UIComponent(Position position) : position {position} { }
-
-UIComponent::Position
-UIComponent::Position::operator+(const Position & parentPos) const
-{
- return *this + parentPos.origin;
-}
-
-UIComponent::Position
-UIComponent::Position::operator+(const glm::vec2 & parentPos) const
-{
- return {origin + parentPos, size};
-}
-
-bool
-UIComponent::Position::operator&(const glm::vec2 & pos) const
-{
- return (pos.x >= origin.x && pos.y >= origin.y && pos.x < origin.x + size.x && pos.y < origin.y + size.y);
-}
-
-bool
-UIComponent::Position::operator&(const SDL_MouseButtonEvent & pos) const
-{
- switch (pos.type) {
- case SDL_MOUSEBUTTONUP:
- case SDL_MOUSEBUTTONDOWN:
- return *this & glm::vec2 {pos.x, pos.y};
- }
- return false;
-}
diff --git a/ui/uiComponent.h b/ui/uiComponent.h
index 71d2659..b2c1a8f 100644
--- a/ui/uiComponent.h
+++ b/ui/uiComponent.h
@@ -1,32 +1,18 @@
#pragma once
-#include <functional>
#include <glm/glm.hpp>
#include <special_members.h>
-class UIShader;
union SDL_Event;
-struct SDL_MouseButtonEvent;
-using UIEvent = std::function<void(const SDL_Event &)>;
class UIComponent {
public:
- struct Position {
- glm::vec2 origin, size;
- Position operator+(const Position &) const;
- Position operator+(const glm::vec2 &) const;
- bool operator&(const SDL_MouseButtonEvent &) const;
- bool operator&(const glm::vec2 &) const;
- };
-
- explicit UIComponent(Position);
+ UIComponent() = default;
virtual ~UIComponent() = default;
NO_MOVE(UIComponent);
NO_COPY(UIComponent);
- virtual void render(const UIShader &, const Position & parentPos) const = 0;
- virtual bool handleInput(const SDL_Event &, const Position & parentPos) = 0;
-
- Position position;
+ virtual void render() = 0;
+ virtual bool handleInput(const SDL_Event &) = 0;
};
diff --git a/ui/uiComponentPlacer.cpp b/ui/uiComponentPlacer.cpp
deleted file mode 100644
index 5e645d8..0000000
--- a/ui/uiComponentPlacer.cpp
+++ /dev/null
@@ -1,27 +0,0 @@
-#include "uiComponentPlacer.h"
-#include <algorithm>
-
-UIComponentPlacer::UIComponentPlacer(glm::vec2 padding, float spacing, glm::length_t axis) :
- padding {padding}, spacing {spacing}, axis {axis}, current {padding[axis]}
-{
-}
-
-glm::vec2
-UIComponentPlacer::next(glm::vec2 size)
-{
- glm::vec2 n {};
- n[axis] = current;
- n[1 - axis] = padding[1 - axis];
- current += spacing + size[axis];
- max = std::max(max, size[1 - axis]);
- return n;
-}
-
-glm::vec2
-UIComponentPlacer::getLimit() const
-{
- glm::vec2 n {};
- n[axis] = current + padding[axis];
- n[1 - axis] = max + padding[1 - axis];
- return n;
-}
diff --git a/ui/uiComponentPlacer.h b/ui/uiComponentPlacer.h
deleted file mode 100644
index 1e64f78..0000000
--- a/ui/uiComponentPlacer.h
+++ /dev/null
@@ -1,19 +0,0 @@
-#pragma once
-
-#include <glm/glm.hpp>
-
-class UIComponentPlacer {
-public:
- UIComponentPlacer(glm::vec2 padding, float spacing, glm::length_t axis = 0);
-
- glm::vec2 next(glm::vec2 size);
- glm::vec2 getLimit() const;
-
-private:
- const glm::vec2 padding;
- const float spacing;
- const glm::length_t axis;
-
- float current {};
- float max {};
-};
diff --git a/ui/window.cpp b/ui/window.cpp
index dd488d7..06857b2 100644
--- a/ui/window.cpp
+++ b/ui/window.cpp
@@ -1,28 +1,16 @@
#include "window.h"
-#include "uiComponent.h"
-#include <format>
#include <glad/gl.h>
#include <glm/glm.hpp>
-#include <stdexcept>
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wold-style-cast"
+#include "backends/imgui_impl_opengl3.h"
+#include "backends/imgui_impl_sdl2.h"
+#pragma GCC diagnostic pop
-Window::GLInitHelper::GLInitHelper()
-{
- [[maybe_unused]] static auto init = []() {
- // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
- if (const auto version = gladLoadGL(reinterpret_cast<GLADloadfunc>(SDL_GL_GetProcAddress)); version < 30003) {
- throw std::runtime_error {std::format("Insufficient OpenGL version: {}", version)};
- }
- else {
- return version;
- }
- }();
-}
-
-Window::Window(size_t width, size_t height, const std::string & title, Uint32 flags) :
- size {static_cast<int>(width), static_cast<int>(height)},
- m_window {title.c_str(), static_cast<int>(SDL_WINDOWPOS_CENTERED), static_cast<int>(SDL_WINDOWPOS_CENTERED), size.x,
- size.y, flags},
- glContext {m_window}, uiShader {width, height}
+Window::Window(ScreenAbsCoord size, const char * title, Uint32 flags) :
+ m_window {title, static_cast<int>(SDL_WINDOWPOS_CENTERED), static_cast<int>(SDL_WINDOWPOS_CENTERED), size.x, size.y,
+ flags},
+ glContext {m_window}
{
}
@@ -39,23 +27,21 @@ Window::swapBuffers() const
SDL_GL_SwapWindow(m_window);
}
+void
+Window::tick(TickDuration elapsed)
+{
+ if (content) {
+ content->tick(elapsed);
+ }
+}
+
bool
Window::handleInput(const SDL_Event & e)
{
if (SDL_GetWindowID(m_window) == e.window.windowID) {
- SDL_Event eAdjusted {e};
- switch (e.type) {
- // SDL and OpenGL have coordinates that are vertically opposed.
- case SDL_MOUSEBUTTONDOWN:
- case SDL_MOUSEBUTTONUP:
- eAdjusted.button.y = size.y - e.button.y;
- break;
- case SDL_MOUSEMOTION:
- eAdjusted.motion.y = size.y - e.motion.y;
- break;
+ if (content) {
+ return content->handleInput(e);
}
- uiComponents.rapplyOne(&UIComponent::handleInput, eAdjusted, UIComponent::Position {{}, size});
- return true;
}
return false;
}
@@ -66,16 +52,15 @@ Window::refresh() const
SDL_GL_MakeCurrent(m_window, glContext);
clear(0.0F, 0.0F, 0.0F, 1.0F);
- render();
+ ImGui_ImplOpenGL3_NewFrame();
+ ImGui_ImplSDL2_NewFrame();
+ ImGui::NewFrame();
+ if (content) {
+ content->render();
+ // Render UI stuff here
+ }
+ ImGui::Render();
+ ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
swapBuffers();
}
-
-void
-Window::render() const
-{
- glEnable(GL_BLEND);
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- glDisable(GL_DEPTH_TEST);
- uiComponents.apply(&UIComponent::render, uiShader, UIComponent::Position {});
-}
diff --git a/ui/window.h b/ui/window.h
index 8f2b70b..99a977f 100644
--- a/ui/window.h
+++ b/ui/window.h
@@ -1,13 +1,12 @@
#pragma once
#include "chronology.h"
-#include "collection.h"
-#include "gfx/gl/uiShader.h"
+#include "config/types.h"
#include "ptr.h"
-#include "uiComponent.h" // IWYU pragma: keep
+#include "special_members.h"
+#include "windowContent.h"
#include <SDL2/SDL.h>
#include <cstddef>
-#include <special_members.h>
#include <string>
using SDL_WindowPtr = wrapped_ptrt<SDL_Window, SDL_CreateWindow, SDL_DestroyWindow>;
@@ -16,30 +15,31 @@ using SDL_GLContextPtr = wrapped_ptrt<GL_Context, SDL_GL_CreateContext, SDL_GL_D
class Window {
public:
- Window(size_t width, size_t height, const std::string & title, Uint32 flags);
+ Window(ScreenAbsCoord size, const char * title, Uint32 flags);
virtual ~Window() = default;
NO_COPY(Window);
NO_MOVE(Window);
- virtual void tick(TickDuration elapsed) = 0;
+ template<typename C, typename... P>
+ void
+ setContent(P &&... p)
+ {
+ glm::ivec2 size {};
+ SDL_GetWindowSizeInPixels(m_window, &size.x, &size.y);
+ content = std::make_unique<C>(ScreenAbsCoord {size.x, size.y}, std::forward<P>(p)...);
+ }
+
+ void tick(TickDuration elapsed);
void refresh() const;
bool handleInput(const SDL_Event & e);
- void clear(float r, float g, float b, float a) const;
void swapBuffers() const;
protected:
- virtual void render() const;
-
- struct GLInitHelper {
- GLInitHelper();
- };
+ void clear(float r, float g, float b, float a) const;
- const ScreenAbsCoord size;
SDL_WindowPtr m_window;
SDL_GLContextPtr glContext;
- GLInitHelper glInithelper;
- Collection<UIComponent> uiComponents;
- UIShader uiShader;
+ WindowContent::Ptr content;
};
diff --git a/ui/windowContent.cpp b/ui/windowContent.cpp
new file mode 100644
index 0000000..0f6dc04
--- /dev/null
+++ b/ui/windowContent.cpp
@@ -0,0 +1,30 @@
+#include "windowContent.h"
+#include "SDL_events.h"
+
+void
+WindowContent::tick(TickDuration)
+{
+}
+
+bool
+WindowContent::handleInput(const SDL_Event & e)
+{
+ SDL_Event eAdjusted {e};
+ const auto size = [&e] {
+ glm::ivec2 size {};
+ SDL_GetWindowSizeInPixels(SDL_GetWindowFromID(e.window.windowID), &size.x, &size.y);
+ return size;
+ }();
+ switch (e.type) {
+ // SDL and OpenGL have coordinates that are vertically opposed.
+ case SDL_MOUSEBUTTONDOWN:
+ case SDL_MOUSEBUTTONUP:
+ eAdjusted.button.y = size.y - e.button.y;
+ break;
+ case SDL_MOUSEMOTION:
+ eAdjusted.motion.y = size.y - e.motion.y;
+ break;
+ }
+ uiComponents.rapplyOne(&UIComponent::handleInput, eAdjusted);
+ return true;
+}
diff --git a/ui/windowContent.h b/ui/windowContent.h
new file mode 100644
index 0000000..34cbea3
--- /dev/null
+++ b/ui/windowContent.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include "chronology.h"
+#include "collection.h"
+#include "special_members.h"
+#include "stdTypeDefs.h"
+#include "uiComponent.h" // IWYU pragma: keep
+
+class WindowContent : public StdTypeDefs<WindowContent> {
+public:
+ WindowContent() = default;
+ virtual ~WindowContent() = default;
+ NO_MOVE(WindowContent);
+ NO_COPY(WindowContent);
+
+ virtual void tick(TickDuration);
+ virtual void render() = 0;
+ virtual bool handleInput(const SDL_Event & e);
+
+protected:
+ UniqueCollection<UIComponent> uiComponents;
+};
diff --git a/ui/worldOverlay.h b/ui/worldOverlay.h
index 18fab3f..a0f3b65 100644
--- a/ui/worldOverlay.h
+++ b/ui/worldOverlay.h
@@ -1,9 +1,10 @@
#pragma once
class SceneShader;
+class Frustum;
class WorldOverlay {
public:
virtual ~WorldOverlay() = default;
- virtual void render(const SceneShader &) const = 0;
+ virtual void render(const SceneShader &, const Frustum &) const = 0;
};