diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/application.hpp | 2 | ||||
-rw-r--r-- | src/math_util.hpp | 2 | ||||
-rw-r--r-- | src/ui/control.cpp | 2 | ||||
-rw-r--r-- | src/ui/controls/list_item.cpp | 2 | ||||
-rw-r--r-- | src/ui/controls/scroll_control.cpp | 2 | ||||
-rw-r--r-- | src/ui/controls/toggle_button.cpp | 2 | ||||
-rw-r--r-- | src/ui/convert_util.hpp | 21 | ||||
-rw-r--r-- | src/ui/d2d_util.hpp | 94 | ||||
-rw-r--r-- | src/ui/render/render_object.cpp | 98 | ||||
-rw-r--r-- | src/ui/render/render_object.hpp | 257 | ||||
-rw-r--r-- | src/ui/ui_base.hpp | 116 |
11 files changed, 564 insertions, 34 deletions
diff --git a/src/application.hpp b/src/application.hpp index a8d59cc8..8d739938 100644 --- a/src/application.hpp +++ b/src/application.hpp @@ -109,7 +109,7 @@ namespace cru std::unordered_map<std::type_index, Object*> singleton_map_; std::list<Object*> singleton_list_; // used for reverse destroy. #ifdef CRU_DEBUG - std::unordered_set<std::type_index> singleton_type_set_; // used for dead recursion. + std::unordered_set<std::type_index> singleton_type_set_; // used for detecting dead recursion. #endif }; diff --git a/src/math_util.hpp b/src/math_util.hpp index b9830d6b..8f0741b8 100644 --- a/src/math_util.hpp +++ b/src/math_util.hpp @@ -9,7 +9,7 @@ namespace cru { - template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>> + template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>> float Coerce(const T n, const std::optional<T> min, const std::optional<T> max) { if (min.has_value() && n < min.value()) diff --git a/src/ui/control.cpp b/src/ui/control.cpp index 3987e818..9388c719 100644 --- a/src/ui/control.cpp +++ b/src/ui/control.cpp @@ -8,7 +8,7 @@ #include "graph/graph.hpp" #include "exception.hpp" #include "cru_debug.hpp" -#include "convert_util.hpp" +#include "d2d_util.hpp" #include "math_util.hpp" #ifdef CRU_DEBUG_LAYOUT diff --git a/src/ui/controls/list_item.cpp b/src/ui/controls/list_item.cpp index e0ca28a9..6dd37fe9 100644 --- a/src/ui/controls/list_item.cpp +++ b/src/ui/controls/list_item.cpp @@ -1,7 +1,7 @@ #include "list_item.hpp" #include "ui/ui_manager.hpp" -#include "ui/convert_util.hpp" +#include "ui/d2d_util.hpp" namespace cru::ui::controls { diff --git a/src/ui/controls/scroll_control.cpp b/src/ui/controls/scroll_control.cpp index 622b4e4c..a202e355 100644 --- a/src/ui/controls/scroll_control.cpp +++ b/src/ui/controls/scroll_control.cpp @@ -3,7 +3,7 @@ #include <limits> #include "cru_debug.hpp" -#include "ui/convert_util.hpp" +#include "ui/d2d_util.hpp" #include "exception.hpp" #include "math_util.hpp" #include "ui/ui_manager.hpp" diff --git a/src/ui/controls/toggle_button.cpp b/src/ui/controls/toggle_button.cpp index 6eb0bc40..db72d7bb 100644 --- a/src/ui/controls/toggle_button.cpp +++ b/src/ui/controls/toggle_button.cpp @@ -3,7 +3,7 @@ #include "graph/graph.hpp" #include "ui/animations/animation.hpp" #include "ui/ui_manager.hpp" -#include "ui/convert_util.hpp" +#include "ui/d2d_util.hpp" namespace cru::ui::controls { diff --git a/src/ui/convert_util.hpp b/src/ui/convert_util.hpp deleted file mode 100644 index 5408f2e4..00000000 --- a/src/ui/convert_util.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -// ReSharper disable once CppUnusedIncludeDirective -#include "pre.hpp" - -#include "system_headers.hpp" - -#include "ui_base.hpp" - -namespace cru::ui -{ - inline D2D1_POINT_2F Convert(const Point& point) - { - return D2D1::Point2F(point.x, point.y); - } - - inline D2D1_RECT_F Convert(const Rect& rect) - { - return D2D1::RectF(rect.left, rect.top, rect.left + rect.width, rect.top + rect.height); - } -} diff --git a/src/ui/d2d_util.hpp b/src/ui/d2d_util.hpp new file mode 100644 index 00000000..5f2e10b2 --- /dev/null +++ b/src/ui/d2d_util.hpp @@ -0,0 +1,94 @@ +#pragma once + +// ReSharper disable once CppUnusedIncludeDirective +#include "pre.hpp" + +#include "system_headers.hpp" + +#include "ui_base.hpp" + +namespace cru::ui +{ + inline D2D1_POINT_2F Convert(const Point& point) + { + return D2D1::Point2F(point.x, point.y); + } + + inline D2D1_RECT_F Convert(const Rect& rect) + { + return D2D1::RectF(rect.left, rect.top, rect.left + rect.width, rect.top + rect.height); + } + + inline D2D1_ROUNDED_RECT Convert(const RoundedRect& rounded_rect) + { + return D2D1::RoundedRect(Convert(rounded_rect.rect), rounded_rect.radius_x, rounded_rect.radius_y); + } + + inline D2D1_ELLIPSE Convert(const Ellipse& ellipse) + { + return D2D1::Ellipse(Convert(ellipse.center), ellipse.radius_x, ellipse.radius_y); + } + + inline Point Convert(const D2D1_POINT_2F& point) + { + return Point(point.x, point.y); + } + + inline Rect Convert(const D2D1_RECT_F& rect) + { + return Rect(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); + } + + inline RoundedRect Convert(const D2D1_ROUNDED_RECT& rounded_rect) + { + return RoundedRect(Convert(rounded_rect.rect), rounded_rect.radiusX, rounded_rect.radiusY); + } + + inline Ellipse Convert(const D2D1_ELLIPSE& ellipse) + { + return Ellipse(Convert(ellipse.point), ellipse.radiusX, ellipse.radiusY); + } + + inline bool operator==(const D2D1_POINT_2F& left, const D2D1_POINT_2F& right) + { + return left.x == right.x && left.y == right.y; + } + + inline bool operator!=(const D2D1_POINT_2F& left, const D2D1_POINT_2F& right) + { + return !(left == right); + } + + inline bool operator==(const D2D1_RECT_F& left, const D2D1_RECT_F& right) + { + return left.left == right.left && + left.top == right.top && + left.right == right.right && + left.bottom == right.bottom; + } + + inline bool operator!=(const D2D1_RECT_F& left, const D2D1_RECT_F& right) + { + return !(left == right); + } + + inline bool operator==(const D2D1_ROUNDED_RECT& left, const D2D1_ROUNDED_RECT& right) + { + return left.rect == right.rect && left.radiusX == right.radiusX && left.radiusY == right.radiusY; + } + + inline bool operator!=(const D2D1_ROUNDED_RECT& left, const D2D1_ROUNDED_RECT& right) + { + return !(left == right); + } + + inline bool operator == (const D2D1_ELLIPSE& left, const D2D1_ELLIPSE& right) + { + return left.point == right.point && left.radiusX == right.radiusX && left.radiusY == right.radiusY; + } + + inline bool operator!=(const D2D1_ELLIPSE& left, const D2D1_ELLIPSE& right) + { + return !(left == right); + } +} diff --git a/src/ui/render/render_object.cpp b/src/ui/render/render_object.cpp new file mode 100644 index 00000000..0828fc9c --- /dev/null +++ b/src/ui/render/render_object.cpp @@ -0,0 +1,98 @@ +#include "render_object.hpp" +#include <utility> + +namespace cru::ui::render +{ + void RenderObject::SetRenderHost(IRenderHost* new_render_host) + { + if (new_render_host == render_host_) + return; + + const auto old = render_host_; + render_host_ = new_render_host; + OnRenderHostChanged(old, new_render_host); + } + + void RenderObject::OnRenderHostChanged(IRenderHost* old_render_host, IRenderHost* new_render_host) + { + + } + + void RenderObject::InvalidateRenderHost() + { + if (render_host_ != nullptr) + render_host_->InvalidateRender(); + } + + void StrokeRenderObject::SetStrokeWidth(const float new_stroke_width) + { + if (stroke_width_ == new_stroke_width) + return; + + stroke_width_ = new_stroke_width; + InvalidateRenderHost(); + } + + void StrokeRenderObject::SetBrush(Microsoft::WRL::ComPtr<ID2D1Brush> new_brush) + { + if (brush_ == new_brush) + return; + + brush_ = std::move(new_brush); + InvalidateRenderHost(); + } + + void StrokeRenderObject::SetStrokeStyle(Microsoft::WRL::ComPtr<ID2D1StrokeStyle> new_stroke_style) + { + if (stroke_style_ == new_stroke_style) + return; + + stroke_style_ = std::move(new_stroke_style); + InvalidateRenderHost(); + } + + void FillRenderObject::SetBrush(Microsoft::WRL::ComPtr<ID2D1Brush> new_brush) + { + if (brush_ == new_brush) + return; + + brush_ = std::move(new_brush); + InvalidateRenderHost(); + } + + namespace details + { + template class ShapeRenderObject<Rect>; + template class ShapeRenderObject<RoundedRect>; + template class ShapeRenderObject<Ellipse>; + } + + namespace details + { + template ShapeStrokeRenderObject<Rect, D2D1_RECT_F, &ID2D1RenderTarget::DrawRectangle>; + template ShapeStrokeRenderObject<RoundedRect, D2D1_ROUNDED_RECT, &ID2D1RenderTarget::DrawRoundedRectangle>; + template ShapeStrokeRenderObject<Ellipse, D2D1_ELLIPSE, &ID2D1RenderTarget::DrawEllipse>; + } + + namespace details + { + template ShapeFillRenderObject<Rect, D2D1_RECT_F, &ID2D1RenderTarget::FillRectangle>; + template ShapeFillRenderObject<RoundedRect, D2D1_ROUNDED_RECT, &ID2D1RenderTarget::FillRoundedRectangle>; + template ShapeFillRenderObject<Ellipse, D2D1_ELLIPSE, &ID2D1RenderTarget::FillEllipse>; + } + + void CustomDrawHandlerRenderObject::SetDrawHandler(DrawHandler new_draw_handler) + { + if (draw_handler_ == nullptr && new_draw_handler == nullptr) + return; + + draw_handler_ = std::move(new_draw_handler); + InvalidateRenderHost(); + } + + void CustomDrawHandlerRenderObject::Draw(ID2D1RenderTarget* render_target) + { + if (draw_handler_ != nullptr) + draw_handler_(render_target); + } +} diff --git a/src/ui/render/render_object.hpp b/src/ui/render/render_object.hpp new file mode 100644 index 00000000..31745be5 --- /dev/null +++ b/src/ui/render/render_object.hpp @@ -0,0 +1,257 @@ +#pragma once + +#include "pre.hpp" + +#include "system_headers.hpp" +#include <functional> +#include <cassert> + +#include "base.hpp" +#include "ui/ui_base.hpp" +#include "ui/d2d_util.hpp" + +namespace cru::ui::render +{ + /* About Render Object + * + * Render object is a concrete subclass of RenderObject class. + * It represents a painting action on a d2d render target. By + * overriding "Draw" virtual method, it can customize its painting + * action. + */ + + + struct IRenderHost : Interface + { + virtual void InvalidateRender() = 0; + }; + + + class RenderObject : public Object + { + protected: + RenderObject() = default; + public: + RenderObject(const RenderObject& other) = delete; + RenderObject(RenderObject&& other) = delete; + RenderObject& operator=(const RenderObject& other) = delete; + RenderObject& operator=(RenderObject&& other) = delete; + ~RenderObject() override = default; + + virtual void Draw(ID2D1RenderTarget* render_target) = 0; + + IRenderHost* GetRenderHost() const + { + return render_host_; + } + + void SetRenderHost(IRenderHost* new_render_host); + + protected: + virtual void OnRenderHostChanged(IRenderHost* old_render_host, IRenderHost* new_render_host); + + void InvalidateRenderHost(); + + private: + IRenderHost* render_host_ = nullptr; + }; + + + class StrokeRenderObject : public virtual RenderObject + { + protected: + StrokeRenderObject() = default; + public: + StrokeRenderObject(const StrokeRenderObject& other) = delete; + StrokeRenderObject(StrokeRenderObject&& other) = delete; + StrokeRenderObject& operator=(const StrokeRenderObject& other) = delete; + StrokeRenderObject& operator=(StrokeRenderObject&& other) = delete; + ~StrokeRenderObject() override = default; + + float GetStrokeWidth() const + { + return stroke_width_; + } + + void SetStrokeWidth(float new_stroke_width); + + Microsoft::WRL::ComPtr<ID2D1Brush> GetBrush() const + { + return brush_; + } + + void SetBrush(Microsoft::WRL::ComPtr<ID2D1Brush> new_brush); + + Microsoft::WRL::ComPtr<ID2D1StrokeStyle> GetStrokeStyle() const + { + return stroke_style_; + } + + void SetStrokeStyle(Microsoft::WRL::ComPtr<ID2D1StrokeStyle> new_stroke_style); + + private: + float stroke_width_ = 1.0f; + Microsoft::WRL::ComPtr<ID2D1Brush> brush_ = nullptr; + Microsoft::WRL::ComPtr<ID2D1StrokeStyle> stroke_style_ = nullptr; + }; + + + class FillRenderObject : public virtual RenderObject + { + protected: + FillRenderObject() = default; + public: + FillRenderObject(const FillRenderObject& other) = delete; + FillRenderObject(FillRenderObject&& other) = delete; + FillRenderObject& operator=(const FillRenderObject& other) = delete; + FillRenderObject& operator=(FillRenderObject&& other) = delete; + ~FillRenderObject() override = default; + + Microsoft::WRL::ComPtr<ID2D1Brush> GetBrush() const + { + return brush_; + } + + void SetBrush(Microsoft::WRL::ComPtr<ID2D1Brush> new_brush); + + private: + Microsoft::WRL::ComPtr<ID2D1Brush> brush_ = nullptr; + }; + + + namespace details + { + template <typename TShapeType> + class ShapeRenderObject : public virtual RenderObject + { + public: + using ShapeType = TShapeType; + protected: + ShapeRenderObject() = default; + public: + ShapeRenderObject(const ShapeRenderObject& other) = delete; + ShapeRenderObject& operator=(const ShapeRenderObject& other) = delete; + ShapeRenderObject(ShapeRenderObject&& other) = delete; + ShapeRenderObject& operator=(ShapeRenderObject&& other) = delete; + ~ShapeRenderObject() override = default; + + ShapeType GetShape() const + { + return shape_; + } + + void SetShape(const ShapeType& new_shape) + { + if (new_shape == shape_) + return; + + shape_ = new_shape; + InvalidateRenderHost(); + } + + private: + ShapeType shape_; + }; + + + extern template class ShapeRenderObject<Rect>; + extern template class ShapeRenderObject<RoundedRect>; + extern template class ShapeRenderObject<Ellipse>; + } + + + using RectangleRenderObject = details::ShapeRenderObject<Rect>; + using RoundedRectangleRenderObject = details::ShapeRenderObject<RoundedRect>; + using EllipseRenderObject = details::ShapeRenderObject<Ellipse>; + + + namespace details + { + template<typename TShapeType, typename TD2D1ShapeType, void (ID2D1RenderTarget::*draw_function)(const TD2D1ShapeType&, ID2D1Brush*, float, ID2D1StrokeStyle*)> + class ShapeStrokeRenderObject : public ShapeRenderObject<TShapeType>, public StrokeRenderObject + { + public: + ShapeStrokeRenderObject() = default; + ShapeStrokeRenderObject(const ShapeStrokeRenderObject& other) = delete; + ShapeStrokeRenderObject& operator=(const ShapeStrokeRenderObject& other) = delete; + ShapeStrokeRenderObject(ShapeStrokeRenderObject&& other) = delete; + ShapeStrokeRenderObject& operator=(ShapeStrokeRenderObject&& other) = delete; + ~ShapeStrokeRenderObject() = default; + + protected: + void Draw(ID2D1RenderTarget* render_target) override + { + const auto brush = GetBrush(); + if (brush != nullptr) + (render_target->*draw_function)(Convert(GetShape()), brush.Get(), GetStrokeWidth(), GetStrokeStyle().Get()); + } + }; + + extern template ShapeStrokeRenderObject<Rect, D2D1_RECT_F, &ID2D1RenderTarget::DrawRectangle>; + extern template ShapeStrokeRenderObject<RoundedRect, D2D1_ROUNDED_RECT, &ID2D1RenderTarget::DrawRoundedRectangle>; + extern template ShapeStrokeRenderObject<Ellipse, D2D1_ELLIPSE, &ID2D1RenderTarget::DrawEllipse>; + } + + using RectangleStrokeRenderObject = details::ShapeStrokeRenderObject<Rect, D2D1_RECT_F, &ID2D1RenderTarget::DrawRectangle>; + using RoundedRectangleStrokeRenderObject = details::ShapeStrokeRenderObject<RoundedRect, D2D1_ROUNDED_RECT, &ID2D1RenderTarget::DrawRoundedRectangle>; + using EllipseStrokeRenderObject = details::ShapeStrokeRenderObject<Ellipse, D2D1_ELLIPSE, &ID2D1RenderTarget::DrawEllipse>; + + + namespace details + { + template<typename TShapeType, typename TD2D1ShapeType, void (ID2D1RenderTarget::*fill_function)(const TD2D1ShapeType&, ID2D1Brush*)> + class ShapeFillRenderObject : public ShapeRenderObject<TShapeType>, public StrokeRenderObject + { + public: + ShapeFillRenderObject() = default; + ShapeFillRenderObject(const ShapeFillRenderObject& other) = delete; + ShapeFillRenderObject& operator=(const ShapeFillRenderObject& other) = delete; + ShapeFillRenderObject(ShapeFillRenderObject&& other) = delete; + ShapeFillRenderObject& operator=(ShapeFillRenderObject&& other) = delete; + ~ShapeFillRenderObject() = default; + + protected: + void Draw(ID2D1RenderTarget* render_target) override + { + const auto brush = GetBrush(); + if (brush != nullptr) + (render_target->*fill_function)(Convert(GetShape()), brush.Get()); + } + }; + + extern template ShapeFillRenderObject<Rect, D2D1_RECT_F, &ID2D1RenderTarget::FillRectangle>; + extern template ShapeFillRenderObject<RoundedRect, D2D1_ROUNDED_RECT, &ID2D1RenderTarget::FillRoundedRectangle>; + extern template ShapeFillRenderObject<Ellipse, D2D1_ELLIPSE, &ID2D1RenderTarget::FillEllipse>; + } + + using RectangleFillRenderObject = details::ShapeFillRenderObject<Rect, D2D1_RECT_F, &ID2D1RenderTarget::FillRectangle>; + using RoundedRectangleFillRenderObject = details::ShapeFillRenderObject<RoundedRect, D2D1_ROUNDED_RECT, &ID2D1RenderTarget::FillRoundedRectangle>; + using EllipseFillRenderObject = details::ShapeFillRenderObject<Ellipse, D2D1_ELLIPSE, &ID2D1RenderTarget::FillEllipse>; + + + class CustomDrawHandlerRenderObject : public RenderObject + { + public: + using DrawHandler = std::function<void(ID2D1RenderTarget*)>; + + CustomDrawHandlerRenderObject() = default; + CustomDrawHandlerRenderObject(const CustomDrawHandlerRenderObject& other) = delete; + CustomDrawHandlerRenderObject& operator=(const CustomDrawHandlerRenderObject& other) = delete; + CustomDrawHandlerRenderObject(CustomDrawHandlerRenderObject&& other) = delete; + CustomDrawHandlerRenderObject& operator=(CustomDrawHandlerRenderObject&& other) = delete; + ~CustomDrawHandlerRenderObject() override = default; + + DrawHandler GetDrawHandler() const + { + return draw_handler_; + } + + void SetDrawHandler(DrawHandler new_draw_handler); + + protected: + void Draw(ID2D1RenderTarget* render_target) override; + + private: + DrawHandler draw_handler_{}; + }; +} diff --git a/src/ui/ui_base.hpp b/src/ui/ui_base.hpp index e57af530..c26bfe0e 100644 --- a/src/ui/ui_base.hpp +++ b/src/ui/ui_base.hpp @@ -8,7 +8,7 @@ namespace cru::ui { - struct Point final + struct Point { constexpr static Point Zero() { @@ -33,7 +33,7 @@ namespace cru::ui } - struct Size final + struct Size { constexpr static Size Zero() { @@ -47,12 +47,12 @@ namespace cru::ui float height = 0; }; - constexpr Size operator+(const Size& left, const Size& right) + constexpr Size operator + (const Size& left, const Size& right) { return Size(left.width + right.width, left.height + right.height); } - constexpr Size operator-(const Size& left, const Size& right) + constexpr Size operator - (const Size& left, const Size& right) { return Size(left.width - right.width, left.height - right.height); } @@ -68,7 +68,7 @@ namespace cru::ui } - struct Thickness final + struct Thickness { constexpr static Thickness Zero() { @@ -122,7 +122,21 @@ namespace cru::ui float bottom; }; - struct Rect final + constexpr bool operator == (const Thickness& left, const Thickness& right) + { + return left.left == right.left && + left.top == right.top && + left.right == right.right && + left.bottom == right.bottom; + } + + constexpr bool operator != (const Thickness& left, const Thickness& right) + { + return !(left == right); + } + + + struct Rect { constexpr Rect() = default; constexpr Rect(const float left, const float top, const float width, const float height) @@ -135,6 +149,11 @@ namespace cru::ui return Rect(left, top, right - left, bottom - top); } + constexpr static Rect FromCenter(const Point& center, const float width, const float height) + { + return Rect(center.x - width / 2.0f, center.y - height / 2.0f, width, height); + } + constexpr float GetRight() const { return left + width; @@ -165,6 +184,11 @@ namespace cru::ui return Point(left + width, top); } + constexpr Point GetCenter() const + { + return Point(left + width / 2.0f, top + height / 2.0f); + } + constexpr Size GetSize() const { return Size(width, height); @@ -190,8 +214,82 @@ namespace cru::ui float height = 0.0f; }; + constexpr bool operator==(const Rect& left, const Rect& right) + { + return left.left == right.left && + left.top == right.top && + left.width == right.width && + left.height == right.height; + } + + constexpr bool operator!=(const Rect& left, const Rect& right) + { + return !(left == right); + } + + + struct RoundedRect + { + constexpr RoundedRect() = default; + constexpr RoundedRect(const Rect& rect, const float radius_x, const float radius_y) + : rect(rect), radius_x(radius_x), radius_y(radius_y) { } + + Rect rect{}; + float radius_x = 0.0f; + float radius_y = 0.0f; + }; + + constexpr bool operator == (const RoundedRect& left, const RoundedRect& right) + { + return left.rect == right.rect && left.radius_x == right.radius_x && left.radius_y == right.radius_y; + } - struct TextRange final + constexpr bool operator != (const RoundedRect& left, const RoundedRect& right) + { + return !(left == right); + } + + struct Ellipse + { + constexpr Ellipse() = default; + constexpr Ellipse(const Point& center, const float radius_x, const float radius_y) + : center(center), radius_x(radius_x), radius_y(radius_y) { } + + constexpr static Ellipse FromRect(const Rect& rect) + { + return Ellipse(rect.GetCenter(), rect.width / 2.0f, rect.height / 2.0f); + } + + constexpr Rect GetBoundRect() const + { + return Rect::FromCenter(center, radius_x * 2.0f, radius_y * 2.0f); + } + + Point center{}; + float radius_x = 0.0f; + float radius_y = 0.0f; + }; + + constexpr bool operator == (const Ellipse& left, const Ellipse& right) + { + return left.center == right.center && left.radius_x == right.radius_x && left.radius_y == right.radius_y; + } + + constexpr bool operator != (const Ellipse& left, const Ellipse& right) + { + return !(left == right); + } + + + enum class MouseButton + { + Left, + Right, + Middle + }; + + + struct TextRange { constexpr static std::optional<TextRange> FromTwoSides(unsigned first, unsigned second) { @@ -219,4 +317,8 @@ namespace cru::ui unsigned position = 0; unsigned count = 0; }; + + bool IsKeyDown(int virtual_code); + bool IsKeyToggled(int virtual_code); + bool IsAnyMouseButtonDown(); } |