diff --git a/examples/HelloAnimation/main.cpp b/examples/HelloAnimation/main.cpp new file mode 100644 index 0000000..d9624d0 --- /dev/null +++ b/examples/HelloAnimation/main.cpp @@ -0,0 +1,45 @@ +import Crafter.Event; +import Crafter.Graphics; +import std; +using namespace Crafter; + +int main() { + WindowWayland window(1280, 720, "Hello Input!"); + + UiElementBufferBuffer* element = new UiElementBufferBuffer( + 2, //bufferWidth: the width of this elements pixel buffer + 1, //bufferHeight: the height of this elements pixel buffer + FractionalToMapped(0.5), //anchorX: relative position where this elements x anchor (top-left) is placed to its parent x anchor + FractionalToMapped(0.5), //anchorY: relative position where this elements y anchor (top-left) is placed to its parent y anchor + FractionalToMapped(0.5), //relativeSizeX: the relative x size this element should be scaled to compared to its parent + FractionalToMapped(0.5), //relativeSizeY: the relative y size this element should be scaled to compared to its parent + FractionalToMapped(0.5), //anchorOffsetX: the amount this element's anchor should be offset from the top left corner (0.5 to in the middle) + FractionalToMapped(0.5), //anchorOffsetY: the amount this element's anchor should be offset from the top left corner (0.5 to place it in the middle) + 0, //z: this elements Z position + false //ignoreScaling: wether this element ignores the scaling of the window, if true its size will be scaled according to the window scale + ); + + window.elements.push_back(element); + + element->UpdatePosition(window); + + Animation> anim({ + {std::chrono::seconds(0), FractionalToMapped(-0.5)}, + {std::chrono::seconds(5), FractionalToMapped(1.5)} + }); + + anim.Start(std::chrono::high_resolution_clock::now()); + + EventListener updateListener(&window.onUpdate, [&](FrameTime time){ + std::tuple value = anim.Play(time.now); + element->transform.anchorX = std::get<0>(value); + element->UpdatePosition(window); + if(anim.currentFrame == anim.keyframes.size()-1) { + anim.Start(time.now); + } + }); + + element->buffer = {{255, 0, 0 ,255}, {0, 255, 0 ,255}}; + window.StartUpdate(); + window.StartSync(); +} diff --git a/examples/HelloAnimation/project.json b/examples/HelloAnimation/project.json new file mode 100644 index 0000000..70e3d7a --- /dev/null +++ b/examples/HelloAnimation/project.json @@ -0,0 +1,15 @@ +{ + "name": "crafter-graphics", + "configurations": [ + { + "name": "executable", + "implementations": ["main"], + "dependencies": [ + { + "path":"../../project.json", + "configuration":"lib-wayland" + } + ] + } + ] +} diff --git a/implementations/Crafter.Graphics-Window_wayland.cpp b/implementations/Crafter.Graphics-Window_wayland.cpp index 622c8ec..8b4f42b 100644 --- a/implementations/Crafter.Graphics-Window_wayland.cpp +++ b/implementations/Crafter.Graphics-Window_wayland.cpp @@ -163,8 +163,8 @@ void RenderElement(UiElementBufferBuffer* element, WindowWayland* window) { std::vector scaled(element->transform.scaled.width*element->transform.scaled.height); element->CopyNearestNeighbour(scaled.data(), element->transform.scaled.width, element->transform.scaled.height); - for (std::int32_t x = element->transform.scaled.x; x - element->transform.scaled.x < element->transform.scaled.width; x++) { - for (std::int32_t y = element->transform.scaled.y; y - element->transform.scaled.y < element->transform.scaled.height; y++) { + for (std::int_fast32_t x = element->transform.scaled.x; x - element->transform.scaled.x < element->transform.scaled.width; x++) { + for (std::int_fast32_t y = element->transform.scaled.y; y - element->transform.scaled.y < element->transform.scaled.height; y++) { if (x >= 0 && x < window->width && y >= 0 && y < window->height) { Pixel_BU8_GU8_RU8_AU8& dst = window->framebuffer[y * window->width + x]; const Pixel_BU8_GU8_RU8_AU8& src = scaled[(y - element->transform.scaled.y) * element->transform.scaled.width + (x - element->transform.scaled.x)]; @@ -191,6 +191,12 @@ void RenderElement(UiElementBufferBuffer* element, WindowWayland* window) { void WindowWayland::Render() { std::sort(elements.begin(), elements.end(), [](UiElementBufferBuffer* a, UiElementBufferBuffer* b){ return a->transform.z < b->transform.z; }); + for (std::int_fast32_t x = 0; x < width; x++) { + for (std::int_fast32_t y = 0; y - height; y++) { + framebuffer[y * width + x] = {0,0,0,0}; + } + } + for(UiElementBufferBuffer* element : elements) { RenderElement(element, this); } @@ -251,12 +257,17 @@ void WindowWayland::wl_surface_frame_done(void* data, struct wl_callback *cb, ui wl_callback_destroy(cb); WindowWayland* window = reinterpret_cast(data); + auto start = std::chrono::high_resolution_clock::now(); + if(window->updating) { cb = wl_surface_frame(window->surface); wl_callback_add_listener(cb, &WindowWayland::wl_callback_listener, window); + window->onUpdate.Invoke({start, start-window->lastFrameEnd}); } window->Render(); + + window->lastFrameEnd = start; } void WindowWayland::pointer_handle_button(void* data, wl_pointer* pointer, std::uint32_t serial, std::uint32_t time, std::uint32_t button, std::uint32_t state) { diff --git a/interfaces/Crafter.Graphics-Animation.cppm b/interfaces/Crafter.Graphics-Animation.cppm new file mode 100644 index 0000000..bccd2ab --- /dev/null +++ b/interfaces/Crafter.Graphics-Animation.cppm @@ -0,0 +1,80 @@ +/* +Crafter®.Graphics +Copyright (C) 2025 Catcrafts® +Catcrafts.net + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 3.0 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +export module Crafter.Graphics:Animation; +import std; + +namespace Crafter { + template + constexpr T Lerp(T a, T b, double elapsed) { + return a + static_cast(elapsed * (b - a)); + } + + template + constexpr auto LerpTupleImpl(const Tuple& a, const Tuple& b, double elapsed, std::index_sequence) { + return std::make_tuple(Lerp(std::get(a), std::get(b), elapsed)...); + } + + template + constexpr auto LerpTuple(const Tuple& a, const Tuple& b, double elapsed) { + return LerpTupleImpl(a, b, elapsed, std::make_index_sequence>{}); + } + + export template + struct Keyframe{ + std::chrono::duration duration; + T values; + }; + + export template + struct Animation { + std::vector> keyframes; + std::uint_fast32_t currentFrame; + std::chrono::time_point startedAt; + Animation(std::vector>&& keyframes) : keyframes(std::move(keyframes)) {} + void Start(std::chrono::time_point time) { + currentFrame = 0; + startedAt = time; + } + T Play(std::chrono::time_point time) { + std::chrono::duration elapsed = time - startedAt; // elapsed time since animation started + std::chrono::duration accumulated(0); + + for (std::uint_fast32_t i = currentFrame; i < keyframes.size() - 1; ++i) { + accumulated += keyframes[i+1].duration; + if (elapsed < accumulated) { + std::chrono::duration frameStartTime = accumulated - keyframes[i+1].duration; + auto t = (elapsed - frameStartTime) / keyframes[i+1].duration.count(); + + currentFrame = i; + return LerpTuple( + keyframes[i].values, + keyframes[i + 1].values, + t.count() + ); + } + } + + // If we get here, we're past the last keyframe + currentFrame = keyframes.size() - 1; + return keyframes.back().values; + } + }; +} \ No newline at end of file diff --git a/interfaces/Crafter.Graphics-Types.cppm b/interfaces/Crafter.Graphics-Types.cppm index 6c1dc24..833ba89 100644 --- a/interfaces/Crafter.Graphics-Types.cppm +++ b/interfaces/Crafter.Graphics-Types.cppm @@ -95,6 +95,10 @@ namespace Crafter { float a; }; + export struct FrameTime { + std::chrono::time_point now; + std::chrono::duration delta; + }; export constexpr std::int_fast32_t BOUND = 9; diff --git a/interfaces/Crafter.Graphics-Window.cppm b/interfaces/Crafter.Graphics-Window.cppm index e7f03c5..89097ca 100644 --- a/interfaces/Crafter.Graphics-Window.cppm +++ b/interfaces/Crafter.Graphics-Window.cppm @@ -47,8 +47,9 @@ export namespace Crafter { class UiElement; class Window { public: + std::chrono::time_point lastFrameEnd; Event onClose; - Event> onUpdate; + Event onUpdate; bool open = true; bool updating = false; Window() = default; diff --git a/interfaces/Crafter.Graphics.cppm b/interfaces/Crafter.Graphics.cppm index 9664d80..9a0e40f 100644 --- a/interfaces/Crafter.Graphics.cppm +++ b/interfaces/Crafter.Graphics.cppm @@ -25,6 +25,7 @@ export import :UiElement; export import :Types; export import :Font; export import :Shm; +export import :Animation; // export import :WindowWaylandVulkan; // export import :VulkanBuffer; diff --git a/project.json b/project.json index 5a23a3e..19a6e4f 100644 --- a/project.json +++ b/project.json @@ -4,7 +4,7 @@ { "name": "base", "implementations": ["implementations/Crafter.Graphics-Font", "implementations/Crafter.Graphics-Shm", "implementations/Crafter.Graphics-UiElement", "implementations/Crafter.Graphics-UiElementBufferBuffer", "implementations/Crafter.Graphics-UiElementBufferBufferBase", "implementations/Crafter.Graphics-UiElementBufferMouseBuffer"], - "interfaces": ["interfaces/Crafter.Graphics-Window", "interfaces/Crafter.Graphics", "interfaces/Crafter.Graphics-Types", "interfaces/Crafter.Graphics-Font", "interfaces/Crafter.Graphics-Shm", "interfaces/Crafter.Graphics-UiElement"], + "interfaces": ["interfaces/Crafter.Graphics-Window", "interfaces/Crafter.Graphics", "interfaces/Crafter.Graphics-Types", "interfaces/Crafter.Graphics-Font", "interfaces/Crafter.Graphics-Shm", "interfaces/Crafter.Graphics-UiElement", "interfaces/Crafter.Graphics-Animation"], "type": "library" }, {