From a654c4b4508ff085c0370a32fada8f80d839020c Mon Sep 17 00:00:00 2001 From: Ryo Suzuki Date: Wed, 30 Aug 2023 22:17:06 +0900 Subject: [PATCH] =?UTF-8?q?UI1=20=E9=80=94=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Siv3D/include/Siv3D.hpp | 2 + Siv3D/include/Siv3D/UI1/ColorRect.hpp | 47 +++++ Siv3D/include/Siv3D/UI1/Margin.hpp | 30 +++ Siv3D/include/Siv3D/UI1/UIContainer.hpp | 9 + Siv3D/include/Siv3D/UI1/UIElement.hpp | 28 ++- Siv3D/include/Siv3D/UI1/UIFwd.hpp | 8 + Siv3D/include/Siv3D/UI1/detail/Margin.ipp | 40 ++++ Siv3D/src/Siv3D/UI1/SivColorRect.cpp | 38 ++++ Siv3D/src/Siv3D/UI1/SivUICanvas.cpp | 2 +- Siv3D/src/Siv3D/UI1/SivUIContainer.cpp | 246 +++++++++++++++++++++- Siv3D/src/Siv3D/UI1/SivUIElement.cpp | 47 ++++- Siv3D/src/Siv3D/UI1/SivUIPanel.cpp | 22 +- WindowsDesktop/Siv3D.vcxproj | 2 + WindowsDesktop/Siv3D.vcxproj.filters | 6 + 14 files changed, 510 insertions(+), 17 deletions(-) create mode 100644 Siv3D/include/Siv3D/UI1/ColorRect.hpp create mode 100644 Siv3D/src/Siv3D/UI1/SivColorRect.cpp diff --git a/Siv3D/include/Siv3D.hpp b/Siv3D/include/Siv3D.hpp index 62e9f80cd..5738d8a25 100644 --- a/Siv3D/include/Siv3D.hpp +++ b/Siv3D/include/Siv3D.hpp @@ -1718,6 +1718,8 @@ # include +# include + ////////////////////////////////////////////////// // // エフェクト | Effect diff --git a/Siv3D/include/Siv3D/UI1/ColorRect.hpp b/Siv3D/include/Siv3D/UI1/ColorRect.hpp new file mode 100644 index 000000000..851534ef2 --- /dev/null +++ b/Siv3D/include/Siv3D/UI1/ColorRect.hpp @@ -0,0 +1,47 @@ +//----------------------------------------------- +// +// This file is part of the Siv3D Engine. +// +// Copyright (c) 2008-2023 Ryo Suzuki +// Copyright (c) 2016-2023 OpenSiv3D Project +// +// Licensed under the MIT License. +// +//----------------------------------------------- + +# pragma once +# include "../Common.hpp" +# include "../PointVector.hpp" +# include "../ColorHSV.hpp" +# include "UIElement.hpp" + +namespace s3d +{ + inline namespace UI1 + { + class ColorRect final : public UIElement + { + public: + + SIV3D_NODISCARD_CXX20 + ColorRect() = default; + + SIV3D_NODISCARD_CXX20 + explicit ColorRect(const SizeF& size, const ColorF& color = ColorF{ 1.0 }); + + [[nodiscard]] + static std::shared_ptr Create(const SizeF& size, const ColorF& color = ColorF{ 1.0 }); + + private: + + [[nodiscard]] + SizeF getSize() const noexcept override; + + void onDraw() const override; + + SizeF m_size = { 0, 0 }; + + ColorF m_color = ColorF{ 1.0 }; + }; + } +} diff --git a/Siv3D/include/Siv3D/UI1/Margin.hpp b/Siv3D/include/Siv3D/UI1/Margin.hpp index 4fd702355..97e41c610 100644 --- a/Siv3D/include/Siv3D/UI1/Margin.hpp +++ b/Siv3D/include/Siv3D/UI1/Margin.hpp @@ -11,6 +11,7 @@ # pragma once # include "../Common.hpp" +# include "../Interpolation.hpp" namespace s3d { @@ -58,6 +59,35 @@ namespace s3d SIV3D_NODISCARD_CXX20 constexpr Margin(double _top, double _right, double _bottom, double _left) noexcept; + [[nodiscard]] + constexpr Vec2 topLeft() const noexcept; + + [[nodiscard]] + constexpr Vec2 topRight() const noexcept; + + [[nodiscard]] + constexpr Vec2 bottomRight() const noexcept; + + [[nodiscard]] + constexpr Vec2 bottomLeft() const noexcept; + + /// @brief 左辺と右辺のマージンの合計を返します。 | Returns the sum of the left and right margin. + /// @return 左辺と右辺のマージンの合計 | The sum of the left and right margin + [[nodiscard]] + constexpr double totalWidth() const noexcept; + + /// @brief 上辺と下辺のマージンの合計を返します。 | Returns the sum of the top and bottom margin. + /// @return 上辺と下辺のマージンの合計 | The sum of the top and bottom margin + [[nodiscard]] + constexpr double totalHeight() const noexcept; + + /// @brief 2 つのマージンを線形補間します。 | Performs a linear interpolation between two margins. + /// @param other 他方のマージン | The other margin + /// @param f 補間係数 | The interpolation coefficient + /// @return 補間結果 | The result of interpolation + [[nodiscard]] + constexpr Margin lerp(const Margin& other, double f) const noexcept; + [[nodiscard]] static constexpr Margin Zero() noexcept; diff --git a/Siv3D/include/Siv3D/UI1/UIContainer.hpp b/Siv3D/include/Siv3D/UI1/UIContainer.hpp index 3b4b9a7ec..08b53e955 100644 --- a/Siv3D/include/Siv3D/UI1/UIContainer.hpp +++ b/Siv3D/include/Siv3D/UI1/UIContainer.hpp @@ -99,6 +99,15 @@ namespace s3d std::weak_ptr m_pCanvas; + [[nodiscard]] + bool onUpdateHelper(bool cursorCapturable, bool shapeMouseOver, const Padding& padding, const std::function& resizeFunction); + + void onDrawHelper(const Padding& padding) const; + + void onDrawOverlayHelper(const Padding& padding) const; + + void onDrawDebugHelper(const Padding& padding) const; + private: UIContainerName m_name; diff --git a/Siv3D/include/Siv3D/UI1/UIElement.hpp b/Siv3D/include/Siv3D/UI1/UIElement.hpp index 847e06467..972eb332c 100644 --- a/Siv3D/include/Siv3D/UI1/UIElement.hpp +++ b/Siv3D/include/Siv3D/UI1/UIElement.hpp @@ -30,9 +30,13 @@ namespace s3d virtual ~UIElement(); + /// @brief UI 要素のサイズ(ピクセル)を返します。 + /// @return UI 要素のサイズ(ピクセル) [[nodiscard]] virtual SizeF getSize() const noexcept = 0; + /// @brief UI 要素のマージンを返します。 + /// @return UI 要素のマージン [[nodiscard]] virtual Margin getMargin() const noexcept; @@ -66,17 +70,37 @@ namespace s3d protected: + /// @brief UI 要素の更新処理を記述します。 + /// @param cursorCapturable キャプチャできるマウスカーソルがあるか + /// @return UI 要素がマウスを遮った場合 true, それ以外の場合は false virtual bool onUpdate(bool cursorCapturable); /// @brief UI 要素の描画処理を記述します。 - virtual void onDraw() const; + virtual void onDraw() const {} /// @brief UI 要素のオーバーレイ描画処理を記述します。 - virtual void onDrawOverlay() const; + virtual void onDrawOverlay() const {} /// @brief UI 要素のデバッグ描画処理を記述します。 virtual void onDrawDebug() const; + /// @brief 要素が押されたときに呼び出される関数 + virtual void onPressed() {} + + /// @brief 要素が離されたときに呼び出される関数 + virtual void onReleased() {} + + /// @brief 要素がクリックされたときに呼び出される関数 + virtual void onClicked() {} + + /// @brief 要素がホバーされたときに呼び出される関数 + virtual void onHovered() {} + + /// @brief 要素がホバーされなくなったときに呼び出される関数 + virtual void onUnhovered() {} + + bool updateState(const bool cursorCapturable, const bool shapeMouseOver); + private: std::weak_ptr m_parentContainer; diff --git a/Siv3D/include/Siv3D/UI1/UIFwd.hpp b/Siv3D/include/Siv3D/UI1/UIFwd.hpp index c76b5b9bd..62e19a929 100644 --- a/Siv3D/include/Siv3D/UI1/UIFwd.hpp +++ b/Siv3D/include/Siv3D/UI1/UIFwd.hpp @@ -19,12 +19,20 @@ namespace s3d struct Padding; struct Margin; struct BoxShadow; + class UICanvas; + + using UIElementName = String; + using UIElementNameView = StringView; class UIElement; + using UIContainerName = String; using UIContainerNameView = StringView; class UIContainer; + class UIPanel; class UIWindow; + + class ColorRect; } } diff --git a/Siv3D/include/Siv3D/UI1/detail/Margin.ipp b/Siv3D/include/Siv3D/UI1/detail/Margin.ipp index e36ff489c..627421120 100644 --- a/Siv3D/include/Siv3D/UI1/detail/Margin.ipp +++ b/Siv3D/include/Siv3D/UI1/detail/Margin.ipp @@ -39,6 +39,46 @@ namespace s3d , bottom{ _bottom } , left{ _left } {} + inline constexpr Vec2 Margin::topLeft() const noexcept + { + return{ left, top }; + } + + inline constexpr Vec2 Margin::topRight() const noexcept + { + return{ right, top }; + } + + inline constexpr Vec2 Margin::bottomRight() const noexcept + { + return{ right, bottom }; + } + + inline constexpr Vec2 Margin::bottomLeft() const noexcept + { + return{ left, bottom }; + } + + inline constexpr double Margin::totalWidth() const noexcept + { + return (left + right); + } + + inline constexpr double Margin::totalHeight() const noexcept + { + return (top + bottom); + } + + inline constexpr Margin Margin::lerp(const Margin& other, const double f) const noexcept + { + return{ + Math::Lerp(top, other.top, f), + Math::Lerp(right, other.right, f), + Math::Lerp(bottom, other.bottom, f), + Math::Lerp(left, other.left, f) + }; + } + inline constexpr Margin Margin::Zero() noexcept { return{ 0, 0, 0, 0 }; diff --git a/Siv3D/src/Siv3D/UI1/SivColorRect.cpp b/Siv3D/src/Siv3D/UI1/SivColorRect.cpp new file mode 100644 index 000000000..803902f74 --- /dev/null +++ b/Siv3D/src/Siv3D/UI1/SivColorRect.cpp @@ -0,0 +1,38 @@ +//----------------------------------------------- +// +// This file is part of the Siv3D Engine. +// +// Copyright (c) 2008-2023 Ryo Suzuki +// Copyright (c) 2016-2023 OpenSiv3D Project +// +// Licensed under the MIT License. +// +//----------------------------------------------- + +# include +# include + +namespace s3d +{ + namespace UI1 + { + ColorRect::ColorRect(const SizeF& size, const ColorF& color) + : m_size{ size } + , m_color{ color } {} + + std::shared_ptr ColorRect::Create(const SizeF& size, const ColorF& color) + { + return std::make_shared(size, color); + } + + SizeF ColorRect::getSize() const noexcept + { + return m_size; + } + + void ColorRect::onDraw() const + { + RectF{ m_size }.draw(m_color); + } + } +} diff --git a/Siv3D/src/Siv3D/UI1/SivUICanvas.cpp b/Siv3D/src/Siv3D/UI1/SivUICanvas.cpp index 796ccc529..805c3092c 100644 --- a/Siv3D/src/Siv3D/UI1/SivUICanvas.cpp +++ b/Siv3D/src/Siv3D/UI1/SivUICanvas.cpp @@ -37,7 +37,7 @@ namespace s3d UIContainer& UICanvas::addContainer(const std::shared_ptr& container) { - return pImpl->addContainer(container); + return pImpl->addContainer(container)._setRoot(pImpl); } void UICanvas::removeContainer(const UIContainerNameView name) diff --git a/Siv3D/src/Siv3D/UI1/SivUIContainer.cpp b/Siv3D/src/Siv3D/UI1/SivUIContainer.cpp index d9aa9ee0a..f3e6f01eb 100644 --- a/Siv3D/src/Siv3D/UI1/SivUIContainer.cpp +++ b/Siv3D/src/Siv3D/UI1/SivUIContainer.cpp @@ -10,10 +10,70 @@ //----------------------------------------------- # include +# include +# include # include namespace s3d { + namespace detail + { + struct LineInfo + { + double areaWidth = 0.0; + + // ペンの X 座標 + double penPosX = 0.0; + + // 最後の要素の右マージン + double previousRightMargin = 0.0; + + // 前の行で最も Y 座標が大きい要素の Y 座標 + double previousMaxElementY = 0.0; + + // 前の行で最も Y 座標が大きいマージン + double previousMaxMarginY = 0.0; + + // 最も Y 座標が大きい要素の Y 座標 + double maxElementY = 0.0; + + // 最も Y 座標が大きいマージン + double maxMarginY = 0.0; + + void lineBreak() noexcept + { + penPosX = 0.0; + previousRightMargin = 0.0; + previousMaxElementY = maxElementY; + previousMaxMarginY = maxMarginY; + maxElementY = 0.0; + maxMarginY = 0.0; + } + + [[nodiscard]] + bool shouldBreak(const SizeF& elementSize, const Margin& elementMargin) const noexcept + { + const double leftMargin = Max(previousRightMargin, elementMargin.left); + const double rightMargin = elementMargin.right; + return (areaWidth < (penPosX + (leftMargin + elementSize.x + rightMargin))); + } + + [[nodiscard]] + double calculatePenPosY(const Margin& elementMargin) const noexcept + { + return Max(previousMaxMarginY, (previousMaxElementY + elementMargin.top)); + } + + void next(const double penPosY, const double leftMargin, const SizeF& elementSize, const Margin& elementMargin) + { + penPosX += (leftMargin + elementSize.x); + previousRightMargin = elementMargin.right; + maxElementY = Max(maxElementY, (penPosY + elementSize.y)); + maxMarginY = Max(maxMarginY, (penPosY + elementSize.y + elementMargin.bottom)); + } + }; + } + namespace UI1 { UIContainer::UIContainer(const UIContainerNameView name) @@ -78,12 +138,12 @@ namespace s3d if (shouldUpdate()) { - result += U"[update]"; + result += U".update "; } if (shouldDraw()) { - result += U"[draw]"; + result += U".draw "; } if (not isShown()) @@ -98,5 +158,187 @@ namespace s3d return result; } + + bool UIContainer::onUpdateHelper(bool cursorCapturable, const bool shapeMouseOver, const Padding& padding, const std::function& resizeFunction) + { + bool childHasCursorCapture = false; + bool childHasMouseCapture = false; + + // 子要素の配置を計算する + { + const Vec2 basePos = (getBounds().pos + padding.topLeft()); + const Transformer2D containerTransform{ Mat3x2::Translate(basePos), TransformCursor::Yes }; + + detail::LineInfo lineInfo + { + .areaWidth = (getBounds().w - padding.totalWidth()), + }; + + double areaHeight = (getBounds().h - padding.totalHeight()); + + for (const auto& element : m_elements) + { + // 要素のサイズ + const SizeF elementSize = element.element->getSize(); + + // 要素のマージン + const Margin elementMargin = element.element->getMargin(); + + // 改行が必要な場合 + if (lineInfo.shouldBreak(elementSize, elementMargin)) + { + lineInfo.lineBreak(); + } + + // 左マージン + const double leftMargin = Max(lineInfo.previousRightMargin, elementMargin.left); + + // 下マージン + const double bottomMargin = elementMargin.bottom; + + // ペンの Y 座標 + const double penPosY = lineInfo.calculatePenPosY(elementMargin); + + if (areaHeight < (penPosY + elementSize.y + bottomMargin)) + { + const SizeF newSize{ getSize().x, (penPosY + elementSize.y + bottomMargin + padding.totalHeight()) }; + resizeFunction(newSize); + } + + { + const Transformer2D elementTransform{ Mat3x2::Translate((lineInfo.penPosX + leftMargin), penPosY), TransformCursor::Yes }; + + if (element.element->update(cursorCapturable)) + { + cursorCapturable = false; + childHasCursorCapture = true; + } + + childHasMouseCapture |= element.element->hasMouseCapture(); + } + + lineInfo.next(penPosY, leftMargin, elementSize, elementMargin); + } + } + + return (updateState((cursorCapturable || childHasCursorCapture || childHasMouseCapture), shapeMouseOver) || childHasCursorCapture); + } + + void UIContainer::onDrawHelper(const Padding& padding) const + { + const Vec2 basePos = (getBounds().pos + padding.topLeft()); + const Transformer2D containerTransform{ Mat3x2::Translate(basePos), TransformCursor::Yes }; + + detail::LineInfo lineInfo + { + .areaWidth = (getBounds().w - padding.totalWidth()), + }; + + for (const auto& element : m_elements) + { + // 要素のサイズ + const SizeF elementSize = element.element->getSize(); + + // 要素のマージン + const Margin elementMargin = element.element->getMargin(); + + // 改行が必要な場合 + if (lineInfo.shouldBreak(elementSize, elementMargin)) + { + lineInfo.lineBreak(); + } + + // 左マージン + const double leftMargin = Max(lineInfo.previousRightMargin, elementMargin.left); + + // ペンの Y 座標 + const double penPosY = lineInfo.calculatePenPosY(elementMargin); + + { + const Transformer2D elementTransform{ Mat3x2::Translate((lineInfo.penPosX + leftMargin), penPosY), TransformCursor::Yes }; + element.element->draw(); + } + + lineInfo.next(penPosY, leftMargin, elementSize, elementMargin); + } + } + + void UIContainer::onDrawOverlayHelper(const Padding& padding) const + { + const Vec2 basePos = (getBounds().pos + padding.topLeft()); + const Transformer2D containerTransform{ Mat3x2::Translate(basePos), TransformCursor::Yes }; + + detail::LineInfo lineInfo + { + .areaWidth = (getBounds().w - padding.totalWidth()), + }; + + for (const auto& element : m_elements) + { + // 要素のサイズ + const SizeF elementSize = element.element->getSize(); + + // 要素のマージン + const Margin elementMargin = element.element->getMargin(); + + // 改行が必要な場合 + if (lineInfo.shouldBreak(elementSize, elementMargin)) + { + lineInfo.lineBreak(); + } + + // 左マージン + const double leftMargin = Max(lineInfo.previousRightMargin, elementMargin.left); + + // ペンの Y 座標 + const double penPosY = lineInfo.calculatePenPosY(elementMargin); + + { + const Transformer2D elementTransform{ Mat3x2::Translate((lineInfo.penPosX + leftMargin), penPosY), TransformCursor::Yes }; + element.element->drawOverlay(); + } + + lineInfo.next(penPosY, leftMargin, elementSize, elementMargin); + } + } + + void UIContainer::onDrawDebugHelper(const Padding& padding) const + { + const Vec2 basePos = (getBounds().pos + padding.topLeft()); + const Transformer2D containerTransform{ Mat3x2::Translate(basePos), TransformCursor::Yes }; + + detail::LineInfo lineInfo + { + .areaWidth = (getBounds().w - padding.totalWidth()), + }; + + for (const auto& element : m_elements) + { + // 要素のサイズ + const SizeF elementSize = element.element->getSize(); + + // 要素のマージン + const Margin elementMargin = element.element->getMargin(); + + // 改行が必要な場合 + if (lineInfo.shouldBreak(elementSize, elementMargin)) + { + lineInfo.lineBreak(); + } + + // 左マージン + const double leftMargin = Max(lineInfo.previousRightMargin, elementMargin.left); + + // ペンの Y 座標 + const double penPosY = lineInfo.calculatePenPosY(elementMargin); + + { + const Transformer2D elementTransform{ Mat3x2::Translate((lineInfo.penPosX + leftMargin), penPosY), TransformCursor::Yes }; + element.element->drawDebug(); + } + + lineInfo.next(penPosY, leftMargin, elementSize, elementMargin); + } + } } } diff --git a/Siv3D/src/Siv3D/UI1/SivUIElement.cpp b/Siv3D/src/Siv3D/UI1/SivUIElement.cpp index 3e5958831..f2ed1ee48 100644 --- a/Siv3D/src/Siv3D/UI1/SivUIElement.cpp +++ b/Siv3D/src/Siv3D/UI1/SivUIElement.cpp @@ -114,10 +114,6 @@ namespace s3d return false; } - void UIElement::onDraw() const {} - - void UIElement::onDrawOverlay() const {} - void UIElement::onDrawDebug() const { const RectF rect{ getSize() }; @@ -143,5 +139,48 @@ namespace s3d Circle{ rect.bl(), 10 }.drawFrame(3, 0, Palette::Red); } } + + bool UIElement::updateState(const bool cursorCapturable, const bool shapeMouseOver) + { + const bool prevHasMouseCapture = m_hasMouseCapture; + + if (isEnabled()) + { + if ((not isHovered()) && ((cursorCapturable || prevHasMouseCapture) && shapeMouseOver)) + { + setHovered(true); + onHovered(); + } + else if (isHovered() && ((not (cursorCapturable || prevHasMouseCapture)) || (not shapeMouseOver))) + { + setHovered(false); + onUnhovered(); + } + + if ((cursorCapturable && shapeMouseOver) && MouseL.down()) + { + setMouseCapture(true); + onPressed(); + } + else if (prevHasMouseCapture && (not MouseL.pressed())) + { + setMouseCapture(false); + onReleased(); + + if (shapeMouseOver) + { + onClicked(); + } + + return true; + } + + return isHovered(); + } + else + { + return (cursorCapturable && shapeMouseOver); + } + } } } diff --git a/Siv3D/src/Siv3D/UI1/SivUIPanel.cpp b/Siv3D/src/Siv3D/UI1/SivUIPanel.cpp index e9afada3c..c8b9887b4 100644 --- a/Siv3D/src/Siv3D/UI1/SivUIPanel.cpp +++ b/Siv3D/src/Siv3D/UI1/SivUIPanel.cpp @@ -10,6 +10,7 @@ //----------------------------------------------- # include + namespace s3d { namespace UI1 @@ -39,32 +40,37 @@ namespace s3d return m_rect; } - bool UIPanel::onUpdate(bool cursorCapturable) + bool UIPanel::onUpdate(const bool cursorCapturable) { - return(false); + return onUpdateHelper(cursorCapturable, getShape().mouseOver(), m_style.padding, [this](SizeF size){ setSize(size); }); } void UIPanel::onDraw() const { drawBackground(); - // 子要素を描画する - { - - } + onDrawHelper(m_style.padding); } void UIPanel::onDrawOverlay() const { - // 子要素を描画する - { + onDrawOverlayHelper(m_style.padding); + // 無効状態の場合、全体にオーバーレイを描画する + if (not isEnabled()) + { + if (m_style.disabledOverlayColor) + { + getShape().draw(*m_style.disabledOverlayColor); + } } } void UIPanel::onDrawDebug() const { drawDebugBackground(); + + onDrawDebugHelper(m_style.padding); } void UIPanel::setPos(const Vec2& pos) noexcept diff --git a/WindowsDesktop/Siv3D.vcxproj b/WindowsDesktop/Siv3D.vcxproj index 4edb87cdc..b7cd75567 100644 --- a/WindowsDesktop/Siv3D.vcxproj +++ b/WindowsDesktop/Siv3D.vcxproj @@ -753,6 +753,7 @@ + @@ -2506,6 +2507,7 @@ + diff --git a/WindowsDesktop/Siv3D.vcxproj.filters b/WindowsDesktop/Siv3D.vcxproj.filters index 47a227a4e..7826f6c96 100644 --- a/WindowsDesktop/Siv3D.vcxproj.filters +++ b/WindowsDesktop/Siv3D.vcxproj.filters @@ -7146,6 +7146,9 @@ include\Siv3D\UI1 + + include\Siv3D\UI1 + @@ -10373,6 +10376,9 @@ src\Siv3D\UI1 + + src\Siv3D\UI1 +