Add some basic controls
This commit is contained in:
1 parent
ae27aa0874
commit
f183070834
9 files changed
+1023
-10
No files matched your search
@@ -20,6 +20,7 @@ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
||||
add_library(msimeui STATIC
|
||||
src/Application.cpp
|
||||
src/Brush.cpp
|
||||
src/Controls.cpp
|
||||
src/DebugLog.cpp
|
||||
src/DeviceResources.cpp
|
||||
src/TextBox.cpp
|
||||
|
||||
+3
-1
@@ -15,6 +15,8 @@
|
||||
- `msimeui-demo` 示例程序
|
||||
- D2D/DWrite 设备资源管理
|
||||
- 基础布局和控件树
|
||||
- `StackPanel` / `HorizontalStackPanel` / `WrapPanel`
|
||||
- `TextBlock` / `Button` / `CheckBox` / `ProgressBar` / `Slider` / `Separator`
|
||||
- `TextBox` 输入控件
|
||||
|
||||
## 构建
|
||||
@@ -82,5 +84,5 @@ build/bin/Debug/msimeui-demo.exe
|
||||
## 后续工作
|
||||
|
||||
- 继续完善 `TextBox` 的输入、样式和布局能力
|
||||
- 增加按钮、滚动容器、列表和样式系统
|
||||
- 增加滚动容器、列表、树、菜单和弹出层
|
||||
- 引入资源字典、主题和声明式界面描述
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "msimeui/Controls.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
namespace msimeui
|
||||
{
|
||||
std::unique_ptr<Scene> CreateDemoScene()
|
||||
@@ -10,9 +12,9 @@ std::unique_ptr<Scene> CreateDemoScene()
|
||||
root->AddChild(std::make_shared<Spacer>(28.0f));
|
||||
root->AddChild(std::make_shared<TextBlock>(L"msimeui", 34.0f, D2D1::ColorF(0x111827), true));
|
||||
root->AddChild(std::make_shared<Spacer>(12.0f));
|
||||
root->AddChild(std::make_shared<TextBlock>(
|
||||
L"Direct2D / DirectWrite rendering with an in-tree TSF text input implementation.",
|
||||
16.0f, D2D1::ColorF(0x5B6472)));
|
||||
root->AddChild(
|
||||
std::make_shared<TextBlock>(L"A retained-mode Win32 GUI framework with Direct2D / DirectWrite and in-tree TSF.",
|
||||
16.0f, D2D1::ColorF(0x5B6472)));
|
||||
root->AddChild(std::make_shared<Spacer>(36.0f));
|
||||
|
||||
Brush surface;
|
||||
@@ -22,13 +24,104 @@ std::unique_ptr<Scene> CreateDemoScene()
|
||||
surface.radiusX = 22.0f;
|
||||
surface.radiusY = 22.0f;
|
||||
|
||||
auto heroCard = std::make_shared<Card>(surface, 20.0f);
|
||||
auto heroStack = std::make_shared<StackPanel>(16.0f);
|
||||
auto statusText =
|
||||
std::make_shared<TextBlock>(L"Framework status: base controls are now interactive and composable.", 15.0f,
|
||||
D2D1::ColorF(0x0F172A));
|
||||
auto featureWrap = std::make_shared<WrapPanel>(10.0f, 10.0f);
|
||||
featureWrap->AddChild(std::make_shared<Button>(L"Direct2D"));
|
||||
featureWrap->AddChild(std::make_shared<Button>(L"TSF Input"));
|
||||
featureWrap->AddChild(std::make_shared<Button>(L"Layout"));
|
||||
featureWrap->AddChild(std::make_shared<Button>(L"Buttons"));
|
||||
featureWrap->AddChild(std::make_shared<Button>(L"Slider"));
|
||||
featureWrap->AddChild(std::make_shared<Button>(L"Progress"));
|
||||
|
||||
auto actionRow = std::make_shared<HorizontalStackPanel>(12.0f);
|
||||
auto primaryButton = std::make_shared<Button>(L"Run Interaction Demo", 46.0f);
|
||||
auto secondaryButton = std::make_shared<Button>(L"Reset Status", 46.0f);
|
||||
primaryButton->SetOnClick([statusText]() {
|
||||
statusText->SetText(L"Interaction check: buttons are dispatching clicks through the visual tree.");
|
||||
});
|
||||
secondaryButton->SetOnClick([statusText]() {
|
||||
statusText->SetText(L"Framework status: base controls are now interactive and composable.");
|
||||
});
|
||||
actionRow->AddChild(primaryButton);
|
||||
actionRow->AddChild(secondaryButton);
|
||||
|
||||
heroStack->AddChild(std::make_shared<TextBlock>(L"Component Gallery", 22.0f, D2D1::ColorF(0x1F2937), true));
|
||||
heroStack->AddChild(std::make_shared<TextBlock>(
|
||||
L"Common controls and containers inspired by classic GUI frameworks, but implemented in this project's current style.",
|
||||
14.0f, D2D1::ColorF(0x6B7280)));
|
||||
heroStack->AddChild(featureWrap);
|
||||
heroStack->AddChild(statusText);
|
||||
heroStack->AddChild(actionRow);
|
||||
heroCard->AddChild(heroStack);
|
||||
root->AddChild(heroCard);
|
||||
root->AddChild(std::make_shared<Spacer>(24.0f));
|
||||
|
||||
auto controlsCard = std::make_shared<Card>(surface, 20.0f);
|
||||
auto controlsStack = std::make_shared<StackPanel>(16.0f);
|
||||
auto progressLabel =
|
||||
std::make_shared<TextBlock>(L"Progress: 35%", 14.0f, D2D1::ColorF(0x475569), true);
|
||||
auto progressBar = std::make_shared<ProgressBar>(14.0f);
|
||||
progressBar->SetValue(0.35f);
|
||||
auto slider = std::make_shared<Slider>(0.0f, 100.0f, 35.0f, 34.0f);
|
||||
slider->SetOnChanged([progressBar, progressLabel](float value) {
|
||||
progressBar->SetValue(value / 100.0f);
|
||||
std::wstringstream stream;
|
||||
stream << L"Progress: " << static_cast<int>(value + 0.5f) << L"%";
|
||||
progressLabel->SetText(stream.str());
|
||||
});
|
||||
|
||||
auto optionText = std::make_shared<TextBlock>(L"Selection: compact mode is enabled.", 14.0f, D2D1::ColorF(0x475569));
|
||||
auto checkboxRow = std::make_shared<HorizontalStackPanel>(18.0f);
|
||||
auto compactMode = std::make_shared<CheckBox>(L"Compact layout", true);
|
||||
auto diagnostics = std::make_shared<CheckBox>(L"Enable diagnostics", false);
|
||||
auto updateOptionText = std::make_shared<std::function<void()>>();
|
||||
*updateOptionText = [optionText, compactMode, diagnostics]() {
|
||||
if (diagnostics->IsChecked())
|
||||
{
|
||||
optionText->SetText(compactMode->IsChecked() ? L"Selection: compact mode + diagnostics are enabled."
|
||||
: L"Selection: diagnostics are enabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
optionText->SetText(compactMode->IsChecked() ? L"Selection: compact mode is enabled."
|
||||
: L"Selection: compact mode is disabled.");
|
||||
};
|
||||
compactMode->SetOnChanged([updateOptionText](bool checked) {
|
||||
(void)checked;
|
||||
(*updateOptionText)();
|
||||
});
|
||||
diagnostics->SetOnChanged([updateOptionText](bool checked) {
|
||||
(void)checked;
|
||||
(*updateOptionText)();
|
||||
});
|
||||
checkboxRow->AddChild(compactMode);
|
||||
checkboxRow->AddChild(diagnostics);
|
||||
|
||||
controlsStack->AddChild(std::make_shared<TextBlock>(L"Interactive Controls", 20.0f, D2D1::ColorF(0x1F2937), true));
|
||||
controlsStack->AddChild(std::make_shared<TextBlock>(
|
||||
L"Slider, progress, buttons and toggles can now be composed into higher-level widgets and panels.",
|
||||
14.0f, D2D1::ColorF(0x6B7280)));
|
||||
controlsStack->AddChild(progressLabel);
|
||||
controlsStack->AddChild(progressBar);
|
||||
controlsStack->AddChild(slider);
|
||||
controlsStack->AddChild(std::make_shared<Separator>(1.0f));
|
||||
controlsStack->AddChild(checkboxRow);
|
||||
controlsStack->AddChild(optionText);
|
||||
controlsCard->AddChild(controlsStack);
|
||||
root->AddChild(controlsCard);
|
||||
root->AddChild(std::make_shared<Spacer>(24.0f));
|
||||
|
||||
auto editorCard = std::make_shared<Card>(surface, 20.0f);
|
||||
auto editorStack = std::make_shared<StackPanel>(16.0f);
|
||||
editorStack->AddChild(std::make_shared<TextBlock>(L"TSF Editor", 20.0f, D2D1::ColorF(0x1F2937), true));
|
||||
editorStack->AddChild(std::make_shared<TextBlock>(
|
||||
L"Multi-line input area with TSF integration.",
|
||||
L"Multi-line input area with TSF integration. This remains the project's flagship control.",
|
||||
14.0f, D2D1::ColorF(0x6B7280)));
|
||||
editorStack->AddChild(std::make_shared<TextBox>(220.0f, L""));
|
||||
editorStack->AddChild(std::make_shared<TextBox>(220.0f, L"Type with any IME here..."));
|
||||
editorCard->AddChild(editorStack);
|
||||
root->AddChild(editorCard);
|
||||
root->AddChild(std::make_shared<Spacer>(28.0f));
|
||||
@@ -36,7 +129,10 @@ std::unique_ptr<Scene> CreateDemoScene()
|
||||
auto searchCard = std::make_shared<Card>(surface, 20.0f);
|
||||
auto searchStack = std::make_shared<StackPanel>(14.0f);
|
||||
searchStack->AddChild(std::make_shared<TextBlock>(L"Search Box", 18.0f, D2D1::ColorF(0x1F2937), true));
|
||||
searchStack->AddChild(std::make_shared<TextBox>(52.0f, L""));
|
||||
searchStack->AddChild(std::make_shared<TextBlock>(
|
||||
L"Single-line input can now sit alongside the broader control set as a first-class widget.",
|
||||
14.0f, D2D1::ColorF(0x6B7280)));
|
||||
searchStack->AddChild(std::make_shared<TextBox>(52.0f, L"Search controls, themes, layout..."));
|
||||
searchCard->AddChild(searchStack);
|
||||
root->AddChild(searchCard);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "Layout.h"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
@@ -28,6 +29,7 @@ class TextBox : public Visual
|
||||
bool OnKeyDown(WPARAM key, LPARAM lParam) override;
|
||||
bool OnChar(wchar_t ch, LPARAM lParam) override;
|
||||
bool OnTimer(UINT_PTR timerId) override;
|
||||
HCURSOR GetCursor() const override;
|
||||
|
||||
private:
|
||||
POINT ToLocalPoint(const POINT &point) const;
|
||||
@@ -43,4 +45,131 @@ class TextBox : public Visual
|
||||
::CTextEditor *editor_ = nullptr;
|
||||
LOGFONT font_ = {};
|
||||
};
|
||||
|
||||
class Button : public Visual
|
||||
{
|
||||
public:
|
||||
using ClickHandler = std::function<void()>;
|
||||
|
||||
Button(std::wstring text, float height = 44.0f);
|
||||
|
||||
void SetOnClick(ClickHandler handler);
|
||||
|
||||
SizeF Measure(const SizeF &availableSize) override;
|
||||
void Arrange(const RectF &finalRect) override;
|
||||
void Render(DeviceResources &deviceResources) override;
|
||||
bool HitTest(const PointF &point) const override;
|
||||
bool IsFocusable() const override;
|
||||
void OnFocusChanged(bool focused) override;
|
||||
bool OnMouseDown(const POINT &point, WPARAM keyState) override;
|
||||
bool OnMouseUp(const POINT &point, WPARAM keyState) override;
|
||||
HCURSOR GetCursor() const override;
|
||||
|
||||
protected:
|
||||
virtual void OnClick();
|
||||
virtual D2D1_COLOR_F GetFillColor() const;
|
||||
virtual D2D1_COLOR_F GetStrokeColor() const;
|
||||
virtual D2D1_COLOR_F GetTextColor() const;
|
||||
|
||||
std::wstring text_;
|
||||
float preferredHeight_ = 44.0f;
|
||||
bool focused_ = false;
|
||||
bool pressed_ = false;
|
||||
ClickHandler onClick_;
|
||||
};
|
||||
|
||||
class CheckBox : public Visual
|
||||
{
|
||||
public:
|
||||
using ChangeHandler = std::function<void(bool checked)>;
|
||||
|
||||
CheckBox(std::wstring text, bool checked = false);
|
||||
|
||||
void SetOnChanged(ChangeHandler handler);
|
||||
bool IsChecked() const;
|
||||
void SetChecked(bool checked);
|
||||
|
||||
SizeF Measure(const SizeF &availableSize) override;
|
||||
void Arrange(const RectF &finalRect) override;
|
||||
void Render(DeviceResources &deviceResources) override;
|
||||
bool HitTest(const PointF &point) const override;
|
||||
bool IsFocusable() const override;
|
||||
void OnFocusChanged(bool focused) override;
|
||||
bool OnMouseDown(const POINT &point, WPARAM keyState) override;
|
||||
bool OnMouseUp(const POINT &point, WPARAM keyState) override;
|
||||
HCURSOR GetCursor() const override;
|
||||
|
||||
private:
|
||||
std::wstring text_;
|
||||
bool checked_ = false;
|
||||
bool focused_ = false;
|
||||
bool pressed_ = false;
|
||||
ChangeHandler onChanged_;
|
||||
};
|
||||
|
||||
class ProgressBar : public Visual
|
||||
{
|
||||
public:
|
||||
explicit ProgressBar(float height = 12.0f);
|
||||
|
||||
void SetValue(float value);
|
||||
float GetValue() const;
|
||||
|
||||
SizeF Measure(const SizeF &availableSize) override;
|
||||
void Arrange(const RectF &finalRect) override;
|
||||
void Render(DeviceResources &deviceResources) override;
|
||||
|
||||
private:
|
||||
float preferredHeight_ = 12.0f;
|
||||
float value_ = 0.0f;
|
||||
};
|
||||
|
||||
class Slider : public Visual
|
||||
{
|
||||
public:
|
||||
using ChangeHandler = std::function<void(float value)>;
|
||||
|
||||
Slider(float minValue, float maxValue, float value, float height = 34.0f);
|
||||
|
||||
void SetOnChanged(ChangeHandler handler);
|
||||
void SetValue(float value);
|
||||
float GetValue() const;
|
||||
|
||||
SizeF Measure(const SizeF &availableSize) override;
|
||||
void Arrange(const RectF &finalRect) override;
|
||||
void Render(DeviceResources &deviceResources) override;
|
||||
bool HitTest(const PointF &point) const override;
|
||||
bool IsFocusable() const override;
|
||||
void OnFocusChanged(bool focused) override;
|
||||
bool OnMouseDown(const POINT &point, WPARAM keyState) override;
|
||||
bool OnMouseUp(const POINT &point, WPARAM keyState) override;
|
||||
bool OnMouseMove(const POINT &point, WPARAM keyState) override;
|
||||
HCURSOR GetCursor() const override;
|
||||
|
||||
private:
|
||||
float NormalizedValue() const;
|
||||
void SetValueInternal(float value, bool notify);
|
||||
bool UpdateFromPoint(const POINT &point, bool notify);
|
||||
|
||||
float minValue_ = 0.0f;
|
||||
float maxValue_ = 100.0f;
|
||||
float value_ = 0.0f;
|
||||
float preferredHeight_ = 34.0f;
|
||||
bool focused_ = false;
|
||||
bool dragging_ = false;
|
||||
ChangeHandler onChanged_;
|
||||
};
|
||||
|
||||
class Separator : public Visual
|
||||
{
|
||||
public:
|
||||
explicit Separator(float height = 1.0f);
|
||||
|
||||
SizeF Measure(const SizeF &availableSize) override;
|
||||
void Arrange(const RectF &finalRect) override;
|
||||
void Render(DeviceResources &deviceResources) override;
|
||||
|
||||
private:
|
||||
float height_ = 1.0f;
|
||||
};
|
||||
} // namespace msimeui
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "Types.h"
|
||||
|
||||
#include <dwrite.h>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -34,6 +35,7 @@ class Visual
|
||||
virtual bool OnKeyDown(WPARAM key, LPARAM lParam);
|
||||
virtual bool OnChar(wchar_t ch, LPARAM lParam);
|
||||
virtual bool OnTimer(UINT_PTR timerId);
|
||||
virtual HCURSOR GetCursor() const;
|
||||
|
||||
const RectF &GetBounds() const;
|
||||
|
||||
@@ -69,6 +71,45 @@ class StackPanel : public Panel
|
||||
std::vector<SizeF> measuredChildren_;
|
||||
};
|
||||
|
||||
class HorizontalStackPanel : public Panel
|
||||
{
|
||||
public:
|
||||
explicit HorizontalStackPanel(float spacing = 0.0f);
|
||||
|
||||
SizeF Measure(const SizeF &availableSize) override;
|
||||
void Arrange(const RectF &finalRect) override;
|
||||
void Render(DeviceResources &deviceResources) override;
|
||||
|
||||
private:
|
||||
float spacing_ = 0.0f;
|
||||
std::vector<SizeF> measuredChildren_;
|
||||
};
|
||||
|
||||
class WrapPanel : public Panel
|
||||
{
|
||||
public:
|
||||
explicit WrapPanel(float spacing = 0.0f, float runSpacing = 0.0f);
|
||||
|
||||
SizeF Measure(const SizeF &availableSize) override;
|
||||
void Arrange(const RectF &finalRect) override;
|
||||
void Render(DeviceResources &deviceResources) override;
|
||||
|
||||
private:
|
||||
struct RowItem
|
||||
{
|
||||
size_t childIndex = 0;
|
||||
SizeF size = {};
|
||||
float x = 0.0f;
|
||||
float y = 0.0f;
|
||||
};
|
||||
|
||||
float spacing_ = 0.0f;
|
||||
float runSpacing_ = 0.0f;
|
||||
SizeF measured_ = {};
|
||||
std::vector<SizeF> measuredChildren_;
|
||||
std::vector<RowItem> rowItems_;
|
||||
};
|
||||
|
||||
class Card : public Panel
|
||||
{
|
||||
public:
|
||||
@@ -88,6 +129,7 @@ class TextBlock : public Visual
|
||||
{
|
||||
public:
|
||||
TextBlock(std::wstring text, float fontSize, D2D1_COLOR_F color, bool bold = false);
|
||||
void SetText(std::wstring text);
|
||||
|
||||
SizeF Measure(const SizeF &availableSize) override;
|
||||
void Arrange(const RectF &finalRect) override;
|
||||
|
||||
@@ -0,0 +1,619 @@
|
||||
#include "msimeui/Controls.h"
|
||||
|
||||
#include "msimeui/DeviceResources.h"
|
||||
#include "msimeui/Window.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <wrl/client.h>
|
||||
|
||||
namespace msimeui
|
||||
{
|
||||
using Microsoft::WRL::ComPtr;
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr float kControlCornerRadius = 12.0f;
|
||||
constexpr float kControlPaddingX = 16.0f;
|
||||
constexpr float kControlPaddingY = 10.0f;
|
||||
constexpr float kCheckBoxIndicatorSize = 20.0f;
|
||||
constexpr float kSliderTrackHeight = 6.0f;
|
||||
constexpr float kSliderThumbRadius = 9.0f;
|
||||
|
||||
RectF MakeInsetRect(const RectF &rect, float insetX, float insetY)
|
||||
{
|
||||
return {rect.x + insetX, rect.y + insetY, std::max(rect.width - insetX * 2.0f, 0.0f),
|
||||
std::max(rect.height - insetY * 2.0f, 0.0f)};
|
||||
}
|
||||
|
||||
bool PointInRect(const RectF &rect, const PointF &point)
|
||||
{
|
||||
return point.x >= rect.x && point.x <= (rect.x + rect.width) && point.y >= rect.y &&
|
||||
point.y <= (rect.y + rect.height);
|
||||
}
|
||||
|
||||
bool PointInRoundedRect(const RectF &rect, float radius, const PointF &point)
|
||||
{
|
||||
if (!PointInRect(rect, point))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const float clampedRadius = std::min(radius, std::min(rect.width, rect.height) * 0.5f);
|
||||
if (clampedRadius <= 0.0f)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const float left = rect.x;
|
||||
const float top = rect.y;
|
||||
const float right = rect.x + rect.width;
|
||||
const float bottom = rect.y + rect.height;
|
||||
|
||||
if ((point.x >= left + clampedRadius && point.x <= right - clampedRadius) ||
|
||||
(point.y >= top + clampedRadius && point.y <= bottom - clampedRadius))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const float centerX = point.x < left + clampedRadius ? left + clampedRadius : right - clampedRadius;
|
||||
const float centerY = point.y < top + clampedRadius ? top + clampedRadius : bottom - clampedRadius;
|
||||
const float dx = point.x - centerX;
|
||||
const float dy = point.y - centerY;
|
||||
return (dx * dx + dy * dy) <= (clampedRadius * clampedRadius);
|
||||
}
|
||||
|
||||
SizeF MeasureText(IDWriteFactory *factory, const std::wstring &text, float fontSize, bool bold, float maxWidth)
|
||||
{
|
||||
if (!factory)
|
||||
{
|
||||
return {maxWidth, std::ceil(fontSize * 1.5f)};
|
||||
}
|
||||
|
||||
ComPtr<IDWriteTextFormat> format;
|
||||
if (FAILED(factory->CreateTextFormat(L"Segoe UI", nullptr,
|
||||
bold ? DWRITE_FONT_WEIGHT_SEMI_BOLD : DWRITE_FONT_WEIGHT_NORMAL,
|
||||
DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, fontSize, L"",
|
||||
format.GetAddressOf())))
|
||||
{
|
||||
return {maxWidth, std::ceil(fontSize * 1.5f)};
|
||||
}
|
||||
|
||||
format->SetWordWrapping(DWRITE_WORD_WRAPPING_WRAP);
|
||||
|
||||
ComPtr<IDWriteTextLayout> layout;
|
||||
if (FAILED(factory->CreateTextLayout(text.c_str(), static_cast<UINT32>(text.size()), format.Get(), maxWidth,
|
||||
std::numeric_limits<float>::max(), layout.GetAddressOf())))
|
||||
{
|
||||
return {maxWidth, std::ceil(fontSize * 1.5f)};
|
||||
}
|
||||
|
||||
DWRITE_TEXT_METRICS metrics = {};
|
||||
if (FAILED(layout->GetMetrics(&metrics)))
|
||||
{
|
||||
return {maxWidth, std::ceil(fontSize * 1.5f)};
|
||||
}
|
||||
|
||||
return {std::ceil(metrics.widthIncludingTrailingWhitespace), std::ceil(metrics.height)};
|
||||
}
|
||||
|
||||
void DrawTextBlock(DeviceResources &deviceResources, const std::wstring &text, float fontSize, bool bold,
|
||||
D2D1_COLOR_F color, const RectF &rect, DWRITE_TEXT_ALIGNMENT textAlignment)
|
||||
{
|
||||
ID2D1HwndRenderTarget *target = deviceResources.GetRenderTarget();
|
||||
IDWriteFactory *factory = deviceResources.GetDWriteFactory();
|
||||
if (!target || !factory)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ComPtr<IDWriteTextFormat> format;
|
||||
if (FAILED(factory->CreateTextFormat(L"Segoe UI", nullptr,
|
||||
bold ? DWRITE_FONT_WEIGHT_SEMI_BOLD : DWRITE_FONT_WEIGHT_NORMAL,
|
||||
DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, fontSize, L"",
|
||||
format.GetAddressOf())))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
format->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
|
||||
format->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER);
|
||||
format->SetTextAlignment(textAlignment);
|
||||
|
||||
ComPtr<ID2D1SolidColorBrush> brush;
|
||||
target->CreateSolidColorBrush(color, brush.GetAddressOf());
|
||||
target->DrawTextW(text.c_str(), static_cast<UINT32>(text.size()), format.Get(),
|
||||
D2D1::RectF(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height), brush.Get());
|
||||
}
|
||||
|
||||
void FillRoundedRect(ID2D1HwndRenderTarget *target, const RectF &bounds, float radius, D2D1_COLOR_F fill,
|
||||
D2D1_COLOR_F stroke, float strokeWidth)
|
||||
{
|
||||
ComPtr<ID2D1SolidColorBrush> fillBrush;
|
||||
ComPtr<ID2D1SolidColorBrush> strokeBrush;
|
||||
target->CreateSolidColorBrush(fill, fillBrush.GetAddressOf());
|
||||
target->CreateSolidColorBrush(stroke, strokeBrush.GetAddressOf());
|
||||
|
||||
const auto rounded =
|
||||
D2D1::RoundedRect(D2D1::RectF(bounds.x, bounds.y, bounds.x + bounds.width, bounds.y + bounds.height), radius, radius);
|
||||
target->FillRoundedRectangle(rounded, fillBrush.Get());
|
||||
target->DrawRoundedRectangle(rounded, strokeBrush.Get(), strokeWidth);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Button::Button(std::wstring text, float height) : text_(std::move(text)), preferredHeight_(height)
|
||||
{
|
||||
}
|
||||
|
||||
void Button::SetOnClick(ClickHandler handler)
|
||||
{
|
||||
onClick_ = std::move(handler);
|
||||
}
|
||||
|
||||
SizeF Button::Measure(const SizeF &availableSize)
|
||||
{
|
||||
const float textWidth = std::max(availableSize.width - kControlPaddingX * 2.0f, 1.0f);
|
||||
const SizeF measuredText = MeasureText(window_ ? window_->GetDeviceResources().GetDWriteFactory() : nullptr, text_, 16.0f, true,
|
||||
textWidth);
|
||||
return {std::min(availableSize.width, measuredText.width + kControlPaddingX * 2.0f),
|
||||
std::max(preferredHeight_, measuredText.height + kControlPaddingY * 2.0f)};
|
||||
}
|
||||
|
||||
void Button::Arrange(const RectF &finalRect)
|
||||
{
|
||||
bounds_ = finalRect;
|
||||
}
|
||||
|
||||
void Button::Render(DeviceResources &deviceResources)
|
||||
{
|
||||
ID2D1HwndRenderTarget *target = deviceResources.GetRenderTarget();
|
||||
if (!target)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FillRoundedRect(target, bounds_, kControlCornerRadius, GetFillColor(), GetStrokeColor(), focused_ ? 2.0f : 1.0f);
|
||||
DrawTextBlock(deviceResources, text_, 16.0f, true, GetTextColor(), bounds_, DWRITE_TEXT_ALIGNMENT_CENTER);
|
||||
}
|
||||
|
||||
bool Button::HitTest(const PointF &point) const
|
||||
{
|
||||
return PointInRoundedRect(bounds_, kControlCornerRadius, point);
|
||||
}
|
||||
|
||||
bool Button::IsFocusable() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void Button::OnFocusChanged(bool focused)
|
||||
{
|
||||
focused_ = focused;
|
||||
if (window_)
|
||||
{
|
||||
window_->Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
bool Button::OnMouseDown(const POINT &point, WPARAM keyState)
|
||||
{
|
||||
(void)keyState;
|
||||
if (!window_)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
pressed_ = HitTest(window_->ClientPixelsToDips(point));
|
||||
window_->Invalidate();
|
||||
return pressed_;
|
||||
}
|
||||
|
||||
bool Button::OnMouseUp(const POINT &point, WPARAM keyState)
|
||||
{
|
||||
(void)keyState;
|
||||
if (!window_ || !pressed_)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool shouldClick = HitTest(window_->ClientPixelsToDips(point));
|
||||
pressed_ = false;
|
||||
window_->Invalidate();
|
||||
if (shouldClick)
|
||||
{
|
||||
OnClick();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
HCURSOR Button::GetCursor() const
|
||||
{
|
||||
return LoadCursor(nullptr, IDC_HAND);
|
||||
}
|
||||
|
||||
void Button::OnClick()
|
||||
{
|
||||
if (onClick_)
|
||||
{
|
||||
onClick_();
|
||||
}
|
||||
}
|
||||
|
||||
D2D1_COLOR_F Button::GetFillColor() const
|
||||
{
|
||||
return pressed_ ? D2D1::ColorF(0x1D4ED8) : D2D1::ColorF(0x2563EB);
|
||||
}
|
||||
|
||||
D2D1_COLOR_F Button::GetStrokeColor() const
|
||||
{
|
||||
return focused_ ? D2D1::ColorF(0x93C5FD) : D2D1::ColorF(0x1D4ED8);
|
||||
}
|
||||
|
||||
D2D1_COLOR_F Button::GetTextColor() const
|
||||
{
|
||||
return D2D1::ColorF(0xFFFFFF);
|
||||
}
|
||||
|
||||
CheckBox::CheckBox(std::wstring text, bool checked) : text_(std::move(text)), checked_(checked)
|
||||
{
|
||||
}
|
||||
|
||||
void CheckBox::SetOnChanged(ChangeHandler handler)
|
||||
{
|
||||
onChanged_ = std::move(handler);
|
||||
}
|
||||
|
||||
bool CheckBox::IsChecked() const
|
||||
{
|
||||
return checked_;
|
||||
}
|
||||
|
||||
void CheckBox::SetChecked(bool checked)
|
||||
{
|
||||
if (checked_ == checked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
checked_ = checked;
|
||||
if (window_)
|
||||
{
|
||||
window_->Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
SizeF CheckBox::Measure(const SizeF &availableSize)
|
||||
{
|
||||
const float textWidth = std::max(availableSize.width - kCheckBoxIndicatorSize - 12.0f, 1.0f);
|
||||
const SizeF measuredText = MeasureText(window_ ? window_->GetDeviceResources().GetDWriteFactory() : nullptr, text_, 15.0f, false,
|
||||
textWidth);
|
||||
return {std::min(availableSize.width, kCheckBoxIndicatorSize + 12.0f + measuredText.width),
|
||||
std::max(28.0f, measuredText.height)};
|
||||
}
|
||||
|
||||
void CheckBox::Arrange(const RectF &finalRect)
|
||||
{
|
||||
bounds_ = finalRect;
|
||||
}
|
||||
|
||||
void CheckBox::Render(DeviceResources &deviceResources)
|
||||
{
|
||||
ID2D1HwndRenderTarget *target = deviceResources.GetRenderTarget();
|
||||
if (!target)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const float indicatorY = bounds_.y + (bounds_.height - kCheckBoxIndicatorSize) * 0.5f;
|
||||
const RectF indicator = {bounds_.x, indicatorY, kCheckBoxIndicatorSize, kCheckBoxIndicatorSize};
|
||||
FillRoundedRect(target, indicator, 6.0f, checked_ ? D2D1::ColorF(0x2563EB) : D2D1::ColorF(0xFFFFFF),
|
||||
focused_ ? D2D1::ColorF(0x60A5FA) : D2D1::ColorF(0xCBD5E1), focused_ ? 2.0f : 1.0f);
|
||||
|
||||
if (checked_)
|
||||
{
|
||||
ComPtr<ID2D1SolidColorBrush> checkBrush;
|
||||
target->CreateSolidColorBrush(D2D1::ColorF(0xFFFFFF), checkBrush.GetAddressOf());
|
||||
target->DrawLine(D2D1::Point2F(indicator.x + 5.0f, indicator.y + 11.0f),
|
||||
D2D1::Point2F(indicator.x + 9.0f, indicator.y + 15.0f), checkBrush.Get(), 2.0f);
|
||||
target->DrawLine(D2D1::Point2F(indicator.x + 9.0f, indicator.y + 15.0f),
|
||||
D2D1::Point2F(indicator.x + 15.0f, indicator.y + 6.0f), checkBrush.Get(), 2.0f);
|
||||
}
|
||||
|
||||
const RectF textRect = {indicator.x + indicator.width + 12.0f, bounds_.y, std::max(bounds_.width - indicator.width - 12.0f, 0.0f),
|
||||
bounds_.height};
|
||||
DrawTextBlock(deviceResources, text_, 15.0f, false, D2D1::ColorF(0x1F2937), textRect, DWRITE_TEXT_ALIGNMENT_LEADING);
|
||||
}
|
||||
|
||||
bool CheckBox::HitTest(const PointF &point) const
|
||||
{
|
||||
return PointInRect(bounds_, point);
|
||||
}
|
||||
|
||||
bool CheckBox::IsFocusable() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void CheckBox::OnFocusChanged(bool focused)
|
||||
{
|
||||
focused_ = focused;
|
||||
if (window_)
|
||||
{
|
||||
window_->Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
bool CheckBox::OnMouseDown(const POINT &point, WPARAM keyState)
|
||||
{
|
||||
(void)keyState;
|
||||
if (!window_)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
pressed_ = HitTest(window_->ClientPixelsToDips(point));
|
||||
return pressed_;
|
||||
}
|
||||
|
||||
bool CheckBox::OnMouseUp(const POINT &point, WPARAM keyState)
|
||||
{
|
||||
(void)keyState;
|
||||
if (!window_ || !pressed_)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool shouldToggle = HitTest(window_->ClientPixelsToDips(point));
|
||||
pressed_ = false;
|
||||
if (shouldToggle)
|
||||
{
|
||||
checked_ = !checked_;
|
||||
if (onChanged_)
|
||||
{
|
||||
onChanged_(checked_);
|
||||
}
|
||||
}
|
||||
|
||||
window_->Invalidate();
|
||||
return true;
|
||||
}
|
||||
|
||||
HCURSOR CheckBox::GetCursor() const
|
||||
{
|
||||
return LoadCursor(nullptr, IDC_HAND);
|
||||
}
|
||||
|
||||
ProgressBar::ProgressBar(float height) : preferredHeight_(height)
|
||||
{
|
||||
}
|
||||
|
||||
void ProgressBar::SetValue(float value)
|
||||
{
|
||||
value_ = std::clamp(value, 0.0f, 1.0f);
|
||||
if (window_)
|
||||
{
|
||||
window_->Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
float ProgressBar::GetValue() const
|
||||
{
|
||||
return value_;
|
||||
}
|
||||
|
||||
SizeF ProgressBar::Measure(const SizeF &availableSize)
|
||||
{
|
||||
return {availableSize.width, preferredHeight_};
|
||||
}
|
||||
|
||||
void ProgressBar::Arrange(const RectF &finalRect)
|
||||
{
|
||||
bounds_ = finalRect;
|
||||
}
|
||||
|
||||
void ProgressBar::Render(DeviceResources &deviceResources)
|
||||
{
|
||||
ID2D1HwndRenderTarget *target = deviceResources.GetRenderTarget();
|
||||
if (!target)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FillRoundedRect(target, bounds_, preferredHeight_ * 0.5f, D2D1::ColorF(0xE2E8F0), D2D1::ColorF(0xCBD5E1), 1.0f);
|
||||
|
||||
const RectF fillRect = {bounds_.x, bounds_.y, bounds_.width * value_, bounds_.height};
|
||||
if (fillRect.width > 0.0f)
|
||||
{
|
||||
FillRoundedRect(target, fillRect, preferredHeight_ * 0.5f, D2D1::ColorF(0x22C55E), D2D1::ColorF(0x22C55E), 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
Slider::Slider(float minValue, float maxValue, float value, float height)
|
||||
: minValue_(minValue), maxValue_(std::max(maxValue, minValue)), preferredHeight_(height)
|
||||
{
|
||||
SetValueInternal(value, false);
|
||||
}
|
||||
|
||||
void Slider::SetOnChanged(ChangeHandler handler)
|
||||
{
|
||||
onChanged_ = std::move(handler);
|
||||
}
|
||||
|
||||
void Slider::SetValue(float value)
|
||||
{
|
||||
SetValueInternal(value, false);
|
||||
}
|
||||
|
||||
float Slider::GetValue() const
|
||||
{
|
||||
return value_;
|
||||
}
|
||||
|
||||
SizeF Slider::Measure(const SizeF &availableSize)
|
||||
{
|
||||
return {availableSize.width, preferredHeight_};
|
||||
}
|
||||
|
||||
void Slider::Arrange(const RectF &finalRect)
|
||||
{
|
||||
bounds_ = finalRect;
|
||||
}
|
||||
|
||||
void Slider::Render(DeviceResources &deviceResources)
|
||||
{
|
||||
ID2D1HwndRenderTarget *target = deviceResources.GetRenderTarget();
|
||||
if (!target)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const RectF track = {bounds_.x, bounds_.y + (bounds_.height - kSliderTrackHeight) * 0.5f, bounds_.width, kSliderTrackHeight};
|
||||
FillRoundedRect(target, track, kSliderTrackHeight * 0.5f, D2D1::ColorF(0xE2E8F0), D2D1::ColorF(0xCBD5E1), 1.0f);
|
||||
|
||||
const RectF active = {track.x, track.y, track.width * NormalizedValue(), track.height};
|
||||
if (active.width > 0.0f)
|
||||
{
|
||||
FillRoundedRect(target, active, kSliderTrackHeight * 0.5f, D2D1::ColorF(0x2563EB), D2D1::ColorF(0x2563EB), 1.0f);
|
||||
}
|
||||
|
||||
const float thumbCenterX = track.x + track.width * NormalizedValue();
|
||||
const float thumbCenterY = bounds_.y + bounds_.height * 0.5f;
|
||||
ComPtr<ID2D1SolidColorBrush> fillBrush;
|
||||
ComPtr<ID2D1SolidColorBrush> strokeBrush;
|
||||
target->CreateSolidColorBrush(D2D1::ColorF(0xFFFFFF), fillBrush.GetAddressOf());
|
||||
target->CreateSolidColorBrush(focused_ || dragging_ ? D2D1::ColorF(0x2563EB) : D2D1::ColorF(0x94A3B8),
|
||||
strokeBrush.GetAddressOf());
|
||||
target->FillEllipse(D2D1::Ellipse(D2D1::Point2F(thumbCenterX, thumbCenterY), kSliderThumbRadius, kSliderThumbRadius),
|
||||
fillBrush.Get());
|
||||
target->DrawEllipse(D2D1::Ellipse(D2D1::Point2F(thumbCenterX, thumbCenterY), kSliderThumbRadius, kSliderThumbRadius),
|
||||
strokeBrush.Get(), focused_ || dragging_ ? 2.0f : 1.0f);
|
||||
}
|
||||
|
||||
bool Slider::HitTest(const PointF &point) const
|
||||
{
|
||||
return PointInRect(bounds_, point);
|
||||
}
|
||||
|
||||
bool Slider::IsFocusable() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void Slider::OnFocusChanged(bool focused)
|
||||
{
|
||||
focused_ = focused;
|
||||
if (window_)
|
||||
{
|
||||
window_->Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
bool Slider::OnMouseDown(const POINT &point, WPARAM keyState)
|
||||
{
|
||||
(void)keyState;
|
||||
dragging_ = UpdateFromPoint(point, true);
|
||||
return dragging_;
|
||||
}
|
||||
|
||||
bool Slider::OnMouseUp(const POINT &point, WPARAM keyState)
|
||||
{
|
||||
(void)keyState;
|
||||
if (!dragging_)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UpdateFromPoint(point, true);
|
||||
dragging_ = false;
|
||||
if (window_)
|
||||
{
|
||||
window_->Invalidate();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Slider::OnMouseMove(const POINT &point, WPARAM keyState)
|
||||
{
|
||||
if (!dragging_ || !(keyState & MK_LBUTTON))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return UpdateFromPoint(point, true);
|
||||
}
|
||||
|
||||
HCURSOR Slider::GetCursor() const
|
||||
{
|
||||
return LoadCursor(nullptr, IDC_HAND);
|
||||
}
|
||||
|
||||
float Slider::NormalizedValue() const
|
||||
{
|
||||
const float range = maxValue_ - minValue_;
|
||||
if (range <= 0.0f)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return std::clamp((value_ - minValue_) / range, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
void Slider::SetValueInternal(float value, bool notify)
|
||||
{
|
||||
const float oldValue = value_;
|
||||
value_ = std::clamp(value, minValue_, maxValue_);
|
||||
if (window_)
|
||||
{
|
||||
window_->Invalidate();
|
||||
}
|
||||
|
||||
if (notify && oldValue != value_ && onChanged_)
|
||||
{
|
||||
onChanged_(value_);
|
||||
}
|
||||
}
|
||||
|
||||
bool Slider::UpdateFromPoint(const POINT &point, bool notify)
|
||||
{
|
||||
if (!window_ || bounds_.width <= 0.0f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const PointF dipPoint = window_->ClientPixelsToDips(point);
|
||||
const float ratio = std::clamp((dipPoint.x - bounds_.x) / bounds_.width, 0.0f, 1.0f);
|
||||
SetValueInternal(minValue_ + (maxValue_ - minValue_) * ratio, notify);
|
||||
return true;
|
||||
}
|
||||
|
||||
Separator::Separator(float height) : height_(height)
|
||||
{
|
||||
}
|
||||
|
||||
SizeF Separator::Measure(const SizeF &availableSize)
|
||||
{
|
||||
return {availableSize.width, height_};
|
||||
}
|
||||
|
||||
void Separator::Arrange(const RectF &finalRect)
|
||||
{
|
||||
bounds_ = finalRect;
|
||||
}
|
||||
|
||||
void Separator::Render(DeviceResources &deviceResources)
|
||||
{
|
||||
ID2D1HwndRenderTarget *target = deviceResources.GetRenderTarget();
|
||||
if (!target)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ComPtr<ID2D1SolidColorBrush> brush;
|
||||
target->CreateSolidColorBrush(D2D1::ColorF(0xE2E8F0), brush.GetAddressOf());
|
||||
const float centerY = bounds_.y + bounds_.height * 0.5f;
|
||||
target->DrawLine(D2D1::Point2F(bounds_.x, centerY), D2D1::Point2F(bounds_.x + bounds_.width, centerY), brush.Get(),
|
||||
std::max(bounds_.height, 1.0f));
|
||||
}
|
||||
} // namespace msimeui
|
||||
@@ -109,6 +109,11 @@ bool Visual::OnTimer(UINT_PTR timerId)
|
||||
return false;
|
||||
}
|
||||
|
||||
HCURSOR Visual::GetCursor() const
|
||||
{
|
||||
return LoadCursor(nullptr, IDC_ARROW);
|
||||
}
|
||||
|
||||
const RectF &Visual::GetBounds() const
|
||||
{
|
||||
return bounds_;
|
||||
@@ -234,6 +239,111 @@ void StackPanel::Render(DeviceResources &deviceResources)
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalStackPanel::HorizontalStackPanel(float spacing) : spacing_(spacing)
|
||||
{
|
||||
}
|
||||
|
||||
SizeF HorizontalStackPanel::Measure(const SizeF &availableSize)
|
||||
{
|
||||
measuredChildren_.clear();
|
||||
|
||||
float width = 0.0f;
|
||||
float height = 0.0f;
|
||||
for (const auto &child : children_)
|
||||
{
|
||||
const SizeF measured = child->Measure(availableSize);
|
||||
measuredChildren_.push_back(measured);
|
||||
width += measured.width;
|
||||
height = std::max(height, measured.height);
|
||||
}
|
||||
|
||||
if (!children_.empty())
|
||||
{
|
||||
width += spacing_ * static_cast<float>(children_.size() - 1);
|
||||
}
|
||||
|
||||
return {std::min(width, availableSize.width), std::min(height, availableSize.height)};
|
||||
}
|
||||
|
||||
void HorizontalStackPanel::Arrange(const RectF &finalRect)
|
||||
{
|
||||
bounds_ = finalRect;
|
||||
|
||||
float cursorX = finalRect.x;
|
||||
for (size_t index = 0; index < children_.size(); ++index)
|
||||
{
|
||||
const SizeF measured = measuredChildren_[index];
|
||||
children_[index]->Arrange({cursorX, finalRect.y, measured.width, finalRect.height});
|
||||
cursorX += measured.width + spacing_;
|
||||
}
|
||||
}
|
||||
|
||||
void HorizontalStackPanel::Render(DeviceResources &deviceResources)
|
||||
{
|
||||
for (const auto &child : children_)
|
||||
{
|
||||
child->Render(deviceResources);
|
||||
}
|
||||
}
|
||||
|
||||
WrapPanel::WrapPanel(float spacing, float runSpacing) : spacing_(spacing), runSpacing_(runSpacing)
|
||||
{
|
||||
}
|
||||
|
||||
SizeF WrapPanel::Measure(const SizeF &availableSize)
|
||||
{
|
||||
measuredChildren_.clear();
|
||||
rowItems_.clear();
|
||||
|
||||
const float maxWidth = std::max(availableSize.width, 1.0f);
|
||||
float currentX = 0.0f;
|
||||
float currentY = 0.0f;
|
||||
float rowHeight = 0.0f;
|
||||
float measuredWidth = 0.0f;
|
||||
|
||||
for (size_t index = 0; index < children_.size(); ++index)
|
||||
{
|
||||
const SizeF childSize = children_[index]->Measure(availableSize);
|
||||
measuredChildren_.push_back(childSize);
|
||||
|
||||
const bool wrap = currentX > 0.0f && (currentX + childSize.width) > maxWidth;
|
||||
if (wrap)
|
||||
{
|
||||
measuredWidth = std::max(measuredWidth, currentX - spacing_);
|
||||
currentX = 0.0f;
|
||||
currentY += rowHeight + runSpacing_;
|
||||
rowHeight = 0.0f;
|
||||
}
|
||||
|
||||
rowItems_.push_back({index, childSize, currentX, currentY});
|
||||
currentX += childSize.width + spacing_;
|
||||
rowHeight = std::max(rowHeight, childSize.height);
|
||||
}
|
||||
|
||||
measuredWidth = std::max(measuredWidth, currentX > 0.0f ? currentX - spacing_ : 0.0f);
|
||||
measured_ = {std::min(measuredWidth, availableSize.width), std::min(currentY + rowHeight, availableSize.height)};
|
||||
return measured_;
|
||||
}
|
||||
|
||||
void WrapPanel::Arrange(const RectF &finalRect)
|
||||
{
|
||||
bounds_ = finalRect;
|
||||
|
||||
for (const RowItem &item : rowItems_)
|
||||
{
|
||||
children_[item.childIndex]->Arrange(
|
||||
{finalRect.x + item.x, finalRect.y + item.y, item.size.width, item.size.height});
|
||||
}
|
||||
}
|
||||
|
||||
void WrapPanel::Render(DeviceResources &deviceResources)
|
||||
{
|
||||
for (const auto &child : children_)
|
||||
{
|
||||
child->Render(deviceResources);
|
||||
}
|
||||
}
|
||||
|
||||
Card::Card(Brush brush, float padding) : brush_(brush), padding_(padding)
|
||||
{
|
||||
}
|
||||
@@ -298,6 +408,15 @@ TextBlock::TextBlock(std::wstring text, float fontSize, D2D1_COLOR_F color, bool
|
||||
{
|
||||
}
|
||||
|
||||
void TextBlock::SetText(std::wstring text)
|
||||
{
|
||||
text_ = std::move(text);
|
||||
if (window_)
|
||||
{
|
||||
window_->Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
SizeF TextBlock::Measure(const SizeF &availableSize)
|
||||
{
|
||||
const float maxWidth = std::max(availableSize.width, 1.0f);
|
||||
|
||||
@@ -458,6 +458,11 @@ bool TextBox::OnTimer(UINT_PTR timerId)
|
||||
return true;
|
||||
}
|
||||
|
||||
HCURSOR TextBox::GetCursor() const
|
||||
{
|
||||
return LoadCursor(nullptr, IDC_IBEAM);
|
||||
}
|
||||
|
||||
POINT TextBox::ToLocalPoint(const POINT &point) const
|
||||
{
|
||||
const RECT hostRect = window_ ? window_->DipsToClientPixels(bounds_) : ToRectPixels(bounds_, 96.0f);
|
||||
|
||||
+3
-3
@@ -307,9 +307,9 @@ bool Window::UpdateCursorForClientPoint(const POINT &point)
|
||||
}
|
||||
|
||||
const PointF dipPoint = ClientPixelsToDips(point);
|
||||
Visual *focusTarget = scene_->FindFocusableAt(dipPoint);
|
||||
SetCursor(LoadCursor(nullptr, focusTarget ? IDC_IBEAM : IDC_ARROW));
|
||||
return focusTarget != nullptr;
|
||||
Visual *target = scene_->FindVisualAt(dipPoint);
|
||||
SetCursor(target ? target->GetCursor() : LoadCursor(nullptr, IDC_ARROW));
|
||||
return target != nullptr;
|
||||
}
|
||||
|
||||
void Window::SetFocusedVisual(Visual *visual)
|
||||
|
||||
Reference in new issue
Block a user