diff --git a/examples/BasicWidgets/CMakeLists.txt b/examples/BasicWidgets/CMakeLists.txt new file mode 100644 index 0000000..7801fc5 --- /dev/null +++ b/examples/BasicWidgets/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.18) +project(BasicWidgets) + +set(CMAKE_CXX_STANDARD 20) + +add_executable(BasicWidgets + src/main.cpp + src/MainContent.cpp + src/MainContent.h + src/ui/MainScene.cpp + src/ui/MainScene.h + src/ui/MainLayer.cpp + src/ui/MainLayer.h) + +target_link_libraries(BasicWidgets PRIVATE algine) + +if (WIN32) + algine_target_mklink( + TARGET BasicWidgets + ALGINE_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/../algine" + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/../bin") +endif() diff --git a/examples/BasicWidgets/README.md b/examples/BasicWidgets/README.md new file mode 100644 index 0000000..69f3b52 --- /dev/null +++ b/examples/BasicWidgets/README.md @@ -0,0 +1,110 @@ +# BasicWidgets + +Supported systems: Linux, Windows + +This very basic example shows how to use basic widgets using XML. + +> [!NOTE] +> Working directory must be set to the `resources` directory. + +This example uses +[Noto Sans Black](https://fonts.google.com/noto/specimen/Noto+Sans) font. + +## Introduction + +Algine is a modular, easy-to-use 3D rendering engine. Like any other +framework, Algine has its own features. + +### Objects + +All classes are divided into two types: objects and ordinary classes. +Objects are special classes, which inherit the `algine::Object` class. +Such classes have some important features: they can have many children, +but only one parent. All objects by default have `algine::GlobalScene` +as the parent and must be created on the heap, using the `new` operator. + +#### Lifecycle + +As mentioned before, objects must be created using operator `new`. + +> [!IMPORTANT] +> Allocating on the heap is crucial, since it will allow to correctly +> destroy all children while destroying the parent. Otherwise, you will +> get a segmentation fault. + +If you want to manually delete an object, you can use operator `delete`. +In that case, all its children will be also deleted, moreover, the object +will be automatically removed from the children list of its parent. + +> [!WARNING] +> If you want to use objects like regular classes, you can just pass +> `nullptr` as the parent. However, it is highly unrecommended and can be +> really dangerous. Do it on your own risk. + +### Display hierarchy + +Okay, now you already know what is an object. But how we can render things? + +#### `Window` + +As the name suggests, `Window` is a Window. It is the base class for +platform-depended windows like e.g. `GLFWWindow` or `QtWindow`. This class +receives events, executes tasks, is responsible for the render loop. But +how exactly this class renders your content? + +#### `Content` + +`Content` is an entry point for rendering. It has an abstract function +`render` which needs to be overridden. It also has virtual functions like +`onShow` and `onHide`. `Content` has width and height, received from the +window. But how we can receive more events? + +##### `UnifiedEventHandler` + +`UnifiedEventHandler` is a cross-platform event handler. Typically, you +want your custom content to inherit this class, and then, somewhere in +`YourContent::onShow`, register a new event handler by using + +```cpp +getWindow()->setEventHandler(this); +``` + +Interesting, but `Content` will not render my scene, will it? + +#### `Scene` + +As the name suggests, this is scene. Scene is an object, which has some +useful functions like `show`, `hide`, `render`, `addChild` etc. `Scene` +represents your scene. Feel free to create as many class members as you +wish. By default, a scene is hidden, so you should call `show` before +`render`, if you want to see something, of course. But can I render my +whole scene just here? Well, yes, but no. In order to render something, +you need to use `Renderer`. + +#### `Renderer` and `RenderPass` + +Finally! `Renderer` performs `RenderPass` rendering. `RenderPass` has +virtual function `render`, which needs to be overridden. So yes, now you +can place all your rendering code in `YourRenderPass::render` and add it +to your renderer. Do not forget to call `Renderer::render` in `YourSceene`! + +## Summary + +So, generally speaking, normally we want to have the following structure: + +- `Window` + - `Content` + - `UnifiedEventHandler` + - `Scene` + - `Renderer` + - `RenderPass` + +Of course, in the current engine architecture, some stages are not obligatory, +but who knows, what will be changed in the future? + +## Conclusion + +After reading this loooong document, you may have some basic knowledge about +some parts of the engine. Congratulations! Now, you can enjoy the code :) + +[//]: # (TODO: move this documentation to the HelloCube example) diff --git a/examples/BasicWidgets/resources/BasicWidgets.xml b/examples/BasicWidgets/resources/BasicWidgets.xml new file mode 100644 index 0000000..6ce1c01 --- /dev/null +++ b/examples/BasicWidgets/resources/BasicWidgets.xml @@ -0,0 +1,27 @@ + + + + + + + \ No newline at end of file diff --git a/examples/BasicWidgets/resources/NotoSans-Black.ttf b/examples/BasicWidgets/resources/NotoSans-Black.ttf new file mode 100644 index 0000000..05771e5 Binary files /dev/null and b/examples/BasicWidgets/resources/NotoSans-Black.ttf differ diff --git a/examples/BasicWidgets/src/MainContent.cpp b/examples/BasicWidgets/src/MainContent.cpp new file mode 100644 index 0000000..d386ea4 --- /dev/null +++ b/examples/BasicWidgets/src/MainContent.cpp @@ -0,0 +1,24 @@ +#include "MainContent.h" +#include "ui/MainScene.h" + +#include +#include + +MainContent::MainContent() + : m_scene(new UI::MainScene(this)) +{ + m_scene->listen(this); +} + +void MainContent::onShow() { + getWindow()->setEventHandler(this); + m_scene->setSize(width(), height()); + m_scene->show(); +} + +void MainContent::render() { + Framebuffer::getDefault()->bind(); + Framebuffer::getDefault()->clear(Framebuffer::ColorBuffer | Framebuffer::DepthBuffer); + Engine::setViewport(width(), height()); + m_scene->render(); +} diff --git a/examples/BasicWidgets/src/MainContent.h b/examples/BasicWidgets/src/MainContent.h new file mode 100644 index 0000000..e75ab0f --- /dev/null +++ b/examples/BasicWidgets/src/MainContent.h @@ -0,0 +1,24 @@ +#ifndef ALGINE_EXAMPLES_MAINCONTENT_H +#define ALGINE_EXAMPLES_MAINCONTENT_H + +#include +#include + +using namespace algine; + +namespace UI { +class MainScene; +} + +class MainContent: public Content, public UnifiedEventHandler { +public: + MainContent(); + + void onShow() override; + void render() override; + +private: + UI::MainScene *m_scene; +}; + +#endif//ALGINE_EXAMPLES_MAINCONTENT_H diff --git a/examples/BasicWidgets/src/main.cpp b/examples/BasicWidgets/src/main.cpp new file mode 100644 index 0000000..734a4fc --- /dev/null +++ b/examples/BasicWidgets/src/main.cpp @@ -0,0 +1,41 @@ +#include + +#ifdef ALGINE_QT_PLATFORM + #include +#else + #include +#endif + +#include "MainContent.h" + +using namespace algine; + +static void exec() { + Engine::setDPI(130); + +#ifdef ALGINE_QT_PLATFORM + auto window = new QtWindow(); + window->setDimensions(512, 512); + + window->addOnInitializedListener([window]() { + window->setContent(new MainContent()); + window->renderLoop(1000); + }); + + window->show(); +#else + GLFWWindow window("BasicWidgets", 1366, 768); + window.setFullscreenDimensions(1366, 768); + window.setMouseTracking(true); + window.setKeyboardTracking(true); + window.setWindowStateTracking(true); + //window.setCursorMode(Window::CursorMode::Disabled); + window.setContentLater(); + window.renderLoop(); +#endif +} + +int main(int argc, char **argv) { + Engine::exec(argc, argv, &exec); + return 0; +} diff --git a/examples/BasicWidgets/src/ui/MainLayer.cpp b/examples/BasicWidgets/src/ui/MainLayer.cpp new file mode 100644 index 0000000..02a1784 --- /dev/null +++ b/examples/BasicWidgets/src/ui/MainLayer.cpp @@ -0,0 +1,45 @@ +#include "MainLayer.h" + +#include +#include +#include + +#include + +namespace dmg = tulz::demangler; + +namespace algine { +template +Logger&& operator<<(L &&logger, const Point &p) { + auto tName = typeid(T).name(); + logger << "Point<" << dmg::demangle(tName) << "> {" << p.getX() << ", " << p.getY() << "}"; + return std::move(logger); +} +} + +namespace UI { +MainLayer::MainLayer(Widgets::Scene *parent) + : Widgets::Layer(parent) +{ + auto container = Widget::constructFromXMLFile("BasicWidgets.xml", this); + setContainer(container); + + container->findChild("label0")->setEventListener(Event::Click, [](Widget *widget, const Event &event) { + auto v2pi = [](const glm::vec<2, T> &v) -> PointI { + return {(int) v.x, (int) v.y}; + }; + + auto &info = event.getPointerInfo(); + + Log::verbose("MainLayer") << "label0 has detected click:\n" + << "label0 local coords: " << info.getPos() << "\n" + << "parent local coords: " << widget->mapFromParent(v2pi(info.getPos())); + }); + + container->findChild("container1")->setEventListener(Event::Click, [](Widget*, const Event &event) { + auto &info = event.getPointerInfo(); + Log::verbose("MainLayer") << "container1 has detected click:\n" + << "container1 local coords: " << info.getPos(); + }); +} +}// namespace ui \ No newline at end of file diff --git a/examples/BasicWidgets/src/ui/MainLayer.h b/examples/BasicWidgets/src/ui/MainLayer.h new file mode 100644 index 0000000..d079e9f --- /dev/null +++ b/examples/BasicWidgets/src/ui/MainLayer.h @@ -0,0 +1,15 @@ +#ifndef ALGINE_EXAMPLES_MAINLAYER_H +#define ALGINE_EXAMPLES_MAINLAYER_H + +#include + +using namespace algine; + +namespace UI { +class MainLayer: public Widgets::Layer { +public: + explicit MainLayer(Widgets::Scene *parent); +}; +}// namespace UI + +#endif//ALGINE_EXAMPLES_MAINLAYER_H diff --git a/examples/BasicWidgets/src/ui/MainScene.cpp b/examples/BasicWidgets/src/ui/MainScene.cpp new file mode 100644 index 0000000..e94c2d5 --- /dev/null +++ b/examples/BasicWidgets/src/ui/MainScene.cpp @@ -0,0 +1,11 @@ +#include "MainScene.h" +#include "MainLayer.h" + +namespace UI { +MainScene::MainScene(Object *parent) + : Widgets::Scene(parent) +{ + auto layer = new MainLayer(this); + layer->show(); +} +} diff --git a/examples/BasicWidgets/src/ui/MainScene.h b/examples/BasicWidgets/src/ui/MainScene.h new file mode 100644 index 0000000..2320b3a --- /dev/null +++ b/examples/BasicWidgets/src/ui/MainScene.h @@ -0,0 +1,15 @@ +#ifndef ALGINE_EXAMPLES_MAINSCENE_H +#define ALGINE_EXAMPLES_MAINSCENE_H + +#include + +using namespace algine; + +namespace UI { +class MainScene : public Widgets::Scene { +public: + explicit MainScene(Object *parent); +}; +} + +#endif//ALGINE_EXAMPLES_MAINSCENE_H diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..e8a341d --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.18) +project(algine_examples) + +set(CMAKE_CXX_STANDARD 20) + +# silence warnings from sol2 +add_compile_options(-Wno-unknown-warning-option) + +include(../cmake/mold.cmake) +try_use_mold() + +macro(set_global name value) + set(${name} ${value} CACHE INTERNAL ${name}) +endmacro() + +set_global(ASSIMP_BUILD_ALL_IMPORTERS_BY_DEFAULT OFF) +set_global(ASSIMP_BUILD_OBJ_IMPORTER ON) +set_global(ASSIMP_BUILD_ZLIB OFF) +set_global(ASSIMP_BUILD_ASSIMP_TOOLS OFF) +set_global(ASSIMP_BUILD_TESTS OFF) + +add_subdirectory(../ algine) + +add_subdirectory(BasicWidgets) \ No newline at end of file diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..6e4dda5 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,20 @@ +# Examples + +> [!NOTE] +> In order to load examples, open `CMakeLists.txt` in the current directory. +> Do not open examples directly. + +## List + +1. [Basic widgets](BasicWidgets) + +## TODO + +- [ ] Hello Cube +- [x] Basic widgets +- [ ] UI Overlay: complete example (with all abstractions included) +- [ ] Basic Lua scripting +- [ ] Cross-platform example: Linux, Android, Windows + +These examples should be like mini-lessons, with all necessary documentation +included.