diff options
Diffstat (limited to 'src/ui/controls')
-rw-r--r-- | src/ui/controls/button.hpp | 3 | ||||
-rw-r--r-- | src/ui/controls/frame_layout.hpp | 3 | ||||
-rw-r--r-- | src/ui/controls/linear_layout.cpp | 12 | ||||
-rw-r--r-- | src/ui/controls/linear_layout.hpp | 3 | ||||
-rw-r--r-- | src/ui/controls/list_item.hpp | 3 | ||||
-rw-r--r-- | src/ui/controls/popup_menu.hpp | 3 | ||||
-rw-r--r-- | src/ui/controls/scroll_control.cpp | 400 | ||||
-rw-r--r-- | src/ui/controls/scroll_control.hpp | 161 | ||||
-rw-r--r-- | src/ui/controls/text_block.hpp | 3 | ||||
-rw-r--r-- | src/ui/controls/text_box.hpp | 3 | ||||
-rw-r--r-- | src/ui/controls/text_control.cpp | 2 | ||||
-rw-r--r-- | src/ui/controls/text_control.hpp | 3 | ||||
-rw-r--r-- | src/ui/controls/toggle_button.hpp | 3 |
13 files changed, 592 insertions, 10 deletions
diff --git a/src/ui/controls/button.hpp b/src/ui/controls/button.hpp index 50640b11..c53f7ed9 100644 --- a/src/ui/controls/button.hpp +++ b/src/ui/controls/button.hpp @@ -1,5 +1,8 @@ #pragma once +// ReSharper disable once CppUnusedIncludeDirective +#include "pre.hpp" + #include <initializer_list> #include "ui/control.hpp" diff --git a/src/ui/controls/frame_layout.hpp b/src/ui/controls/frame_layout.hpp index ca022780..45971584 100644 --- a/src/ui/controls/frame_layout.hpp +++ b/src/ui/controls/frame_layout.hpp @@ -1,5 +1,8 @@ #pragma once +// ReSharper disable once CppUnusedIncludeDirective +#include "pre.hpp" + #include <initializer_list> #include "ui/control.hpp" diff --git a/src/ui/controls/linear_layout.cpp b/src/ui/controls/linear_layout.cpp index 3789b305..8fb91513 100644 --- a/src/ui/controls/linear_layout.cpp +++ b/src/ui/controls/linear_layout.cpp @@ -2,6 +2,8 @@ #include <algorithm> +#include "math_util.hpp" + namespace cru::ui::controls { LinearLayout::LinearLayout(const Orientation orientation) @@ -10,16 +12,6 @@ namespace cru::ui::controls } - inline float AtLeast0(const float value) - { - return value < 0 ? 0 : value; - } - - inline Size AtLeast0(const Size& size) - { - return Size(AtLeast0(size.width), AtLeast0(size.height)); - } - StringView LinearLayout::GetControlType() const { return control_type; diff --git a/src/ui/controls/linear_layout.hpp b/src/ui/controls/linear_layout.hpp index b7ca42ec..deb51bd1 100644 --- a/src/ui/controls/linear_layout.hpp +++ b/src/ui/controls/linear_layout.hpp @@ -1,5 +1,8 @@ #pragma once +// ReSharper disable once CppUnusedIncludeDirective +#include "pre.hpp" + #include "ui/control.hpp" namespace cru::ui::controls diff --git a/src/ui/controls/list_item.hpp b/src/ui/controls/list_item.hpp index 1de89b5f..a77d13e6 100644 --- a/src/ui/controls/list_item.hpp +++ b/src/ui/controls/list_item.hpp @@ -1,5 +1,8 @@ #pragma once +// ReSharper disable once CppUnusedIncludeDirective +#include "pre.hpp" + #include <map> #include <initializer_list> diff --git a/src/ui/controls/popup_menu.hpp b/src/ui/controls/popup_menu.hpp index d47e3eb6..a2916590 100644 --- a/src/ui/controls/popup_menu.hpp +++ b/src/ui/controls/popup_menu.hpp @@ -1,5 +1,8 @@ #pragma once +// ReSharper disable once CppUnusedIncludeDirective +#include "pre.hpp" + #include <vector> #include <utility> #include <functional> diff --git a/src/ui/controls/scroll_control.cpp b/src/ui/controls/scroll_control.cpp new file mode 100644 index 00000000..aa5403d4 --- /dev/null +++ b/src/ui/controls/scroll_control.cpp @@ -0,0 +1,400 @@ +#include "scroll_control.hpp" + +#include <limits> + +#include "cru_debug.hpp" +#include "format.hpp" +#include "ui/convert_util.hpp" +#include "exception.hpp" +#include "math_util.hpp" +#include "ui/ui_manager.hpp" +#include "ui/window.hpp" + +namespace cru::ui::controls +{ + constexpr auto scroll_bar_width = 15.0f; + + ScrollControl::ScrollControl(const bool container) : Control(container) + { + SetClipContent(true); + } + + ScrollControl::~ScrollControl() + { + + } + + StringView ScrollControl::GetControlType() const + { + return control_type; + } + + void ScrollControl::SetHorizontalScrollEnabled(const bool enable) + { + horizontal_scroll_enabled_ = enable; + InvalidateLayout(); + InvalidateDraw(); + } + + void ScrollControl::SetVerticalScrollEnabled(const bool enable) + { + vertical_scroll_enabled_ = enable; + InvalidateLayout(); + InvalidateDraw(); + } + + void ScrollControl::SetHorizontalScrollBarVisibility(const ScrollBarVisibility visibility) + { + if (visibility != horizontal_scroll_bar_visibility_) + { + horizontal_scroll_bar_visibility_ = visibility; + switch (visibility) + { + case ScrollBarVisibility::Always: + is_horizontal_scroll_bar_visible_ = true; + break; + case ScrollBarVisibility::None: + is_horizontal_scroll_bar_visible_ = false; + break; + case ScrollBarVisibility::Auto: + UpdateScrollBarVisibility(); + } + InvalidateDraw(); + } + } + + void ScrollControl::SetVerticalScrollBarVisibility(const ScrollBarVisibility visibility) + { + if (visibility != vertical_scroll_bar_visibility_) + { + vertical_scroll_bar_visibility_ = visibility; + switch (visibility) + { + case ScrollBarVisibility::Always: + is_vertical_scroll_bar_visible_ = true; + break; + case ScrollBarVisibility::None: + is_vertical_scroll_bar_visible_ = false; + break; + case ScrollBarVisibility::Auto: + UpdateScrollBarVisibility(); + } + InvalidateDraw(); + } + + } + + void ScrollControl::SetScrollOffset(std::optional<float> x, std::optional<float> y) + { + CoerceAndSetOffsets(x.value_or(GetScrollOffsetX()), y.value_or(GetScrollOffsetY())); + } + + void ScrollControl::SetViewWidth(const float length) + { + view_width_ = length; + } + + void ScrollControl::SetViewHeight(const float length) + { + view_height_ = length; + } + + Size ScrollControl::OnMeasureContent(const Size& available_size) + { + const auto layout_params = GetLayoutParams(); + + auto available_size_for_children = available_size; + if (IsHorizontalScrollEnabled()) + { + if (layout_params->width.mode == MeasureMode::Content) + debug::DebugMessage(L"ScrollControl: Width measure mode is Content and horizontal scroll is enabled. So Stretch is used instead."); + + for (auto child : GetChildren()) + { + const auto child_layout_params = child->GetLayoutParams(); + if (child_layout_params->width.mode == MeasureMode::Stretch) + throw std::runtime_error(Format("ScrollControl: Horizontal scroll is enabled but a child {} 's width measure mode is Stretch which may cause infinite length.", ToUtf8String(child->GetControlType()))); + } + + available_size_for_children.width = std::numeric_limits<float>::max(); + } + + if (IsVerticalScrollEnabled()) + { + if (layout_params->height.mode == MeasureMode::Content) + debug::DebugMessage(L"ScrollControl: Height measure mode is Content and vertical scroll is enabled. So Stretch is used instead."); + + for (auto child : GetChildren()) + { + const auto child_layout_params = child->GetLayoutParams(); + if (child_layout_params->height.mode == MeasureMode::Stretch) + throw std::runtime_error(Format("ScrollControl: Vertical scroll is enabled but a child {} 's height measure mode is Stretch which may cause infinite length.", ToUtf8String(child->GetControlType()))); + } + + available_size_for_children.height = std::numeric_limits<float>::max(); + } + + auto max_child_size = Size::Zero(); + for (auto control: GetChildren()) + { + control->Measure(available_size_for_children); + const auto&& size = control->GetDesiredSize(); + if (max_child_size.width < size.width) + max_child_size.width = size.width; + if (max_child_size.height < size.height) + max_child_size.height = size.height; + } + + // coerce size fro stretch. + for (auto control: GetChildren()) + { + auto size = control->GetDesiredSize(); + const auto child_layout_params = control->GetLayoutParams(); + if (child_layout_params->width.mode == MeasureMode::Stretch) + size.width = max_child_size.width; + if (child_layout_params->height.mode == MeasureMode::Stretch) + size.height = max_child_size.height; + control->SetDesiredSize(size); + } + + auto result = max_child_size; + if (IsHorizontalScrollEnabled()) + { + SetViewWidth(max_child_size.width); + result.width = available_size.width; + } + if (IsVerticalScrollEnabled()) + { + SetViewHeight(max_child_size.height); + result.height = available_size.height; + } + + return result; + } + + void ScrollControl::OnLayoutContent(const Rect& rect) + { + auto layout_rect = rect; + + if (IsHorizontalScrollEnabled()) + layout_rect.width = GetViewWidth(); + if (IsVerticalScrollEnabled()) + layout_rect.height = GetViewHeight(); + + for (auto control: GetChildren()) + { + const auto size = control->GetDesiredSize(); + // Ignore alignment, always center aligned. + auto&& calculate_anchor = [](const float anchor, const float layout_length, const float control_length, const float offset) -> float + { + return anchor + (layout_length - control_length) / 2 - offset; + }; + + control->Layout(Rect(Point( + calculate_anchor(rect.left, layout_rect.width, size.width, offset_x_), + calculate_anchor(rect.top, layout_rect.height, size.height, offset_y_) + ), size)); + } + } + + void ScrollControl::AfterLayoutSelf() + { + UpdateScrollBarBorderInfo(); + CoerceAndSetOffsets(offset_x_, offset_y_, false); + UpdateScrollBarVisibility(); + } + + void ScrollControl::OnDrawForeground(ID2D1DeviceContext* device_context) + { + Control::OnDrawForeground(device_context); + + const auto predefined = UiManager::GetInstance()->GetPredefineResources(); + + if (is_horizontal_scroll_bar_visible_) + { + device_context->FillRectangle( + Convert(horizontal_bar_info_.border), + predefined->scroll_bar_background_brush.Get() + ); + + device_context->FillRectangle( + Convert(horizontal_bar_info_.bar), + predefined->scroll_bar_brush.Get() + ); + + device_context->DrawLine( + Convert(horizontal_bar_info_.border.GetLeftTop()), + Convert(horizontal_bar_info_.border.GetRightTop()), + predefined->scroll_bar_border_brush.Get() + ); + } + + if (is_vertical_scroll_bar_visible_) + { + device_context->FillRectangle( + Convert(vertical_bar_info_.border), + predefined->scroll_bar_background_brush.Get() + ); + + device_context->FillRectangle( + Convert(vertical_bar_info_.bar), + predefined->scroll_bar_brush.Get() + ); + + device_context->DrawLine( + Convert(vertical_bar_info_.border.GetLeftTop()), + Convert(vertical_bar_info_.border.GetLeftBottom()), + predefined->scroll_bar_border_brush.Get() + ); + } + } + + void ScrollControl::OnMouseDownCore(events::MouseButtonEventArgs& args) + { + Control::OnMouseDownCore(args); + + if (args.GetMouseButton() == MouseButton::Left) + { + const auto point = args.GetPoint(this); + if (is_vertical_scroll_bar_visible_ && vertical_bar_info_.bar.IsPointInside(point)) + { + GetWindow()->CaptureMouseFor(this); + is_pressing_scroll_bar_ = Orientation::Vertical; + pressing_delta_ = point.y - vertical_bar_info_.bar.top; + return; + } + + if (is_horizontal_scroll_bar_visible_ && horizontal_bar_info_.bar.IsPointInside(point)) + { + GetWindow()->CaptureMouseFor(this); + pressing_delta_ = point.x - horizontal_bar_info_.bar.left; + is_pressing_scroll_bar_ = Orientation::Horizontal; + return; + } + } + } + + void ScrollControl::OnMouseMoveCore(events::MouseEventArgs& args) + { + Control::OnMouseMoveCore(args); + + const auto mouse_point = args.GetPoint(this); + + if (is_pressing_scroll_bar_ == Orientation::Horizontal) + { + const auto new_head_position = mouse_point.x - pressing_delta_; + const auto new_offset = new_head_position / horizontal_bar_info_.border.width * view_width_; + SetScrollOffset(new_offset, std::nullopt); + return; + } + + if (is_pressing_scroll_bar_ == Orientation::Vertical) + { + const auto new_head_position = mouse_point.y - pressing_delta_; + const auto new_offset = new_head_position / vertical_bar_info_.border.height * view_height_; + SetScrollOffset(std::nullopt, new_offset); + return; + } + } + + void ScrollControl::OnMouseUpCore(events::MouseButtonEventArgs& args) + { + Control::OnMouseUpCore(args); + + if (args.GetMouseButton() == MouseButton::Left && is_pressing_scroll_bar_.has_value()) + { + GetWindow()->ReleaseCurrentMouseCapture(); + is_pressing_scroll_bar_ = std::nullopt; + } + } + + void ScrollControl::OnMouseWheelCore(events::MouseWheelEventArgs& args) + { + Control::OnMouseWheelCore(args); + + constexpr const auto view_delta = 30.0f; + + if (args.GetDelta() == 0.0f) + return; + + const auto content_rect = GetRect(RectRange::Content); + if (IsVerticalScrollEnabled() && GetScrollOffsetY() != (args.GetDelta() > 0.0f ? 0.0f : AtLeast0(GetViewHeight() - content_rect.height))) + { + SetScrollOffset(std::nullopt, GetScrollOffsetY() - args.GetDelta() / WHEEL_DELTA * view_delta); + return; + } + + if (IsHorizontalScrollEnabled() && GetScrollOffsetX() != (args.GetDelta() > 0.0f ? 0.0f : AtLeast0(GetViewWidth() - content_rect.width))) + { + SetScrollOffset(GetScrollOffsetX() - args.GetDelta() / WHEEL_DELTA * view_delta, std::nullopt); + return; + } + } + + void ScrollControl::CoerceAndSetOffsets(const float offset_x, const float offset_y, const bool update_children) + { + const auto old_offset_x = offset_x_; + const auto old_offset_y = offset_y_; + + const auto content_rect = GetRect(RectRange::Content); + offset_x_ = Coerce(offset_x, 0.0f, AtLeast0(view_width_ - content_rect.width)); + offset_y_ = Coerce(offset_y, 0.0f, AtLeast0(view_height_ - content_rect.height)); + UpdateScrollBarBarInfo(); + + if (update_children) + { + for (auto child : GetChildren()) + { + const auto old_position = child->GetPositionRelative(); + child->SetPositionRelative(Point( + old_position.x + old_offset_x - offset_x_, + old_position.y + old_offset_y - offset_y_ + )); + } + } + InvalidateDraw(); + } + + void ScrollControl::UpdateScrollBarVisibility() + { + const auto content_rect = GetRect(RectRange::Content); + if (GetHorizontalScrollBarVisibility() == ScrollBarVisibility::Auto) + is_horizontal_scroll_bar_visible_ = view_width_ > content_rect.width; + if (GetVerticalScrollBarVisibility() == ScrollBarVisibility::Auto) + is_vertical_scroll_bar_visible_ = view_height_ > content_rect.height; + } + + void ScrollControl::UpdateScrollBarBorderInfo() + { + const auto content_rect = GetRect(RectRange::Content); + horizontal_bar_info_.border = Rect(content_rect.left, content_rect.GetBottom() - scroll_bar_width, content_rect.width, scroll_bar_width); + vertical_bar_info_.border = Rect(content_rect.GetRight() - scroll_bar_width , content_rect.top, scroll_bar_width, content_rect.height); + } + + void ScrollControl::UpdateScrollBarBarInfo() + { + const auto content_rect = GetRect(RectRange::Content); + { + const auto& border = horizontal_bar_info_.border; + if (view_width_ <= content_rect.width) + horizontal_bar_info_.bar = border; + else + { + const auto bar_length = border.width * content_rect.width / view_width_; + const auto offset = border.width * offset_x_ / view_width_; + horizontal_bar_info_.bar = Rect(border.left + offset, border.top, bar_length, border.height); + } + } + { + const auto& border = vertical_bar_info_.border; + if (view_height_ <= content_rect.height) + vertical_bar_info_.bar = border; + else + { + const auto bar_length = border.height * content_rect.height / view_height_; + const auto offset = border.height * offset_y_ / view_height_; + vertical_bar_info_.bar = Rect(border.left, border.top + offset, border.width, bar_length); + } + } + } +} diff --git a/src/ui/controls/scroll_control.hpp b/src/ui/controls/scroll_control.hpp new file mode 100644 index 00000000..0541a010 --- /dev/null +++ b/src/ui/controls/scroll_control.hpp @@ -0,0 +1,161 @@ +#pragma once + +// ReSharper disable once CppUnusedIncludeDirective +#include "pre.hpp" + +#include <optional> +#include <initializer_list> + +#include "ui/control.hpp" + +namespace cru::ui::controls +{ + // Done: OnMeasureContent + // Done: OnLayoutContent + // Done: HitTest(no need) + // Done: Draw(no need) + // Done: API + // Done: ScrollBar + // Done: MouseEvent + class ScrollControl : public Control + { + private: + struct ScrollBarInfo + { + Rect border = Rect(); + Rect bar = Rect(); + }; + + enum class Orientation + { + Horizontal, + Vertical + }; + + public: + enum class ScrollBarVisibility + { + None, + Auto, + Always + }; + + static ScrollControl* Create(const std::initializer_list<Control*>& children = std::initializer_list<Control*>{}) + { + const auto control = new ScrollControl(true); + for (auto child : children) + control->AddChild(child); + return control; + } + + static constexpr auto control_type = L"ScrollControl"; + + protected: + explicit ScrollControl(bool container); + public: + ScrollControl(const ScrollControl& other) = delete; + ScrollControl(ScrollControl&& other) = delete; + ScrollControl& operator=(const ScrollControl& other) = delete; + ScrollControl& operator=(ScrollControl&& other) = delete; + ~ScrollControl() override; + + StringView GetControlType() const override final; + + bool IsHorizontalScrollEnabled() const + { + return horizontal_scroll_enabled_; + } + + void SetHorizontalScrollEnabled(bool enable); + + bool IsVerticalScrollEnabled() const + { + return vertical_scroll_enabled_; + } + + void SetVerticalScrollEnabled(bool enable); + + + ScrollBarVisibility GetHorizontalScrollBarVisibility() const + { + return horizontal_scroll_bar_visibility_; + } + + void SetHorizontalScrollBarVisibility(ScrollBarVisibility visibility); + + ScrollBarVisibility GetVerticalScrollBarVisibility() const + { + return vertical_scroll_bar_visibility_; + } + + void SetVerticalScrollBarVisibility(ScrollBarVisibility visibility); + + float GetViewWidth() const + { + return view_width_; + } + + float GetViewHeight() const + { + return view_height_; + } + + float GetScrollOffsetX() const + { + return offset_x_; + } + + float GetScrollOffsetY() const + { + return offset_y_; + } + + // nullopt for not set. value is auto-coerced. + void SetScrollOffset(std::optional<float> x, std::optional<float> y); + + protected: + void SetViewWidth(float length); + void SetViewHeight(float length); + + Size OnMeasureContent(const Size& available_size) override final; + void OnLayoutContent(const Rect& rect) override final; + + void AfterLayoutSelf() override; + + void OnDrawForeground(ID2D1DeviceContext* device_context) override; + + void OnMouseDownCore(events::MouseButtonEventArgs& args) override final; + void OnMouseMoveCore(events::MouseEventArgs& args) override final; + void OnMouseUpCore(events::MouseButtonEventArgs& args) override final; + + void OnMouseWheelCore(events::MouseWheelEventArgs& args) override; + + private: + void CoerceAndSetOffsets(float offset_x, float offset_y, bool update_children = true); + void UpdateScrollBarVisibility(); + void UpdateScrollBarBorderInfo(); + void UpdateScrollBarBarInfo(); + + private: + bool horizontal_scroll_enabled_ = true; + bool vertical_scroll_enabled_ = true; + + ScrollBarVisibility horizontal_scroll_bar_visibility_ = ScrollBarVisibility::Auto; + ScrollBarVisibility vertical_scroll_bar_visibility_ = ScrollBarVisibility::Auto; + + bool is_horizontal_scroll_bar_visible_ = false; + bool is_vertical_scroll_bar_visible_ = false; + + float offset_x_ = 0.0f; + float offset_y_ = 0.0f; + + float view_width_ = 0.0f; + float view_height_ = 0.0f; + + ScrollBarInfo horizontal_bar_info_; + ScrollBarInfo vertical_bar_info_; + + std::optional<Orientation> is_pressing_scroll_bar_ = std::nullopt; + float pressing_delta_ = 0.0f; + }; +} diff --git a/src/ui/controls/text_block.hpp b/src/ui/controls/text_block.hpp index 4d017da5..66f5defa 100644 --- a/src/ui/controls/text_block.hpp +++ b/src/ui/controls/text_block.hpp @@ -1,5 +1,8 @@ #pragma once +// ReSharper disable once CppUnusedIncludeDirective +#include "pre.hpp" + #include "text_control.hpp" namespace cru::ui::controls diff --git a/src/ui/controls/text_box.hpp b/src/ui/controls/text_box.hpp index 65f81fc3..3a30ecb2 100644 --- a/src/ui/controls/text_box.hpp +++ b/src/ui/controls/text_box.hpp @@ -1,5 +1,8 @@ #pragma once +// ReSharper disable once CppUnusedIncludeDirective +#include "pre.hpp" + #include "text_control.hpp" #include "timer.hpp" diff --git a/src/ui/controls/text_control.cpp b/src/ui/controls/text_control.cpp index f7f88d4e..d7d6b810 100644 --- a/src/ui/controls/text_control.cpp +++ b/src/ui/controls/text_control.cpp @@ -19,6 +19,8 @@ namespace cru::ui::controls brush_ = init_brush; selection_brush_ = UiManager::GetInstance()->GetPredefineResources()->text_control_selection_brush; + + SetClipContent(true); } diff --git a/src/ui/controls/text_control.hpp b/src/ui/controls/text_control.hpp index 93120a44..762d85f3 100644 --- a/src/ui/controls/text_control.hpp +++ b/src/ui/controls/text_control.hpp @@ -1,5 +1,8 @@ #pragma once +// ReSharper disable once CppUnusedIncludeDirective +#include "pre.hpp" + #include "ui/control.hpp" namespace cru::ui::controls diff --git a/src/ui/controls/toggle_button.hpp b/src/ui/controls/toggle_button.hpp index 5de40ca5..4cbb4f37 100644 --- a/src/ui/controls/toggle_button.hpp +++ b/src/ui/controls/toggle_button.hpp @@ -1,5 +1,8 @@ #pragma once +// ReSharper disable once CppUnusedIncludeDirective +#include "pre.hpp" + #include "ui/control.hpp" namespace cru::ui::controls |