diff --git a/ChangeLog.md b/ChangeLog.md index 100ee3c89..587e763bd 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -55,7 +55,7 @@ Nazara Engine: - Fix RigidBody3D copy constructor not copying all physics states (angular/linear damping/velocity, mass center, position and rotation) - Add RigidBody3D simulation control (via EnableSimulation and IsSimulationEnabled), which allows to disable physics and collisions at will. - Fix some uninitialized values (found by Valgrind) in Network module -- Fix possible infinite recursion when outputting a Thread::Id object +- Fix possible infinite recursion when outputting a Thread::Id object - ⚠️ Replaced implicit conversion from a Nz::String to a std::string by an explicit method ToStdString() - Fix LuaInstance movement constructor/assignment operator which was corrupting Lua memory - Fix potential bug on SocketImpl::Connect (used by TcpClient::Connect) on POSIX platforms @@ -112,7 +112,7 @@ Nazara Engine: - Fixed SocketPoller not be able to recover from some errors (like invalid sockets and such) - Add LuaImplQuery implementation for std::vector - Fixed LuaState::PushGlobal & LuaState::PushField to copy the object before moving it -- ⚠️ Replaced currentBitPos and currentByte fields by [read|write][BitPos][Byte] to handle properly bit reading/writing. +- ⚠️ Replaced currentBitPos and currentByte fields by [read|write][BitPos][Byte] to handle properly bit reading/writing. - InstancedRenderable::SetMaterial methods are now public. - Fixed Model copy constructor not copying materials - ⚠️ Added InstancedRenderable::Clone() method @@ -148,8 +148,8 @@ Nazara Engine: - ⚠️ CullingList now handles full and partial visibility testing - Added math class Angle, capable of handling both degrees and radians angles and converting them to euler angles/quaternions to improve 2D interface. - ⚠️ AbstractSocket::OnStateChange has been replaced by OnStateChanged, which is now called after state has been changed (with oldState and newState as parameters). -- ⚠️ TcpClient::WaitForconnected now returns the new socket state. -- Added TcpClient::PollForConnected +- ⚠️ TcpClient::WaitForconnected now returns the new socket state. +- Added TcpClient::PollForConnected - ⚠️ Use of the new Angle class instead of floating point angle - It is now possible to set elasticity/friction/surface bodies of 2D colliders and change it at runtime on RigidBody2D - ObjectHandle were remade and should be way more optimized now @@ -188,7 +188,9 @@ Nazara Engine: - Fixed TextSprite not handling multiple textures well - ⚠ TextSprite will now use multiple render layers by itself (the current one and the one right before, ex: [-1, 0] if base layer is 0) if you use text outlines. - ⚠ SimpleTextDrawer no longer supports faux bold rendering -- Added PhysWorld2D::[RaycastQuery, RegionQuery] overloads taking a callback +- Added PhysWorld2D::[RaycastQuery, RegionQuery] overloads taking a callback +- Added x and y mouse position to MouseWheelEvent +- Added SimpleTextDrawer::[Get|Set]MaxLineWidth (which does line wrap) Nazara Development Kit: - Added ImageWidget (#139) @@ -265,6 +267,21 @@ Nazara Development Kit: - Added LifetimeComponent and LifetimeSystem - Fixed a subtle bug regarding entities invalidation and kill (ex: if an entity #2 kills entity #1 during Entity::Destroy callbacks, entity #1 will survive destruction). - Added PhysicsSystem2D::[RaycastQuery, RegionQuery] overloads taking a callback +- Added TextAreaWidget support for outline +- Fixed possible crash when disabling BaseWidget background +- Added BaseWidget::OnMouseWheelMoved +- Added Entity::OnEntity[Disabled|Enabled] signals +- Added BaseWidget::SetParent +- BaseWidget::Show will no longer show entities disabled by the widget +- BaseWidget now has a rendering rect property (allowing to tell a widget what part of it will be rendered) +- Added ScrollAreaWidget +- Console has been remade with widgets (allowing to scroll back history, select text, etc.) +- Added TextAreaWidget line wrap option +- TextAreaWidget will now shift the text to the left/right in order to keep the cursor visible +- Added TextAreaWidget::[Get|Set]TextFont +- ⚠️ TextAreaWidget::OnTextAreaCursorMove signal now uses a Vector2ui* position as its second argument (instead of a std::size_t*) +- Added TextAreaWidget::OnTextAreaSelection +- ⚠️ Console class is no longer bound to a LuaState and now has a OnCommand signal # 0.4: @@ -352,8 +369,8 @@ Nazara Engine: - Added [Nz::TcpClient::SendMultiple](https://nazara.digitalpulsesoftware.net/doc/class_nz_1_1_tcp_client.html#a495c32beb46ed9192699a3b82d358035) method, allowing to send multiple buffers at once. - Added [Nz::PlacementDestroy](https://nazara.digitalpulsesoftware.net/doc/namespace_nz.html#a27c8667def991fc896c5beff3e62668a). (ea985fa76586762f008e4054938db3234eeaf0cb) - Added [Nz::String::Format](https://nazara.digitalpulsesoftware.net/doc/class_nz_1_1_string.html#a4b699982e7f9ea38f6d44b43ac1e2040) and [Nz::String::FormatVA](https://nazara.digitalpulsesoftware.net/doc/class_nz_1_1_string.html#abe0fcbce11224b157ac756b60e8dee92) static methods. (cc6e4127dc6c61799a64404770992cef0804ad34). -- Added [Nz::ParticleGroup::GetBuffer](https://nazara.digitalpulsesoftware.net/doc/class_nz_1_1_particle_mapper.html#aefe1b251efc8c9b8668842275561be0c) method. (4dc85789b59e50d964c83321dbd4b6485c04bef6) -- Added Nz::ParticleMapper::GetPointer method. (1f4e6c2d1594b7bb9dd6f4ea5480fdd16cf5f208) +- Added [Nz::ParticleGroup::GetBuffer](https://nazara.digitalpulsesoftware.net/doc/class_nz_1_1_particle_mapper.html#aefe1b251efc8c9b8668842275561be0c) method. (4dc85789b59e50d964c83321dbd4b6485c04bef6) +- Added Nz::ParticleMapper::GetPointer method. (1f4e6c2d1594b7bb9dd6f4ea5480fdd16cf5f208) - ⚠️ Structures provied by ParticleStruct header now have a float life. (472d964d587d906764ad1e05bfcc9ab1bf979483) - Fixed scale property of Nz::TextSprite not affecting its bounding volume. (52b29bac775823294c4ad7de70f4dc3f4adfa743) - ⚠️ Nz:MeshParams::flipUVs has been replaced by texCoordOffset and texCoordScale. (a1a7d908adc060fd7a43491c903dfe3b501d98e5) @@ -380,7 +397,7 @@ Nazara Engine: - All noises classes now uses std::mt19937 as a random number generator, to ensure the same results on every machine. (1f5ea9839016964c173d919263827dee69ecb65d) Nazara Development Kit: -- **Added basic widgets**. (c8a12083b3133e946bf60dd060331a4b4631f8d8) +- **Added basic widgets**. (c8a12083b3133e946bf60dd060331a4b4631f8d8) - VelocitySystem will no longer affect entities with PhysicsComponent2D. (a6853234412c744cdcb28344f02f7b0c92704d77) - Fixed EulerAngles constructor in Lua. (d55149a0a70f6230b6f1c3fb50e37dc82a2feb9f) - Fixed Component::OnDetached not being called on entity destruction. (5b777eb4853639d7aeb232ca46d17f0d432f47ca) @@ -391,7 +408,7 @@ Nazara Engine: Nazara Engine: - Nazara binaries are now compiled with Run-Time Type-Information. (a70acdc8f44010627a65282fd3099202116d3e13) -- Nazara demos are now compiled with relative dependencies on Linux. +- Nazara demos are now compiled with relative dependencies on Linux. (d6fbb4c408d48c4a768fad7b43460c76a0df1777) - Added [**Nz::BitCount**](https://nazara.digitalpulsesoftware.net/doc/group__core.html#ga6bfbcff78eb6cfbe3ddaedcfc8c04196) function. (82e31a3ec8449da6618f41690164c2e1d883edb4) - Added [**Nz::Bitset::AppendBits**](https://nazara.digitalpulsesoftware.net/doc/class_nz_1_1_bitset.html#a5ca8f365006c86d6d699d02471904f7e) method. (b018a400499a2356c4455a40d9f6a6c12b3cb36b) diff --git a/SDK/include/NDK/Application.hpp b/SDK/include/NDK/Application.hpp index 401f80228..69e97bdce 100644 --- a/SDK/include/NDK/Application.hpp +++ b/SDK/include/NDK/Application.hpp @@ -15,6 +15,7 @@ #include #ifndef NDK_SERVER +#include #include #include #include @@ -81,7 +82,7 @@ namespace Ndk #ifndef NDK_SERVER struct ConsoleOverlay { - std::unique_ptr console; + Console* console; Nz::LuaInstance lua; NazaraSlot(Nz::EventHandler, OnEvent, eventSlot); @@ -114,10 +115,11 @@ namespace Ndk Nz::RenderTarget* renderTarget; std::unique_ptr window; std::unique_ptr console; + std::unique_ptr canvas; std::unique_ptr fpsCounter; std::unique_ptr overlayWorld; }; - + void SetupConsole(WindowInfo& info); void SetupFPSCounter(WindowInfo& info); void SetupOverlay(WindowInfo& info); diff --git a/SDK/include/NDK/Application.inl b/SDK/include/NDK/Application.inl index e01fb2ce4..42139db0c 100644 --- a/SDK/include/NDK/Application.inl +++ b/SDK/include/NDK/Application.inl @@ -112,7 +112,6 @@ namespace Ndk } m_overlayFlags |= OverlayFlags_Console; - } else { diff --git a/SDK/include/NDK/BaseWidget.hpp b/SDK/include/NDK/BaseWidget.hpp index 26c7f5539..010abd437 100644 --- a/SDK/include/NDK/BaseWidget.hpp +++ b/SDK/include/NDK/BaseWidget.hpp @@ -26,8 +26,6 @@ namespace Ndk friend Canvas; public: - struct Padding; - BaseWidget(BaseWidget* parent); BaseWidget(const BaseWidget&) = delete; BaseWidget(BaseWidget&&) = delete; @@ -41,6 +39,7 @@ namespace Ndk inline void CenterVertical(); void ClearFocus(); + inline void ClearRenderingRect(); void Destroy(); @@ -68,6 +67,8 @@ namespace Ndk inline Nz::Vector2f GetPreferredSize() const; inline float GetPreferredWidth() const; + inline const Nz::Rectf& GetRenderingRect() const; + inline Nz::Vector2f GetSize() const; inline float GetWidth() const; inline std::size_t GetWidgetChildCount() const; @@ -81,6 +82,7 @@ namespace Ndk void SetBackgroundColor(const Nz::Color& color); void SetCursor(Nz::SystemCursor systemCursor); void SetFocus(); + void SetParent(BaseWidget* widget); inline void SetFixedHeight(float fixedHeight); inline void SetFixedSize(const Nz::Vector2f& fixedSize); @@ -94,6 +96,8 @@ namespace Ndk inline void SetMinimumSize(const Nz::Vector2f& minimumSize); inline void SetMinimumWidth(float minimumWidth); + virtual void SetRenderingRect(const Nz::Rectf& renderingRect); + void Show(bool show = true); BaseWidget& operator=(const BaseWidget&) = delete; @@ -115,6 +119,7 @@ namespace Ndk virtual void OnMouseMoved(int x, int y, int deltaX, int deltaY); virtual void OnMouseButtonPress(int x, int y, Nz::Mouse::Button button); virtual void OnMouseButtonRelease(int x, int y, Nz::Mouse::Button button); + virtual void OnMouseWheelMoved(int x, int y, float delta); virtual void OnMouseExit(); virtual void OnParentResized(const Nz::Vector2f& newSize); virtual void OnTextEntered(char32_t character, bool repeated); @@ -136,6 +141,10 @@ namespace Ndk struct WidgetEntity { EntityOwner handle; + bool isEnabled = true; + + NazaraSlot(Ndk::Entity, OnEntityDisabled, onDisabledSlot); + NazaraSlot(Ndk::Entity, OnEntityEnabled, onEnabledSlot); }; static constexpr std::size_t InvalidCanvasIndex = std::numeric_limits::max(); @@ -147,6 +156,7 @@ namespace Ndk EntityOwner m_backgroundEntity; WorldHandle m_world; Nz::Color m_backgroundColor; + Nz::Rectf m_renderingRect; Nz::SpriteRef m_backgroundSprite; Nz::SystemCursor m_cursor; Nz::Vector2f m_maximumSize; diff --git a/SDK/include/NDK/BaseWidget.inl b/SDK/include/NDK/BaseWidget.inl index e24b89285..605220774 100644 --- a/SDK/include/NDK/BaseWidget.inl +++ b/SDK/include/NDK/BaseWidget.inl @@ -5,6 +5,7 @@ #include #include #include +#include namespace Ndk { @@ -12,6 +13,7 @@ namespace Ndk m_canvasIndex(InvalidCanvasIndex), m_canvas(nullptr), m_backgroundColor(Nz::Color(230, 230, 230, 255)), + m_renderingRect(-std::numeric_limits::infinity(), -std::numeric_limits::infinity(), std::numeric_limits::infinity(), std::numeric_limits::infinity()), m_cursor(Nz::SystemCursor_Default), m_size(50.f, 50.f), m_maximumSize(std::numeric_limits::infinity()), @@ -66,6 +68,11 @@ namespace Ndk SetPosition(GetPosition(Nz::CoordSys_Local).x, (parentSize.y - mySize.y) / 2.f); } + inline void BaseWidget::ClearRenderingRect() + { + SetRenderingRect(Nz::Rectf(-std::numeric_limits::infinity(), -std::numeric_limits::infinity(), std::numeric_limits::infinity(), std::numeric_limits::infinity())); + } + template inline void BaseWidget::ForEachWidgetChild(F iterator) { @@ -145,6 +152,11 @@ namespace Ndk return m_preferredSize.x; } + inline const Nz::Rectf& BaseWidget::GetRenderingRect() const + { + return m_renderingRect; + } + inline Nz::Vector2f BaseWidget::GetSize() const { return Nz::Vector2f(GetWidth(), GetHeight()); @@ -237,7 +249,7 @@ namespace Ndk { m_preferredSize = preferredSize; - Resize(m_preferredSize); + //Resize(m_preferredSize); } inline bool BaseWidget::IsRegisteredToCanvas() const diff --git a/SDK/include/NDK/Canvas.hpp b/SDK/include/NDK/Canvas.hpp index d563c6cd3..26cc36cbd 100644 --- a/SDK/include/NDK/Canvas.hpp +++ b/SDK/include/NDK/Canvas.hpp @@ -31,6 +31,9 @@ namespace Ndk Canvas& operator=(const Canvas&) = delete; Canvas& operator=(Canvas&&) = delete; + NazaraSignal(OnUnhandledKeyPressed, const Nz::EventHandler* /*eventHandler*/, const Nz::WindowEvent::KeyEvent& /*event*/); + NazaraSignal(OnUnhandledKeyReleased, const Nz::EventHandler* /*eventHandler*/, const Nz::WindowEvent::KeyEvent& /*event*/); + protected: inline void ClearKeyboardOwner(std::size_t canvasIndex); @@ -48,8 +51,9 @@ namespace Ndk private: void OnEventMouseButtonPressed(const Nz::EventHandler* eventHandler, const Nz::WindowEvent::MouseButtonEvent& event); void OnEventMouseButtonRelease(const Nz::EventHandler* eventHandler, const Nz::WindowEvent::MouseButtonEvent& event); - void OnEventMouseMoved(const Nz::EventHandler* eventHandler, const Nz::WindowEvent::MouseMoveEvent& event); void OnEventMouseLeft(const Nz::EventHandler* eventHandler); + void OnEventMouseMoved(const Nz::EventHandler* eventHandler, const Nz::WindowEvent::MouseMoveEvent& event); + void OnEventMouseWheelMoved(const Nz::EventHandler* eventHandler, const Nz::WindowEvent::MouseWheelEvent& event); void OnEventKeyPressed(const Nz::EventHandler* eventHandler, const Nz::WindowEvent::KeyEvent& event); void OnEventKeyReleased(const Nz::EventHandler* eventHandler, const Nz::WindowEvent::KeyEvent& event); void OnEventTextEntered(const Nz::EventHandler* eventHandler, const Nz::WindowEvent::TextEvent& event); @@ -65,8 +69,9 @@ namespace Ndk NazaraSlot(Nz::EventHandler, OnKeyReleased, m_keyReleasedSlot); NazaraSlot(Nz::EventHandler, OnMouseButtonPressed, m_mouseButtonPressedSlot); NazaraSlot(Nz::EventHandler, OnMouseButtonReleased, m_mouseButtonReleasedSlot); - NazaraSlot(Nz::EventHandler, OnMouseMoved, m_mouseMovedSlot); NazaraSlot(Nz::EventHandler, OnMouseLeft, m_mouseLeftSlot); + NazaraSlot(Nz::EventHandler, OnMouseMoved, m_mouseMovedSlot); + NazaraSlot(Nz::EventHandler, OnMouseWheelMoved, m_mouseWheelMovedSlot); NazaraSlot(Nz::EventHandler, OnTextEntered, m_textEnteredSlot); std::size_t m_keyboardOwner; diff --git a/SDK/include/NDK/Canvas.inl b/SDK/include/NDK/Canvas.inl index 7a602cffb..75b642328 100644 --- a/SDK/include/NDK/Canvas.inl +++ b/SDK/include/NDK/Canvas.inl @@ -24,8 +24,9 @@ namespace Ndk m_keyReleasedSlot.Connect(eventHandler.OnKeyReleased, this, &Canvas::OnEventKeyReleased); m_mouseButtonPressedSlot.Connect(eventHandler.OnMouseButtonPressed, this, &Canvas::OnEventMouseButtonPressed); m_mouseButtonReleasedSlot.Connect(eventHandler.OnMouseButtonReleased, this, &Canvas::OnEventMouseButtonRelease); - m_mouseMovedSlot.Connect(eventHandler.OnMouseMoved, this, &Canvas::OnEventMouseMoved); m_mouseLeftSlot.Connect(eventHandler.OnMouseLeft, this, &Canvas::OnEventMouseLeft); + m_mouseMovedSlot.Connect(eventHandler.OnMouseMoved, this, &Canvas::OnEventMouseMoved); + m_mouseWheelMovedSlot.Connect(eventHandler.OnMouseWheelMoved, this, &Canvas::OnEventMouseWheelMoved); m_textEnteredSlot.Connect(eventHandler.OnTextEntered, this, &Canvas::OnEventTextEntered); } diff --git a/SDK/include/NDK/Console.hpp b/SDK/include/NDK/Console.hpp index 728cbcbb0..32d0cd76a 100644 --- a/SDK/include/NDK/Console.hpp +++ b/SDK/include/NDK/Console.hpp @@ -14,24 +14,27 @@ #include #include #include +#include #include namespace Nz { - class LuaState; struct WindowEvent; } namespace Ndk { class Console; + class Entity; + class ScrollAreaWidget; + class TextAreaWidget; using ConsoleHandle = Nz::ObjectHandle; - class NDK_API Console : public Nz::Node, public Nz::HandledObject + class NDK_API Console : public BaseWidget, public Nz::HandledObject { public: - Console(World& world, const Nz::Vector2f& size, Nz::LuaState& state); + Console(BaseWidget* parent); Console(const Console& console) = delete; Console(Console&& console) = default; ~Console() = default; @@ -39,34 +42,25 @@ namespace Ndk void AddLine(const Nz::String& text, const Nz::Color& color = Nz::Color::White); void Clear(); + void ClearFocus(); inline unsigned int GetCharacterSize() const; - inline const EntityHandle& GetHistory() const; - inline const EntityHandle& GetHistoryBackground() const; - inline const EntityHandle& GetInput() const; - inline const EntityHandle& GetInputBackground() const; - inline const Nz::Vector2f& GetSize() const; + inline const TextAreaWidget* GetHistory() const; + inline const TextAreaWidget* GetInput() const; inline const Nz::FontRef& GetTextFont() const; - inline bool IsVisible() const; - - void SendCharacter(char32_t character); - void SendEvent(const Nz::WindowEvent& event); - void SetCharacterSize(unsigned int size); - void SetSize(const Nz::Vector2f& size); + void SetFocus(); void SetTextFont(Nz::FontRef font); - void Show(bool show = true); - Console& operator=(const Console& console) = delete; Console& operator=(Console&& console) = default; + NazaraSignal(OnCommand, Console* /*console*/, const Nz::String& /*command*/); + private: - void AddLineInternal(const Nz::String& text, const Nz::Color& color = Nz::Color::White); - void ExecuteInput(); - void Layout(); - void RefreshHistory(); + void ExecuteInput(const TextAreaWidget* textArea, bool* ignoreDefaultAction); + void Layout() override; struct Line { @@ -77,20 +71,10 @@ namespace Ndk std::size_t m_historyPosition; std::vector m_commandHistory; std::vector m_historyLines; - EntityOwner m_historyBackground; - EntityOwner m_history; - EntityOwner m_input; - EntityOwner m_inputBackground; + ScrollAreaWidget* m_historyArea; + TextAreaWidget* m_history; + TextAreaWidget* m_input; Nz::FontRef m_defaultFont; - Nz::LuaState& m_state; - Nz::SpriteRef m_historyBackgroundSprite; - Nz::SpriteRef m_inputBackgroundSprite; - Nz::SimpleTextDrawer m_historyDrawer; - Nz::SimpleTextDrawer m_inputDrawer; - Nz::TextSpriteRef m_historyTextSprite; - Nz::TextSpriteRef m_inputTextSprite; - Nz::Vector2f m_size; - bool m_opened; unsigned int m_characterSize; unsigned int m_maxHistoryLines; }; diff --git a/SDK/include/NDK/Console.inl b/SDK/include/NDK/Console.inl index 746616ffd..0e5733f75 100644 --- a/SDK/include/NDK/Console.inl +++ b/SDK/include/NDK/Console.inl @@ -19,51 +19,21 @@ namespace Ndk * \return History of the console */ - inline const EntityHandle& Console::GetHistory() const + inline const TextAreaWidget* Console::GetHistory() const { return m_history; } - /*! - * \brief Gets the entity representing the background of the console's history - * \return Background history of the console - */ - - inline const EntityHandle& Console::GetHistoryBackground() const - { - return m_historyBackground; - } - /*! * \brief Gets the entity representing the input of the console * \return Input of the console */ - inline const EntityHandle& Console::GetInput() const + inline const TextAreaWidget* Console::GetInput() const { return m_input; } - /*! - * \brief Gets the entity representing the background of the console's input - * \return Background input of the console - */ - - inline const EntityHandle& Console::GetInputBackground() const - { - return m_inputBackground; - } - - /*! - * \brief Gets the size of the console - * \return Size (Width, Height) of the console - */ - - inline const Nz::Vector2f& Console::GetSize() const - { - return m_size; - } - /*! * \brief Gets the font used by the console * \return A reference to the font currenty used @@ -73,14 +43,4 @@ namespace Ndk { return m_defaultFont; } - - /*! - * \brief Checks whether the console is visible - * \return true If it is the case - */ - - inline bool Console::IsVisible() const - { - return m_opened; - } } diff --git a/SDK/include/NDK/Entity.hpp b/SDK/include/NDK/Entity.hpp index 43a4a43c7..e09f4eac4 100644 --- a/SDK/include/NDK/Entity.hpp +++ b/SDK/include/NDK/Entity.hpp @@ -74,6 +74,8 @@ namespace Ndk Entity& operator=(Entity&&) = delete; NazaraSignal(OnEntityDestruction, Entity* /*entity*/); + NazaraSignal(OnEntityDisabled, Entity* /*entity*/); + NazaraSignal(OnEntityEnabled, Entity* /*entity*/); private: Entity(World* world, EntityId id); diff --git a/SDK/include/NDK/Widgets.hpp b/SDK/include/NDK/Widgets.hpp index a4b74b2d6..303b22519 100644 --- a/SDK/include/NDK/Widgets.hpp +++ b/SDK/include/NDK/Widgets.hpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #endif // NDK_WIDGETS_GLOBAL_HPP diff --git a/SDK/include/NDK/Widgets/ScrollAreaWidget.hpp b/SDK/include/NDK/Widgets/ScrollAreaWidget.hpp new file mode 100644 index 000000000..17ada16ef --- /dev/null +++ b/SDK/include/NDK/Widgets/ScrollAreaWidget.hpp @@ -0,0 +1,74 @@ +// Copyright (C) 2019 Jérôme Leclercq +// This file is part of the "Nazara Development Kit" +// For conditions of distribution and use, see copyright notice in Prerequisites.hpp + +#pragma once + +#ifndef NDK_WIDGETS_SCROLLAREAWIDGET_HPP +#define NDK_WIDGETS_SCROLLAREAWIDGET_HPP + +#include +#include +#include + +namespace Ndk +{ + class NDK_API ScrollAreaWidget : public BaseWidget + { + public: + ScrollAreaWidget(BaseWidget* parent, BaseWidget* content); + ScrollAreaWidget(const ScrollAreaWidget&) = delete; + ScrollAreaWidget(ScrollAreaWidget&&) = default; + ~ScrollAreaWidget() = default; + + void EnableScrollbar(bool enable); + + inline float GetScrollHeight() const; + inline float GetScrollRatio() const; + + inline bool HasScrollbar() const; + inline bool IsScrollbarEnabled() const; + inline bool IsScrollbarVisible() const; + + inline void ScrollToHeight(float height); + void ScrollToRatio(float ratio); + + ScrollAreaWidget& operator=(const ScrollAreaWidget&) = delete; + ScrollAreaWidget& operator=(ScrollAreaWidget&&) = default; + + private: + enum class ScrollBarStatus + { + Grabbed, + Hovered, + None + }; + + Nz::Rectf GetScrollbarRect() const; + + void Layout() override; + + void OnMouseButtonPress(int x, int y, Nz::Mouse::Button button) override; + void OnMouseButtonRelease(int x, int y, Nz::Mouse::Button button) override; + void OnMouseExit() override; + void OnMouseMoved(int x, int y, int deltaX, int deltaY) override; + void OnMouseWheelMoved(int x, int y, float delta) override; + + void UpdateScrollbarStatus(ScrollBarStatus status); + + BaseWidget* m_content; + EntityHandle m_scrollbarBackgroundEntity; + EntityHandle m_scrollbarEntity; + Nz::SpriteRef m_scrollbarBackgroundSprite; + Nz::SpriteRef m_scrollbarSprite; + Nz::Vector2i m_grabbedDelta; + ScrollBarStatus m_scrollbarStatus; + bool m_isScrollbarEnabled; + bool m_hasScrollbar; + float m_scrollRatio; + }; +} + +#include + +#endif // NDK_WIDGETS_SCROLLAREAWIDGET_HPP diff --git a/SDK/include/NDK/Widgets/ScrollAreaWidget.inl b/SDK/include/NDK/Widgets/ScrollAreaWidget.inl new file mode 100644 index 000000000..89bea6d47 --- /dev/null +++ b/SDK/include/NDK/Widgets/ScrollAreaWidget.inl @@ -0,0 +1,39 @@ +// Copyright (C) 2019 Jérôme Leclercq +// This file is part of the "Nazara Development Kit" +// For conditions of distribution and use, see copyright notice in Prerequisites.hpp + +#include + +namespace Ndk +{ + inline float ScrollAreaWidget::GetScrollHeight() const + { + return m_scrollRatio * m_content->GetHeight(); + } + + inline float ScrollAreaWidget::GetScrollRatio() const + { + return m_scrollRatio; + } + + inline bool ScrollAreaWidget::HasScrollbar() const + { + return m_hasScrollbar; + } + + inline bool ScrollAreaWidget::IsScrollbarEnabled() const + { + return m_isScrollbarEnabled; + } + + inline bool ScrollAreaWidget::IsScrollbarVisible() const + { + return HasScrollbar() && IsScrollbarEnabled(); + } + + inline void ScrollAreaWidget::ScrollToHeight(float height) + { + float contentHeight = m_content->GetHeight(); + ScrollToRatio(height / contentHeight); + } +} diff --git a/SDK/include/NDK/Widgets/TextAreaWidget.hpp b/SDK/include/NDK/Widgets/TextAreaWidget.hpp index 9a5d33335..ebce9e464 100644 --- a/SDK/include/NDK/Widgets/TextAreaWidget.hpp +++ b/SDK/include/NDK/Widgets/TextAreaWidget.hpp @@ -32,7 +32,7 @@ namespace Ndk //virtual TextAreaWidget* Clone() const = 0; - + void EnableLineWrap(bool enable = true); inline void EnableMultiline(bool enable = true); inline void EnableTabWriting(bool enable = true); @@ -46,14 +46,19 @@ namespace Ndk inline Nz::Vector2ui GetCursorPosition(std::size_t glyphIndex) const; inline const Nz::String& GetDisplayText() const; inline EchoMode GetEchoMode() const; - inline std::size_t GetGlyphIndex(const Nz::Vector2ui& cursorPosition); + inline std::size_t GetGlyphIndex() const; + inline std::size_t GetGlyphIndex(const Nz::Vector2ui& cursorPosition) const; inline const Nz::String& GetText() const; inline const Nz::Color& GetTextColor() const; + inline Nz::Font* GetTextFont() const; + inline const Nz::Color& GetTextOulineColor() const; + inline float GetTextOulineThickness() const; Nz::Vector2ui GetHoveredGlyph(float x, float y) const; inline bool HasSelection() const; + inline bool IsLineWrapEnabled() const; inline bool IsMultilineEnabled() const; inline bool IsReadOnly() const; inline bool IsTabWritingEnabled() const; @@ -61,6 +66,8 @@ namespace Ndk inline void MoveCursor(int offset); inline void MoveCursor(const Nz::Vector2i& offset); + inline Nz::Vector2ui NormalizeCursorPosition(Nz::Vector2ui cursorPosition) const; + inline void SetCharacterFilter(CharacterFilter filter); void SetCharacterSize(unsigned int characterSize); inline void SetCursorPosition(std::size_t glyphIndex); @@ -70,6 +77,9 @@ namespace Ndk inline void SetSelection(Nz::Vector2ui fromPosition, Nz::Vector2ui toPosition); inline void SetText(const Nz::String& text); inline void SetTextColor(const Nz::Color& text); + inline void SetTextFont(Nz::FontRef font); + inline void SetTextOutlineColor(const Nz::Color& color); + inline void SetTextOutlineThickness(float thickness); inline void Write(const Nz::String& text); inline void Write(const Nz::String& text, const Nz::Vector2ui& glyphPosition); @@ -78,7 +88,7 @@ namespace Ndk TextAreaWidget& operator=(const TextAreaWidget&) = delete; TextAreaWidget& operator=(TextAreaWidget&&) = default; - NazaraSignal(OnTextAreaCursorMove, const TextAreaWidget* /*textArea*/, std::size_t* /*newCursorPosition*/); + NazaraSignal(OnTextAreaCursorMove, const TextAreaWidget* /*textArea*/, Nz::Vector2ui* /*newCursorPosition*/); NazaraSignal(OnTextAreaKeyBackspace, const TextAreaWidget* /*textArea*/, bool* /*ignoreDefaultAction*/); NazaraSignal(OnTextAreaKeyDown, const TextAreaWidget* /*textArea*/, bool* /*ignoreDefaultAction*/); NazaraSignal(OnTextAreaKeyEnd, const TextAreaWidget* /*textArea*/, bool* /*ignoreDefaultAction*/); @@ -87,6 +97,7 @@ namespace Ndk NazaraSignal(OnTextAreaKeyReturn, const TextAreaWidget* /*textArea*/, bool* /*ignoreDefaultAction*/); NazaraSignal(OnTextAreaKeyRight, const TextAreaWidget* /*textArea*/, bool* /*ignoreDefaultAction*/); NazaraSignal(OnTextAreaKeyUp, const TextAreaWidget* /*textArea*/, bool* /*ignoreDefaultAction*/); + NazaraSignal(OnTextAreaSelection, const TextAreaWidget* /*textArea*/, Nz::Vector2ui* /*start*/, Nz::Vector2ui* /*end*/); NazaraSignal(OnTextChanged, const TextAreaWidget* /*textArea*/, const Nz::String& /*text*/); private: @@ -103,8 +114,12 @@ namespace Ndk void OnMouseMoved(int x, int y, int deltaX, int deltaY) override; void OnTextEntered(char32_t character, bool repeated) override; + inline void SetCursorPositionInternal(std::size_t glyphIndex); + inline void SetCursorPositionInternal(Nz::Vector2ui cursorPosition); + void RefreshCursor(); void UpdateDisplayText(); + void UpdateTextSprite(); CharacterFilter m_characterFilter; EchoMode m_echoMode; @@ -117,6 +132,7 @@ namespace Ndk Nz::Vector2ui m_cursorPositionEnd; Nz::Vector2ui m_selectionCursor; std::vector m_cursorSprites; + bool m_isLineWrapEnabled; bool m_isMouseButtonDown; bool m_multiLineEnabled; bool m_readOnly; diff --git a/SDK/include/NDK/Widgets/TextAreaWidget.inl b/SDK/include/NDK/Widgets/TextAreaWidget.inl index e4c9f2c30..bdb95854e 100644 --- a/SDK/include/NDK/Widgets/TextAreaWidget.inl +++ b/SDK/include/NDK/Widgets/TextAreaWidget.inl @@ -13,6 +13,7 @@ namespace Ndk m_drawer.Clear(); m_text.Clear(); m_textSprite->Update(m_drawer); + SetPreferredSize(Nz::Vector2f(m_textSprite->GetBoundingVolume().obb.localBox.GetLengths())); RefreshCursor(); OnTextChanged(this, m_text); @@ -76,7 +77,17 @@ namespace Ndk return m_drawer.GetText(); } - inline std::size_t TextAreaWidget::GetGlyphIndex(const Nz::Vector2ui& cursorPosition) + inline EchoMode TextAreaWidget::GetEchoMode() const + { + return m_echoMode; + } + + inline std::size_t TextAreaWidget::GetGlyphIndex() const + { + return GetGlyphIndex(m_cursorPositionBegin); + } + + inline std::size_t TextAreaWidget::GetGlyphIndex(const Nz::Vector2ui& cursorPosition) const { std::size_t glyphIndex = m_drawer.GetLine(cursorPosition.y).glyphIndex + cursorPosition.x; if (m_drawer.GetLineCount() > cursorPosition.y + 1) @@ -87,11 +98,6 @@ namespace Ndk return glyphIndex; } - inline EchoMode TextAreaWidget::GetEchoMode() const - { - return m_echoMode; - } - inline const Nz::String& TextAreaWidget::GetText() const { return m_text; @@ -102,11 +108,31 @@ namespace Ndk return m_drawer.GetColor(); } + inline Nz::Font* TextAreaWidget::GetTextFont() const + { + return m_drawer.GetFont(); + } + + inline const Nz::Color& TextAreaWidget::GetTextOulineColor() const + { + return m_drawer.GetOutlineColor(); + } + + inline float TextAreaWidget::GetTextOulineThickness() const + { + return m_drawer.GetOutlineThickness(); + } + inline bool TextAreaWidget::HasSelection() const { return m_cursorPositionBegin != m_cursorPositionEnd; } + inline bool TextAreaWidget::IsLineWrapEnabled() const + { + return m_isLineWrapEnabled; + } + inline bool TextAreaWidget::IsMultilineEnabled() const { return m_multiLineEnabled; @@ -160,29 +186,12 @@ namespace Ndk SetCursorPosition(cursorPosition); } - inline void TextAreaWidget::SetCharacterFilter(CharacterFilter filter) - { - m_characterFilter = std::move(filter); - } - - inline void TextAreaWidget::SetCursorPosition(std::size_t glyphIndex) - { - OnTextAreaCursorMove(this, &glyphIndex); - - m_cursorPositionBegin = GetCursorPosition(glyphIndex); - m_cursorPositionEnd = m_cursorPositionBegin; - - RefreshCursor(); - } - - inline void TextAreaWidget::SetCursorPosition(Nz::Vector2ui cursorPosition) + inline Nz::Vector2ui TextAreaWidget::NormalizeCursorPosition(Nz::Vector2ui cursorPosition) const { std::size_t lineCount = m_drawer.GetLineCount(); if (cursorPosition.y >= lineCount) cursorPosition.y = static_cast(lineCount - 1); - m_cursorPositionBegin = cursorPosition; - const auto& lineInfo = m_drawer.GetLine(cursorPosition.y); if (cursorPosition.y + 1 < lineCount) { @@ -190,13 +199,32 @@ namespace Ndk cursorPosition.x = std::min(cursorPosition.x, static_cast(nextLineInfo.glyphIndex - lineInfo.glyphIndex - 1)); } - m_cursorPositionEnd = m_cursorPositionBegin; + return cursorPosition; + } - std::size_t glyphIndex = lineInfo.glyphIndex + cursorPosition.x; + inline void TextAreaWidget::SetCharacterFilter(CharacterFilter filter) + { + m_characterFilter = std::move(filter); + } - OnTextAreaCursorMove(this, &glyphIndex); + inline void TextAreaWidget::SetCursorPosition(std::size_t glyphIndex) + { + Nz::Vector2ui position = GetCursorPosition(glyphIndex); + Nz::Vector2ui newPosition = position; - RefreshCursor(); + OnTextAreaCursorMove(this, &newPosition); + + if (position == newPosition) + SetCursorPositionInternal(position); + else + SetCursorPositionInternal(GetGlyphIndex(newPosition)); + } + + inline void TextAreaWidget::SetCursorPosition(Nz::Vector2ui cursorPosition) + { + OnTextAreaCursorMove(this, &cursorPosition); + + return SetCursorPositionInternal(NormalizeCursorPosition(cursorPosition)); } inline void TextAreaWidget::SetEchoMode(EchoMode echoMode) @@ -214,16 +242,20 @@ namespace Ndk inline void TextAreaWidget::SetSelection(Nz::Vector2ui fromPosition, Nz::Vector2ui toPosition) { - ///TODO: Check if position are valid - // Ensure begin is before end if (toPosition.y < fromPosition.y || (toPosition.y == fromPosition.y && toPosition.x < fromPosition.x)) std::swap(fromPosition, toPosition); if (m_cursorPositionBegin != fromPosition || m_cursorPositionEnd != toPosition) { - m_cursorPositionBegin = fromPosition; - m_cursorPositionEnd = toPosition; + OnTextAreaSelection(this, &fromPosition, &toPosition); + + // Ensure begin is before end a second time (in case signal changed it) + if (toPosition.y < fromPosition.y || (toPosition.y == fromPosition.y && toPosition.x < fromPosition.x)) + std::swap(fromPosition, toPosition); + + m_cursorPositionBegin = NormalizeCursorPosition(fromPosition); + m_cursorPositionEnd = NormalizeCursorPosition(toPosition); RefreshCursor(); } @@ -241,7 +273,28 @@ namespace Ndk { m_drawer.SetColor(text); - m_textSprite->Update(m_drawer); + UpdateDisplayText(); + } + + inline void TextAreaWidget::SetTextFont(Nz::FontRef font) + { + m_drawer.SetFont(font); + + UpdateDisplayText(); + } + + inline void TextAreaWidget::SetTextOutlineColor(const Nz::Color& color) + { + m_drawer.SetOutlineColor(color); + + UpdateDisplayText(); + } + + inline void TextAreaWidget::SetTextOutlineThickness(float thickness) + { + m_drawer.SetOutlineThickness(thickness); + + UpdateDisplayText(); } inline void TextAreaWidget::Write(const Nz::String& text) @@ -253,4 +306,17 @@ namespace Ndk { Write(text, GetGlyphIndex(glyphPosition)); } + + void TextAreaWidget::SetCursorPositionInternal(std::size_t glyphIndex) + { + return SetCursorPositionInternal(GetCursorPosition(glyphIndex)); + } + + inline void TextAreaWidget::SetCursorPositionInternal(Nz::Vector2ui cursorPosition) + { + m_cursorPositionBegin = cursorPosition; + m_cursorPositionEnd = m_cursorPositionBegin; + + RefreshCursor(); + } } diff --git a/SDK/src/NDK/Application.cpp b/SDK/src/NDK/Application.cpp index a4fca055a..cf6fcdbb5 100644 --- a/SDK/src/NDK/Application.cpp +++ b/SDK/src/NDK/Application.cpp @@ -147,16 +147,22 @@ namespace Ndk Nz::Vector2ui windowDimensions; if (info.window->IsValid()) - { windowDimensions = info.window->GetSize(); - windowDimensions.y /= 4; - } else windowDimensions.MakeZero(); - overlay->console = std::make_unique(*info.overlayWorld, Nz::Vector2f(windowDimensions), overlay->lua); + Nz::LuaInstance& lua = overlay->lua; + + overlay->console = info.canvas->Add(); + overlay->console->OnCommand.Connect([&lua](Ndk::Console* console, const Nz::String& command) + { + if (!lua.Execute(command)) + console->AddLine(lua.GetLastError(), Nz::Color::Red); + }); Console& consoleRef = *overlay->console; + consoleRef.Resize({float(windowDimensions.x), windowDimensions.y / 4.f}); + consoleRef.Show(false); // Redirect logs toward the console overlay->logSlot.Connect(Nz::Log::OnLogWrite, [&consoleRef] (const Nz::String& str) @@ -164,11 +170,11 @@ namespace Ndk consoleRef.AddLine(str); }); - overlay->lua.LoadLibraries(); - LuaAPI::RegisterClasses(overlay->lua); + lua.LoadLibraries(); + LuaAPI::RegisterClasses(lua); // Override "print" function to add a line in the console - overlay->lua.PushFunction([&consoleRef] (Nz::LuaState& state) + lua.PushFunction([&consoleRef] (Nz::LuaState& state) { Nz::StringStream stream; @@ -192,31 +198,37 @@ namespace Ndk consoleRef.AddLine(stream); return 0; }); - overlay->lua.SetGlobal("print"); + lua.SetGlobal("print"); // Define a few base variables to allow our interface to interact with the application - overlay->lua.PushGlobal("Application", Ndk::Application::Instance()); - overlay->lua.PushGlobal("Console", consoleRef.CreateHandle()); + lua.PushGlobal("Application", Ndk::Application::Instance()); + lua.PushGlobal("Console", consoleRef.CreateHandle()); // Setup a few event callback to handle the console Nz::EventHandler& eventHandler = info.window->GetEventHandler(); - overlay->eventSlot.Connect(eventHandler.OnEvent, [&consoleRef] (const Nz::EventHandler*, const Nz::WindowEvent& event) - { - if (consoleRef.IsVisible()) - consoleRef.SendEvent(event); - }); - overlay->keyPressedSlot.Connect(eventHandler.OnKeyPressed, [&consoleRef] (const Nz::EventHandler*, const Nz::WindowEvent::KeyEvent& event) { if (event.code == Nz::Keyboard::F9) - consoleRef.Show(!consoleRef.IsVisible()); + { + // Toggle console visibility and focus + if (consoleRef.IsVisible()) + { + consoleRef.ClearFocus(); + consoleRef.Show(false); + } + else + { + consoleRef.Show(true); + consoleRef.SetFocus(); + } + } }); overlay->resizedSlot.Connect(info.renderTarget->OnRenderTargetSizeChange, [&consoleRef] (const Nz::RenderTarget* renderTarget) { Nz::Vector2ui size = renderTarget->GetSize(); - consoleRef.SetSize({float(size.x), size.y / 4.f}); + consoleRef.Resize({float(size.x), size.y / 4.f}); }); info.console = std::move(overlay); @@ -238,6 +250,9 @@ namespace Ndk { info.overlayWorld = std::make_unique(false); //< No default system + if (info.window->IsValid()) + info.canvas = std::make_unique(info.overlayWorld->CreateHandle(), info.window->GetEventHandler(), info.window->GetCursorController().CreateHandle()); + RenderSystem& renderSystem = info.overlayWorld->AddSystem(); renderSystem.ChangeRenderTechnique(); renderSystem.SetDefaultBackground(nullptr); diff --git a/SDK/src/NDK/BaseWidget.cpp b/SDK/src/NDK/BaseWidget.cpp index 89ca6bb9c..be7049938 100644 --- a/SDK/src/NDK/BaseWidget.cpp +++ b/SDK/src/NDK/BaseWidget.cpp @@ -89,6 +89,7 @@ namespace Ndk } else { + DestroyEntity(m_backgroundEntity); m_backgroundEntity.Reset(); m_backgroundSprite.Reset(); } @@ -144,6 +145,27 @@ namespace Ndk m_canvas->SetKeyboardOwner(m_canvasIndex); } + void BaseWidget::SetParent(BaseWidget* widget) + { + Canvas* oldCanvas = m_canvas; + Canvas* newCanvas = widget->GetCanvas(); + + // Changing a widget canvas is a problem because of the canvas entities + NazaraAssert(oldCanvas == newCanvas, "Transferring a widget between canvas is not yet supported"); + + Node::SetParent(widget); + m_widgetParent = widget; + + Layout(); + } + + void BaseWidget::SetRenderingRect(const Nz::Rectf& renderingRect) + { + m_renderingRect = renderingRect; + + UpdatePositionAndSize(); + } + void BaseWidget::Show(bool show) { if (m_visible != show) @@ -156,21 +178,45 @@ namespace Ndk UnregisterFromCanvas(); for (WidgetEntity& entity : m_entities) - entity.handle->Enable(show); + { + if (entity.isEnabled) + { + entity.handle->Enable(show); //< This will override isEnabled + entity.isEnabled = true; + } + } for (const auto& widgetPtr : m_children) widgetPtr->Show(show); } } - const Ndk::EntityHandle& BaseWidget::CreateEntity() + const EntityHandle& BaseWidget::CreateEntity() { const EntityHandle& newEntity = m_world->CreateEntity(); newEntity->Enable(m_visible); m_entities.emplace_back(); - WidgetEntity& widgetEntity = m_entities.back(); - widgetEntity.handle = newEntity; + WidgetEntity& newWidgetEntity = m_entities.back(); + newWidgetEntity.handle = newEntity; + newWidgetEntity.onDisabledSlot.Connect(newEntity->OnEntityDisabled, [this](Entity* entity) + { + auto it = std::find_if(m_entities.begin(), m_entities.end(), [&](const WidgetEntity& widgetEntity) { return widgetEntity.handle == entity; }); + NazaraAssert(it != m_entities.end(), "Entity does not belong to this widget"); + + it->isEnabled = false; + }); + + newWidgetEntity.onEnabledSlot.Connect(newEntity->OnEntityEnabled, [this](Entity* entity) + { + auto it = std::find_if(m_entities.begin(), m_entities.end(), [&](const WidgetEntity& widgetEntity) { return widgetEntity.handle == entity; }); + NazaraAssert(it != m_entities.end(), "Entity does not belong to this widget"); + + if (!IsVisible()) + entity->Disable(); // Next line will override isEnabled status + + it->isEnabled = true; + }); return newEntity; } @@ -185,7 +231,7 @@ namespace Ndk void BaseWidget::Layout() { - if (m_backgroundEntity) + if (m_backgroundSprite) m_backgroundSprite->SetSize(m_size.x, m_size.y); UpdatePositionAndSize(); @@ -236,6 +282,10 @@ namespace Ndk { } + void BaseWidget::OnMouseWheelMoved(int /*x*/, int /*y*/, float /*delta*/) + { + } + void BaseWidget::OnMouseExit() { } @@ -289,7 +339,13 @@ namespace Ndk Nz::Vector2f widgetPos = Nz::Vector2f(GetPosition()); Nz::Vector2f widgetSize = GetSize(); - Nz::Recti fullBounds(Nz::Rectf(widgetPos.x, widgetPos.y, widgetSize.x, widgetSize.y)); + Nz::Rectf widgetRect(widgetPos.x, widgetPos.y, widgetSize.x, widgetSize.y); + Nz::Rectf widgetRenderingRect(widgetPos.x + m_renderingRect.x, widgetPos.y + m_renderingRect.y, m_renderingRect.width, m_renderingRect.height); + + Nz::Rectf widgetBounds; + widgetRect.Intersect(widgetRenderingRect, &widgetBounds); + + Nz::Recti fullBounds(widgetBounds); for (WidgetEntity& widgetEntity : m_entities) { const Ndk::EntityHandle& entity = widgetEntity.handle; diff --git a/SDK/src/NDK/Canvas.cpp b/SDK/src/NDK/Canvas.cpp index 3959f1765..f6d27ad15 100644 --- a/SDK/src/NDK/Canvas.cpp +++ b/SDK/src/NDK/Canvas.cpp @@ -61,7 +61,7 @@ namespace Ndk } } - void Canvas::OnEventMouseButtonRelease(const Nz::EventHandler* /*eventHandler*/, const Nz::WindowEvent::MouseButtonEvent & event) + void Canvas::OnEventMouseButtonRelease(const Nz::EventHandler* /*eventHandler*/, const Nz::WindowEvent::MouseButtonEvent& event) { if (m_hoveredWidget != InvalidCanvasIndex) { @@ -128,6 +128,19 @@ namespace Ndk } } + void Canvas::OnEventMouseWheelMoved(const Nz::EventHandler* /*eventHandler*/, const Nz::WindowEvent::MouseWheelEvent& event) + { + if (m_hoveredWidget != InvalidCanvasIndex) + { + WidgetEntry& hoveredWidget = m_widgetEntries[m_hoveredWidget]; + + int x = static_cast(std::round(event.x - hoveredWidget.box.x)); + int y = static_cast(std::round(event.y - hoveredWidget.box.y)); + + hoveredWidget.widget->OnMouseWheelMoved(x, y, event.delta); + } + } + void Canvas::OnEventMouseLeft(const Nz::EventHandler* /*eventHandler*/) { if (m_hoveredWidget != InvalidCanvasIndex) @@ -137,7 +150,7 @@ namespace Ndk } } - void Canvas::OnEventKeyPressed(const Nz::EventHandler* /*eventHandler*/, const Nz::WindowEvent::KeyEvent& event) + void Canvas::OnEventKeyPressed(const Nz::EventHandler* eventHandler, const Nz::WindowEvent::KeyEvent& event) { if (m_keyboardOwner != InvalidCanvasIndex) { @@ -191,12 +204,16 @@ namespace Ndk } } } + + OnUnhandledKeyPressed(eventHandler, event); } - void Canvas::OnEventKeyReleased(const Nz::EventHandler* /*eventHandler*/, const Nz::WindowEvent::KeyEvent& event) + void Canvas::OnEventKeyReleased(const Nz::EventHandler* eventHandler, const Nz::WindowEvent::KeyEvent& event) { if (m_keyboardOwner != InvalidCanvasIndex) m_widgetEntries[m_keyboardOwner].widget->OnKeyReleased(event); + + OnUnhandledKeyReleased(eventHandler, event); } void Canvas::OnEventTextEntered(const Nz::EventHandler* /*eventHandler*/, const Nz::WindowEvent::TextEvent& event) diff --git a/SDK/src/NDK/Console.cpp b/SDK/src/NDK/Console.cpp index e5b4f1306..abd333bd3 100644 --- a/SDK/src/NDK/Console.cpp +++ b/SDK/src/NDK/Console.cpp @@ -4,10 +4,10 @@ #include #include -#include #include #include #include +#include #include ///TODO: For now is unable to display different color in the history, it needs a RichTextDrawer to do so @@ -34,71 +34,70 @@ namespace Ndk * \param instance Lua instance that will interact with the world */ - Console::Console(World& world, const Nz::Vector2f& size, Nz::LuaState& state) : + Console::Console(BaseWidget* parent) : + BaseWidget(parent), m_historyPosition(0), m_defaultFont(Nz::Font::GetDefault()), - m_state(state), - m_size(size), - m_opened(false), - m_characterSize(24) + m_characterSize(24), + m_maxHistoryLines(200) { - Nz::MaterialRef backgroundMaterial = Nz::Material::New(); - backgroundMaterial->EnableBlending(true); - backgroundMaterial->EnableDepthBuffer(false); - backgroundMaterial->SetDstBlend(Nz::BlendFunc_InvSrcAlpha); - backgroundMaterial->SetSrcBlend(Nz::BlendFunc_SrcAlpha); - - // History bakckground - m_historyBackgroundSprite = Nz::Sprite::New(); - m_historyBackgroundSprite->SetColor(Nz::Color(80, 80, 160, 128)); - m_historyBackgroundSprite->SetMaterial(backgroundMaterial); - - m_historyBackground = world.CreateEntity(); - m_historyBackground->Enable(m_opened); - m_historyBackground->AddComponent().Attach(m_historyBackgroundSprite, -1); - m_historyBackground->AddComponent().SetParent(this); - // History - m_historyDrawer.SetCharacterSize(m_characterSize); - m_historyDrawer.SetColor(Nz::Color(200, 200, 200)); - m_historyDrawer.SetFont(m_defaultFont); + m_history = Add(); + m_history->EnableBackground(true); + m_history->EnableLineWrap(true); + m_history->SetReadOnly(true); + m_history->SetBackgroundColor(Nz::Color(80, 80, 160, 128)); - m_historyTextSprite = Nz::TextSprite::New(); - - m_history = world.CreateEntity(); - m_history->Enable(m_opened); - m_history->AddComponent().Attach(m_historyTextSprite); - - Ndk::NodeComponent& historyNode = m_history->AddComponent(); - historyNode.SetParent(this); - - // Input background - m_inputBackgroundSprite = Nz::Sprite::New(); - m_inputBackgroundSprite->SetColor(Nz::Color(255, 255, 255, 200)); - m_inputBackgroundSprite->SetMaterial(backgroundMaterial); - - m_inputBackground = world.CreateEntity(); - m_inputBackground->Enable(m_opened); - m_inputBackground->AddComponent().Attach(m_inputBackgroundSprite, -1); - m_inputBackground->AddComponent().SetParent(this); + m_historyArea = Add(m_history); // Input - m_inputDrawer.SetColor(Nz::Color::Black); - m_inputDrawer.SetCharacterSize(m_characterSize); - m_inputDrawer.SetFont(m_defaultFont); - m_inputDrawer.SetText(s_inputPrefix); + m_input = Add(); + m_input->EnableBackground(true); + m_input->SetText(s_inputPrefix); + m_input->SetTextColor(Nz::Color::Black); - m_inputTextSprite = Nz::TextSprite::New(); - m_inputTextSprite->Update(m_inputDrawer); + m_input->OnTextAreaKeyReturn.Connect(this, &Console::ExecuteInput); - m_input = world.CreateEntity(); - m_input->Enable(m_opened); - m_input->AddComponent().Attach(m_inputTextSprite); + // Protect input prefix from erasure/selection + m_input->SetCursorPosition(s_inputPrefixSize); - Ndk::NodeComponent& inputNode = m_input->AddComponent(); - inputNode.SetParent(this); + m_input->OnTextAreaCursorMove.Connect([](const TextAreaWidget* textArea, Nz::Vector2ui* newCursorPos) + { + newCursorPos->x = std::max(newCursorPos->x, static_cast(s_inputPrefixSize)); + }); - Layout(); + m_input->OnTextAreaSelection.Connect([](const TextAreaWidget* textArea, Nz::Vector2ui* start, Nz::Vector2ui* end) + { + start->x = std::max(start->x, static_cast(s_inputPrefixSize)); + end->x = std::max(end->x, static_cast(s_inputPrefixSize)); + }); + + m_input->OnTextAreaKeyBackspace.Connect([](const TextAreaWidget* textArea, bool* ignoreDefaultAction) + { + if (textArea->GetGlyphIndex() <= s_inputPrefixSize) + *ignoreDefaultAction = true; + }); + + // Handle history + m_input->OnTextAreaKeyUp.Connect([&] (const TextAreaWidget* textArea, bool* ignoreDefaultAction) + { + *ignoreDefaultAction = true; + + if (m_historyPosition > 0) + m_historyPosition--; + + m_input->SetText(s_inputPrefix + m_commandHistory[m_historyPosition]); + }); + + m_input->OnTextAreaKeyDown.Connect([&] (const TextAreaWidget* textArea, bool* ignoreDefaultAction) + { + *ignoreDefaultAction = true; + + if (++m_historyPosition >= m_commandHistory.size()) + m_historyPosition = 0; + + m_input->SetText(s_inputPrefix + m_commandHistory[m_historyPosition]); + }); } /*! @@ -107,110 +106,40 @@ namespace Ndk * \param text New line of text * \param color Color for the text */ - void Console::AddLine(const Nz::String& text, const Nz::Color& color) { - AddLineInternal(text, color); - RefreshHistory(); + if (m_historyLines.size() >= m_maxHistoryLines) + m_historyLines.erase(m_historyLines.begin()); + + m_historyLines.emplace_back(Line{ color, text }); + m_history->AppendText(text + '\n'); + m_history->Resize(m_history->GetPreferredSize()); + m_historyArea->Resize(m_historyArea->GetSize()); + m_historyArea->ScrollToRatio(1.f); } /*! * \brief Clears the console + * + * Clears the console history and input */ - void Console::Clear() { m_historyLines.clear(); - RefreshHistory(); + m_history->Clear(); + m_history->Resize(m_history->GetPreferredSize()); + m_historyArea->Resize(m_historyArea->GetSize()); + m_input->SetText(s_inputPrefix); } /*! - * \brief Sends a character to the console + * \brief Clears the console focus * - * \param character Character that will be added to the console + * Clear console input widget focus (if owned) */ - - void Console::SendCharacter(char32_t character) + void Console::ClearFocus() { - switch (character) - { - case '\b': - { - Nz::String input = m_inputDrawer.GetText(); - if (input.GetLength() <= s_inputPrefixSize) // Prevent removal of the input prefix - return; // Ignore if no user character is there - - input.Resize(-1, Nz::String::HandleUtf8); - m_inputDrawer.SetText(input); - break; - } - - case '\r': - case '\n': - ExecuteInput(); - break; - - default: - { - if (Nz::Unicode::GetCategory(character) == Nz::Unicode::Category_Other_Control) - return; - - m_inputDrawer.AppendText(Nz::String::Unicode(character)); - break; - } - } - - m_inputTextSprite->Update(m_inputDrawer); - } - - /*! - * \brief Sends an event to the console - * - * \param event Event to be takin into consideration by the console - */ - void Console::SendEvent(const Nz::WindowEvent& event) - { - switch (event.type) - { - case Nz::WindowEventType_TextEntered: - SendCharacter(event.text.character); - break; - - case Nz::WindowEventType_KeyPressed: - { - switch (event.key.code) - { - case Nz::Keyboard::Down: - case Nz::Keyboard::Up: - { - if (event.key.code == Nz::Keyboard::Up) - m_historyPosition = std::min(m_commandHistory.size(), m_historyPosition + 1); - else - { - if (m_historyPosition > 1) - m_historyPosition--; - else if (m_historyPosition == 0) - m_historyPosition = 1; - } - - if (!m_commandHistory.empty()) - { - Nz::String text = m_commandHistory[m_commandHistory.size() - m_historyPosition]; - m_inputDrawer.SetText(s_inputPrefix + text); - m_inputTextSprite->Update(m_inputDrawer); - } - break; - } - - default: - break; - } - break; - } - - default: - break; - } + m_input->ClearFocus(); } /*! @@ -218,30 +147,23 @@ namespace Ndk * * \param size Size of the font */ - void Console::SetCharacterSize(unsigned int size) { m_characterSize = size; - m_historyDrawer.SetCharacterSize(m_characterSize); - m_historyTextSprite->Update(m_historyDrawer); - m_inputDrawer.SetCharacterSize(m_characterSize); - m_inputTextSprite->Update(m_inputDrawer); + m_history->SetCharacterSize(size); + m_input->SetCharacterSize(size); Layout(); } /*! - * \brief Sets the console size + * \brief Give the console input focus * - * \param size (Width, Height) of the console */ - - void Console::SetSize(const Nz::Vector2f& size) + void Console::SetFocus() { - m_size = size; - m_historyBackgroundSprite->SetSize(m_size); - Layout(); + m_input->SetFocus(); } /*! @@ -251,121 +173,55 @@ namespace Ndk * * \remark Produces a NazaraAssert if font is invalid or null */ - void Console::SetTextFont(Nz::FontRef font) { NazaraAssert(font && font->IsValid(), "Invalid font"); m_defaultFont = std::move(font); - m_historyDrawer.SetFont(m_defaultFont); - m_inputDrawer.SetFont(m_defaultFont); + m_history->SetTextFont(m_defaultFont); + m_input->SetTextFont(m_defaultFont); Layout(); } - /*! - * \brief Shows the console - * - * \param show Should the console be showed - */ - - void Console::Show(bool show) - { - if (m_opened != show) - { - m_historyBackground->Enable(show); - m_history->Enable(show); - m_input->Enable(show); - m_inputBackground->Enable(show); - - m_opened = show; - } - } - - /*! - * \brief Adds a line to the history of the console - * - * \param text New line of text - * \param color Color for the text - */ - - void Console::AddLineInternal(const Nz::String& text, const Nz::Color& color) - { - m_historyLines.emplace_back(Line{color, text}); - } - /*! * \brief Performs this action when an input is added to the console */ - - void Console::ExecuteInput() + void Console::ExecuteInput(const TextAreaWidget* textArea, bool* ignoreDefaultAction) { - Nz::String input = m_inputDrawer.GetText(); + NazaraAssert(textArea == m_input, "Unexpected signal from an other text area"); + + *ignoreDefaultAction = true; + + Nz::String input = m_input->GetText(); Nz::String inputCmd = input.SubString(s_inputPrefixSize); - m_inputDrawer.SetText(s_inputPrefix); + m_input->SetText(s_inputPrefix); if (m_commandHistory.empty() || m_commandHistory.back() != inputCmd) m_commandHistory.push_back(inputCmd); - m_historyPosition = 0; + m_historyPosition = m_commandHistory.size(); - AddLineInternal(input); //< With the input prefix + AddLine(input); //< With the input prefix - if (!m_state.Execute(inputCmd)) - AddLineInternal(m_state.GetLastError(), Nz::Color::Red); - - RefreshHistory(); + OnCommand(this, inputCmd); } /*! * \brief Places the console according to its layout */ - void Console::Layout() { + Nz::Vector2f origin = Nz::Vector2f(GetPosition()); + const Nz::Vector2f& size = GetSize(); + unsigned int lineHeight = m_defaultFont->GetSizeInfo(m_characterSize).lineHeight; + float historyHeight = size.y - lineHeight; - Ndk::NodeComponent& inputNode = m_input->GetComponent(); - inputNode.SetPosition(0.f, m_size.y - lineHeight - 5.f); + m_historyArea->SetPosition(origin.x, origin.y); + m_historyArea->Resize({ size.x, historyHeight - 4.f }); - float historyHeight = m_size.y - lineHeight - 5.f - 2.f; - m_historyBackgroundSprite->SetSize(m_size.x, historyHeight); - - m_maxHistoryLines = static_cast(std::ceil(historyHeight / lineHeight)); - - Ndk::NodeComponent& historyNode = m_history->GetComponent(); - historyNode.SetPosition(0.f, historyHeight - m_maxHistoryLines * lineHeight); - - Ndk::NodeComponent& inputBackgroundNode = m_inputBackground->GetComponent(); - inputBackgroundNode.SetPosition(0.f, historyHeight + 2.f); - - m_inputBackgroundSprite->SetSize(m_size.x, m_size.y - historyHeight); - } - - /*! - * \brief Refreshes the history of the console - */ - - void Console::RefreshHistory() - { - m_historyDrawer.Clear(); - auto it = m_historyLines.end(); - if (m_historyLines.size() > m_maxHistoryLines) - it -= m_maxHistoryLines; - else - it = m_historyLines.begin(); - - for (unsigned int i = 0; i < m_maxHistoryLines; ++i) - { - if (m_maxHistoryLines - i <= m_historyLines.size() && it != m_historyLines.end()) - { - m_historyDrawer.AppendText(it->text); - ++it; - } - - m_historyDrawer.AppendText(Nz::String('\n')); - } - - m_historyTextSprite->Update(m_historyDrawer); + m_input->Resize({size.x, size.y - historyHeight}); + m_input->SetPosition(origin.x, origin.y + historyHeight); } } diff --git a/SDK/src/NDK/Entity.cpp b/SDK/src/NDK/Entity.cpp index 7bc81982d..4e211f0db 100644 --- a/SDK/src/NDK/Entity.cpp +++ b/SDK/src/NDK/Entity.cpp @@ -111,11 +111,15 @@ namespace Ndk { for (std::size_t i = m_componentBits.FindFirst(); i != m_componentBits.npos; i = m_componentBits.FindNext(i)) m_components[i]->OnEntityEnabled(); + + OnEntityEnabled(this); } else { for (std::size_t i = m_componentBits.FindFirst(); i != m_componentBits.npos; i = m_componentBits.FindNext(i)) m_components[i]->OnEntityDisabled(); + + OnEntityDisabled(this); } Invalidate(); diff --git a/SDK/src/NDK/Lua/LuaBinding_SDK.cpp b/SDK/src/NDK/Lua/LuaBinding_SDK.cpp index 36c28d973..805f7742d 100644 --- a/SDK/src/NDK/Lua/LuaBinding_SDK.cpp +++ b/SDK/src/NDK/Lua/LuaBinding_SDK.cpp @@ -62,24 +62,14 @@ namespace Ndk console.BindMethod("AddLine", &Console::AddLine, Nz::Color::White); console.BindMethod("Clear", &Console::Clear); console.BindMethod("GetCharacterSize", &Console::GetCharacterSize); - console.BindMethod("GetHistory", &Console::GetHistory); - console.BindMethod("GetHistoryBackground", &Console::GetHistoryBackground); - console.BindMethod("GetInput", &Console::GetInput); - console.BindMethod("GetInputBackground", &Console::GetInputBackground); - console.BindMethod("GetSize", &Console::GetSize); + //console.BindMethod("GetHistory", &Console::GetHistory); + //console.BindMethod("GetInput", &Console::GetInput); console.BindMethod("GetTextFont", &Console::GetTextFont); console.BindMethod("IsValidHandle", &ConsoleHandle::IsValid); - console.BindMethod("IsVisible", &Console::IsVisible); - - console.BindMethod("SendCharacter", &Console::SendCharacter); - //consoleClass.SetMethod("SendEvent", &Console::SendEvent); console.BindMethod("SetCharacterSize", &Console::SetCharacterSize); - console.BindMethod("SetSize", &Console::SetSize); console.BindMethod("SetTextFont", &Console::SetTextFont); - - console.BindMethod("Show", &Console::Show, true); } #endif diff --git a/SDK/src/NDK/Widgets/ScrollAreaWidget.cpp b/SDK/src/NDK/Widgets/ScrollAreaWidget.cpp new file mode 100644 index 000000000..0d7c1f934 --- /dev/null +++ b/SDK/src/NDK/Widgets/ScrollAreaWidget.cpp @@ -0,0 +1,198 @@ +// Copyright (C) 2019 Jérôme Leclercq +// This file is part of the "Nazara Development Kit" +// For conditions of distribution and use, see copyright notice in Prerequisites.hpp + +#include +#include +#include +#include + +namespace Ndk +{ + namespace + { + constexpr float scrollbarPadding = 5.f; + } + + ScrollAreaWidget::ScrollAreaWidget(BaseWidget* parent, BaseWidget* content) : + BaseWidget(parent), + m_content(content), + m_scrollbarStatus(ScrollBarStatus::None), + m_isScrollbarEnabled(true), + m_scrollRatio(0.f) + { + m_content->SetParent(this); + m_content->SetPosition(Nz::Vector3f::Zero()); + + m_scrollbarBackgroundSprite = Nz::Sprite::New(); + m_scrollbarBackgroundSprite->SetColor(Nz::Color(62, 62, 62)); + + m_scrollbarBackgroundEntity = CreateEntity(); + m_scrollbarBackgroundEntity->AddComponent().SetParent(this); + m_scrollbarBackgroundEntity->AddComponent().Attach(m_scrollbarBackgroundSprite, 1); + + m_scrollbarSprite = Nz::Sprite::New(); + m_scrollbarSprite->SetColor(Nz::Color(104, 104, 104)); + + m_scrollbarEntity = CreateEntity(); + m_scrollbarEntity->AddComponent().SetParent(this); + m_scrollbarEntity->AddComponent().Attach(m_scrollbarSprite); + + Resize(m_content->GetSize()); + } + + void ScrollAreaWidget::EnableScrollbar(bool enable) + { + if (m_isScrollbarEnabled != enable) + { + m_isScrollbarEnabled = enable; + + bool isVisible = IsScrollbarVisible(); + m_scrollbarEntity->Enable(isVisible); + m_scrollbarBackgroundEntity->Enable(isVisible); + } + } + + void ScrollAreaWidget::ScrollToRatio(float ratio) + { + m_scrollRatio = Nz::Clamp(ratio, 0.f, 1.f); + + float widgetHeight = GetHeight(); + float maxHeight = widgetHeight - m_scrollbarSprite->GetSize().y - 2.f * scrollbarPadding; + + auto& scrollbarNode = m_scrollbarEntity->GetComponent(); + scrollbarNode.SetPosition(Nz::Vector2f(scrollbarNode.GetPosition(Nz::CoordSys_Local).x, scrollbarPadding + m_scrollRatio * maxHeight)); + + float contentPosition = m_scrollRatio * (widgetHeight - m_content->GetHeight()); + + m_content->SetPosition(0.f, contentPosition); + m_content->SetRenderingRect(Nz::Rectf(-std::numeric_limits::infinity(), -contentPosition, std::numeric_limits::infinity(), widgetHeight)); + } + + Nz::Rectf ScrollAreaWidget::GetScrollbarRect() const + { + Nz::Vector2f scrollBarPosition = Nz::Vector2f(m_scrollbarEntity->GetComponent().GetPosition(Nz::CoordSys_Local)); + Nz::Vector2f scrollBarSize = m_scrollbarSprite->GetSize(); + return Nz::Rectf(scrollBarPosition.x, scrollBarPosition.y, scrollBarSize.x, scrollBarSize.y); + } + + void ScrollAreaWidget::Layout() + { + constexpr float scrollBarBackgroundWidth = 20.f; + constexpr float scrollBarWidth = scrollBarBackgroundWidth - 2.f * scrollbarPadding; + + float areaHeight = GetHeight(); + float contentHeight = m_content->GetHeight(); + + if (contentHeight > areaHeight) + { + m_hasScrollbar = true; + + Nz::Vector2f contentSize(GetWidth() - scrollBarBackgroundWidth, contentHeight); + m_content->Resize(contentSize); + + if (m_isScrollbarEnabled) + { + m_scrollbarEntity->Enable(); + m_scrollbarBackgroundEntity->Enable(); + } + + float scrollBarHeight = std::max(std::floor(areaHeight * (areaHeight / contentHeight)), 20.f); + + m_scrollbarBackgroundSprite->SetSize(scrollBarBackgroundWidth, areaHeight); + m_scrollbarSprite->SetSize(scrollBarWidth, scrollBarHeight); + + m_scrollbarBackgroundEntity->GetComponent().SetPosition(contentSize.x, 0.f); + m_scrollbarEntity->GetComponent().SetPosition(contentSize.x + (scrollBarBackgroundWidth - scrollBarWidth) / 2.f, 0.f); + + ScrollToRatio(m_scrollRatio); + } + else + { + m_hasScrollbar = false; + + m_content->Resize(GetSize()); + + m_scrollbarEntity->Disable(); + m_scrollbarBackgroundEntity->Disable(); + + ScrollToRatio(0.f); + } + + BaseWidget::Layout(); + } + + void ScrollAreaWidget::OnMouseButtonPress(int x, int y, Nz::Mouse::Button button) + { + if (button != Nz::Mouse::Left) + return; + + if (m_scrollbarStatus == ScrollBarStatus::Hovered) + { + UpdateScrollbarStatus(ScrollBarStatus::Grabbed); + + auto& scrollbarNode = m_scrollbarEntity->GetComponent(); + + m_grabbedDelta.Set(x, int(y - scrollbarNode.GetPosition(Nz::CoordSys_Local).y)); + } + } + + void ScrollAreaWidget::OnMouseButtonRelease(int x, int y, Nz::Mouse::Button button) + { + if (button != Nz::Mouse::Left) + return; + + if (m_scrollbarStatus == ScrollBarStatus::Grabbed) + { + Nz::Rectf scrollBarRect = GetScrollbarRect(); + UpdateScrollbarStatus((scrollBarRect.Contains(Nz::Vector2f(float(x), float(y)))) ? ScrollBarStatus::Hovered : ScrollBarStatus::None); + } + } + + void ScrollAreaWidget::OnMouseExit() + { + //if (m_scrollbarStatus == ScrollBarStatus::Hovered) + UpdateScrollbarStatus(ScrollBarStatus::None); + } + + void ScrollAreaWidget::OnMouseMoved(int x, int y, int /*deltaX*/, int /*deltaY*/) + { + if (m_scrollbarStatus == ScrollBarStatus::Grabbed) + { + float height = GetHeight(); + float maxHeight = height - m_scrollbarSprite->GetSize().y; + float newHeight = Nz::Clamp(float(y - m_grabbedDelta.y), 0.f, maxHeight); + + ScrollToHeight(newHeight / maxHeight * m_content->GetHeight()); + } + else + { + Nz::Rectf scrollBarRect = GetScrollbarRect(); + UpdateScrollbarStatus((scrollBarRect.Contains(Nz::Vector2f(float(x), float(y)))) ? ScrollBarStatus::Hovered : ScrollBarStatus::None); + } + } + + void ScrollAreaWidget::OnMouseWheelMoved(int /*x*/, int /*y*/, float delta) + { + constexpr float scrollStep = 100.f; + + ScrollToHeight(GetScrollHeight() - scrollStep * delta); + } + + void ScrollAreaWidget::UpdateScrollbarStatus(ScrollBarStatus status) + { + if (m_scrollbarStatus != status) + { + Nz::Color newColor; + switch (status) + { + case ScrollBarStatus::Grabbed: newColor = Nz::Color(235, 235, 235); break; + case ScrollBarStatus::Hovered: newColor = Nz::Color(152, 152, 152); break; + case ScrollBarStatus::None: newColor = Nz::Color(104, 104, 104); break; + } + + m_scrollbarSprite->SetColor(newColor); + m_scrollbarStatus = status; + } + } +} diff --git a/SDK/src/NDK/Widgets/TextAreaWidget.cpp b/SDK/src/NDK/Widgets/TextAreaWidget.cpp index 09355c09c..03a6c30b6 100644 --- a/SDK/src/NDK/Widgets/TextAreaWidget.cpp +++ b/SDK/src/NDK/Widgets/TextAreaWidget.cpp @@ -10,29 +10,38 @@ namespace Ndk { + namespace + { + constexpr float paddingWidth = 5.f; + constexpr float paddingHeight = 3.f; + } + TextAreaWidget::TextAreaWidget(BaseWidget* parent) : BaseWidget(parent), m_characterFilter(), m_echoMode(EchoMode_Normal), m_cursorPositionBegin(0U, 0U), m_cursorPositionEnd(0U, 0U), + m_isLineWrapEnabled(false), m_isMouseButtonDown(false), m_multiLineEnabled(false), m_readOnly(false), m_tabEnabled(false) { - m_cursorEntity = CreateEntity(); - m_cursorEntity->AddComponent(); - m_cursorEntity->AddComponent().SetParent(this); - m_cursorEntity->GetComponent().SetPosition(5.f, 3.f); - m_cursorEntity->Enable(false); - m_textSprite = Nz::TextSprite::New(); m_textEntity = CreateEntity(); m_textEntity->AddComponent().Attach(m_textSprite); - m_textEntity->AddComponent().SetParent(this); - m_textEntity->GetComponent().SetPosition(5.f, 3.f); + + auto& textNode = m_textEntity->AddComponent(); + textNode.SetParent(this); + textNode.SetPosition(paddingWidth, paddingHeight); + + m_cursorEntity = CreateEntity(); + m_cursorEntity->AddComponent(); + m_cursorEntity->AddComponent().SetParent(m_textEntity); + m_cursorEntity->GetComponent(); + m_cursorEntity->Enable(false); SetCursor(Nz::SystemCursor_Text); SetCharacterSize(GetCharacterSize()); //< Actualize minimum / preferred size @@ -72,11 +81,26 @@ namespace Ndk } } - m_textSprite->Update(m_drawer); + UpdateTextSprite(); OnTextChanged(this, m_text); } + void TextAreaWidget::EnableLineWrap(bool enable) + { + if (m_isLineWrapEnabled != enable) + { + m_isLineWrapEnabled = enable; + + if (enable) + m_drawer.SetMaxLineWidth(GetWidth()); + else + m_drawer.SetMaxLineWidth(std::numeric_limits::infinity()); + + UpdateTextSprite(); + } + } + void TextAreaWidget::Erase(std::size_t firstGlyph, std::size_t lastGlyph) { if (firstGlyph > lastGlyph) @@ -116,6 +140,11 @@ namespace Ndk Nz::Vector2ui TextAreaWidget::GetHoveredGlyph(float x, float y) const { + auto& textNode = m_textEntity->GetComponent(); + Nz::Vector2f textPosition = Nz::Vector2f(textNode.GetPosition(Nz::CoordSys_Local)); + x -= textPosition.x; + y -= textPosition.y; + std::size_t glyphCount = m_drawer.GetGlyphCount(); if (glyphCount > 0) { @@ -139,7 +168,7 @@ namespace Ndk break; } - return Nz::Vector2ui(i - firstLineGlyph, line); + return Nz::Vector2ui(Nz::Vector2(i - firstLineGlyph, line)); } return Nz::Vector2ui::Zero(); @@ -163,7 +192,6 @@ namespace Ndk Nz::Vector2f size = { float(spaceAdvance), float(lineHeight) + 5.f }; SetMinimumSize(size); - SetPreferredSize({ size.x * 6.f, size.y }); } void TextAreaWidget::Write(const Nz::String& text, std::size_t glyphPosition) @@ -186,6 +214,12 @@ namespace Ndk { BaseWidget::Layout(); + if (m_isLineWrapEnabled) + { + m_drawer.SetMaxLineWidth(GetWidth()); + UpdateTextSprite(); + } + RefreshCursor(); } @@ -209,6 +243,39 @@ namespace Ndk { switch (key.code) { + case Nz::Keyboard::Backspace: + { + bool ignoreDefaultAction = false; + OnTextAreaKeyBackspace(this, &ignoreDefaultAction); + + std::size_t cursorGlyphBegin = GetGlyphIndex(m_cursorPositionBegin); + std::size_t cursorGlyphEnd = GetGlyphIndex(m_cursorPositionEnd); + + if (ignoreDefaultAction || cursorGlyphEnd == 0) + return true; + + // When a text is selected, delete key does the same as delete and leave the character behind it + if (HasSelection()) + EraseSelection(); + else + { + Nz::String newText; + + if (cursorGlyphBegin > 1) + newText.Append(m_text.SubString(0, m_text.GetCharacterPosition(cursorGlyphBegin - 1) - 1)); + + if (cursorGlyphEnd < m_text.GetLength()) + newText.Append(m_text.SubString(m_text.GetCharacterPosition(cursorGlyphEnd))); + + // Move cursor before setting text (to prevent SetText to move our cursor) + MoveCursor(-1); + + SetText(newText); + } + + return true; + } + case Nz::Keyboard::Delete: { if (HasSelection()) @@ -301,6 +368,24 @@ namespace Ndk return true; } + case Nz::Keyboard::Return: + { + bool ignoreDefaultAction = false; + OnTextAreaKeyReturn(this, &ignoreDefaultAction); + + if (ignoreDefaultAction) + return true; + + if (!m_multiLineEnabled) + break; + + if (HasSelection()) + EraseSelection(); + + Write(Nz::String('\n')); + return true;; + } + case Nz::Keyboard::Right: { bool ignoreDefaultAction = false; @@ -378,14 +463,14 @@ namespace Ndk { Erase(firstGlyph); SetSelection(cursorPositionBegin - (cursorPositionBegin.y == line && cursorPositionBegin.x != 0U ? Nz::Vector2ui { 1U, 0U } : Nz::Vector2ui {}), - cursorPositionEnd - (cursorPositionEnd.y == line && cursorPositionEnd.x != 0U ? Nz::Vector2ui { 1U, 0U } : Nz::Vector2ui {})); + cursorPositionEnd - (cursorPositionEnd.y == line && cursorPositionEnd.x != 0U ? Nz::Vector2ui { 1U, 0U } : Nz::Vector2ui {})); } } else { Write(Nz::String('\t'), { 0U, line }); SetSelection(cursorPositionBegin + (cursorPositionBegin.y == line && cursorPositionBegin.x != 0U ? Nz::Vector2ui { 1U, 0U } : Nz::Vector2ui {}), - cursorPositionEnd + (cursorPositionEnd.y == line ? Nz::Vector2ui { 1U, 0U } : Nz::Vector2ui {})); + cursorPositionEnd + (cursorPositionEnd.y == line ? Nz::Vector2ui { 1U, 0U } : Nz::Vector2ui {})); } } } @@ -408,8 +493,10 @@ namespace Ndk } default: - return false; + break; } + + return false; } void TextAreaWidget::OnKeyReleased(const Nz::WindowEvent::KeyEvent& /*key*/) @@ -422,7 +509,7 @@ namespace Ndk { SetFocus(); - Nz::Vector2ui hoveredGlyph = GetHoveredGlyph(float(x) - 5.f, float(y) - 5.f); + Nz::Vector2ui hoveredGlyph = GetHoveredGlyph(float(x), float(y)); // Shift extends selection if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::LShift) || Nz::Keyboard::IsKeyPressed(Nz::Keyboard::RShift)) @@ -452,7 +539,7 @@ namespace Ndk void TextAreaWidget::OnMouseMoved(int x, int y, int deltaX, int deltaY) { if (m_isMouseButtonDown) - SetSelection(m_selectionCursor, GetHoveredGlyph(float(x) - 5.f, float(y) - 3.f)); + SetSelection(m_selectionCursor, GetHoveredGlyph(float(x), float(y))); } void TextAreaWidget::OnTextEntered(char32_t character, bool /*repeated*/) @@ -460,68 +547,13 @@ namespace Ndk if (m_readOnly) return; - switch (character) - { - case '\b': - { - bool ignoreDefaultAction = false; - OnTextAreaKeyBackspace(this, &ignoreDefaultAction); + if (Nz::Unicode::GetCategory(character) == Nz::Unicode::Category_Other_Control || (m_characterFilter && !m_characterFilter(character))) + return; - std::size_t cursorGlyphBegin = GetGlyphIndex(m_cursorPositionBegin); - std::size_t cursorGlyphEnd = GetGlyphIndex(m_cursorPositionEnd); + if (HasSelection()) + EraseSelection(); - if (ignoreDefaultAction || cursorGlyphEnd == 0) - break; - - // When a text is selected, delete key does the same as delete and leave the character behind it - if (HasSelection()) - EraseSelection(); - else - { - Nz::String newText; - - if (cursorGlyphBegin > 1) - newText.Append(m_text.SubString(0, m_text.GetCharacterPosition(cursorGlyphBegin - 1) - 1)); - - if (cursorGlyphEnd < m_text.GetLength()) - newText.Append(m_text.SubString(m_text.GetCharacterPosition(cursorGlyphEnd))); - - // Move cursor before setting text (to prevent SetText to move our cursor) - MoveCursor(-1); - - SetText(newText); - } - break; - } - - case '\r': - case '\n': - { - bool ignoreDefaultAction = false; - OnTextAreaKeyReturn(this, &ignoreDefaultAction); - - if (ignoreDefaultAction || !m_multiLineEnabled) - break; - - if (HasSelection()) - EraseSelection(); - - Write(Nz::String('\n')); - break; - } - - default: - { - if (Nz::Unicode::GetCategory(character) == Nz::Unicode::Category_Other_Control || (m_characterFilter && !m_characterFilter(character))) - break; - - if (HasSelection()) - EraseSelection(); - - Write(Nz::String::Unicode(character)); - break; - } - } + Write(Nz::String::Unicode(character)); } void TextAreaWidget::RefreshCursor() @@ -529,6 +561,45 @@ namespace Ndk if (m_readOnly) return; + auto GetGlyph = [&](const Nz::Vector2ui& glyphPosition, std::size_t* glyphIndex) -> const Nz::AbstractTextDrawer::Glyph* + { + const auto& lineInfo = m_drawer.GetLine(glyphPosition.y); + + std::size_t cursorGlyph = GetGlyphIndex({ glyphPosition.x, glyphPosition.y }); + if (glyphIndex) + *glyphIndex = cursorGlyph; + + std::size_t glyphCount = m_drawer.GetGlyphCount(); + if (glyphCount > 0 && lineInfo.glyphIndex < cursorGlyph) + { + const auto& glyph = m_drawer.GetGlyph(std::min(cursorGlyph, glyphCount - 1)); + return &glyph; + } + else + return nullptr; + }; + + // Move text so that cursor is always visible + const auto* lastGlyph = GetGlyph(m_cursorPositionEnd, nullptr); + float glyphPos = (lastGlyph) ? lastGlyph->bounds.x : 0.f; + float glyphWidth = (lastGlyph) ? lastGlyph->bounds.width : 0.f; + + auto& node = m_textEntity->GetComponent(); + float textPosition = node.GetPosition(Nz::CoordSys_Local).x - paddingWidth; + float cursorPosition = glyphPos + textPosition; + float width = GetWidth(); + + if (width <= m_drawer.GetBounds().width) + { + if (cursorPosition + glyphWidth > width) + node.Move(width - cursorPosition - glyphWidth, 0.f); + else if (cursorPosition - glyphWidth < 0.f) + node.Move(-cursorPosition + glyphWidth, 0.f); + } + else + node.Move(-textPosition, 0.f); // Reset text position if we have enough room to show everything + + // Show cursor/selection std::size_t selectionLineCount = m_cursorPositionEnd.y - m_cursorPositionBegin.y + 1; std::size_t oldSpriteCount = m_cursorSprites.size(); if (m_cursorSprites.size() != selectionLineCount) @@ -553,27 +624,24 @@ namespace Ndk Nz::SpriteRef& cursorSprite = m_cursorSprites[i - m_cursorPositionBegin.y]; if (i == m_cursorPositionBegin.y || i == m_cursorPositionEnd.y) { - auto GetGlyphPos = [&](unsigned int localGlyphPos) + auto GetGlyphPos = [&](const Nz::Vector2ui& glyphPosition) { - std::size_t cursorGlyph = GetGlyphIndex({ localGlyphPos, i }); - - std::size_t glyphCount = m_drawer.GetGlyphCount(); - float position; - if (glyphCount > 0 && lineInfo.glyphIndex < cursorGlyph) + std::size_t glyphIndex; + const auto* glyph = GetGlyph(glyphPosition, &glyphIndex); + if (glyph) { - const auto& glyph = m_drawer.GetGlyph(std::min(cursorGlyph, glyphCount - 1)); - position = glyph.bounds.x; - if (cursorGlyph >= glyphCount) - position += glyph.bounds.width; + float position = glyph->bounds.x; + if (glyphIndex >= m_drawer.GetGlyphCount()) + position += glyph->bounds.width; + + return position; } else - position = 0.f; - - return position; + return 0.f; }; - float beginX = (i == m_cursorPositionBegin.y) ? GetGlyphPos(m_cursorPositionBegin.x) : 0.f; - float endX = (i == m_cursorPositionEnd.y) ? GetGlyphPos(m_cursorPositionEnd.x) : lineInfo.bounds.width; + float beginX = (i == m_cursorPositionBegin.y) ? GetGlyphPos({ m_cursorPositionBegin.x, i }) : 0.f; + float endX = (i == m_cursorPositionEnd.y) ? GetGlyphPos({ m_cursorPositionEnd.x, i }) : lineInfo.bounds.width; float spriteSize = std::max(endX - beginX, 1.f); cursorSprite->SetColor((m_cursorPositionBegin == m_cursorPositionEnd) ? Nz::Color::Black : Nz::Color(0, 0, 0, 50)); @@ -605,8 +673,14 @@ namespace Ndk break; } - m_textSprite->Update(m_drawer); + UpdateTextSprite(); SetCursorPosition(m_cursorPositionBegin); //< Refresh cursor position (prevent it from being outside of the text) } + + void TextAreaWidget::UpdateTextSprite() + { + m_textSprite->Update(m_drawer); + SetPreferredSize(Nz::Vector2f(m_textSprite->GetBoundingVolume().obb.localBox.GetLengths())); + } } diff --git a/include/Nazara/Platform/Event.hpp b/include/Nazara/Platform/Event.hpp index 1fe219e8e..02a842889 100644 --- a/include/Nazara/Platform/Event.hpp +++ b/include/Nazara/Platform/Event.hpp @@ -55,6 +55,8 @@ namespace Nz struct MouseWheelEvent { float delta; + int x; + int y; }; // Used by: diff --git a/include/Nazara/Utility/SimpleTextDrawer.hpp b/include/Nazara/Utility/SimpleTextDrawer.hpp index 5b4225805..525f49cc5 100644 --- a/include/Nazara/Utility/SimpleTextDrawer.hpp +++ b/include/Nazara/Utility/SimpleTextDrawer.hpp @@ -38,6 +38,7 @@ namespace Nz std::size_t GetGlyphCount() const override; const Line& GetLine(std::size_t index) const override; std::size_t GetLineCount() const override; + float GetMaxLineWidth() const; const Color& GetOutlineColor() const; float GetOutlineThickness() const; TextStyleFlags GetStyle() const; @@ -46,6 +47,7 @@ namespace Nz void SetCharacterSize(unsigned int characterSize); void SetColor(const Color& color); void SetFont(Font* font); + void SetMaxLineWidth(float lineWidth); void SetOutlineColor(const Color& color); void SetOutlineThickness(float thickness); void SetStyle(TextStyleFlags style); @@ -60,21 +62,29 @@ namespace Nz static SimpleTextDrawer Draw(Font* font, const String& str, unsigned int characterSize, TextStyleFlags style, const Color& color, float outlineThickness, const Color& outlineColor); private: + void AppendNewLine() const; + void AppendNewLine(std::size_t glyphIndex, unsigned int glyphPosition) const; void ClearGlyphs() const; void ConnectFontSlots(); void DisconnectFontSlots(); + bool GenerateGlyph(Glyph& glyph, char32_t character, float outlineThickness, bool lineWrap, Nz::Color color, int renderOrder, int* advance) const; void GenerateGlyphs(const String& text) const; void OnFontAtlasLayerChanged(const Font* font, AbstractImage* oldLayer, AbstractImage* newLayer); void OnFontInvalidated(const Font* font); void OnFontRelease(const Font* object); + bool ShouldLineWrap(Glyph& glyph, float size, bool checkFirstGlyph = true) const; void UpdateGlyphColor() const; void UpdateGlyphs() const; + static constexpr std::size_t InvalidGlyph = std::numeric_limits::max(); + NazaraSlot(Font, OnFontAtlasChanged, m_atlasChangedSlot); NazaraSlot(Font, OnFontAtlasLayerChanged, m_atlasLayerChangedSlot); NazaraSlot(Font, OnFontGlyphCacheCleared, m_glyphCacheClearedSlot); NazaraSlot(Font, OnFontRelease, m_fontReleaseSlot); + mutable std::size_t m_lastSeparatorGlyph; + mutable unsigned int m_lastSeparatorPosition; mutable std::vector m_glyphs; mutable std::vector m_lines; Color m_color; @@ -88,6 +98,7 @@ namespace Nz mutable Vector2ui m_drawPos; mutable bool m_colorUpdated; mutable bool m_glyphUpdated; + float m_maxLineWidth; float m_outlineThickness; unsigned int m_characterSize; }; diff --git a/plugins/Assimp/Plugin.cpp b/plugins/Assimp/Plugin.cpp index 5295d2f5a..3f1e5200a 100644 --- a/plugins/Assimp/Plugin.cpp +++ b/plugins/Assimp/Plugin.cpp @@ -255,14 +255,14 @@ MeshRef LoadMesh(Stream& stream, const MeshParams& parameters) bool animatedMesh = false; if (parameters.animated) { - for (unsigned int i = 0; i < scene->mNumMeshes; ++i) + for (unsigned int meshIdx = 0; meshIdx < scene->mNumMeshes; ++meshIdx) { - aiMesh* currentMesh = scene->mMeshes[i]; + aiMesh* currentMesh = scene->mMeshes[meshIdx]; if (currentMesh->HasBones()) // Inline functions can be safely called { animatedMesh = true; - for (unsigned int j = 0; j < currentMesh->mNumBones; ++j) - joints.insert(currentMesh->mBones[j]->mName.C_Str()); + for (unsigned int boneIdx = 0; boneIdx < currentMesh->mNumBones; ++boneIdx) + joints.insert(currentMesh->mBones[boneIdx]->mName.C_Str()); } } } @@ -284,9 +284,9 @@ MeshRef LoadMesh(Stream& stream, const MeshParams& parameters) // aiMaterial index in scene => Material index and data in Mesh std::unordered_map> materials; - for (unsigned int i = 0; i < scene->mNumMeshes; ++i) + for (unsigned int meshIdx = 0; meshIdx < scene->mNumMeshes; ++meshIdx) { - aiMesh* iMesh = scene->mMeshes[i]; + aiMesh* iMesh = scene->mMeshes[meshIdx]; if (iMesh->HasBones()) { // For now, process only skeletal meshes @@ -303,9 +303,9 @@ MeshRef LoadMesh(Stream& stream, const MeshParams& parameters) IndexMapper indexMapper(indexBuffer, BufferAccess_DiscardAndWrite); IndexIterator index = indexMapper.begin(); - for (unsigned int j = 0; j < iMesh->mNumFaces; ++j) + for (unsigned int faceIdx = 0; faceIdx < iMesh->mNumFaces; ++faceIdx) { - aiFace& face = iMesh->mFaces[j]; + aiFace& face = iMesh->mFaces[faceIdx]; if (face.mNumIndices != 3) NazaraWarning("Assimp plugin: This face is not a triangle!"); @@ -324,30 +324,30 @@ MeshRef LoadMesh(Stream& stream, const MeshParams& parameters) BufferMapper vertexMapper(vertexBuffer, BufferAccess_ReadWrite); SkeletalMeshVertex* vertices = static_cast(vertexMapper.GetPointer()); - for (std::size_t i = 0; i < vertexCount; ++i) + for (std::size_t vertexIdx = 0; vertexIdx < vertexCount; ++vertexIdx) { - aiVector3D normal = iMesh->mNormals[i]; - aiVector3D position = iMesh->mVertices[i]; - aiVector3D tangent = iMesh->mTangents[i]; - aiVector3D uv = iMesh->mTextureCoords[0][i]; + aiVector3D normal = iMesh->mNormals[vertexIdx]; + aiVector3D position = iMesh->mVertices[vertexIdx]; + aiVector3D tangent = iMesh->mTangents[vertexIdx]; + aiVector3D uv = iMesh->mTextureCoords[0][vertexIdx]; - vertices[i].weightCount = 0; - vertices[i].normal = normalTangentMatrix.Transform({ normal.x, normal.y, normal.z }, 0.f); - vertices[i].position = parameters.matrix * Vector3f(position.x, position.y, position.z); - vertices[i].tangent = normalTangentMatrix.Transform({ tangent.x, tangent.y, tangent.z }, 0.f); - vertices[i].uv = parameters.texCoordOffset + Vector2f(uv.x, uv.y) * parameters.texCoordScale; + vertices[vertexIdx].weightCount = 0; + vertices[vertexIdx].normal = normalTangentMatrix.Transform({ normal.x, normal.y, normal.z }, 0.f); + vertices[vertexIdx].position = parameters.matrix * Vector3f(position.x, position.y, position.z); + vertices[vertexIdx].tangent = normalTangentMatrix.Transform({ tangent.x, tangent.y, tangent.z }, 0.f); + vertices[vertexIdx].uv = parameters.texCoordOffset + Vector2f(uv.x, uv.y) * parameters.texCoordScale; } - for (unsigned int i = 0; i < iMesh->mNumBones; ++i) + for (unsigned int boneIdx = 0; boneIdx < iMesh->mNumBones; ++boneIdx) { - aiBone* bone = iMesh->mBones[i]; - for (unsigned int j = 0; j < bone->mNumWeights; ++j) + aiBone* bone = iMesh->mBones[boneIdx]; + for (unsigned int weightIdx = 0; weightIdx < bone->mNumWeights; ++weightIdx) { - aiVertexWeight& vertexWeight = bone->mWeights[j]; + aiVertexWeight& vertexWeight = bone->mWeights[weightIdx]; SkeletalMeshVertex& vertex = vertices[vertexWeight.mVertexId]; std::size_t weightIndex = vertex.weightCount++; - vertex.jointIndexes[weightIndex] = i; + vertex.jointIndexes[weightIndex] = boneIdx; vertex.weights[weightIndex] = vertexWeight.mWeight; } } @@ -445,9 +445,9 @@ MeshRef LoadMesh(Stream& stream, const MeshParams& parameters) // aiMaterial index in scene => Material index and data in Mesh std::unordered_map> materials; - for (unsigned int i = 0; i < scene->mNumMeshes; ++i) + for (unsigned int meshIdx = 0; meshIdx < scene->mNumMeshes; ++meshIdx) { - aiMesh* iMesh = scene->mMeshes[i]; + aiMesh* iMesh = scene->mMeshes[meshIdx]; if (!iMesh->HasBones()) // Don't process skeletal meshs { unsigned int indexCount = iMesh->mNumFaces * 3; @@ -461,9 +461,9 @@ MeshRef LoadMesh(Stream& stream, const MeshParams& parameters) IndexMapper indexMapper(indexBuffer, BufferAccess_DiscardAndWrite); IndexIterator index = indexMapper.begin(); - for (unsigned int j = 0; j < iMesh->mNumFaces; ++j) + for (unsigned int faceIdx = 0; faceIdx < iMesh->mNumFaces; ++faceIdx) { - aiFace& face = iMesh->mFaces[j]; + aiFace& face = iMesh->mFaces[faceIdx]; if (face.mNumIndices != 3) NazaraWarning("Assimp plugin: This face is not a triangle!"); @@ -485,17 +485,17 @@ MeshRef LoadMesh(Stream& stream, const MeshParams& parameters) VertexMapper vertexMapper(vertexBuffer, BufferAccess_DiscardAndWrite); auto posPtr = vertexMapper.GetComponentPtr(VertexComponent_Position); - for (unsigned int j = 0; j < vertexCount; ++j) + for (unsigned int vertexIdx = 0; vertexIdx < vertexCount; ++vertexIdx) { - aiVector3D position = iMesh->mVertices[j]; + aiVector3D position = iMesh->mVertices[vertexIdx]; *posPtr++ = parameters.matrix * Vector3f(position.x, position.y, position.z); } if (auto normalPtr = vertexMapper.GetComponentPtr(VertexComponent_Normal)) { - for (unsigned int j = 0; j < vertexCount; ++j) + for (unsigned int vertexIdx = 0; vertexIdx < vertexCount; ++vertexIdx) { - aiVector3D normal = iMesh->mNormals[j]; + aiVector3D normal = iMesh->mNormals[vertexIdx]; *normalPtr++ = normalTangentMatrix.Transform({normal.x, normal.y, normal.z}, 0.f); } } @@ -505,9 +505,9 @@ MeshRef LoadMesh(Stream& stream, const MeshParams& parameters) { if (iMesh->HasTangentsAndBitangents()) { - for (unsigned int j = 0; j < vertexCount; ++j) + for (unsigned int vertexIdx = 0; vertexIdx < vertexCount; ++vertexIdx) { - aiVector3D tangent = iMesh->mTangents[j]; + aiVector3D tangent = iMesh->mTangents[vertexIdx]; *tangentPtr++ = normalTangentMatrix.Transform({tangent.x, tangent.y, tangent.z}, 0.f); } } @@ -519,15 +519,15 @@ MeshRef LoadMesh(Stream& stream, const MeshParams& parameters) { if (iMesh->HasTextureCoords(0)) { - for (unsigned int j = 0; j < vertexCount; ++j) + for (unsigned int vertexIdx = 0; vertexIdx < vertexCount; ++vertexIdx) { - aiVector3D uv = iMesh->mTextureCoords[0][j]; + aiVector3D uv = iMesh->mTextureCoords[0][vertexIdx]; *uvPtr++ = parameters.texCoordOffset + Vector2f(uv.x, uv.y) * parameters.texCoordScale; } } else { - for (unsigned int j = 0; j < vertexCount; ++j) + for (unsigned int vertexIdx = 0; vertexIdx < vertexCount; ++vertexIdx) *uvPtr++ = Vector2f::Zero(); } } diff --git a/src/Nazara/Physics2D/PhysWorld2D.cpp b/src/Nazara/Physics2D/PhysWorld2D.cpp index 1390bc84f..6e0c9c22a 100644 --- a/src/Nazara/Physics2D/PhysWorld2D.cpp +++ b/src/Nazara/Physics2D/PhysWorld2D.cpp @@ -403,11 +403,7 @@ namespace Nz const Callback* customCallbacks = static_cast(data); if (customCallbacks->startCallback(*world, arbiter, *firstRigidBody, *secondRigidBody, customCallbacks->userdata)) - { - cpBool retA = cpArbiterCallWildcardBeginA(arb, space); - cpBool retB = cpArbiterCallWildcardBeginB(arb, space); - return retA && retB; - } + return cpTrue; else return cpFalse; }; @@ -416,9 +412,7 @@ namespace Nz { handler->beginFunc = [](cpArbiter* arb, cpSpace* space, void*) -> cpBool { - cpBool retA = cpArbiterCallWildcardBeginA(arb, space); - cpBool retB = cpArbiterCallWildcardBeginB(arb, space); - return retA && retB; + return cpTrue; }; } @@ -438,17 +432,12 @@ namespace Nz const Callback* customCallbacks = static_cast(data); customCallbacks->endCallback(*world, arbiter, *firstRigidBody, *secondRigidBody, customCallbacks->userdata); - - cpArbiterCallWildcardSeparateA(arb, space); - cpArbiterCallWildcardSeparateB(arb, space); }; } else { handler->separateFunc = [](cpArbiter* arb, cpSpace* space, void*) { - cpArbiterCallWildcardSeparateA(arb, space); - cpArbiterCallWildcardSeparateB(arb, space); }; } @@ -468,11 +457,7 @@ namespace Nz const Callback* customCallbacks = static_cast(data); if (customCallbacks->preSolveCallback(*world, arbiter, *firstRigidBody, *secondRigidBody, customCallbacks->userdata)) - { - cpBool retA = cpArbiterCallWildcardPreSolveA(arb, space); - cpBool retB = cpArbiterCallWildcardPreSolveB(arb, space); - return retA && retB; - } + return cpTrue; else return cpFalse; }; @@ -481,9 +466,7 @@ namespace Nz { handler->preSolveFunc = [](cpArbiter* arb, cpSpace* space, void* data) -> cpBool { - cpBool retA = cpArbiterCallWildcardPreSolveA(arb, space); - cpBool retB = cpArbiterCallWildcardPreSolveB(arb, space); - return retA && retB; + return cpTrue; }; } @@ -503,17 +486,12 @@ namespace Nz const Callback* customCallbacks = static_cast(data); customCallbacks->postSolveCallback(*world, arbiter, *firstRigidBody, *secondRigidBody, customCallbacks->userdata); - - cpArbiterCallWildcardPostSolveA(arb, space); - cpArbiterCallWildcardPostSolveB(arb, space); }; } else { handler->postSolveFunc = [](cpArbiter* arb, cpSpace* space, void* data) { - cpArbiterCallWildcardPostSolveA(arb, space); - cpArbiterCallWildcardPostSolveB(arb, space); }; } } diff --git a/src/Nazara/Platform/Win32/WindowImpl.cpp b/src/Nazara/Platform/Win32/WindowImpl.cpp index 27b4fa889..6ee3365ac 100644 --- a/src/Nazara/Platform/Win32/WindowImpl.cpp +++ b/src/Nazara/Platform/Win32/WindowImpl.cpp @@ -712,7 +712,10 @@ namespace Nz { WindowEvent event; event.type = WindowEventType_MouseWheelMoved; - event.mouseWheel.delta = static_cast(GET_WHEEL_DELTA_WPARAM(wParam))/WHEEL_DELTA; + event.mouseWheel.delta = static_cast(GET_WHEEL_DELTA_WPARAM(wParam)) / WHEEL_DELTA; + event.mouseWheel.x = GET_X_LPARAM(lParam); + event.mouseWheel.y = GET_Y_LPARAM(lParam); + m_parent->PushEvent(event); } else @@ -722,7 +725,10 @@ namespace Nz { WindowEvent event; event.type = WindowEventType_MouseWheelMoved; - event.mouseWheel.delta = static_cast(m_scrolling/WHEEL_DELTA); + event.mouseWheel.delta = static_cast(m_scrolling / WHEEL_DELTA); + event.mouseWheel.x = GET_X_LPARAM(lParam); + event.mouseWheel.y = GET_Y_LPARAM(lParam); + m_parent->PushEvent(event); m_scrolling %= WHEEL_DELTA; diff --git a/src/Nazara/Platform/X11/WindowImpl.cpp b/src/Nazara/Platform/X11/WindowImpl.cpp index 0e8215e37..abf2db675 100644 --- a/src/Nazara/Platform/X11/WindowImpl.cpp +++ b/src/Nazara/Platform/X11/WindowImpl.cpp @@ -1233,6 +1233,8 @@ namespace Nz { event.type = Nz::WindowEventType_MouseWheelMoved; event.mouseWheel.delta = (buttonReleaseEvent->detail == XCB_BUTTON_INDEX_4) ? 1 : -1; + event.mouseWheel.x = buttonReleaseEvent->event_x; + event.mouseWheel.y = buttonReleaseEvent->event_y; break; } default: diff --git a/src/Nazara/Utility/SimpleTextDrawer.cpp b/src/Nazara/Utility/SimpleTextDrawer.cpp index 86ba50764..6bf0830df 100644 --- a/src/Nazara/Utility/SimpleTextDrawer.cpp +++ b/src/Nazara/Utility/SimpleTextDrawer.cpp @@ -3,6 +3,7 @@ // For conditions of distribution and use, see copyright notice in Config.hpp #include +#include #include #include @@ -14,6 +15,7 @@ namespace Nz m_style(TextStyle_Regular), m_colorUpdated(true), m_glyphUpdated(true), + m_maxLineWidth(std::numeric_limits::infinity()), m_outlineThickness(0.f), m_characterSize(24) { @@ -27,6 +29,7 @@ namespace Nz m_colorUpdated(false), m_glyphUpdated(false), m_outlineColor(drawer.m_outlineColor), + m_maxLineWidth(drawer.m_maxLineWidth), m_outlineThickness(drawer.m_outlineThickness), m_characterSize(drawer.m_characterSize) { @@ -124,6 +127,11 @@ namespace Nz return m_lines.size(); } + float SimpleTextDrawer::GetMaxLineWidth() const + { + return m_maxLineWidth; + } + const Color& SimpleTextDrawer::GetOutlineColor() const { return m_outlineColor; @@ -173,6 +181,15 @@ namespace Nz } } + void SimpleTextDrawer::SetMaxLineWidth(float lineWidth) + { + NazaraAssert(m_maxLineWidth > 0.f, "Max line width must be positive"); + + m_maxLineWidth = lineWidth; + + m_glyphUpdated = false; + } + void SimpleTextDrawer::SetOutlineColor(const Color& color) { m_outlineColor = color; @@ -230,6 +247,7 @@ namespace Nz m_glyphs = std::move(drawer.m_glyphs); m_glyphUpdated = std::move(drawer.m_glyphUpdated); m_font = std::move(drawer.m_font); + m_maxLineWidth = drawer.m_maxLineWidth; m_outlineColor = std::move(drawer.m_outlineColor); m_outlineThickness = std::move(drawer.m_outlineThickness); m_style = std::move(drawer.m_style); @@ -291,11 +309,66 @@ namespace Nz return drawer; } + void SimpleTextDrawer::AppendNewLine() const + { + AppendNewLine(InvalidGlyph, 0.f); + } + + void SimpleTextDrawer::AppendNewLine(std::size_t glyphIndex, unsigned int glyphPosition) const + { + // Ensure we're appending from last line + Line& lastLine = m_lines.back(); + + const Font::SizeInfo& sizeInfo = m_font->GetSizeInfo(m_characterSize); + + unsigned int previousDrawPos = m_drawPos.x; + + // Reset cursor + m_drawPos.x = 0; + m_drawPos.y += sizeInfo.lineHeight; + m_lastSeparatorGlyph = InvalidGlyph; + + m_workingBounds.ExtendTo(lastLine.bounds); + m_lines.emplace_back(Line{ Rectf(0.f, float(sizeInfo.lineHeight * m_lines.size()), 0.f, float(sizeInfo.lineHeight)), m_glyphs.size() + 1 }); + + if (glyphIndex != InvalidGlyph && glyphIndex > lastLine.glyphIndex) + { + Line& newLine = m_lines.back(); + newLine.glyphIndex = glyphIndex; + + for (std::size_t i = glyphIndex; i < m_glyphs.size(); ++i) + { + Glyph& glyph = m_glyphs[i]; + glyph.bounds.x -= glyphPosition; + glyph.bounds.y += sizeInfo.lineHeight; + + for (auto& corner : glyph.corners) + { + corner.x -= glyphPosition; + corner.y += sizeInfo.lineHeight; + } + + newLine.bounds.ExtendTo(glyph.bounds); + } + + assert(previousDrawPos >= glyphPosition); + m_drawPos.x += previousDrawPos - glyphPosition; + + lastLine.bounds.width -= lastLine.bounds.GetMaximum().x - m_lastSeparatorPosition; + + // Regenerate working bounds + m_workingBounds.MakeZero(); + for (std::size_t i = 0; i < m_lines.size(); ++i) + m_workingBounds.ExtendTo(m_lines[i].bounds); + } + } + void SimpleTextDrawer::ClearGlyphs() const { m_bounds.MakeZero(); m_colorUpdated = true; m_drawPos.Set(0, m_characterSize); //< Our draw "cursor" + m_lastSeparatorGlyph = InvalidGlyph; m_lines.clear(); m_glyphs.clear(); m_glyphUpdated = true; @@ -324,6 +397,46 @@ namespace Nz m_glyphCacheClearedSlot.Disconnect(); } + bool SimpleTextDrawer::GenerateGlyph(Glyph& glyph, char32_t character, float outlineThickness, bool lineWrap, Nz::Color color, int renderOrder, int* advance) const + { + const Font::Glyph& fontGlyph = m_font->GetGlyph(m_characterSize, m_style, outlineThickness, character); + if (fontGlyph.valid && fontGlyph.fauxOutlineThickness <= 0.f) + { + glyph.atlas = m_font->GetAtlas()->GetLayer(fontGlyph.layerIndex); + glyph.atlasRect = fontGlyph.atlasRect; + glyph.color = color; + glyph.flipped = fontGlyph.flipped; + glyph.renderOrder = renderOrder; + + glyph.bounds.Set(fontGlyph.aabb); + + if (lineWrap && ShouldLineWrap(glyph, glyph.bounds.width)) + AppendNewLine(m_lastSeparatorGlyph, m_lastSeparatorPosition); + + glyph.bounds.x += m_drawPos.x; + glyph.bounds.y += m_drawPos.y; + + // Faux bold and faux outline thickness are not supported + + // We "lean" the glyph to simulate italics style + float italic = (fontGlyph.requireFauxItalic) ? 0.208f : 0.f; + float italicTop = italic * glyph.bounds.y; + float italicBottom = italic * glyph.bounds.GetMaximum().y; + + glyph.corners[0].Set(glyph.bounds.x - italicTop - outlineThickness, glyph.bounds.y - outlineThickness); + glyph.corners[1].Set(glyph.bounds.x + glyph.bounds.width - italicTop - outlineThickness, glyph.bounds.y - outlineThickness); + glyph.corners[2].Set(glyph.bounds.x - italicBottom - outlineThickness, glyph.bounds.y + glyph.bounds.height - outlineThickness); + glyph.corners[3].Set(glyph.bounds.x + glyph.bounds.width - italicBottom - outlineThickness, glyph.bounds.y + glyph.bounds.height - outlineThickness); + + if (advance) + *advance = fontGlyph.advance; + + return true; + } + else + return false; + }; + void SimpleTextDrawer::GenerateGlyphs(const String& text) const { if (text.IsEmpty()) @@ -365,63 +478,30 @@ namespace Nz break; } - auto GenerateGlyph = [this](Glyph& glyph, char32_t character, float outlineThickness, Nz::Color color, int renderOrder, int* advance) - { - const Font::Glyph& fontGlyph = m_font->GetGlyph(m_characterSize, m_style, outlineThickness, character); - if (fontGlyph.valid && fontGlyph.fauxOutlineThickness <= 0.f) - { - glyph.atlas = m_font->GetAtlas()->GetLayer(fontGlyph.layerIndex); - glyph.atlasRect = fontGlyph.atlasRect; - glyph.color = color; - glyph.flipped = fontGlyph.flipped; - glyph.renderOrder = renderOrder; - - glyph.bounds.Set(fontGlyph.aabb); - glyph.bounds.x += m_drawPos.x; - glyph.bounds.y += m_drawPos.y; - - // Faux bold and faux outline thickness are not supported - - // We "lean" the glyph to simulate italics style - float italic = (fontGlyph.requireFauxItalic) ? 0.208f : 0.f; - float italicTop = italic * glyph.bounds.y; - float italicBottom = italic * glyph.bounds.GetMaximum().y; - - glyph.corners[0].Set(glyph.bounds.x - italicTop - outlineThickness, glyph.bounds.y - outlineThickness); - glyph.corners[1].Set(glyph.bounds.x + glyph.bounds.width - italicTop - outlineThickness, glyph.bounds.y - outlineThickness); - glyph.corners[2].Set(glyph.bounds.x - italicBottom - outlineThickness, glyph.bounds.y + glyph.bounds.height - outlineThickness); - glyph.corners[3].Set(glyph.bounds.x + glyph.bounds.width - italicBottom - outlineThickness, glyph.bounds.y + glyph.bounds.height - outlineThickness); - - if (advance) - *advance = fontGlyph.advance; - - return true; - } - else - return false; - }; - Glyph glyph; if (!whitespace) { - if (!GenerateGlyph(glyph, character, 0.f, m_color, 0, &advance)) + if (!GenerateGlyph(glyph, character, 0.f, true, m_color, 0, &advance)) continue; // Glyph failed to load, just skip it (can't do much) if (m_outlineThickness > 0.f) { Glyph outlineGlyph; - if (GenerateGlyph(outlineGlyph, character, m_outlineThickness, m_outlineColor, -1, nullptr)) + if (GenerateGlyph(outlineGlyph, character, m_outlineThickness, false, m_outlineColor, -1, nullptr)) { - m_lines.back().bounds.ExtendTo(outlineGlyph.bounds); m_glyphs.push_back(outlineGlyph); } } } else { - glyph.atlas = nullptr; + float glyphAdvance = advance; - glyph.bounds.Set(float(m_drawPos.x), m_lines.back().bounds.y, float(advance), float(sizeInfo.lineHeight)); + if (ShouldLineWrap(glyph, glyphAdvance)) + AppendNewLine(m_lastSeparatorGlyph, m_lastSeparatorPosition); + + glyph.atlas = nullptr; + glyph.bounds.Set(float(m_drawPos.x), m_lines.back().bounds.y, glyphAdvance, float(sizeInfo.lineHeight)); glyph.corners[0].Set(glyph.bounds.GetCorner(RectCorner_LeftTop)); glyph.corners[1].Set(glyph.bounds.GetCorner(RectCorner_RightTop)); @@ -435,13 +515,7 @@ namespace Nz { case '\n': { - // Reset cursor - advance = 0; - m_drawPos.x = 0; - m_drawPos.y += sizeInfo.lineHeight; - - m_workingBounds.ExtendTo(m_lines.back().bounds); - m_lines.emplace_back(Line{Rectf(0.f, float(sizeInfo.lineHeight * m_lines.size()), 0.f, float(sizeInfo.lineHeight)), m_glyphs.size() + 1}); + AppendNewLine(); break; } @@ -450,6 +524,12 @@ namespace Nz break; } + if (whitespace) + { + m_lastSeparatorGlyph = m_glyphs.size(); + m_lastSeparatorPosition = m_drawPos.x; + } + m_glyphs.push_back(glyph); } @@ -513,6 +593,14 @@ namespace Nz SetFont(nullptr); } + bool SimpleTextDrawer::ShouldLineWrap(Glyph& glyph, float size, bool checkFirstGlyph) const + { + if (checkFirstGlyph && m_lines.back().glyphIndex > m_glyphs.size()) + return false; + + return m_lines.back().bounds.GetMaximum().x + size > m_maxLineWidth; + } + void SimpleTextDrawer::UpdateGlyphColor() const { if (m_outlineThickness > 0.f) diff --git a/tests/Engine/Network/IpAddress.cpp b/tests/Engine/Network/IpAddress.cpp index abcb65909..571e7ec75 100644 --- a/tests/Engine/Network/IpAddress.cpp +++ b/tests/Engine/Network/IpAddress.cpp @@ -40,7 +40,9 @@ SCENARIO("IpAddress", "[NETWORK][IPADDRESS]") Nz::IpAddress google(8, 8, 8, 8); THEN("Google (DNS) is 8.8.8.8") { - CHECK(Nz::IpAddress::ResolveAddress(google) == "google-public-dns-a.google.com"); + Nz::String dnsAddress = Nz::IpAddress::ResolveAddress(google); + bool dnsCheck = dnsAddress == "google-public-dns-a.google.com" || dnsAddress == "dns.google"; + CHECK(dnsCheck); } } } diff --git a/thirdparty/src/chipmunk/chipmunk.c b/thirdparty/src/chipmunk/chipmunk.c index 503265cf6..a6cc9d6d4 100644 --- a/thirdparty/src/chipmunk/chipmunk.c +++ b/thirdparty/src/chipmunk/chipmunk.c @@ -89,7 +89,7 @@ cpAreaForSegment(cpVect a, cpVect b, cpFloat r) } cpFloat -cpMomentForPoly(cpFloat m, const int count, const cpVect *verts, cpVect offset, cpFloat r) +cpMomentForPoly(cpFloat m, int count, const cpVect *verts, cpVect offset, cpFloat r) { // TODO account for radius. if(count == 2) return cpMomentForSegment(m, verts[0], verts[1], 0.0f);