aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CruUI-Generate/cru_ui.cpp648
-rw-r--r--CruUI-Generate/cru_ui.hpp463
-rw-r--r--CruUI.vcxproj5
-rw-r--r--CruUI.vcxproj.filters15
-rw-r--r--src/any_map.hpp3
-rw-r--r--src/application.cpp15
-rw-r--r--src/application.hpp3
-rw-r--r--src/base.hpp33
-rw-r--r--src/cru_debug.hpp3
-rw-r--r--src/cru_event.hpp3
-rw-r--r--src/exception.hpp3
-rw-r--r--src/format.hpp3
-rw-r--r--src/global_macros.hpp9
-rw-r--r--src/graph/graph.hpp3
-rw-r--r--src/main.cpp20
-rw-r--r--src/math_util.hpp69
-rw-r--r--src/pre.hpp18
-rw-r--r--src/system_headers.hpp4
-rw-r--r--src/timer.hpp2
-rw-r--r--src/ui/animations/animation.hpp3
-rw-r--r--src/ui/border_property.hpp3
-rw-r--r--src/ui/control.cpp147
-rw-r--r--src/ui/control.hpp53
-rw-r--r--src/ui/controls/button.hpp3
-rw-r--r--src/ui/controls/frame_layout.hpp3
-rw-r--r--src/ui/controls/linear_layout.cpp12
-rw-r--r--src/ui/controls/linear_layout.hpp3
-rw-r--r--src/ui/controls/list_item.hpp3
-rw-r--r--src/ui/controls/popup_menu.hpp3
-rw-r--r--src/ui/controls/scroll_control.cpp400
-rw-r--r--src/ui/controls/scroll_control.hpp161
-rw-r--r--src/ui/controls/text_block.hpp3
-rw-r--r--src/ui/controls/text_box.hpp3
-rw-r--r--src/ui/controls/text_control.cpp2
-rw-r--r--src/ui/controls/text_control.hpp3
-rw-r--r--src/ui/controls/toggle_button.hpp3
-rw-r--r--src/ui/convert_util.hpp3
-rw-r--r--src/ui/cursor.cpp7
-rw-r--r--src/ui/cursor.hpp7
-rw-r--r--src/ui/events/ui_event.hpp28
-rw-r--r--src/ui/layout_base.hpp3
-rw-r--r--src/ui/ui_base.hpp13
-rw-r--r--src/ui/ui_manager.cpp6
-rw-r--r--src/ui/ui_manager.hpp7
-rw-r--r--src/ui/window.cpp45
-rw-r--r--src/ui/window.hpp18
46 files changed, 1996 insertions, 271 deletions
diff --git a/CruUI-Generate/cru_ui.cpp b/CruUI-Generate/cru_ui.cpp
index 497af786..afd588d6 100644
--- a/CruUI-Generate/cru_ui.cpp
+++ b/CruUI-Generate/cru_ui.cpp
@@ -123,16 +123,6 @@ namespace cru {
return instance_;
}
- namespace
- {
- void LoadSystemCursor(HINSTANCE h_instance)
- {
- ui::cursors::arrow = std::make_shared<ui::Cursor>(::LoadCursorW(nullptr, IDC_ARROW), false);
- ui::cursors::hand = std::make_shared<ui::Cursor>(::LoadCursorW(nullptr, IDC_HAND), false);
- ui::cursors::i_beam = std::make_shared<ui::Cursor>(::LoadCursorW(nullptr, IDC_IBEAM), false);
- }
- }
-
Application::Application(HINSTANCE h_instance)
: h_instance_(h_instance) {
@@ -141,9 +131,12 @@ namespace cru {
instance_ = this;
+ if (!::IsWindows8OrGreater())
+ throw std::runtime_error("Must run on Windows 8 or later.");
+
god_window_ = std::make_unique<GodWindow>(this);
- LoadSystemCursor(h_instance);
+ ui::cursors::LoadSystemCursors();
}
Application::~Application()
@@ -267,6 +260,7 @@ namespace cru
//-------begin of file: src\main.cpp
//--------------------------------------------------------
+
using cru::String;
using cru::StringView;
using cru::Application;
@@ -284,6 +278,7 @@ using cru::ui::controls::Button;
using cru::ui::controls::TextBox;
using cru::ui::controls::ListItem;
using cru::ui::controls::FrameLayout;
+using cru::ui::controls::ScrollControl;
int APIENTRY wWinMain(
HINSTANCE hInstance,
@@ -291,6 +286,10 @@ int APIENTRY wWinMain(
LPWSTR lpCmdLine,
int nCmdShow) {
+#ifdef CRU_DEBUG
+ _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
+#endif
+
Application application(hInstance);
const auto window = Window::CreateOverlapped();
@@ -440,9 +439,16 @@ int APIENTRY wWinMain(
}
{
- const auto text_block = CreateWithLayout<TextBlock>(LayoutSideParams::Stretch(), LayoutSideParams::Stretch(), L"This is a very very very very very long sentence!!!");
+ const auto scroll_view = CreateWithLayout<ScrollControl>(LayoutSideParams::Stretch(), LayoutSideParams::Stretch());
+
+ scroll_view->SetVerticalScrollBarVisibility(ScrollControl::ScrollBarVisibility::Always);
+
+ const auto text_block = TextBlock::Create(
+ L"Love myself I do. Not everything, but I love the good as well as the bad. I love my crazy lifestyle, and I love my hard discipline. I love my freedom of speech and the way my eyes get dark when I'm tired. I love that I have learned to trust people with my heart, even if it will get broken. I am proud of everything that I am and will become.");
text_block->SetSelectable(true);
- layout->AddChild(text_block);
+
+ scroll_view->AddChild(text_block);
+ layout->AddChild(scroll_view);
}
layout->AddChild(CreateWithLayout<TextBlock>(LayoutSideParams::Content(Alignment::Start), LayoutSideParams::Content(), L"This is a little short sentence!!!"));
@@ -473,7 +479,6 @@ int APIENTRY wWinMain(
window.AddChild(linear_layout);
*/
-
window->Show();
return application.Run();
@@ -992,26 +997,67 @@ namespace cru::ui
bool Control::IsPointInside(const Point & point)
{
- if (border_geometry_ != nullptr)
+ const auto border_geometry = geometry_info_.border_geometry;
+ if (border_geometry != nullptr)
{
if (IsBordered())
{
BOOL contains;
- border_geometry_->FillContainsPoint(Convert(point), D2D1::Matrix3x2F::Identity(), &contains);
+ border_geometry->FillContainsPoint(Convert(point), D2D1::Matrix3x2F::Identity(), &contains);
if (!contains)
- border_geometry_->StrokeContainsPoint(Convert(point), GetBorderProperty().GetStrokeWidth(), nullptr, D2D1::Matrix3x2F::Identity(), &contains);
+ border_geometry->StrokeContainsPoint(Convert(point), GetBorderProperty().GetStrokeWidth(), nullptr, D2D1::Matrix3x2F::Identity(), &contains);
return contains != 0;
}
else
{
BOOL contains;
- border_geometry_->FillContainsPoint(Convert(point), D2D1::Matrix3x2F::Identity(), &contains);
+ border_geometry->FillContainsPoint(Convert(point), D2D1::Matrix3x2F::Identity(), &contains);
return contains != 0;
}
}
return false;
}
+ Control* Control::HitTest(const Point& point)
+ {
+ const auto point_inside = IsPointInside(point);
+
+ if (IsClipContent())
+ {
+ if (!point_inside)
+ return nullptr;
+ if (geometry_info_.content_geometry != nullptr)
+ {
+ BOOL contains;
+ ThrowIfFailed(geometry_info_.content_geometry->FillContainsPoint(Convert(point), D2D1::Matrix3x2F::Identity(), &contains));
+ if (contains == 0)
+ return this;
+ }
+ }
+
+ const auto& children = GetChildren();
+
+ for (auto i = children.crbegin(); i != children.crend(); ++i)
+ {
+ const auto&& lefttop = (*i)->GetPositionRelative();
+ const auto&& coerced_point = Point(point.x - lefttop.x, point.y - lefttop.y);
+ const auto child_hit_test_result = (*i)->HitTest(coerced_point);
+ if (child_hit_test_result != nullptr)
+ return child_hit_test_result;
+ }
+
+ return point_inside ? this : nullptr;
+ }
+
+ void Control::SetClipContent(const bool clip)
+ {
+ if (clip_content_ == clip)
+ return;
+
+ clip_content_ = clip;
+ InvalidateDraw();
+ }
+
void Control::Draw(ID2D1DeviceContext* device_context)
{
D2D1::Matrix3x2F old_transform;
@@ -1020,11 +1066,20 @@ namespace cru::ui
const auto position = GetPositionRelative();
device_context->SetTransform(old_transform * D2D1::Matrix3x2F::Translation(position.x, position.y));
+ OnDrawDecoration(device_context);
+
+ const auto set_layer = geometry_info_.content_geometry != nullptr && IsClipContent();
+ if (set_layer)
+ device_context->PushLayer(D2D1::LayerParameters(D2D1::InfiniteRect(), geometry_info_.content_geometry.Get()), nullptr);
+
OnDrawCore(device_context);
for (auto child : GetChildren())
child->Draw(device_context);
+ if (set_layer)
+ device_context->PopLayer();
+
device_context->SetTransform(old_transform);
}
@@ -1067,6 +1122,7 @@ namespace cru::ui
{
SetPositionRelative(rect.GetLeftTop());
SetSize(rect.GetSize());
+ AfterLayoutSelf();
OnLayoutCore(Rect(Point::Zero(), rect.GetSize()));
}
@@ -1136,7 +1192,7 @@ namespace cru::ui
void Control::UpdateBorder()
{
- RegenerateBorderGeometry();
+ RegenerateGeometryInfo();
InvalidateLayout();
InvalidateDraw();
}
@@ -1168,7 +1224,6 @@ namespace cru::ui
child->TraverseDescendants([window](Control* control) {
control->OnAttachToWindow(window);
});
- window->RefreshControlList();
InvalidateLayout();
}
}
@@ -1180,7 +1235,6 @@ namespace cru::ui
child->TraverseDescendants([window](Control* control) {
control->OnDetachToWindow(window);
});
- window->RefreshControlList();
InvalidateLayout();
}
}
@@ -1195,9 +1249,9 @@ namespace cru::ui
window_ = nullptr;
}
- void Control::OnDrawCore(ID2D1DeviceContext* device_context)
+ void Control::OnDrawDecoration(ID2D1DeviceContext* device_context)
{
- #ifdef CRU_DEBUG_LAYOUT
+#ifdef CRU_DEBUG_LAYOUT
if (GetWindow()->IsDebugLayout())
{
if (padding_geometry_ != nullptr)
@@ -1208,17 +1262,21 @@ namespace cru::ui
}
#endif
- if (is_bordered_ && border_geometry_ != nullptr)
+ if (is_bordered_ && geometry_info_.border_geometry != nullptr)
device_context->DrawGeometry(
- border_geometry_.Get(),
+ geometry_info_.border_geometry.Get(),
GetBorderProperty().GetBrush().Get(),
GetBorderProperty().GetStrokeWidth(),
GetBorderProperty().GetStrokeStyle().Get()
);
+ }
+ void Control::OnDrawCore(ID2D1DeviceContext* device_context)
+ {
+ const auto ground_geometry = geometry_info_.padding_content_geometry;
//draw background.
- if (in_border_geometry_ != nullptr && background_brush_ != nullptr)
- device_context->FillGeometry(in_border_geometry_.Get(), background_brush_.Get());
+ if (ground_geometry != nullptr && background_brush_ != nullptr)
+ device_context->FillGeometry(ground_geometry.Get(), background_brush_.Get());
const auto padding_rect = GetRect(RectRange::Padding);
graph::WithTransform(device_context, D2D1::Matrix3x2F::Translation(padding_rect.left, padding_rect.top),
[this](ID2D1DeviceContext* device_context)
@@ -1240,8 +1298,8 @@ namespace cru::ui
//draw foreground.
- if (in_border_geometry_ != nullptr && foreground_brush_ != nullptr)
- device_context->FillGeometry(in_border_geometry_.Get(), foreground_brush_.Get());
+ if (ground_geometry != nullptr && foreground_brush_ != nullptr)
+ device_context->FillGeometry(ground_geometry.Get(), foreground_brush_.Get());
graph::WithTransform(device_context, D2D1::Matrix3x2F::Translation(padding_rect.left, padding_rect.top),
[this](ID2D1DeviceContext* device_context)
{
@@ -1303,7 +1361,7 @@ namespace cru::ui
void Control::OnSizeChangedCore(SizeChangedEventArgs & args)
{
- RegenerateBorderGeometry();
+ RegenerateGeometryInfo();
#ifdef CRU_DEBUG_LAYOUT
margin_geometry_ = CalculateSquareRingGeometry(GetRect(RectRange::Margin), GetRect(RectRange::FullBorder));
padding_geometry_ = CalculateSquareRingGeometry(GetRect(RectRange::Padding), GetRect(RectRange::Content));
@@ -1324,7 +1382,7 @@ namespace cru::ui
size_changed_event.Raise(args);
}
- void Control::RegenerateBorderGeometry()
+ void Control::RegenerateGeometryInfo()
{
if (IsBordered())
{
@@ -1337,10 +1395,10 @@ namespace cru::ui
ThrowIfFailed(
graph::GraphManager::GetInstance()->GetD2D1Factory()->CreateRoundedRectangleGeometry(bound_rounded_rect, &geometry)
);
- border_geometry_ = std::move(geometry);
+ geometry_info_.border_geometry = std::move(geometry);
- const auto in_border_rect = GetRect(RectRange::Padding);
- const auto in_border_rounded_rect = D2D1::RoundedRect(Convert(in_border_rect),
+ const auto padding_rect = GetRect(RectRange::Padding);
+ const auto in_border_rounded_rect = D2D1::RoundedRect(Convert(padding_rect),
GetBorderProperty().GetRadiusX() - GetBorderProperty().GetStrokeWidth() / 2.0f,
GetBorderProperty().GetRadiusY() - GetBorderProperty().GetStrokeWidth() / 2.0f);
@@ -1348,7 +1406,24 @@ namespace cru::ui
ThrowIfFailed(
graph::GraphManager::GetInstance()->GetD2D1Factory()->CreateRoundedRectangleGeometry(in_border_rounded_rect, &geometry2)
);
- in_border_geometry_ = std::move(geometry2);
+ geometry_info_.padding_content_geometry = geometry2;
+
+
+ Microsoft::WRL::ComPtr<ID2D1RectangleGeometry> geometry3;
+ ThrowIfFailed(
+ graph::GraphManager::GetInstance()->GetD2D1Factory()->CreateRectangleGeometry(Convert(GetRect(RectRange::Content)), &geometry3)
+ );
+ Microsoft::WRL::ComPtr<ID2D1PathGeometry> geometry4;
+ ThrowIfFailed(
+ graph::GraphManager::GetInstance()->GetD2D1Factory()->CreatePathGeometry(&geometry4)
+ );
+ Microsoft::WRL::ComPtr<ID2D1GeometrySink> sink;
+ geometry4->Open(&sink);
+ ThrowIfFailed(
+ geometry3->CombineWithGeometry(geometry2.Get(), D2D1_COMBINE_MODE_INTERSECT, D2D1::Matrix3x2F::Identity(), sink.Get())
+ );
+ sink->Close();
+ geometry_info_.content_geometry = std::move(geometry4);
}
else
{
@@ -1357,8 +1432,14 @@ namespace cru::ui
ThrowIfFailed(
graph::GraphManager::GetInstance()->GetD2D1Factory()->CreateRectangleGeometry(Convert(bound_rect), &geometry)
);
- border_geometry_ = geometry;
- in_border_geometry_ = std::move(geometry);
+ geometry_info_.border_geometry = geometry;
+ geometry_info_.padding_content_geometry = std::move(geometry);
+
+ Microsoft::WRL::ComPtr<ID2D1RectangleGeometry> geometry2;
+ ThrowIfFailed(
+ graph::GraphManager::GetInstance()->GetD2D1Factory()->CreateRectangleGeometry(Convert(GetRect(RectRange::Content)), &geometry2)
+ );
+ geometry_info_.content_geometry = std::move(geometry2);
}
}
@@ -1433,6 +1514,16 @@ namespace cru::ui
}
+ void Control::OnMouseWheel(events::MouseWheelEventArgs& args)
+ {
+
+ }
+
+ void Control::OnMouseWheelCore(events::MouseWheelEventArgs& args)
+ {
+
+ }
+
void Control::RaiseMouseEnterEvent(MouseEventArgs& args)
{
OnMouseEnterCore(args);
@@ -1475,6 +1566,13 @@ namespace cru::ui
mouse_click_event.Raise(args);
}
+ void Control::RaiseMouseWheelEvent(MouseWheelEventArgs& args)
+ {
+ OnMouseWheelCore(args);
+ OnMouseWheel(args);
+ mouse_wheel_event.Raise(args);
+ }
+
void Control::OnMouseClickBegin(MouseButton button)
{
@@ -1635,7 +1733,7 @@ namespace cru::ui
auto parent = GetParent();
while (parent != nullptr)
{
- auto lp = parent->GetLayoutParams();
+ const auto lp = parent->GetLayoutParams();
if (!stretch_width_determined)
{
@@ -1770,6 +1868,11 @@ namespace cru::ui
}
}
+ void Control::AfterLayoutSelf()
+ {
+
+ }
+
void Control::CheckAndNotifyPositionChanged()
{
if (this->old_position_ != this->position_)
@@ -1867,6 +1970,13 @@ namespace cru::ui
Cursor::Ptr arrow{};
Cursor::Ptr hand{};
Cursor::Ptr i_beam{};
+
+ void LoadSystemCursors()
+ {
+ arrow = std::make_shared<Cursor>(::LoadCursorW(nullptr, IDC_ARROW), false);
+ hand = std::make_shared<Cursor>(::LoadCursorW(nullptr, IDC_HAND), false);
+ i_beam = std::make_shared<Cursor>(::LoadCursorW(nullptr, IDC_IBEAM), false);
+ }
}
}
//--------------------------------------------------------
@@ -2060,7 +2170,11 @@ namespace cru::ui
list_item_hover_border_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::SkyBlue))},
list_item_hover_fill_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::SkyBlue, 0.3f))},
list_item_select_border_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::MediumBlue))},
- list_item_select_fill_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::SkyBlue, 0.3f))}
+ list_item_select_fill_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::SkyBlue, 0.3f))},
+
+ scroll_bar_background_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::Gainsboro, 0.3f))},
+ scroll_bar_border_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::DimGray))},
+ scroll_bar_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::DimGray))}
#ifdef CRU_DEBUG_LAYOUT
,
@@ -2221,7 +2335,8 @@ namespace cru::ui
return new Window(tag_popup_constructor{}, parent, caption);
}
- Window::Window(tag_overlapped_constructor) : Control(WindowConstructorTag{}, this), control_list_({ this }) {
+ Window::Window(tag_overlapped_constructor) : Control(WindowConstructorTag{}, this)
+ {
const auto window_manager = WindowManager::GetInstance();
hwnd_ = CreateWindowEx(0,
@@ -2237,7 +2352,7 @@ namespace cru::ui
AfterCreateHwnd(window_manager);
}
- Window::Window(tag_popup_constructor, Window* parent, const bool caption) : Control(WindowConstructorTag{}, this), control_list_({ this })
+ Window::Window(tag_popup_constructor, Window* parent, const bool caption) : Control(WindowConstructorTag{}, this)
{
if (parent != nullptr && !parent->IsWindowValid())
throw std::runtime_error("Parent window is not valid.");
@@ -2500,6 +2615,14 @@ namespace cru::ui
result = 0;
return true;
}
+ case WM_MOUSEWHEEL:
+ POINT point;
+ point.x = GET_X_LPARAM(l_param);
+ point.y = GET_Y_LPARAM(l_param);
+ ScreenToClient(hwnd, &point);
+ OnMouseWheelInternal(GET_WHEEL_DELTA_WPARAM(w_param), point);
+ result = 0;
+ return true;
case WM_KEYDOWN:
OnKeyDownInternal(static_cast<int>(w_param));
result = 0;
@@ -2595,24 +2718,6 @@ namespace cru::ui
is_layout_invalid_ = false;
}
- void Window::RefreshControlList() {
- control_list_.clear();
- TraverseDescendants([this](Control* control) {
- this->control_list_.push_back(control);
- });
- }
-
- Control * Window::HitTest(const Point & point)
- {
- for (auto i = control_list_.crbegin(); i != control_list_.crend(); ++i) {
- auto control = *i;
- if (control->IsPointInside(control->WindowToControl(point))) {
- return control;
- }
- }
- return nullptr;
- }
-
bool Window::RequestFocusFor(Control * control)
{
if (control == nullptr)
@@ -2828,6 +2933,20 @@ namespace cru::ui
DispatchEvent(control, &Control::RaiseMouseUpEvent, nullptr, dip_point, button);
}
+ void Window::OnMouseWheelInternal(short delta, POINT point)
+ {
+ const auto dip_point = PiToDip(point);
+
+ Control* control;
+
+ if (mouse_capture_control_)
+ control = mouse_capture_control_;
+ else
+ control = HitTest(dip_point);
+
+ DispatchEvent(control, &Control::RaiseMouseWheelEvent, nullptr, dip_point, static_cast<float>(delta));
+ }
+
void Window::OnKeyDownInternal(int virtual_code)
{
DispatchEvent(focus_control_, &Control::RaiseKeyDownEvent, nullptr, virtual_code);
@@ -3113,6 +3232,7 @@ namespace cru::ui::controls
#include <algorithm>
+
namespace cru::ui::controls
{
LinearLayout::LinearLayout(const Orientation orientation)
@@ -3121,16 +3241,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;
@@ -3395,6 +3505,404 @@ namespace cru::ui::controls
//-------end of file: src\ui\controls\popup_menu.cpp
//--------------------------------------------------------
//--------------------------------------------------------
+//-------begin of file: src\ui\controls\scroll_control.cpp
+//--------------------------------------------------------
+
+#include <limits>
+
+
+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);
+ }
+ }
+ }
+}
+//--------------------------------------------------------
+//-------end of file: src\ui\controls\scroll_control.cpp
+//--------------------------------------------------------
+//--------------------------------------------------------
//-------begin of file: src\ui\controls\text_block.cpp
//--------------------------------------------------------
@@ -3640,6 +4148,8 @@ namespace cru::ui::controls
brush_ = init_brush;
selection_brush_ = UiManager::GetInstance()->GetPredefineResources()->text_control_selection_brush;
+
+ SetClipContent(true);
}
diff --git a/CruUI-Generate/cru_ui.hpp b/CruUI-Generate/cru_ui.hpp
index 93360e3a..6dff57cd 100644
--- a/CruUI-Generate/cru_ui.hpp
+++ b/CruUI-Generate/cru_ui.hpp
@@ -3,19 +3,9 @@
//-------begin of file: src\any_map.hpp
//--------------------------------------------------------
-#include <any>
-#include <unordered_map>
-#include <functional>
-#include <optional>
-#include <typeinfo>
-
-//--------------------------------------------------------
-//-------begin of file: src\base.hpp
-//--------------------------------------------------------
-
// ReSharper disable once CppUnusedIncludeDirective
//--------------------------------------------------------
-//-------begin of file: src\global_macros.hpp
+//-------begin of file: src\pre.hpp
//--------------------------------------------------------
#ifdef _DEBUG
@@ -25,18 +15,35 @@
#ifdef CRU_DEBUG
#define CRU_DEBUG_LAYOUT
#endif
+
+
+#ifdef CRU_DEBUG
+// ReSharper disable once IdentifierTypo
+// ReSharper disable once CppInconsistentNaming
+#define _CRTDBG_MAP_ALLOC
+#include <cstdlib>
+#include <crtdbg.h>
+#endif
+//--------------------------------------------------------
+//-------end of file: src\pre.hpp
+//--------------------------------------------------------
+
+#include <any>
+#include <unordered_map>
+#include <functional>
+#include <optional>
+#include <typeinfo>
+
//--------------------------------------------------------
-//-------end of file: src\global_macros.hpp
+//-------begin of file: src\base.hpp
//--------------------------------------------------------
+// ReSharper disable once CppUnusedIncludeDirective
#include <string>
#include <stdexcept>
#include <string_view>
#include <chrono>
-#include <optional>
-// ReSharper disable once CppUnusedIncludeDirective
-#include <type_traits>
namespace cru
{
@@ -86,33 +93,6 @@ namespace cru
if (!condition)
throw std::invalid_argument(error_message.data());
}
-
- 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())
- return min.value();
- if (max.has_value() && n > max.value())
- return max.value();
- return n;
- }
-
- template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
- float Coerce(const T n, const std::nullopt_t, const std::optional<T> max)
- {
- if (max.has_value() && n > max.value())
- return max.value();
- return n;
- }
-
- 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::nullopt_t)
- {
- if (min.has_value() && n < min.value())
- return min.value();
- return n;
- }
-
}
//--------------------------------------------------------
//-------end of file: src\base.hpp
@@ -121,6 +101,8 @@ namespace cru
//-------begin of file: src\format.hpp
//--------------------------------------------------------
+// ReSharper disable once CppUnusedIncludeDirective
+
namespace cru
{
@@ -325,10 +307,13 @@ namespace cru
//-------begin of file: src\application.hpp
//--------------------------------------------------------
+// ReSharper disable once CppUnusedIncludeDirective
+
//--------------------------------------------------------
//-------begin of file: src\system_headers.hpp
//--------------------------------------------------------
+// ReSharper disable once CppUnusedIncludeDirective
//include system headers
@@ -348,6 +333,8 @@ namespace cru
#include <dxgi1_2.h>
#include <wrl/client.h>
+
+#include <VersionHelpers.h>
//--------------------------------------------------------
//-------end of file: src\system_headers.hpp
//--------------------------------------------------------
@@ -469,6 +456,8 @@ namespace cru
//-------begin of file: src\exception.hpp
//--------------------------------------------------------
+// ReSharper disable once CppUnusedIncludeDirective
+
#include <optional>
@@ -529,6 +518,7 @@ namespace cru {
//-------begin of file: src\timer.hpp
//--------------------------------------------------------
+// ReSharper disable once CppUnusedIncludeDirective
#include <map>
#include <chrono>
@@ -594,14 +584,17 @@ namespace cru
//-------begin of file: src\ui\window.hpp
//--------------------------------------------------------
+// ReSharper disable once CppUnusedIncludeDirective
+
#include <map>
-#include <list>
#include <memory>
//--------------------------------------------------------
//-------begin of file: src\ui\control.hpp
//--------------------------------------------------------
+// ReSharper disable once CppUnusedIncludeDirective
+
#include <unordered_map>
#include <any>
#include <utility>
@@ -610,6 +603,8 @@ namespace cru
//-------begin of file: src\ui\ui_base.hpp
//--------------------------------------------------------
+// ReSharper disable once CppUnusedIncludeDirective
+
#include <optional>
@@ -760,6 +755,16 @@ namespace cru::ui
return Point(left + width, top + height);
}
+ constexpr Point GetLeftBottom() const
+ {
+ return Point(left, top + height);
+ }
+
+ constexpr Point GetRightTop() const
+ {
+ return Point(left + width, top);
+ }
+
constexpr Size GetSize() const
{
return Size(width, height);
@@ -832,6 +837,8 @@ namespace cru::ui
//-------begin of file: src\ui\layout_base.hpp
//--------------------------------------------------------
+// ReSharper disable once CppUnusedIncludeDirective
+
#include <unordered_set>
@@ -977,12 +984,16 @@ namespace cru::ui
//-------begin of file: src\ui\events\ui_event.hpp
//--------------------------------------------------------
+// ReSharper disable once CppUnusedIncludeDirective
+
#include <optional>
//--------------------------------------------------------
//-------begin of file: src\cru_event.hpp
//--------------------------------------------------------
+// ReSharper disable once CppUnusedIncludeDirective
+
#include <type_traits>
#include <functional>
#include <unordered_map>
@@ -1147,6 +1158,30 @@ namespace cru::ui::events
};
+ class MouseWheelEventArgs : public MouseEventArgs
+ {
+ public:
+ MouseWheelEventArgs(Object* sender, Object* original_sender, const Point& point, const float delta)
+ : MouseEventArgs(sender, original_sender, point), delta_(delta)
+ {
+
+ }
+ MouseWheelEventArgs(const MouseWheelEventArgs& other) = default;
+ MouseWheelEventArgs(MouseWheelEventArgs&& other) = default;
+ MouseWheelEventArgs& operator=(const MouseWheelEventArgs& other) = default;
+ MouseWheelEventArgs& operator=(MouseWheelEventArgs&& other) = default;
+ ~MouseWheelEventArgs() override = default;
+
+ float GetDelta() const
+ {
+ return delta_;
+ }
+
+ private:
+ float delta_;
+ };
+
+
class DrawEventArgs : public UiEventArgs
{
public:
@@ -1366,6 +1401,7 @@ namespace cru::ui::events
using UiEvent = Event<UiEventArgs>;
using MouseEvent = Event<MouseEventArgs>;
using MouseButtonEvent = Event<MouseButtonEventArgs>;
+ using MouseWheelEvent = Event<MouseWheelEventArgs>;
using DrawEvent = Event<DrawEventArgs>;
using PositionChangedEvent = Event<PositionChangedEventArgs>;
using SizeChangedEvent = Event<SizeChangedEventArgs>;
@@ -1382,6 +1418,8 @@ namespace cru::ui::events
//-------begin of file: src\ui\border_property.hpp
//--------------------------------------------------------
+// ReSharper disable once CppUnusedIncludeDirective
+
@@ -1469,6 +1507,8 @@ namespace cru::ui
//-------begin of file: src\ui\cursor.hpp
//--------------------------------------------------------
+// ReSharper disable once CppUnusedIncludeDirective
+
#include <memory>
@@ -1500,7 +1540,9 @@ namespace cru::ui
{
extern Cursor::Ptr arrow;
extern Cursor::Ptr hand;
- extern Cursor::Ptr i_beam;
+ extern Cursor::Ptr i_beam;
+
+ void LoadSystemCursors();
}
}
//--------------------------------------------------------
@@ -1520,12 +1562,22 @@ namespace cru::ui
Point lefttop_position_absolute;
};
+
class Control : public Object
{
friend class Window;
friend class LayoutManager;
protected:
+ struct GeometryInfo
+ {
+ Microsoft::WRL::ComPtr<ID2D1Geometry> border_geometry = nullptr;
+ Microsoft::WRL::ComPtr<ID2D1Geometry> padding_content_geometry = nullptr;
+ Microsoft::WRL::ComPtr<ID2D1Geometry> content_geometry = nullptr;
+ };
+
+
+ protected:
struct WindowConstructorTag {}; //Used for constructor for class Window.
explicit Control(bool container = false);
@@ -1615,9 +1667,18 @@ namespace cru::ui
// fill and stroke with width of border.
virtual bool IsPointInside(const Point& point);
+ // Get the top control among all descendants (including self) in local coordinate.
+ virtual Control* HitTest(const Point& point);
//*************** region: graphic ***************
+ bool IsClipContent() const
+ {
+ return clip_content_;
+ }
+
+ void SetClipContent(bool clip);
+
//Draw this control and its child controls.
void Draw(ID2D1DeviceContext* device_context);
@@ -1733,6 +1794,8 @@ namespace cru::ui
//Raised when a mouse button is pressed in the control and released in the control with mouse not leaving it between two operations.
events::MouseButtonEvent mouse_click_event;
+ events::MouseWheelEvent mouse_wheel_event;
+
events::KeyEvent key_down_event;
events::KeyEvent key_up_event;
events::CharEvent char_event;
@@ -1758,12 +1821,11 @@ namespace cru::ui
//Invoked when the control is detached to a window. Overrides should invoke base.
virtual void OnDetachToWindow(Window* window);
+ //*************** region: graphic events ***************
private:
+ void OnDrawDecoration(ID2D1DeviceContext* device_context);
void OnDrawCore(ID2D1DeviceContext* device_context);
-
protected:
-
- //*************** region: graphic events ***************
virtual void OnDrawContent(ID2D1DeviceContext* device_context);
virtual void OnDrawForeground(ID2D1DeviceContext* device_context);
virtual void OnDrawBackground(ID2D1DeviceContext* device_context);
@@ -1785,7 +1847,12 @@ namespace cru::ui
void RaisePositionChangedEvent(events::PositionChangedEventArgs& args);
void RaiseSizeChangedEvent(events::SizeChangedEventArgs& args);
- void RegenerateBorderGeometry();
+ void RegenerateGeometryInfo();
+
+ const GeometryInfo& GetGeometryInfo() const
+ {
+ return geometry_info_;
+ }
//*************** region: mouse event ***************
virtual void OnMouseEnter(events::MouseEventArgs& args);
@@ -1802,6 +1869,9 @@ namespace cru::ui
virtual void OnMouseUpCore(events::MouseButtonEventArgs& args);
virtual void OnMouseClickCore(events::MouseButtonEventArgs& args);
+ virtual void OnMouseWheel(events::MouseWheelEventArgs& args);
+ virtual void OnMouseWheelCore(events::MouseWheelEventArgs& args);
+
void RaiseMouseEnterEvent(events::MouseEventArgs& args);
void RaiseMouseLeaveEvent(events::MouseEventArgs& args);
void RaiseMouseMoveEvent(events::MouseEventArgs& args);
@@ -1809,6 +1879,8 @@ namespace cru::ui
void RaiseMouseUpEvent(events::MouseButtonEventArgs& args);
void RaiseMouseClickEvent(events::MouseButtonEventArgs& args);
+ void RaiseMouseWheelEvent(events::MouseWheelEventArgs& args);
+
virtual void OnMouseClickBegin(MouseButton button);
virtual void OnMouseClickEnd(MouseButton button);
@@ -1842,6 +1914,9 @@ namespace cru::ui
virtual Size OnMeasureContent(const Size& available_size);
virtual void OnLayoutContent(const Rect& rect);
+ // Called by Layout after set position and size.
+ virtual void AfterLayoutSelf();
+
private:
// Only for layout manager to use.
// Check if the old position is updated to current position.
@@ -1858,10 +1933,8 @@ namespace cru::ui
private:
bool is_container_;
- protected:
- Window * window_ = nullptr; // protected for Window class to write it as itself in constructor.
+ Window * window_ = nullptr;
- private:
Control * parent_ = nullptr;
std::vector<Control*> children_{};
@@ -1891,8 +1964,9 @@ namespace cru::ui
bool is_bordered_ = false;
BorderProperty border_property_;
- Microsoft::WRL::ComPtr<ID2D1Geometry> border_geometry_ = nullptr;
- Microsoft::WRL::ComPtr<ID2D1Geometry> in_border_geometry_ = nullptr; //used for foreground and background brush.
+ GeometryInfo geometry_info_{};
+
+ bool clip_content_ = false;
Microsoft::WRL::ComPtr<ID2D1Brush> foreground_brush_ = nullptr;
Microsoft::WRL::ComPtr<ID2D1Brush> background_brush_ = nullptr;
@@ -2155,15 +2229,6 @@ namespace cru::ui
void SetSizeFitContent(const Size& max_size = Size(1000, 1000));
- //*************** region: functions ***************
-
- //Refresh control list.
- //It should be invoked every time a control is added or removed from the tree.
- void RefreshControlList();
-
- //Get the most top control at "point".
- Control* HitTest(const Point& point);
-
//*************** region: focus ***************
@@ -2224,7 +2289,8 @@ namespace cru::ui
void OnMouseLeaveInternal();
void OnMouseDownInternal(MouseButton button, POINT point);
void OnMouseUpInternal(MouseButton button, POINT point);
-
+
+ void OnMouseWheelInternal(short delta, POINT point);
void OnKeyDownInternal(int virtual_code);
void OnKeyUpInternal(int virtual_code);
void OnCharInternal(wchar_t c);
@@ -2267,8 +2333,6 @@ namespace cru::ui
Window* parent_window_ = nullptr;
std::shared_ptr<graph::WindowRenderTarget> render_target_{};
- std::list<Control*> control_list_{};
-
Control* mouse_hover_control_ = nullptr;
bool window_focus_ = false;
@@ -2290,6 +2354,8 @@ namespace cru::ui
//-------begin of file: src\cru_debug.hpp
//--------------------------------------------------------
+// ReSharper disable once CppUnusedIncludeDirective
+
#include <functional>
@@ -2337,6 +2403,8 @@ namespace cru::debug
//-------begin of file: src\ui\controls\linear_layout.hpp
//--------------------------------------------------------
+// ReSharper disable once CppUnusedIncludeDirective
+
namespace cru::ui::controls
{
@@ -2388,10 +2456,14 @@ namespace cru::ui::controls
//-------begin of file: src\ui\controls\text_block.hpp
//--------------------------------------------------------
+// ReSharper disable once CppUnusedIncludeDirective
+
//--------------------------------------------------------
//-------begin of file: src\ui\controls\text_control.hpp
//--------------------------------------------------------
+// ReSharper disable once CppUnusedIncludeDirective
+
namespace cru::ui::controls
{
@@ -2529,6 +2601,8 @@ namespace cru::ui::controls
//-------begin of file: src\ui\controls\toggle_button.hpp
//--------------------------------------------------------
+// ReSharper disable once CppUnusedIncludeDirective
+
namespace cru::ui::controls
{
@@ -2598,6 +2672,8 @@ namespace cru::ui::controls
//-------begin of file: src\ui\controls\button.hpp
//--------------------------------------------------------
+// ReSharper disable once CppUnusedIncludeDirective
+
#include <initializer_list>
@@ -2644,6 +2720,8 @@ namespace cru::ui::controls
//-------begin of file: src\ui\controls\text_box.hpp
//--------------------------------------------------------
+// ReSharper disable once CppUnusedIncludeDirective
+
namespace cru::ui::controls
{
@@ -2699,6 +2777,8 @@ namespace cru::ui::controls
//-------begin of file: src\ui\controls\list_item.hpp
//--------------------------------------------------------
+// ReSharper disable once CppUnusedIncludeDirective
+
#include <map>
#include <initializer_list>
@@ -2770,6 +2850,8 @@ namespace cru::ui::controls
//-------begin of file: src\ui\controls\popup_menu.hpp
//--------------------------------------------------------
+// ReSharper disable once CppUnusedIncludeDirective
+
#include <vector>
#include <utility>
#include <functional>
@@ -2793,6 +2875,8 @@ namespace cru::ui::controls
//-------begin of file: src\ui\controls\frame_layout.hpp
//--------------------------------------------------------
+// ReSharper disable once CppUnusedIncludeDirective
+
#include <initializer_list>
@@ -2827,9 +2911,175 @@ namespace cru::ui::controls
//-------end of file: src\ui\controls\frame_layout.hpp
//--------------------------------------------------------
//--------------------------------------------------------
+//-------begin of file: src\ui\controls\scroll_control.hpp
+//--------------------------------------------------------
+
+// ReSharper disable once CppUnusedIncludeDirective
+
+#include <optional>
+#include <initializer_list>
+
+
+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;
+ };
+}
+//--------------------------------------------------------
+//-------end of file: src\ui\controls\scroll_control.hpp
+//--------------------------------------------------------
+//--------------------------------------------------------
//-------begin of file: src\graph\graph.hpp
//--------------------------------------------------------
+// ReSharper disable once CppUnusedIncludeDirective
+
#include <memory>
#include <functional>
@@ -3010,6 +3260,8 @@ namespace cru::graph
//-------begin of file: src\ui\ui_manager.hpp
//--------------------------------------------------------
+// ReSharper disable once CppUnusedIncludeDirective
+
namespace cru::graph
@@ -3068,6 +3320,10 @@ namespace cru::ui
Microsoft::WRL::ComPtr<ID2D1Brush> list_item_select_border_brush;
Microsoft::WRL::ComPtr<ID2D1Brush> list_item_select_fill_brush;
+ //region ScrollControl
+ Microsoft::WRL::ComPtr<ID2D1Brush> scroll_bar_background_brush;
+ Microsoft::WRL::ComPtr<ID2D1Brush> scroll_bar_border_brush;
+ Microsoft::WRL::ComPtr<ID2D1Brush> scroll_bar_brush;
#ifdef CRU_DEBUG_LAYOUT
//region debug
@@ -3113,6 +3369,8 @@ namespace cru::ui
//-------begin of file: src\ui\convert_util.hpp
//--------------------------------------------------------
+// ReSharper disable once CppUnusedIncludeDirective
+
namespace cru::ui
@@ -3131,9 +3389,84 @@ namespace cru::ui
//-------end of file: src\ui\convert_util.hpp
//--------------------------------------------------------
//--------------------------------------------------------
+//-------begin of file: src\math_util.hpp
+//--------------------------------------------------------
+
+// ReSharper disable once CppUnusedIncludeDirective
+
+// ReSharper disable once CppUnusedIncludeDirective
+#include <type_traits>
+#include <optional>
+
+namespace cru
+{
+ 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())
+ return min.value();
+ if (max.has_value() && n > max.value())
+ return max.value();
+ return n;
+ }
+
+ template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
+ float Coerce(const T n, const T min, const T max)
+ {
+ if (n < min)
+ return min;
+ if (n > max)
+ return max;
+ return n;
+ }
+
+ template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
+ float Coerce(const T n, const std::nullopt_t, const std::optional<T> max)
+ {
+ if (max.has_value() && n > max.value())
+ return max.value();
+ return n;
+ }
+
+ 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::nullopt_t)
+ {
+ if (min.has_value() && n < min.value())
+ return min.value();
+ return n;
+ }
+
+ template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
+ float Coerce(const T n, const std::nullopt_t, const T max)
+ {
+ if (n > max)
+ return max;
+ return n;
+ }
+
+ template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
+ float Coerce(const T n, const T min, const std::nullopt_t)
+ {
+ if (n < min)
+ return min;
+ return n;
+ }
+
+ template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
+ T AtLeast0(const T value)
+ {
+ return value < static_cast<T>(0) ? static_cast<T>(0) : value;
+ }
+}
+//--------------------------------------------------------
+//-------end of file: src\math_util.hpp
+//--------------------------------------------------------
+//--------------------------------------------------------
//-------begin of file: src\ui\animations\animation.hpp
//--------------------------------------------------------
+// ReSharper disable once CppUnusedIncludeDirective
+
#include <unordered_map>
diff --git a/CruUI.vcxproj b/CruUI.vcxproj
index 932726cf..30f39f28 100644
--- a/CruUI.vcxproj
+++ b/CruUI.vcxproj
@@ -133,14 +133,17 @@
<ClCompile Include="src\ui\controls\linear_layout.cpp" />
<ClCompile Include="src\ui\controls\list_item.cpp" />
<ClCompile Include="src\ui\controls\popup_menu.cpp" />
+ <ClCompile Include="src\ui\controls\scroll_control.cpp" />
<ClCompile Include="src\ui\controls\text_block.cpp" />
<ClCompile Include="src\ui\controls\text_box.cpp" />
<ClInclude Include="src\any_map.hpp" />
<ClInclude Include="src\format.hpp" />
+ <ClInclude Include="src\math_util.hpp" />
<ClInclude Include="src\ui\border_property.hpp" />
<ClInclude Include="src\ui\controls\frame_layout.hpp" />
<ClInclude Include="src\ui\controls\list_item.hpp" />
<ClInclude Include="src\ui\controls\popup_menu.hpp" />
+ <ClInclude Include="src\ui\controls\scroll_control.hpp" />
<ClInclude Include="src\ui\controls\text_control.hpp" />
<ClCompile Include="src\ui\controls\toggle_button.cpp" />
<ClCompile Include="src\ui\cursor.cpp" />
@@ -156,7 +159,7 @@
<ClInclude Include="src\cru_event.hpp" />
<ClInclude Include="src\cru_debug.hpp" />
<ClInclude Include="src\exception.hpp" />
- <ClInclude Include="src\global_macros.hpp" />
+ <ClInclude Include="src\pre.hpp" />
<ClInclude Include="src\graph\graph.hpp" />
<ClInclude Include="src\system_headers.hpp" />
<ClInclude Include="src\timer.hpp" />
diff --git a/CruUI.vcxproj.filters b/CruUI.vcxproj.filters
index b8c89bb1..71025931 100644
--- a/CruUI.vcxproj.filters
+++ b/CruUI.vcxproj.filters
@@ -90,6 +90,9 @@
<ClCompile Include="src\ui\controls\frame_layout.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="src\ui\controls\scroll_control.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\graph\graph.hpp">
@@ -155,9 +158,6 @@
<ClInclude Include="src\format.hpp">
<Filter>Header Files</Filter>
</ClInclude>
- <ClInclude Include="src\global_macros.hpp">
- <Filter>Header Files</Filter>
- </ClInclude>
<ClInclude Include="src\system_headers.hpp">
<Filter>Header Files</Filter>
</ClInclude>
@@ -182,6 +182,15 @@
<ClInclude Include="src\ui\controls\frame_layout.hpp">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="src\ui\controls\scroll_control.hpp">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="src\math_util.hpp">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="src\pre.hpp">
+ <Filter>Header Files</Filter>
+ </ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\application.cpp">
diff --git a/src/any_map.hpp b/src/any_map.hpp
index ea6044b1..dfc54f3f 100644
--- a/src/any_map.hpp
+++ b/src/any_map.hpp
@@ -1,5 +1,8 @@
#pragma once
+// ReSharper disable once CppUnusedIncludeDirective
+#include "pre.hpp"
+
#include <any>
#include <unordered_map>
#include <functional>
diff --git a/src/application.cpp b/src/application.cpp
index fa71c37e..c3669f72 100644
--- a/src/application.cpp
+++ b/src/application.cpp
@@ -86,16 +86,6 @@ namespace cru {
return instance_;
}
- namespace
- {
- void LoadSystemCursor(HINSTANCE h_instance)
- {
- ui::cursors::arrow = std::make_shared<ui::Cursor>(::LoadCursorW(nullptr, IDC_ARROW), false);
- ui::cursors::hand = std::make_shared<ui::Cursor>(::LoadCursorW(nullptr, IDC_HAND), false);
- ui::cursors::i_beam = std::make_shared<ui::Cursor>(::LoadCursorW(nullptr, IDC_IBEAM), false);
- }
- }
-
Application::Application(HINSTANCE h_instance)
: h_instance_(h_instance) {
@@ -104,9 +94,12 @@ namespace cru {
instance_ = this;
+ if (!::IsWindows8OrGreater())
+ throw std::runtime_error("Must run on Windows 8 or later.");
+
god_window_ = std::make_unique<GodWindow>(this);
- LoadSystemCursor(h_instance);
+ ui::cursors::LoadSystemCursors();
}
Application::~Application()
diff --git a/src/application.hpp b/src/application.hpp
index b9427826..a8d59cc8 100644
--- a/src/application.hpp
+++ b/src/application.hpp
@@ -1,5 +1,8 @@
#pragma once
+// ReSharper disable once CppUnusedIncludeDirective
+#include "pre.hpp"
+
#include "system_headers.hpp"
#include <memory>
#include <optional>
diff --git a/src/base.hpp b/src/base.hpp
index 5d8cb9ce..64ce7f6e 100644
--- a/src/base.hpp
+++ b/src/base.hpp
@@ -1,16 +1,12 @@
#pragma once
// ReSharper disable once CppUnusedIncludeDirective
-#include "global_macros.hpp"
-
+#include "pre.hpp"
#include <string>
#include <stdexcept>
#include <string_view>
#include <chrono>
-#include <optional>
-// ReSharper disable once CppUnusedIncludeDirective
-#include <type_traits>
namespace cru
{
@@ -60,31 +56,4 @@ namespace cru
if (!condition)
throw std::invalid_argument(error_message.data());
}
-
- 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())
- return min.value();
- if (max.has_value() && n > max.value())
- return max.value();
- return n;
- }
-
- template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
- float Coerce(const T n, const std::nullopt_t, const std::optional<T> max)
- {
- if (max.has_value() && n > max.value())
- return max.value();
- return n;
- }
-
- 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::nullopt_t)
- {
- if (min.has_value() && n < min.value())
- return min.value();
- return n;
- }
-
}
diff --git a/src/cru_debug.hpp b/src/cru_debug.hpp
index ed6fcaf6..17cc7b53 100644
--- a/src/cru_debug.hpp
+++ b/src/cru_debug.hpp
@@ -1,5 +1,8 @@
#pragma once
+// ReSharper disable once CppUnusedIncludeDirective
+#include "pre.hpp"
+
#include <functional>
#include "base.hpp"
diff --git a/src/cru_event.hpp b/src/cru_event.hpp
index 76a36b22..69832a0e 100644
--- a/src/cru_event.hpp
+++ b/src/cru_event.hpp
@@ -1,5 +1,8 @@
#pragma once
+// ReSharper disable once CppUnusedIncludeDirective
+#include "pre.hpp"
+
#include <type_traits>
#include <functional>
#include <unordered_map>
diff --git a/src/exception.hpp b/src/exception.hpp
index 68558478..b8cef604 100644
--- a/src/exception.hpp
+++ b/src/exception.hpp
@@ -1,5 +1,8 @@
#pragma once
+// ReSharper disable once CppUnusedIncludeDirective
+#include "pre.hpp"
+
#include "system_headers.hpp"
#include <optional>
diff --git a/src/format.hpp b/src/format.hpp
index 3f6253ff..efd25f89 100644
--- a/src/format.hpp
+++ b/src/format.hpp
@@ -1,5 +1,8 @@
#pragma once
+// ReSharper disable once CppUnusedIncludeDirective
+#include "pre.hpp"
+
#include "base.hpp"
namespace cru
diff --git a/src/global_macros.hpp b/src/global_macros.hpp
deleted file mode 100644
index eda57187..00000000
--- a/src/global_macros.hpp
+++ /dev/null
@@ -1,9 +0,0 @@
-#pragma once
-
-#ifdef _DEBUG
-#define CRU_DEBUG
-#endif
-
-#ifdef CRU_DEBUG
-#define CRU_DEBUG_LAYOUT
-#endif
diff --git a/src/graph/graph.hpp b/src/graph/graph.hpp
index 7771b48f..440b0594 100644
--- a/src/graph/graph.hpp
+++ b/src/graph/graph.hpp
@@ -1,5 +1,8 @@
#pragma once
+// ReSharper disable once CppUnusedIncludeDirective
+#include "pre.hpp"
+
#include "system_headers.hpp"
#include <memory>
#include <functional>
diff --git a/src/main.cpp b/src/main.cpp
index 376c03b8..7b27d95e 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,3 +1,5 @@
+#include "pre.hpp"
+
#include "application.hpp"
#include "ui/window.hpp"
#include "ui/controls/linear_layout.hpp"
@@ -8,6 +10,7 @@
#include "ui/controls/list_item.hpp"
#include "ui/controls/popup_menu.hpp"
#include "ui/controls/frame_layout.hpp"
+#include "ui/controls/scroll_control.hpp"
#include "graph/graph.hpp"
using cru::String;
@@ -27,6 +30,7 @@ using cru::ui::controls::Button;
using cru::ui::controls::TextBox;
using cru::ui::controls::ListItem;
using cru::ui::controls::FrameLayout;
+using cru::ui::controls::ScrollControl;
int APIENTRY wWinMain(
HINSTANCE hInstance,
@@ -34,6 +38,10 @@ int APIENTRY wWinMain(
LPWSTR lpCmdLine,
int nCmdShow) {
+#ifdef CRU_DEBUG
+ _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
+#endif
+
Application application(hInstance);
const auto window = Window::CreateOverlapped();
@@ -183,9 +191,16 @@ int APIENTRY wWinMain(
}
{
- const auto text_block = CreateWithLayout<TextBlock>(LayoutSideParams::Stretch(), LayoutSideParams::Stretch(), L"This is a very very very very very long sentence!!!");
+ const auto scroll_view = CreateWithLayout<ScrollControl>(LayoutSideParams::Stretch(), LayoutSideParams::Stretch());
+
+ scroll_view->SetVerticalScrollBarVisibility(ScrollControl::ScrollBarVisibility::Always);
+
+ const auto text_block = TextBlock::Create(
+ L"Love myself I do. Not everything, but I love the good as well as the bad. I love my crazy lifestyle, and I love my hard discipline. I love my freedom of speech and the way my eyes get dark when I'm tired. I love that I have learned to trust people with my heart, even if it will get broken. I am proud of everything that I am and will become.");
text_block->SetSelectable(true);
- layout->AddChild(text_block);
+
+ scroll_view->AddChild(text_block);
+ layout->AddChild(scroll_view);
}
layout->AddChild(CreateWithLayout<TextBlock>(LayoutSideParams::Content(Alignment::Start), LayoutSideParams::Content(), L"This is a little short sentence!!!"));
@@ -216,7 +231,6 @@ int APIENTRY wWinMain(
window.AddChild(linear_layout);
*/
-
window->Show();
return application.Run();
diff --git a/src/math_util.hpp b/src/math_util.hpp
new file mode 100644
index 00000000..b9830d6b
--- /dev/null
+++ b/src/math_util.hpp
@@ -0,0 +1,69 @@
+#pragma once
+
+// ReSharper disable once CppUnusedIncludeDirective
+#include "pre.hpp"
+
+// ReSharper disable once CppUnusedIncludeDirective
+#include <type_traits>
+#include <optional>
+
+namespace cru
+{
+ 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())
+ return min.value();
+ if (max.has_value() && n > max.value())
+ return max.value();
+ return n;
+ }
+
+ template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
+ float Coerce(const T n, const T min, const T max)
+ {
+ if (n < min)
+ return min;
+ if (n > max)
+ return max;
+ return n;
+ }
+
+ template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
+ float Coerce(const T n, const std::nullopt_t, const std::optional<T> max)
+ {
+ if (max.has_value() && n > max.value())
+ return max.value();
+ return n;
+ }
+
+ 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::nullopt_t)
+ {
+ if (min.has_value() && n < min.value())
+ return min.value();
+ return n;
+ }
+
+ template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
+ float Coerce(const T n, const std::nullopt_t, const T max)
+ {
+ if (n > max)
+ return max;
+ return n;
+ }
+
+ template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
+ float Coerce(const T n, const T min, const std::nullopt_t)
+ {
+ if (n < min)
+ return min;
+ return n;
+ }
+
+ template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
+ T AtLeast0(const T value)
+ {
+ return value < static_cast<T>(0) ? static_cast<T>(0) : value;
+ }
+}
diff --git a/src/pre.hpp b/src/pre.hpp
new file mode 100644
index 00000000..03c51a94
--- /dev/null
+++ b/src/pre.hpp
@@ -0,0 +1,18 @@
+#pragma once
+
+#ifdef _DEBUG
+#define CRU_DEBUG
+#endif
+
+#ifdef CRU_DEBUG
+#define CRU_DEBUG_LAYOUT
+#endif
+
+
+#ifdef CRU_DEBUG
+// ReSharper disable once IdentifierTypo
+// ReSharper disable once CppInconsistentNaming
+#define _CRTDBG_MAP_ALLOC
+#include <cstdlib>
+#include <crtdbg.h>
+#endif
diff --git a/src/system_headers.hpp b/src/system_headers.hpp
index 99c091e1..eabc7c25 100644
--- a/src/system_headers.hpp
+++ b/src/system_headers.hpp
@@ -1,5 +1,7 @@
#pragma once
+// ReSharper disable once CppUnusedIncludeDirective
+#include "pre.hpp"
//include system headers
@@ -19,3 +21,5 @@
#include <dxgi1_2.h>
#include <wrl/client.h>
+
+#include <VersionHelpers.h>
diff --git a/src/timer.hpp b/src/timer.hpp
index 3488db45..5055a3d8 100644
--- a/src/timer.hpp
+++ b/src/timer.hpp
@@ -1,5 +1,7 @@
#pragma once
+// ReSharper disable once CppUnusedIncludeDirective
+#include "pre.hpp"
#include "system_headers.hpp"
#include <map>
diff --git a/src/ui/animations/animation.hpp b/src/ui/animations/animation.hpp
index f25e4699..2226f021 100644
--- a/src/ui/animations/animation.hpp
+++ b/src/ui/animations/animation.hpp
@@ -1,5 +1,8 @@
#pragma once
+// ReSharper disable once CppUnusedIncludeDirective
+#include "pre.hpp"
+
#include <unordered_map>
#include "base.hpp"
diff --git a/src/ui/border_property.hpp b/src/ui/border_property.hpp
index 7766f5a3..4dee0e0f 100644
--- a/src/ui/border_property.hpp
+++ b/src/ui/border_property.hpp
@@ -1,5 +1,8 @@
#pragma once
+// ReSharper disable once CppUnusedIncludeDirective
+#include "pre.hpp"
+
#include "system_headers.hpp"
#include "base.hpp"
diff --git a/src/ui/control.cpp b/src/ui/control.cpp
index 8b91b25a..2a81427a 100644
--- a/src/ui/control.cpp
+++ b/src/ui/control.cpp
@@ -8,6 +8,7 @@
#include "exception.hpp"
#include "cru_debug.hpp"
#include "convert_util.hpp"
+#include "math_util.hpp"
#ifdef CRU_DEBUG_LAYOUT
#include "ui_manager.hpp"
@@ -191,26 +192,67 @@ namespace cru::ui
bool Control::IsPointInside(const Point & point)
{
- if (border_geometry_ != nullptr)
+ const auto border_geometry = geometry_info_.border_geometry;
+ if (border_geometry != nullptr)
{
if (IsBordered())
{
BOOL contains;
- border_geometry_->FillContainsPoint(Convert(point), D2D1::Matrix3x2F::Identity(), &contains);
+ border_geometry->FillContainsPoint(Convert(point), D2D1::Matrix3x2F::Identity(), &contains);
if (!contains)
- border_geometry_->StrokeContainsPoint(Convert(point), GetBorderProperty().GetStrokeWidth(), nullptr, D2D1::Matrix3x2F::Identity(), &contains);
+ border_geometry->StrokeContainsPoint(Convert(point), GetBorderProperty().GetStrokeWidth(), nullptr, D2D1::Matrix3x2F::Identity(), &contains);
return contains != 0;
}
else
{
BOOL contains;
- border_geometry_->FillContainsPoint(Convert(point), D2D1::Matrix3x2F::Identity(), &contains);
+ border_geometry->FillContainsPoint(Convert(point), D2D1::Matrix3x2F::Identity(), &contains);
return contains != 0;
}
}
return false;
}
+ Control* Control::HitTest(const Point& point)
+ {
+ const auto point_inside = IsPointInside(point);
+
+ if (IsClipContent())
+ {
+ if (!point_inside)
+ return nullptr;
+ if (geometry_info_.content_geometry != nullptr)
+ {
+ BOOL contains;
+ ThrowIfFailed(geometry_info_.content_geometry->FillContainsPoint(Convert(point), D2D1::Matrix3x2F::Identity(), &contains));
+ if (contains == 0)
+ return this;
+ }
+ }
+
+ const auto& children = GetChildren();
+
+ for (auto i = children.crbegin(); i != children.crend(); ++i)
+ {
+ const auto&& lefttop = (*i)->GetPositionRelative();
+ const auto&& coerced_point = Point(point.x - lefttop.x, point.y - lefttop.y);
+ const auto child_hit_test_result = (*i)->HitTest(coerced_point);
+ if (child_hit_test_result != nullptr)
+ return child_hit_test_result;
+ }
+
+ return point_inside ? this : nullptr;
+ }
+
+ void Control::SetClipContent(const bool clip)
+ {
+ if (clip_content_ == clip)
+ return;
+
+ clip_content_ = clip;
+ InvalidateDraw();
+ }
+
void Control::Draw(ID2D1DeviceContext* device_context)
{
D2D1::Matrix3x2F old_transform;
@@ -219,11 +261,20 @@ namespace cru::ui
const auto position = GetPositionRelative();
device_context->SetTransform(old_transform * D2D1::Matrix3x2F::Translation(position.x, position.y));
+ OnDrawDecoration(device_context);
+
+ const auto set_layer = geometry_info_.content_geometry != nullptr && IsClipContent();
+ if (set_layer)
+ device_context->PushLayer(D2D1::LayerParameters(D2D1::InfiniteRect(), geometry_info_.content_geometry.Get()), nullptr);
+
OnDrawCore(device_context);
for (auto child : GetChildren())
child->Draw(device_context);
+ if (set_layer)
+ device_context->PopLayer();
+
device_context->SetTransform(old_transform);
}
@@ -266,6 +317,7 @@ namespace cru::ui
{
SetPositionRelative(rect.GetLeftTop());
SetSize(rect.GetSize());
+ AfterLayoutSelf();
OnLayoutCore(Rect(Point::Zero(), rect.GetSize()));
}
@@ -335,7 +387,7 @@ namespace cru::ui
void Control::UpdateBorder()
{
- RegenerateBorderGeometry();
+ RegenerateGeometryInfo();
InvalidateLayout();
InvalidateDraw();
}
@@ -367,7 +419,6 @@ namespace cru::ui
child->TraverseDescendants([window](Control* control) {
control->OnAttachToWindow(window);
});
- window->RefreshControlList();
InvalidateLayout();
}
}
@@ -379,7 +430,6 @@ namespace cru::ui
child->TraverseDescendants([window](Control* control) {
control->OnDetachToWindow(window);
});
- window->RefreshControlList();
InvalidateLayout();
}
}
@@ -394,9 +444,9 @@ namespace cru::ui
window_ = nullptr;
}
- void Control::OnDrawCore(ID2D1DeviceContext* device_context)
+ void Control::OnDrawDecoration(ID2D1DeviceContext* device_context)
{
- #ifdef CRU_DEBUG_LAYOUT
+#ifdef CRU_DEBUG_LAYOUT
if (GetWindow()->IsDebugLayout())
{
if (padding_geometry_ != nullptr)
@@ -407,17 +457,21 @@ namespace cru::ui
}
#endif
- if (is_bordered_ && border_geometry_ != nullptr)
+ if (is_bordered_ && geometry_info_.border_geometry != nullptr)
device_context->DrawGeometry(
- border_geometry_.Get(),
+ geometry_info_.border_geometry.Get(),
GetBorderProperty().GetBrush().Get(),
GetBorderProperty().GetStrokeWidth(),
GetBorderProperty().GetStrokeStyle().Get()
);
+ }
+ void Control::OnDrawCore(ID2D1DeviceContext* device_context)
+ {
+ const auto ground_geometry = geometry_info_.padding_content_geometry;
//draw background.
- if (in_border_geometry_ != nullptr && background_brush_ != nullptr)
- device_context->FillGeometry(in_border_geometry_.Get(), background_brush_.Get());
+ if (ground_geometry != nullptr && background_brush_ != nullptr)
+ device_context->FillGeometry(ground_geometry.Get(), background_brush_.Get());
const auto padding_rect = GetRect(RectRange::Padding);
graph::WithTransform(device_context, D2D1::Matrix3x2F::Translation(padding_rect.left, padding_rect.top),
[this](ID2D1DeviceContext* device_context)
@@ -439,8 +493,8 @@ namespace cru::ui
//draw foreground.
- if (in_border_geometry_ != nullptr && foreground_brush_ != nullptr)
- device_context->FillGeometry(in_border_geometry_.Get(), foreground_brush_.Get());
+ if (ground_geometry != nullptr && foreground_brush_ != nullptr)
+ device_context->FillGeometry(ground_geometry.Get(), foreground_brush_.Get());
graph::WithTransform(device_context, D2D1::Matrix3x2F::Translation(padding_rect.left, padding_rect.top),
[this](ID2D1DeviceContext* device_context)
{
@@ -502,7 +556,7 @@ namespace cru::ui
void Control::OnSizeChangedCore(SizeChangedEventArgs & args)
{
- RegenerateBorderGeometry();
+ RegenerateGeometryInfo();
#ifdef CRU_DEBUG_LAYOUT
margin_geometry_ = CalculateSquareRingGeometry(GetRect(RectRange::Margin), GetRect(RectRange::FullBorder));
padding_geometry_ = CalculateSquareRingGeometry(GetRect(RectRange::Padding), GetRect(RectRange::Content));
@@ -523,7 +577,7 @@ namespace cru::ui
size_changed_event.Raise(args);
}
- void Control::RegenerateBorderGeometry()
+ void Control::RegenerateGeometryInfo()
{
if (IsBordered())
{
@@ -536,10 +590,10 @@ namespace cru::ui
ThrowIfFailed(
graph::GraphManager::GetInstance()->GetD2D1Factory()->CreateRoundedRectangleGeometry(bound_rounded_rect, &geometry)
);
- border_geometry_ = std::move(geometry);
+ geometry_info_.border_geometry = std::move(geometry);
- const auto in_border_rect = GetRect(RectRange::Padding);
- const auto in_border_rounded_rect = D2D1::RoundedRect(Convert(in_border_rect),
+ const auto padding_rect = GetRect(RectRange::Padding);
+ const auto in_border_rounded_rect = D2D1::RoundedRect(Convert(padding_rect),
GetBorderProperty().GetRadiusX() - GetBorderProperty().GetStrokeWidth() / 2.0f,
GetBorderProperty().GetRadiusY() - GetBorderProperty().GetStrokeWidth() / 2.0f);
@@ -547,7 +601,24 @@ namespace cru::ui
ThrowIfFailed(
graph::GraphManager::GetInstance()->GetD2D1Factory()->CreateRoundedRectangleGeometry(in_border_rounded_rect, &geometry2)
);
- in_border_geometry_ = std::move(geometry2);
+ geometry_info_.padding_content_geometry = geometry2;
+
+
+ Microsoft::WRL::ComPtr<ID2D1RectangleGeometry> geometry3;
+ ThrowIfFailed(
+ graph::GraphManager::GetInstance()->GetD2D1Factory()->CreateRectangleGeometry(Convert(GetRect(RectRange::Content)), &geometry3)
+ );
+ Microsoft::WRL::ComPtr<ID2D1PathGeometry> geometry4;
+ ThrowIfFailed(
+ graph::GraphManager::GetInstance()->GetD2D1Factory()->CreatePathGeometry(&geometry4)
+ );
+ Microsoft::WRL::ComPtr<ID2D1GeometrySink> sink;
+ geometry4->Open(&sink);
+ ThrowIfFailed(
+ geometry3->CombineWithGeometry(geometry2.Get(), D2D1_COMBINE_MODE_INTERSECT, D2D1::Matrix3x2F::Identity(), sink.Get())
+ );
+ sink->Close();
+ geometry_info_.content_geometry = std::move(geometry4);
}
else
{
@@ -556,8 +627,14 @@ namespace cru::ui
ThrowIfFailed(
graph::GraphManager::GetInstance()->GetD2D1Factory()->CreateRectangleGeometry(Convert(bound_rect), &geometry)
);
- border_geometry_ = geometry;
- in_border_geometry_ = std::move(geometry);
+ geometry_info_.border_geometry = geometry;
+ geometry_info_.padding_content_geometry = std::move(geometry);
+
+ Microsoft::WRL::ComPtr<ID2D1RectangleGeometry> geometry2;
+ ThrowIfFailed(
+ graph::GraphManager::GetInstance()->GetD2D1Factory()->CreateRectangleGeometry(Convert(GetRect(RectRange::Content)), &geometry2)
+ );
+ geometry_info_.content_geometry = std::move(geometry2);
}
}
@@ -632,6 +709,16 @@ namespace cru::ui
}
+ void Control::OnMouseWheel(events::MouseWheelEventArgs& args)
+ {
+
+ }
+
+ void Control::OnMouseWheelCore(events::MouseWheelEventArgs& args)
+ {
+
+ }
+
void Control::RaiseMouseEnterEvent(MouseEventArgs& args)
{
OnMouseEnterCore(args);
@@ -674,6 +761,13 @@ namespace cru::ui
mouse_click_event.Raise(args);
}
+ void Control::RaiseMouseWheelEvent(MouseWheelEventArgs& args)
+ {
+ OnMouseWheelCore(args);
+ OnMouseWheel(args);
+ mouse_wheel_event.Raise(args);
+ }
+
void Control::OnMouseClickBegin(MouseButton button)
{
@@ -834,7 +928,7 @@ namespace cru::ui
auto parent = GetParent();
while (parent != nullptr)
{
- auto lp = parent->GetLayoutParams();
+ const auto lp = parent->GetLayoutParams();
if (!stretch_width_determined)
{
@@ -969,6 +1063,11 @@ namespace cru::ui
}
}
+ void Control::AfterLayoutSelf()
+ {
+
+ }
+
void Control::CheckAndNotifyPositionChanged()
{
if (this->old_position_ != this->position_)
diff --git a/src/ui/control.hpp b/src/ui/control.hpp
index 2ca5fa9e..d6ad9f02 100644
--- a/src/ui/control.hpp
+++ b/src/ui/control.hpp
@@ -1,5 +1,8 @@
#pragma once
+// ReSharper disable once CppUnusedIncludeDirective
+#include "pre.hpp"
+
#include "system_headers.hpp"
#include <unordered_map>
#include <any>
@@ -26,12 +29,22 @@ namespace cru::ui
Point lefttop_position_absolute;
};
+
class Control : public Object
{
friend class Window;
friend class LayoutManager;
protected:
+ struct GeometryInfo
+ {
+ Microsoft::WRL::ComPtr<ID2D1Geometry> border_geometry = nullptr;
+ Microsoft::WRL::ComPtr<ID2D1Geometry> padding_content_geometry = nullptr;
+ Microsoft::WRL::ComPtr<ID2D1Geometry> content_geometry = nullptr;
+ };
+
+
+ protected:
struct WindowConstructorTag {}; //Used for constructor for class Window.
explicit Control(bool container = false);
@@ -121,9 +134,18 @@ namespace cru::ui
// fill and stroke with width of border.
virtual bool IsPointInside(const Point& point);
+ // Get the top control among all descendants (including self) in local coordinate.
+ virtual Control* HitTest(const Point& point);
//*************** region: graphic ***************
+ bool IsClipContent() const
+ {
+ return clip_content_;
+ }
+
+ void SetClipContent(bool clip);
+
//Draw this control and its child controls.
void Draw(ID2D1DeviceContext* device_context);
@@ -239,6 +261,8 @@ namespace cru::ui
//Raised when a mouse button is pressed in the control and released in the control with mouse not leaving it between two operations.
events::MouseButtonEvent mouse_click_event;
+ events::MouseWheelEvent mouse_wheel_event;
+
events::KeyEvent key_down_event;
events::KeyEvent key_up_event;
events::CharEvent char_event;
@@ -264,12 +288,11 @@ namespace cru::ui
//Invoked when the control is detached to a window. Overrides should invoke base.
virtual void OnDetachToWindow(Window* window);
+ //*************** region: graphic events ***************
private:
+ void OnDrawDecoration(ID2D1DeviceContext* device_context);
void OnDrawCore(ID2D1DeviceContext* device_context);
-
protected:
-
- //*************** region: graphic events ***************
virtual void OnDrawContent(ID2D1DeviceContext* device_context);
virtual void OnDrawForeground(ID2D1DeviceContext* device_context);
virtual void OnDrawBackground(ID2D1DeviceContext* device_context);
@@ -291,7 +314,12 @@ namespace cru::ui
void RaisePositionChangedEvent(events::PositionChangedEventArgs& args);
void RaiseSizeChangedEvent(events::SizeChangedEventArgs& args);
- void RegenerateBorderGeometry();
+ void RegenerateGeometryInfo();
+
+ const GeometryInfo& GetGeometryInfo() const
+ {
+ return geometry_info_;
+ }
//*************** region: mouse event ***************
virtual void OnMouseEnter(events::MouseEventArgs& args);
@@ -308,6 +336,9 @@ namespace cru::ui
virtual void OnMouseUpCore(events::MouseButtonEventArgs& args);
virtual void OnMouseClickCore(events::MouseButtonEventArgs& args);
+ virtual void OnMouseWheel(events::MouseWheelEventArgs& args);
+ virtual void OnMouseWheelCore(events::MouseWheelEventArgs& args);
+
void RaiseMouseEnterEvent(events::MouseEventArgs& args);
void RaiseMouseLeaveEvent(events::MouseEventArgs& args);
void RaiseMouseMoveEvent(events::MouseEventArgs& args);
@@ -315,6 +346,8 @@ namespace cru::ui
void RaiseMouseUpEvent(events::MouseButtonEventArgs& args);
void RaiseMouseClickEvent(events::MouseButtonEventArgs& args);
+ void RaiseMouseWheelEvent(events::MouseWheelEventArgs& args);
+
virtual void OnMouseClickBegin(MouseButton button);
virtual void OnMouseClickEnd(MouseButton button);
@@ -348,6 +381,9 @@ namespace cru::ui
virtual Size OnMeasureContent(const Size& available_size);
virtual void OnLayoutContent(const Rect& rect);
+ // Called by Layout after set position and size.
+ virtual void AfterLayoutSelf();
+
private:
// Only for layout manager to use.
// Check if the old position is updated to current position.
@@ -364,10 +400,8 @@ namespace cru::ui
private:
bool is_container_;
- protected:
- Window * window_ = nullptr; // protected for Window class to write it as itself in constructor.
+ Window * window_ = nullptr;
- private:
Control * parent_ = nullptr;
std::vector<Control*> children_{};
@@ -397,8 +431,9 @@ namespace cru::ui
bool is_bordered_ = false;
BorderProperty border_property_;
- Microsoft::WRL::ComPtr<ID2D1Geometry> border_geometry_ = nullptr;
- Microsoft::WRL::ComPtr<ID2D1Geometry> in_border_geometry_ = nullptr; //used for foreground and background brush.
+ GeometryInfo geometry_info_{};
+
+ bool clip_content_ = false;
Microsoft::WRL::ComPtr<ID2D1Brush> foreground_brush_ = nullptr;
Microsoft::WRL::ComPtr<ID2D1Brush> background_brush_ = nullptr;
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
diff --git a/src/ui/convert_util.hpp b/src/ui/convert_util.hpp
index 6deb7fff..5408f2e4 100644
--- a/src/ui/convert_util.hpp
+++ b/src/ui/convert_util.hpp
@@ -1,5 +1,8 @@
#pragma once
+// ReSharper disable once CppUnusedIncludeDirective
+#include "pre.hpp"
+
#include "system_headers.hpp"
#include "ui_base.hpp"
diff --git a/src/ui/cursor.cpp b/src/ui/cursor.cpp
index cf88cd25..91b94b16 100644
--- a/src/ui/cursor.cpp
+++ b/src/ui/cursor.cpp
@@ -21,5 +21,12 @@ namespace cru::ui
Cursor::Ptr arrow{};
Cursor::Ptr hand{};
Cursor::Ptr i_beam{};
+
+ void LoadSystemCursors()
+ {
+ arrow = std::make_shared<Cursor>(::LoadCursorW(nullptr, IDC_ARROW), false);
+ hand = std::make_shared<Cursor>(::LoadCursorW(nullptr, IDC_HAND), false);
+ i_beam = std::make_shared<Cursor>(::LoadCursorW(nullptr, IDC_IBEAM), false);
+ }
}
}
diff --git a/src/ui/cursor.hpp b/src/ui/cursor.hpp
index e3657171..273e524d 100644
--- a/src/ui/cursor.hpp
+++ b/src/ui/cursor.hpp
@@ -1,5 +1,8 @@
#pragma once
+// ReSharper disable once CppUnusedIncludeDirective
+#include "pre.hpp"
+
#include "system_headers.hpp"
#include <memory>
@@ -33,6 +36,8 @@ namespace cru::ui
{
extern Cursor::Ptr arrow;
extern Cursor::Ptr hand;
- extern Cursor::Ptr i_beam;
+ extern Cursor::Ptr i_beam;
+
+ void LoadSystemCursors();
}
}
diff --git a/src/ui/events/ui_event.hpp b/src/ui/events/ui_event.hpp
index c0585506..321e7135 100644
--- a/src/ui/events/ui_event.hpp
+++ b/src/ui/events/ui_event.hpp
@@ -1,5 +1,8 @@
#pragma once
+// ReSharper disable once CppUnusedIncludeDirective
+#include "pre.hpp"
+
#include "system_headers.hpp"
#include <optional>
@@ -85,6 +88,30 @@ namespace cru::ui::events
};
+ class MouseWheelEventArgs : public MouseEventArgs
+ {
+ public:
+ MouseWheelEventArgs(Object* sender, Object* original_sender, const Point& point, const float delta)
+ : MouseEventArgs(sender, original_sender, point), delta_(delta)
+ {
+
+ }
+ MouseWheelEventArgs(const MouseWheelEventArgs& other) = default;
+ MouseWheelEventArgs(MouseWheelEventArgs&& other) = default;
+ MouseWheelEventArgs& operator=(const MouseWheelEventArgs& other) = default;
+ MouseWheelEventArgs& operator=(MouseWheelEventArgs&& other) = default;
+ ~MouseWheelEventArgs() override = default;
+
+ float GetDelta() const
+ {
+ return delta_;
+ }
+
+ private:
+ float delta_;
+ };
+
+
class DrawEventArgs : public UiEventArgs
{
public:
@@ -304,6 +331,7 @@ namespace cru::ui::events
using UiEvent = Event<UiEventArgs>;
using MouseEvent = Event<MouseEventArgs>;
using MouseButtonEvent = Event<MouseButtonEventArgs>;
+ using MouseWheelEvent = Event<MouseWheelEventArgs>;
using DrawEvent = Event<DrawEventArgs>;
using PositionChangedEvent = Event<PositionChangedEventArgs>;
using SizeChangedEvent = Event<SizeChangedEventArgs>;
diff --git a/src/ui/layout_base.hpp b/src/ui/layout_base.hpp
index 7ae6f65c..2ae21837 100644
--- a/src/ui/layout_base.hpp
+++ b/src/ui/layout_base.hpp
@@ -1,5 +1,8 @@
#pragma once
+// ReSharper disable once CppUnusedIncludeDirective
+#include "pre.hpp"
+
#include <unordered_set>
#include "base.hpp"
diff --git a/src/ui/ui_base.hpp b/src/ui/ui_base.hpp
index d9c9d0b2..b898b2ed 100644
--- a/src/ui/ui_base.hpp
+++ b/src/ui/ui_base.hpp
@@ -1,5 +1,8 @@
#pragma once
+// ReSharper disable once CppUnusedIncludeDirective
+#include "pre.hpp"
+
#include <optional>
@@ -150,6 +153,16 @@ namespace cru::ui
return Point(left + width, top + height);
}
+ constexpr Point GetLeftBottom() const
+ {
+ return Point(left, top + height);
+ }
+
+ constexpr Point GetRightTop() const
+ {
+ return Point(left + width, top);
+ }
+
constexpr Size GetSize() const
{
return Size(width, height);
diff --git a/src/ui/ui_manager.cpp b/src/ui/ui_manager.cpp
index 36fb2fb0..689a04a2 100644
--- a/src/ui/ui_manager.cpp
+++ b/src/ui/ui_manager.cpp
@@ -75,7 +75,11 @@ namespace cru::ui
list_item_hover_border_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::SkyBlue))},
list_item_hover_fill_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::SkyBlue, 0.3f))},
list_item_select_border_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::MediumBlue))},
- list_item_select_fill_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::SkyBlue, 0.3f))}
+ list_item_select_fill_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::SkyBlue, 0.3f))},
+
+ scroll_bar_background_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::Gainsboro, 0.3f))},
+ scroll_bar_border_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::DimGray))},
+ scroll_bar_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::DimGray))}
#ifdef CRU_DEBUG_LAYOUT
,
diff --git a/src/ui/ui_manager.hpp b/src/ui/ui_manager.hpp
index 6b368e12..f0e1e8ce 100644
--- a/src/ui/ui_manager.hpp
+++ b/src/ui/ui_manager.hpp
@@ -1,5 +1,8 @@
#pragma once
+// ReSharper disable once CppUnusedIncludeDirective
+#include "pre.hpp"
+
#include "system_headers.hpp"
#include "base.hpp"
@@ -61,6 +64,10 @@ namespace cru::ui
Microsoft::WRL::ComPtr<ID2D1Brush> list_item_select_border_brush;
Microsoft::WRL::ComPtr<ID2D1Brush> list_item_select_fill_brush;
+ //region ScrollControl
+ Microsoft::WRL::ComPtr<ID2D1Brush> scroll_bar_background_brush;
+ Microsoft::WRL::ComPtr<ID2D1Brush> scroll_bar_border_brush;
+ Microsoft::WRL::ComPtr<ID2D1Brush> scroll_bar_brush;
#ifdef CRU_DEBUG_LAYOUT
//region debug
diff --git a/src/ui/window.cpp b/src/ui/window.cpp
index 87656cdc..9352b747 100644
--- a/src/ui/window.cpp
+++ b/src/ui/window.cpp
@@ -132,7 +132,8 @@ namespace cru::ui
return new Window(tag_popup_constructor{}, parent, caption);
}
- Window::Window(tag_overlapped_constructor) : Control(WindowConstructorTag{}, this), control_list_({ this }) {
+ Window::Window(tag_overlapped_constructor) : Control(WindowConstructorTag{}, this)
+ {
const auto window_manager = WindowManager::GetInstance();
hwnd_ = CreateWindowEx(0,
@@ -148,7 +149,7 @@ namespace cru::ui
AfterCreateHwnd(window_manager);
}
- Window::Window(tag_popup_constructor, Window* parent, const bool caption) : Control(WindowConstructorTag{}, this), control_list_({ this })
+ Window::Window(tag_popup_constructor, Window* parent, const bool caption) : Control(WindowConstructorTag{}, this)
{
if (parent != nullptr && !parent->IsWindowValid())
throw std::runtime_error("Parent window is not valid.");
@@ -411,6 +412,14 @@ namespace cru::ui
result = 0;
return true;
}
+ case WM_MOUSEWHEEL:
+ POINT point;
+ point.x = GET_X_LPARAM(l_param);
+ point.y = GET_Y_LPARAM(l_param);
+ ScreenToClient(hwnd, &point);
+ OnMouseWheelInternal(GET_WHEEL_DELTA_WPARAM(w_param), point);
+ result = 0;
+ return true;
case WM_KEYDOWN:
OnKeyDownInternal(static_cast<int>(w_param));
result = 0;
@@ -506,24 +515,6 @@ namespace cru::ui
is_layout_invalid_ = false;
}
- void Window::RefreshControlList() {
- control_list_.clear();
- TraverseDescendants([this](Control* control) {
- this->control_list_.push_back(control);
- });
- }
-
- Control * Window::HitTest(const Point & point)
- {
- for (auto i = control_list_.crbegin(); i != control_list_.crend(); ++i) {
- auto control = *i;
- if (control->IsPointInside(control->WindowToControl(point))) {
- return control;
- }
- }
- return nullptr;
- }
-
bool Window::RequestFocusFor(Control * control)
{
if (control == nullptr)
@@ -739,6 +730,20 @@ namespace cru::ui
DispatchEvent(control, &Control::RaiseMouseUpEvent, nullptr, dip_point, button);
}
+ void Window::OnMouseWheelInternal(short delta, POINT point)
+ {
+ const auto dip_point = PiToDip(point);
+
+ Control* control;
+
+ if (mouse_capture_control_)
+ control = mouse_capture_control_;
+ else
+ control = HitTest(dip_point);
+
+ DispatchEvent(control, &Control::RaiseMouseWheelEvent, nullptr, dip_point, static_cast<float>(delta));
+ }
+
void Window::OnKeyDownInternal(int virtual_code)
{
DispatchEvent(focus_control_, &Control::RaiseKeyDownEvent, nullptr, virtual_code);
diff --git a/src/ui/window.hpp b/src/ui/window.hpp
index d98e60e2..e82aa585 100644
--- a/src/ui/window.hpp
+++ b/src/ui/window.hpp
@@ -1,8 +1,10 @@
#pragma once
+// ReSharper disable once CppUnusedIncludeDirective
+#include "pre.hpp"
+
#include "system_headers.hpp"
#include <map>
-#include <list>
#include <memory>
#include "control.hpp"
@@ -208,15 +210,6 @@ namespace cru::ui
void SetSizeFitContent(const Size& max_size = Size(1000, 1000));
- //*************** region: functions ***************
-
- //Refresh control list.
- //It should be invoked every time a control is added or removed from the tree.
- void RefreshControlList();
-
- //Get the most top control at "point".
- Control* HitTest(const Point& point);
-
//*************** region: focus ***************
@@ -277,7 +270,8 @@ namespace cru::ui
void OnMouseLeaveInternal();
void OnMouseDownInternal(MouseButton button, POINT point);
void OnMouseUpInternal(MouseButton button, POINT point);
-
+
+ void OnMouseWheelInternal(short delta, POINT point);
void OnKeyDownInternal(int virtual_code);
void OnKeyUpInternal(int virtual_code);
void OnCharInternal(wchar_t c);
@@ -320,8 +314,6 @@ namespace cru::ui
Window* parent_window_ = nullptr;
std::shared_ptr<graph::WindowRenderTarget> render_target_{};
- std::list<Control*> control_list_{};
-
Control* mouse_hover_control_ = nullptr;
bool window_focus_ = false;