aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CruUI/CruUI.vcxproj2
-rw-r--r--CruUI/CruUI.vcxproj.filters6
-rw-r--r--CruUI/application.cpp6
-rw-r--r--CruUI/application.h12
-rw-r--r--CruUI/base.cpp2
-rw-r--r--CruUI/graph/graph.cpp22
-rw-r--r--CruUI/graph/graph.h1
-rw-r--r--CruUI/main.cpp10
-rw-r--r--CruUI/ui/control.cpp45
-rw-r--r--CruUI/ui/control.h17
-rw-r--r--CruUI/ui/controls/text_block.cpp97
-rw-r--r--CruUI/ui/controls/text_block.h24
-rw-r--r--CruUI/ui/controls/text_box.cpp244
-rw-r--r--CruUI/ui/controls/text_box.h85
-rw-r--r--CruUI/ui/events/ui_event.h46
-rw-r--r--CruUI/ui/ui_base.h13
-rw-r--r--CruUI/ui/window.cpp30
-rw-r--r--CruUI/ui/window.h5
18 files changed, 576 insertions, 91 deletions
diff --git a/CruUI/CruUI.vcxproj b/CruUI/CruUI.vcxproj
index 3d15905b..84462410 100644
--- a/CruUI/CruUI.vcxproj
+++ b/CruUI/CruUI.vcxproj
@@ -168,6 +168,7 @@
<ClInclude Include="ui\controls\linear_layout.h" />
<ClInclude Include="ui\controls\margin_container.h" />
<ClInclude Include="ui\controls\text_block.h" />
+ <ClInclude Include="ui\controls\text_box.h" />
<ClInclude Include="ui\controls\toggle_button.h" />
<ClInclude Include="ui\events\ui_event.h" />
<ClInclude Include="ui\layout_base.h" />
@@ -181,6 +182,7 @@
<ClCompile Include="ui\animations\animation.cpp" />
<ClCompile Include="ui\controls\button.cpp" />
<ClCompile Include="ui\controls\margin_container.cpp" />
+ <ClCompile Include="ui\controls\text_box.cpp" />
<ClCompile Include="ui\layout_base.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="graph\graph.cpp" />
diff --git a/CruUI/CruUI.vcxproj.filters b/CruUI/CruUI.vcxproj.filters
index aea865eb..3e4538fc 100644
--- a/CruUI/CruUI.vcxproj.filters
+++ b/CruUI/CruUI.vcxproj.filters
@@ -75,6 +75,9 @@
<ClInclude Include="ui\controls\margin_container.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="ui\controls\text_box.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="application.cpp">
@@ -128,5 +131,8 @@
<ClCompile Include="ui\controls\margin_container.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="ui\controls\text_box.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
</ItemGroup>
</Project> \ No newline at end of file
diff --git a/CruUI/application.cpp b/CruUI/application.cpp
index 1375ed99..af38116f 100644
--- a/CruUI/application.cpp
+++ b/CruUI/application.cpp
@@ -104,6 +104,12 @@ namespace cru {
#ifdef CRU_DEBUG_DRAW_CONTROL_BORDER
debug_border_brush_ = graph::CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::Crimson));
#endif
+
+ caret_info_.caret_blink_duration = std::chrono::milliseconds(::GetCaretBlinkTime());
+ DWORD caret_width;
+ if (!::SystemParametersInfoW(SPI_GETCARETWIDTH, 0 , &caret_width, 0))
+ throw Win32Error(::GetLastError(), "Failed to get system caret width.");
+ caret_info_.half_caret_width = caret_width / 2.0f;
}
Application::~Application()
diff --git a/CruUI/application.h b/CruUI/application.h
index 1ce6f035..106e7171 100644
--- a/CruUI/application.h
+++ b/CruUI/application.h
@@ -28,6 +28,11 @@ namespace cru
class TimerManager;
+ struct CaretInfo
+ {
+ std::chrono::milliseconds caret_blink_duration;
+ float half_caret_width;
+ };
class GodWindow : public Object
{
@@ -101,6 +106,11 @@ namespace cru
return god_window_.get();
}
+ CaretInfo GetCaretInfo() const
+ {
+ return caret_info_;
+ }
+
#ifdef CRU_DEBUG_DRAW_CONTROL_BORDER
Microsoft::WRL::ComPtr<ID2D1Brush> GetDebugBorderBrush() const
{
@@ -121,6 +131,8 @@ namespace cru
#ifdef CRU_DEBUG_DRAW_CONTROL_BORDER
Microsoft::WRL::ComPtr<ID2D1Brush> debug_border_brush_;
#endif
+
+ CaretInfo caret_info_;
};
diff --git a/CruUI/base.cpp b/CruUI/base.cpp
index c6b57e33..f5868170 100644
--- a/CruUI/base.cpp
+++ b/CruUI/base.cpp
@@ -13,7 +13,7 @@ namespace cru
const auto length = ::WideCharToMultiByte(CP_UTF8, 0, string.data(), -1, nullptr, 0, nullptr, nullptr);
MultiByteString result;
result.reserve(length);
- if (::WideCharToMultiByte(CP_UTF8, 0, string.data(), -1, result.data(), result.capacity(), nullptr, nullptr) == 0)
+ if (::WideCharToMultiByte(CP_UTF8, 0, string.data(), -1, result.data(), static_cast<int>(result.capacity()), nullptr, nullptr) == 0)
throw Win32Error(::GetLastError(), "Failed to convert wide string to UTF-8.");
return result;
}
diff --git a/CruUI/graph/graph.cpp b/CruUI/graph/graph.cpp
index 94c7029e..30b51413 100644
--- a/CruUI/graph/graph.cpp
+++ b/CruUI/graph/graph.cpp
@@ -216,10 +216,30 @@ namespace cru {
ComPtr<ID2D1SolidColorBrush> CreateSolidBrush(const D2D1_COLOR_F& color)
{
- const auto device_context = graph::GraphManager::GetInstance()->GetD2D1DeviceContext();
+ const auto device_context = GraphManager::GetInstance()->GetD2D1DeviceContext();
ComPtr<ID2D1SolidColorBrush> solid_color_brush;
device_context->CreateSolidColorBrush(color, &solid_color_brush);
return solid_color_brush;
}
+
+ ComPtr<IDWriteTextFormat> CreateDefaultTextFormat()
+ {
+ const auto dwrite_factory = GraphManager::GetInstance()->GetDWriteFactory();
+
+ ComPtr<IDWriteTextFormat> text_format;
+
+ ThrowIfFailed(dwrite_factory->CreateTextFormat(
+ L"等线", nullptr,
+ DWRITE_FONT_WEIGHT_NORMAL,
+ DWRITE_FONT_STYLE_NORMAL,
+ DWRITE_FONT_STRETCH_NORMAL,
+ 24.0, L"zh-cn",
+ &text_format
+ ));
+
+ ThrowIfFailed(text_format->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER));
+ ThrowIfFailed(text_format->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER));
+ return text_format;
+ }
}
}
diff --git a/CruUI/graph/graph.h b/CruUI/graph/graph.h
index 01d95797..0f1d29d1 100644
--- a/CruUI/graph/graph.h
+++ b/CruUI/graph/graph.h
@@ -166,6 +166,7 @@ namespace cru
}
Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> CreateSolidBrush(const D2D1_COLOR_F& color);
+ Microsoft::WRL::ComPtr<IDWriteTextFormat> CreateDefaultTextFormat();
inline void WithTransform(ID2D1DeviceContext* device_context, const D2D1_MATRIX_3X2_F matrix, Function<void(ID2D1DeviceContext*)>&& action)
{
diff --git a/CruUI/main.cpp b/CruUI/main.cpp
index 56d42894..67b35406 100644
--- a/CruUI/main.cpp
+++ b/CruUI/main.cpp
@@ -6,6 +6,7 @@
#include "ui/controls/button.h"
#include "ui/controls/margin_container.h"
#include "ui/events/ui_event.h"
+#include "ui/controls/text_box.h"
using cru::String;
@@ -20,6 +21,7 @@ using cru::ui::controls::TextBlock;
using cru::ui::controls::ToggleButton;
using cru::ui::controls::Button;
using cru::ui::controls::MarginContainer;
+using cru::ui::controls::TextBox;
int APIENTRY wWinMain(
HINSTANCE hInstance,
@@ -29,7 +31,7 @@ int APIENTRY wWinMain(
Application application(hInstance);
Window window;
-
+ /*
window.native_message_event.AddHandler([](cru::ui::events::WindowNativeMessageEventArgs& args)
{
if (args.GetWindowMessage().msg == WM_PAINT)
@@ -38,7 +40,7 @@ int APIENTRY wWinMain(
//args.SetResult(0);
}
});
-
+ */
/*
// test1
cru::ui::controls::TextBlock text_block;
@@ -84,6 +86,7 @@ int APIENTRY wWinMain(
});
*/
+ /*
//test 2
const auto layout = CreateWithLayout<LinearLayout>(LayoutSideParams::Exactly(500), LayoutSideParams::Content());
@@ -137,6 +140,9 @@ int APIENTRY wWinMain(
window.AddChild(layout);
+ */
+
+ window.AddChild(CreateWithLayout<TextBox>(LayoutSideParams::Stretch(), LayoutSideParams::Stretch()));
window.Show();
diff --git a/CruUI/ui/control.cpp b/CruUI/ui/control.cpp
index 25f7c028..8aec8640 100644
--- a/CruUI/ui/control.cpp
+++ b/CruUI/ui/control.cpp
@@ -472,6 +472,51 @@ namespace cru {
}
+ void Control::OnKeyDown(KeyEventArgs& args)
+ {
+ }
+
+ void Control::OnKeyUp(KeyEventArgs& args)
+ {
+ }
+
+ void Control::OnChar(CharEventArgs& args)
+ {
+ }
+
+ void Control::OnKeyDownCore(KeyEventArgs& args)
+ {
+ }
+
+ void Control::OnKeyUpCore(KeyEventArgs& args)
+ {
+ }
+
+ void Control::OnCharCore(CharEventArgs& args)
+ {
+ }
+
+ void Control::RaiseKeyDownEvent(KeyEventArgs& args)
+ {
+ OnKeyDownCore(args);
+ OnKeyDown(args);
+ key_down_event.Raise(args);
+ }
+
+ void Control::RaiseKeyUpEvent(KeyEventArgs& args)
+ {
+ OnKeyUpCore(args);
+ OnKeyUp(args);
+ key_up_event.Raise(args);
+ }
+
+ void Control::RaiseCharEvent(CharEventArgs& args)
+ {
+ OnCharCore(args);
+ OnChar(args);
+ char_event.Raise(args);
+ }
+
void Control::OnGetFocus(FocusChangeEventArgs& args)
{
diff --git a/CruUI/ui/control.h b/CruUI/ui/control.h
index 78261a80..d6cbae40 100644
--- a/CruUI/ui/control.h
+++ b/CruUI/ui/control.h
@@ -214,6 +214,10 @@ namespace cru
//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::KeyEvent key_down_event;
+ events::KeyEvent key_up_event;
+ events::CharEvent char_event;
+
events::FocusChangeEvent get_focus_event;
events::FocusChangeEvent lose_focus_event;
@@ -278,6 +282,19 @@ namespace cru
virtual void OnMouseClickBegin(MouseButton button);
virtual void OnMouseClickEnd(MouseButton button);
+ //*************** region: keyboard event ***************
+ virtual void OnKeyDown(events::KeyEventArgs& args);
+ virtual void OnKeyUp(events::KeyEventArgs& args);
+ virtual void OnChar(events::CharEventArgs& args);
+
+ virtual void OnKeyDownCore(events::KeyEventArgs& args);
+ virtual void OnKeyUpCore(events::KeyEventArgs& args);
+ virtual void OnCharCore(events::CharEventArgs& args);
+
+ void RaiseKeyDownEvent(events::KeyEventArgs& args);
+ void RaiseKeyUpEvent(events::KeyEventArgs& args);
+ void RaiseCharEvent(events::CharEventArgs& args);
+
//*************** region: focus event ***************
virtual void OnGetFocus(events::FocusChangeEventArgs& args);
virtual void OnLoseFocus(events::FocusChangeEventArgs& args);
diff --git a/CruUI/ui/controls/text_block.cpp b/CruUI/ui/controls/text_block.cpp
index 8800fd91..93d66ba6 100644
--- a/CruUI/ui/controls/text_block.cpp
+++ b/CruUI/ui/controls/text_block.cpp
@@ -20,9 +20,11 @@ namespace cru
TextBlock::TextBlock(const Microsoft::WRL::ComPtr<IDWriteTextFormat>& init_text_format,
const Microsoft::WRL::ComPtr<ID2D1Brush>& init_brush) : Control(false)
{
- text_format_ = init_text_format;
- if (init_brush == nullptr)
- brush_ = CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::Black));
+ text_format_ = init_text_format == nullptr ? graph::CreateDefaultTextFormat() : init_text_format;
+
+ RecreateTextLayout();
+
+ brush_ = init_brush == nullptr ? CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::Black)) : init_brush;
selection_brush_ = CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::LightSkyBlue));
}
@@ -60,7 +62,7 @@ namespace cru
void TextBlock::RemoveTextLayoutHandler(const TextLayoutHandlerPtr& handler)
{
const auto find_result = std::find(text_layout_handlers_.cbegin(), text_layout_handlers_.cend(),
- handler);
+ handler);
if (find_result != text_layout_handlers_.cend())
text_layout_handlers_.erase(find_result);
}
@@ -96,42 +98,42 @@ namespace cru
void TextBlock::OnDraw(ID2D1DeviceContext* device_context)
{
Control::OnDraw(device_context);
- if (text_layout_ != nullptr)
+ if (selected_range_.has_value())
{
- if (selected_range_.has_value())
+ DWRITE_TEXT_METRICS text_metrics{};
+ ThrowIfFailed(text_layout_->GetMetrics(&text_metrics));
+ const auto metrics_count = text_metrics.lineCount * text_metrics.maxBidiReorderingDepth;
+
+ Vector<DWRITE_HIT_TEST_METRICS> hit_test_metrics(metrics_count);
+ UINT32 actual_count;
+ text_layout_->HitTestTextRange(
+ selected_range_.value().position, selected_range_.value().count,
+ 0, 0,
+ hit_test_metrics.data(), metrics_count, &actual_count
+ );
+
+ hit_test_metrics.erase(hit_test_metrics.cbegin() + actual_count, hit_test_metrics.cend());
+
+ for (const auto& metrics : hit_test_metrics)
{
- DWRITE_TEXT_METRICS text_metrics{};
- ThrowIfFailed(text_layout_->GetMetrics(&text_metrics));
- const auto metrics_count = text_metrics.lineCount * text_metrics.maxBidiReorderingDepth;
-
- Vector<DWRITE_HIT_TEST_METRICS> hit_test_metrics(metrics_count);
- UINT32 actual_count;
- text_layout_->HitTestTextRange(
- selected_range_.value().position, selected_range_.value().count,
- 0, 0,
- hit_test_metrics.data(), metrics_count, &actual_count
- );
-
- hit_test_metrics.erase(hit_test_metrics.cbegin() + actual_count, hit_test_metrics.cend());
-
- for (const auto& metrics : hit_test_metrics)
- {
- device_context->FillRoundedRectangle(D2D1::RoundedRect(D2D1::RectF(metrics.left, metrics.top, metrics.left + metrics.width, metrics.top + metrics.height), 3, 3), selection_brush_.Get());
- }
+ device_context->FillRoundedRectangle(D2D1::RoundedRect(D2D1::RectF(metrics.left, metrics.top, metrics.left + metrics.width, metrics.top + metrics.height), 3, 3), selection_brush_.Get());
}
- device_context->DrawTextLayout(D2D1::Point2F(), text_layout_.Get(), brush_.Get());
}
+ device_context->DrawTextLayout(D2D1::Point2F(), text_layout_.Get(), brush_.Get());
}
- std::optional<unsigned> TextLayoutHitTest(IDWriteTextLayout* text_layout, const Point& point, bool test_inside = true)
+ namespace
{
- BOOL is_trailing, is_inside;
- DWRITE_HIT_TEST_METRICS metrics{};
- text_layout->HitTestPoint(point.x, point.y, &is_trailing, &is_inside, &metrics);
- if (!test_inside || is_inside)
- return is_trailing == 0 ? metrics.textPosition : metrics.textPosition + 1;
- else
- return std::nullopt;
+ std::optional<unsigned> TextLayoutHitTest(IDWriteTextLayout* text_layout, const Point& point, const bool test_inside = true)
+ {
+ BOOL is_trailing, is_inside;
+ DWRITE_HIT_TEST_METRICS metrics{};
+ text_layout->HitTestPoint(point.x, point.y, &is_trailing, &is_inside, &metrics);
+ if (!test_inside || is_inside)
+ return is_trailing == 0 ? metrics.textPosition : metrics.textPosition + 1;
+ else
+ return std::nullopt;
+ }
}
void TextBlock::OnMouseDownCore(events::MouseButtonEventArgs& args)
@@ -197,9 +199,6 @@ namespace cru
Size TextBlock::OnMeasure(const Size& available_size)
{
- if (text_.empty())
- return Size::Zero();
-
const auto layout_params = GetLayoutParams();
if (layout_params->width.mode == MeasureMode::Stretch && layout_params->height.mode == MeasureMode::Stretch)
@@ -259,36 +258,12 @@ namespace cru
Repaint();
}
- void TextBlock::CreateDefaultTextFormat()
- {
- const auto dwrite_factory = GetDWriteFactory();
-
- ThrowIfFailed(dwrite_factory->CreateTextFormat(
- L"µÈÏß", nullptr,
- DWRITE_FONT_WEIGHT_NORMAL,
- DWRITE_FONT_STYLE_NORMAL,
- DWRITE_FONT_STRETCH_NORMAL,
- 24.0, L"zh-cn",
- &text_format_
- ));
-
- ThrowIfFailed(text_format_->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER));
- ThrowIfFailed(text_format_->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER));
- }
-
void TextBlock::RecreateTextLayout()
{
- if (text_.empty())
- {
- text_layout_ = nullptr;
- return;
- }
+ assert(text_format_ != nullptr);
const auto dwrite_factory = GetDWriteFactory();
- if (text_format_ == nullptr)
- CreateDefaultTextFormat();
-
const auto&& size = GetSize();
ThrowIfFailed(dwrite_factory->CreateTextLayout(
diff --git a/CruUI/ui/controls/text_block.h b/CruUI/ui/controls/text_block.h
index b05e7ff2..c87ffc51 100644
--- a/CruUI/ui/controls/text_block.h
+++ b/CruUI/ui/controls/text_block.h
@@ -11,29 +11,6 @@ namespace cru
{
namespace controls
{
- struct TextRange
- {
- TextRange() = default;
- TextRange(const int position, const int count)
- : position(position), count(count)
- {
-
- }
- TextRange(const TextRange& other) = default;
- TextRange(TextRange&& other) = default;
- TextRange& operator=(const TextRange& other) = default;
- TextRange& operator=(TextRange&& other) = default;
- ~TextRange() = default;
-
- unsigned position;
- unsigned count;
-
- explicit operator DWRITE_TEXT_RANGE() const
- {
- return DWRITE_TEXT_RANGE { position, count };
- }
- };
-
class TextBlock : public Control
{
public:
@@ -116,7 +93,6 @@ namespace cru
private:
void OnTextChangedCore(const String& old_text, const String& new_text);
- void CreateDefaultTextFormat();
void RecreateTextLayout();
private:
diff --git a/CruUI/ui/controls/text_box.cpp b/CruUI/ui/controls/text_box.cpp
new file mode 100644
index 00000000..a8d78398
--- /dev/null
+++ b/CruUI/ui/controls/text_box.cpp
@@ -0,0 +1,244 @@
+#include "text_box.h"
+
+#include <cwctype>
+
+#include "graph/graph.h"
+#include "exception.h"
+
+namespace cru::ui::controls
+{
+ using graph::CreateSolidBrush;
+
+ inline Microsoft::WRL::ComPtr<IDWriteFactory> GetDWriteFactory()
+ {
+ return graph::GraphManager::GetInstance()->GetDWriteFactory();
+ }
+
+ TextBox::TextBox(const Microsoft::WRL::ComPtr<IDWriteTextFormat>& init_text_format,
+ const Microsoft::WRL::ComPtr<ID2D1Brush>& init_brush) : Control(false)
+ {
+ text_format_ = init_text_format == nullptr ? graph::CreateDefaultTextFormat() : init_text_format;
+
+ RecreateTextLayout();
+
+ brush_ = init_brush == nullptr ? CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::Black)) : init_brush;
+
+ caret_brush_ = CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::Black));
+
+ caret_action_ = CreateActionPtr([this]
+ {
+ is_caret_show_ = !is_caret_show_;
+ Repaint();
+ });
+
+ //selection_brush_ = CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::LightSkyBlue));
+ }
+
+ TextBox::~TextBox() = default;
+
+ void TextBox::SetText(const String& text)
+ {
+ if (text_ != text)
+ {
+ const auto old_text = text_;
+ text_ = text;
+ OnTextChangedCore(old_text, text);
+ }
+ }
+
+ void TextBox::SetBrush(const Microsoft::WRL::ComPtr<ID2D1Brush>& brush)
+ {
+ brush_ = brush;
+ Repaint();
+ }
+
+ void TextBox::SetTextFormat(const Microsoft::WRL::ComPtr<IDWriteTextFormat>& text_format)
+ {
+ text_format_ = text_format;
+ RecreateTextLayout();
+ Repaint();
+ }
+
+ void TextBox::OnSizeChangedCore(events::SizeChangedEventArgs& args)
+ {
+ Control::OnSizeChangedCore(args);
+ text_layout_->SetMaxWidth(args.GetNewSize().width);
+ text_layout_->SetMaxHeight(args.GetNewSize().height);
+ Repaint();
+ }
+
+ void TextBox::OnDraw(ID2D1DeviceContext* device_context)
+ {
+ Control::OnDraw(device_context);
+ if (text_layout_ != nullptr)
+ {
+ device_context->DrawTextLayout(D2D1::Point2F(), text_layout_.Get(), brush_.Get());
+ if (is_caret_show_)
+ {
+ const auto caret_half_width = Application::GetInstance()->GetCaretInfo().half_caret_width;
+ FLOAT x, y;
+ DWRITE_HIT_TEST_METRICS metrics{};
+ ThrowIfFailed(text_layout_->HitTestTextPosition(position_, FALSE, &x, &y, &metrics));
+ device_context->FillRectangle(D2D1::RectF(metrics.left - caret_half_width, metrics.top, metrics.left + caret_half_width, metrics.top + metrics.height), caret_brush_.Get());
+ }
+ }
+ }
+
+ namespace
+ {
+ std::optional<unsigned> TextLayoutHitTest(IDWriteTextLayout* text_layout, const Point& point, bool test_inside = true)
+ {
+ BOOL is_trailing, is_inside;
+ DWRITE_HIT_TEST_METRICS metrics{};
+ text_layout->HitTestPoint(point.x, point.y, &is_trailing, &is_inside, &metrics);
+ if (!test_inside || is_inside)
+ return is_trailing == 0 ? metrics.textPosition : metrics.textPosition + 1;
+ else
+ return std::nullopt;
+ }
+ }
+
+ void TextBox::OnMouseDownCore(events::MouseButtonEventArgs& args)
+ {
+ Control::OnMouseDownCore(args);
+ if (args.GetMouseButton() == MouseButton::Left)
+ {
+ position_ = TextLayoutHitTest(text_layout_.Get(), args.GetPoint(this), false).value();
+
+ Repaint();
+ }
+ }
+
+ void TextBox::OnGetFocusCore(events::FocusChangeEventArgs& args)
+ {
+ Control::OnGetFocusCore(args);
+ assert(caret_timer_ == nullptr);
+ is_caret_show_ = true;
+ caret_timer_ = SetInterval(Application::GetInstance()->GetCaretInfo().caret_blink_duration, caret_action_);
+ }
+
+ void TextBox::OnLoseFocusCore(events::FocusChangeEventArgs& args)
+ {
+ Control::OnLoseFocusCore(args);
+ assert(caret_timer_ != nullptr);
+ caret_timer_->Cancel();
+ is_caret_show_ = false;
+ }
+
+ void TextBox::OnKeyDownCore(events::KeyEventArgs& args)
+ {
+ Control::OnKeyDownCore(args);
+ if (args.GetVirtualCode() == VK_LEFT && position_ > 0)
+ {
+ position_--;
+ Repaint();
+ }
+
+ if (args.GetVirtualCode() == VK_RIGHT && position_ < GetText().size())
+ {
+ position_++;
+ Repaint();
+ }
+ }
+
+ void TextBox::OnCharCore(events::CharEventArgs& args)
+ {
+ Control::OnCharCore(args);
+ if (args.GetChar() == L'\b')
+ {
+ auto text = GetText();
+ if (!text.empty() && position_ > 0)
+ {
+ const auto position = --position_;
+ text.erase(position);
+ SetText(text);
+ }
+ return;
+ }
+
+ if (std::iswprint(args.GetChar()))
+ {
+ const auto position = position_++;
+ auto text = GetText();
+ text.insert(text.cbegin() + position, { args.GetChar() });
+ SetText(text);
+ }
+ }
+
+ Size TextBox::OnMeasure(const Size& available_size)
+ {
+ const auto layout_params = GetLayoutParams();
+
+ if (layout_params->width.mode == MeasureMode::Stretch && layout_params->height.mode == MeasureMode::Stretch)
+ return available_size;
+
+ auto&& get_measure_length = [](const LayoutSideParams& layout_length, const float available_length) -> float
+ {
+ switch (layout_length.mode)
+ {
+ case MeasureMode::Exactly:
+ {
+ return std::min(layout_length.length, available_length);
+ }
+ case MeasureMode::Stretch:
+ case MeasureMode::Content:
+ {
+ return available_length;
+ }
+ default:
+ UnreachableCode();
+ }
+ };
+
+ const Size measure_size(get_measure_length(layout_params->width, available_size.width),
+ get_measure_length(layout_params->height, available_size.height));
+
+ ThrowIfFailed(text_layout_->SetMaxWidth(measure_size.width));
+ ThrowIfFailed(text_layout_->SetMaxHeight(measure_size.height));
+
+ DWRITE_TEXT_METRICS metrics{};
+
+ ThrowIfFailed(text_layout_->GetMetrics(&metrics));
+
+ const Size measure_result(metrics.width, metrics.height);
+
+ auto&& calculate_final_length = [](const LayoutSideParams& layout_length, const float measure_length, const float measure_result_length) -> float
+ {
+ if ((layout_length.mode == MeasureMode::Stretch ||
+ layout_length.mode == MeasureMode::Exactly)
+ && measure_result_length < measure_length)
+ return measure_length;
+ else
+ return measure_result_length;
+ };
+
+ const Size result_size(
+ calculate_final_length(layout_params->width, measure_size.width, measure_result.width),
+ calculate_final_length(layout_params->height, measure_size.height, measure_result.height)
+ );
+
+ return result_size;
+ }
+
+ void TextBox::OnTextChangedCore(const String& old_text, const String& new_text)
+ {
+ RecreateTextLayout();
+ Repaint();
+ }
+
+ void TextBox::RecreateTextLayout()
+ {
+ assert(text_format_ != nullptr);
+
+ const auto dwrite_factory = GetDWriteFactory();
+
+ const auto&& size = GetSize();
+
+ ThrowIfFailed(dwrite_factory->CreateTextLayout(
+ text_.c_str(), static_cast<UINT32>(text_.size()),
+ text_format_.Get(),
+ size.width, size.height,
+ &text_layout_
+ ));
+ }
+}
diff --git a/CruUI/ui/controls/text_box.h b/CruUI/ui/controls/text_box.h
new file mode 100644
index 00000000..b815ed1f
--- /dev/null
+++ b/CruUI/ui/controls/text_box.h
@@ -0,0 +1,85 @@
+#pragma once
+
+#include "ui/control.h"
+#include "timer.h"
+
+namespace cru::ui::controls
+{
+ class TextBox : public Control
+ {
+ public:
+ static TextBox* Create(
+ const Microsoft::WRL::ComPtr<IDWriteTextFormat>& init_text_format = nullptr,
+ const Microsoft::WRL::ComPtr<ID2D1Brush>& init_brush = nullptr)
+ {
+ return new TextBox(init_text_format, init_brush);
+ }
+
+ protected:
+ explicit TextBox(
+ const Microsoft::WRL::ComPtr<IDWriteTextFormat>& init_text_format = nullptr,
+ const Microsoft::WRL::ComPtr<ID2D1Brush>& init_brush = nullptr
+ );
+ public:
+ TextBox(const TextBox& other) = delete;
+ TextBox(TextBox&& other) = delete;
+ TextBox& operator=(const TextBox& other) = delete;
+ TextBox& operator=(TextBox&& other) = delete;
+ ~TextBox() override;
+
+ String GetText() const
+ {
+ return text_;
+ }
+
+ void SetText(const String& text);
+
+ Microsoft::WRL::ComPtr<ID2D1Brush> GetBrush() const
+ {
+ return brush_;
+ }
+
+ void SetBrush(const Microsoft::WRL::ComPtr<ID2D1Brush>& brush);
+
+ Microsoft::WRL::ComPtr<IDWriteTextFormat> GetTextFormat() const
+ {
+ return text_format_;
+ }
+
+ void SetTextFormat(const Microsoft::WRL::ComPtr<IDWriteTextFormat>& text_format);
+
+ protected:
+ void OnSizeChangedCore(events::SizeChangedEventArgs& args) override final;
+ void OnDraw(ID2D1DeviceContext* device_context) override;
+
+ void OnMouseDownCore(events::MouseButtonEventArgs& args) override final;
+
+ void OnGetFocusCore(events::FocusChangeEventArgs& args) override final;
+ void OnLoseFocusCore(events::FocusChangeEventArgs& args) override final;
+
+ void OnKeyDownCore(events::KeyEventArgs& args) override final;
+ void OnCharCore(events::CharEventArgs& args) override final;
+
+ Size OnMeasure(const Size& available_size) override final;
+
+ private:
+ void OnTextChangedCore(const String& old_text, const String& new_text);
+
+ void RecreateTextLayout();
+
+ private:
+ String text_;
+
+ Microsoft::WRL::ComPtr<ID2D1Brush> brush_;
+ Microsoft::WRL::ComPtr<ID2D1Brush> caret_brush_;
+ //Microsoft::WRL::ComPtr<ID2D1Brush> selection_brush_;
+ Microsoft::WRL::ComPtr<IDWriteTextFormat> text_format_;
+ Microsoft::WRL::ComPtr<IDWriteTextLayout> text_layout_;
+
+ unsigned position_ = 0;
+
+ TimerTask caret_timer_;
+ ActionPtr caret_action_;
+ bool is_caret_show_;
+ };
+}
diff --git a/CruUI/ui/events/ui_event.h b/CruUI/ui/events/ui_event.h
index a17067c7..b042b706 100644
--- a/CruUI/ui/events/ui_event.h
+++ b/CruUI/ui/events/ui_event.h
@@ -257,6 +257,50 @@ namespace cru
std::optional<LRESULT> result_;
};
+ class KeyEventArgs : public UiEventArgs
+ {
+ public:
+ KeyEventArgs(Object* sender, Object* original_sender, int virtual_code)
+ : UiEventArgs(sender, original_sender), virtual_code_(virtual_code)
+ {
+ }
+ KeyEventArgs(const KeyEventArgs& other) = default;
+ KeyEventArgs(KeyEventArgs&& other) = default;
+ KeyEventArgs& operator=(const KeyEventArgs& other) = default;
+ KeyEventArgs& operator=(KeyEventArgs&& other) = default;
+ ~KeyEventArgs() override = default;
+
+ int GetVirtualCode() const
+ {
+ return virtual_code_;
+ }
+
+ private:
+ int virtual_code_;
+ };
+
+ class CharEventArgs : public UiEventArgs
+ {
+ public:
+ CharEventArgs(Object* sender, Object* original_sender, wchar_t c)
+ : UiEventArgs(sender, original_sender), c_(c)
+ {
+ }
+ CharEventArgs(const CharEventArgs& other) = default;
+ CharEventArgs(CharEventArgs&& other) = default;
+ CharEventArgs& operator=(const CharEventArgs& other) = default;
+ CharEventArgs& operator=(CharEventArgs&& other) = default;
+ ~CharEventArgs() override = default;
+
+ wchar_t GetChar() const
+ {
+ return c_;
+ }
+
+ private:
+ wchar_t c_;
+ };
+
using UiEvent = Event<UiEventArgs>;
using MouseEvent = Event<MouseEventArgs>;
using MouseButtonEvent = Event<MouseButtonEventArgs>;
@@ -266,6 +310,8 @@ namespace cru
using FocusChangeEvent = Event<FocusChangeEventArgs>;
using ToggleEvent = Event<ToggleEventArgs>;
using WindowNativeMessageEvent = Event<WindowNativeMessageEventArgs>;
+ using KeyEvent = Event<KeyEventArgs>;
+ using CharEvent = Event<CharEventArgs>;
}
}
} \ No newline at end of file
diff --git a/CruUI/ui/ui_base.h b/CruUI/ui/ui_base.h
index c9ae6017..43f3c498 100644
--- a/CruUI/ui/ui_base.h
+++ b/CruUI/ui/ui_base.h
@@ -137,5 +137,18 @@ namespace cru
Right,
Middle
};
+
+ struct TextRange
+ {
+ constexpr TextRange() = default;
+ constexpr TextRange(const int position, const int count)
+ : position(position), count(count)
+ {
+
+ }
+
+ unsigned position = 0;
+ unsigned count = 0;
+ };
}
}
diff --git a/CruUI/ui/window.cpp b/CruUI/ui/window.cpp
index 6ff962b6..34a54512 100644
--- a/CruUI/ui/window.cpp
+++ b/CruUI/ui/window.cpp
@@ -204,8 +204,7 @@ namespace cru
if (!native_message_event.IsNoHandler())
{
- const events::WindowNativeMessage message{hwnd, msg, w_param, l_param};
- events::WindowNativeMessageEventArgs args(this, this, message);
+ events::WindowNativeMessageEventArgs args(this, this, {hwnd, msg, w_param, l_param});
native_message_event.Raise(args);
if (args.GetResult().has_value())
{
@@ -293,6 +292,18 @@ namespace cru
result = 0;
return true;
}
+ case WM_KEYDOWN:
+ OnKeyDownInternal(static_cast<int>(w_param));
+ result = 0;
+ return true;
+ case WM_KEYUP:
+ OnKeyUpInternal(static_cast<int>(w_param));
+ result = 0;
+ return true;
+ case WM_CHAR:
+ OnCharInternal(static_cast<wchar_t>(w_param));
+ result = 0;
+ return true;
case WM_SIZE:
OnResizeInternal(LOWORD(l_param), HIWORD(l_param));
result = 0;
@@ -553,6 +564,21 @@ namespace cru
DispatchEvent(control, &Control::RaiseMouseUpEvent, nullptr, dip_point, button);
}
+ void Window::OnKeyDownInternal(int virtual_code)
+ {
+ DispatchEvent(focus_control_, &Control::RaiseKeyDownEvent, nullptr, virtual_code);
+ }
+
+ void Window::OnKeyUpInternal(int virtual_code)
+ {
+ DispatchEvent(focus_control_, &Control::RaiseKeyUpEvent, nullptr, virtual_code);
+ }
+
+ void Window::OnCharInternal(wchar_t c)
+ {
+ DispatchEvent(focus_control_, &Control::RaiseCharEvent, nullptr, c);
+ }
+
void Window::OnActivatedInternal()
{
events::UiEventArgs args(this, this);
diff --git a/CruUI/ui/window.h b/CruUI/ui/window.h
index 42ebf477..40d81a06 100644
--- a/CruUI/ui/window.h
+++ b/CruUI/ui/window.h
@@ -6,6 +6,7 @@
#include <memory>
#include "control.h"
+#include "events/ui_event.h"
namespace cru {
namespace graph {
@@ -219,6 +220,10 @@ namespace cru {
void OnMouseDownInternal(MouseButton button, POINT point);
void OnMouseUpInternal(MouseButton button, POINT point);
+ void OnKeyDownInternal(int virtual_code);
+ void OnKeyUpInternal(int virtual_code);
+ void OnCharInternal(wchar_t c);
+
void OnActivatedInternal();
void OnDeactivatedInternal();