521 lines
16 KiB
C++
521 lines
16 KiB
C++
#pragma once
|
|
|
|
#include "Layout.h"
|
|
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <wrl/client.h>
|
|
|
|
class CTextEditor;
|
|
|
|
namespace msimeui
|
|
{
|
|
enum class PopupPlacement
|
|
{
|
|
BelowLeading,
|
|
AboveLeading,
|
|
};
|
|
|
|
class TextBox : public Visual
|
|
{
|
|
public:
|
|
TextBox(float height, std::wstring placeholder);
|
|
~TextBox() override;
|
|
|
|
SizeF Measure(const SizeF &availableSize) override;
|
|
void Arrange(const RectF &finalRect) override;
|
|
void Render(DeviceResources &deviceResources) override;
|
|
void Attach(Window *window) 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;
|
|
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:
|
|
RECT ComputeEditorHostRect() const;
|
|
RECT ComputeEditorContentPadding() const;
|
|
POINT ToLocalPoint(const POINT &point) const;
|
|
bool EnsureInitialized(DeviceResources *deviceResources);
|
|
bool AlertMouseSink(const POINT &point, WPARAM keyState);
|
|
|
|
float preferredHeight_ = 44.0f;
|
|
std::wstring placeholder_;
|
|
bool focused_ = false;
|
|
bool tsfInitialized_ = false;
|
|
bool renderInitialized_ = false;
|
|
UINT dragSelectionStart_ = static_cast<UINT>(-1);
|
|
::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:
|
|
void InvalidateTextLayoutCache();
|
|
|
|
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_;
|
|
std::wstring cachedFontFamily_;
|
|
float cachedLayoutWidth_ = -1.0f;
|
|
Microsoft::WRL::ComPtr<IDWriteTextLayout> cachedTextLayout_;
|
|
};
|
|
|
|
class Popup : public Visual
|
|
{
|
|
public:
|
|
explicit Popup(std::shared_ptr<Visual> child);
|
|
|
|
void SetAnchorRect(const RectF &anchorRect);
|
|
void SetPlacement(PopupPlacement placement);
|
|
void SetOffset(float x, float y);
|
|
void SetMatchAnchorWidth(bool matchAnchorWidth);
|
|
|
|
SizeF Measure(const SizeF &availableSize) override;
|
|
void Arrange(const RectF &finalRect) override;
|
|
void Render(DeviceResources &deviceResources) override;
|
|
void Attach(Window *window) override;
|
|
Visual *FindVisualAt(const PointF &point) override;
|
|
Visual *FindFocusableAt(const PointF &point) override;
|
|
Visual *FindFirstFocusableDescendant() override;
|
|
bool HitTest(const PointF &point) const override;
|
|
void LayoutOverlay(const SizeF &viewportSize) override;
|
|
|
|
private:
|
|
std::shared_ptr<Visual> child_;
|
|
RectF anchorRect_ = {};
|
|
PopupPlacement placement_ = PopupPlacement::BelowLeading;
|
|
float offsetX_ = 0.0f;
|
|
float offsetY_ = 8.0f;
|
|
bool matchAnchorWidth_ = true;
|
|
};
|
|
|
|
class PopupHost : public Visual
|
|
{
|
|
public:
|
|
PopupHost(std::shared_ptr<Visual> trigger, std::shared_ptr<Popup> popup);
|
|
|
|
SizeF Measure(const SizeF &availableSize) override;
|
|
void Arrange(const RectF &finalRect) override;
|
|
void Render(DeviceResources &deviceResources) override;
|
|
void Attach(Window *window) override;
|
|
Visual *FindVisualAt(const PointF &point) override;
|
|
Visual *FindFocusableAt(const PointF &point) override;
|
|
Visual *FindFirstFocusableDescendant() override;
|
|
bool HitTest(const PointF &point) const override;
|
|
bool OnMouseDown(const POINT &point, WPARAM keyState) override;
|
|
bool OnMouseUp(const POINT &point, WPARAM keyState) override;
|
|
HCURSOR GetCursor() const override;
|
|
bool KeepsPopupsOpenOnClick() const override;
|
|
|
|
void OpenPopup();
|
|
void ClosePopup();
|
|
void TogglePopup();
|
|
bool IsOpen() const;
|
|
|
|
private:
|
|
std::shared_ptr<Visual> trigger_;
|
|
std::shared_ptr<Popup> popup_;
|
|
bool pressed_ = false;
|
|
bool open_ = false;
|
|
};
|
|
|
|
class ContextMenuHost : public Visual
|
|
{
|
|
public:
|
|
ContextMenuHost(std::shared_ptr<Visual> trigger, std::shared_ptr<Popup> popup);
|
|
|
|
SizeF Measure(const SizeF &availableSize) override;
|
|
void Arrange(const RectF &finalRect) override;
|
|
void Render(DeviceResources &deviceResources) override;
|
|
void Attach(Window *window) override;
|
|
Visual *FindVisualAt(const PointF &point) override;
|
|
Visual *FindFocusableAt(const PointF &point) override;
|
|
Visual *FindFirstFocusableDescendant() override;
|
|
bool HitTest(const PointF &point) const override;
|
|
bool OnContextMenu(const POINT &point, WPARAM keyState) override;
|
|
HCURSOR GetCursor() const override;
|
|
bool KeepsPopupsOpenOnClick() const override;
|
|
|
|
void OpenPopupAt(const PointF &anchorPoint);
|
|
void ClosePopup();
|
|
bool IsOpen() const;
|
|
|
|
private:
|
|
std::shared_ptr<Visual> trigger_;
|
|
std::shared_ptr<Popup> popup_;
|
|
bool open_ = false;
|
|
};
|
|
|
|
class ComboBox : public Visual
|
|
{
|
|
public:
|
|
using SelectionChangedHandler = std::function<void(size_t selectedIndex, const std::wstring &value)>;
|
|
|
|
explicit ComboBox(float height = 44.0f);
|
|
|
|
void AddItem(std::wstring item);
|
|
void ClearItems();
|
|
void SetSelectedIndex(size_t index);
|
|
size_t GetSelectedIndex() const;
|
|
const std::wstring &GetSelectedText() const;
|
|
void SetOnSelectionChanged(SelectionChangedHandler handler);
|
|
|
|
SizeF Measure(const SizeF &availableSize) override;
|
|
void Arrange(const RectF &finalRect) override;
|
|
void Render(DeviceResources &deviceResources) override;
|
|
void Attach(Window *window) 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;
|
|
bool KeepsPopupsOpenOnClick() const override;
|
|
|
|
private:
|
|
void OpenPopup();
|
|
void ClosePopup();
|
|
void TogglePopup();
|
|
void NotifySelectionChanged();
|
|
void SyncPopupState();
|
|
|
|
std::vector<std::wstring> items_;
|
|
float preferredHeight_ = 44.0f;
|
|
bool focused_ = false;
|
|
bool pressed_ = false;
|
|
bool open_ = false;
|
|
size_t selectedIndex_ = static_cast<size_t>(-1);
|
|
SelectionChangedHandler onSelectionChanged_;
|
|
std::shared_ptr<Visual> popupContent_;
|
|
std::shared_ptr<Popup> popup_;
|
|
};
|
|
|
|
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;
|
|
};
|
|
|
|
class ListView : public Visual
|
|
{
|
|
public:
|
|
struct Item
|
|
{
|
|
std::wstring title;
|
|
std::wstring subtitle;
|
|
std::wstring badge;
|
|
};
|
|
|
|
using SelectionChangedHandler = std::function<void(size_t selectedIndex)>;
|
|
|
|
explicit ListView(float itemHeight = 68.0f);
|
|
|
|
void AddItem(Item item);
|
|
void ClearItems();
|
|
void SetOnSelectionChanged(SelectionChangedHandler handler);
|
|
void SetSelectedIndex(size_t index);
|
|
size_t GetSelectedIndex() 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;
|
|
HCURSOR GetCursor() const override;
|
|
|
|
private:
|
|
struct ItemLayoutCache
|
|
{
|
|
float titleWidth = -1.0f;
|
|
float subtitleWidth = -1.0f;
|
|
float badgeWidth = -1.0f;
|
|
std::wstring fontFamily;
|
|
Microsoft::WRL::ComPtr<IDWriteTextLayout> titleLayout;
|
|
Microsoft::WRL::ComPtr<IDWriteTextLayout> subtitleLayout;
|
|
Microsoft::WRL::ComPtr<IDWriteTextLayout> badgeLayout;
|
|
};
|
|
|
|
void InvalidateLayoutCache();
|
|
size_t HitTestItem(const PointF &point) const;
|
|
|
|
std::vector<Item> items_;
|
|
std::vector<ItemLayoutCache> layoutCache_;
|
|
float itemHeight_ = 68.0f;
|
|
bool focused_ = false;
|
|
bool pressed_ = false;
|
|
size_t pressedIndex_ = static_cast<size_t>(-1);
|
|
size_t selectedIndex_ = 0;
|
|
SelectionChangedHandler onSelectionChanged_;
|
|
};
|
|
|
|
class TreeView : public Visual
|
|
{
|
|
public:
|
|
struct Node
|
|
{
|
|
std::wstring title;
|
|
std::wstring subtitle;
|
|
bool expanded = true;
|
|
std::vector<Node> children;
|
|
};
|
|
|
|
using SelectionChangedHandler = std::function<void(const std::wstring &selectedTitle)>;
|
|
|
|
explicit TreeView(float itemHeight = 62.0f);
|
|
|
|
void AddRoot(Node node);
|
|
void Clear();
|
|
void SetOnSelectionChanged(SelectionChangedHandler 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;
|
|
|
|
private:
|
|
struct VisibleNode
|
|
{
|
|
Node *node = nullptr;
|
|
size_t depth = 0;
|
|
RectF rowRect = {};
|
|
RectF expanderRect = {};
|
|
float titleWidth = -1.0f;
|
|
float subtitleWidth = -1.0f;
|
|
std::wstring fontFamily;
|
|
Microsoft::WRL::ComPtr<IDWriteTextLayout> titleLayout;
|
|
Microsoft::WRL::ComPtr<IDWriteTextLayout> subtitleLayout;
|
|
};
|
|
|
|
void BuildVisibleNodes();
|
|
void AppendVisibleNodes(Node &node, size_t depth);
|
|
void InvalidateLayoutCache();
|
|
VisibleNode *HitTestVisibleNode(const PointF &point);
|
|
const VisibleNode *HitTestVisibleNode(const PointF &point) const;
|
|
void SelectNode(Node *node);
|
|
|
|
std::vector<Node> roots_;
|
|
std::vector<VisibleNode> visibleNodes_;
|
|
float itemHeight_ = 62.0f;
|
|
bool focused_ = false;
|
|
bool pressed_ = false;
|
|
Node *pressedNode_ = nullptr;
|
|
bool pressedExpander_ = false;
|
|
Node *selectedNode_ = nullptr;
|
|
SelectionChangedHandler onSelectionChanged_;
|
|
};
|
|
|
|
class TabControl : public Visual
|
|
{
|
|
public:
|
|
struct Tab
|
|
{
|
|
std::wstring title;
|
|
std::shared_ptr<Visual> content;
|
|
};
|
|
|
|
using SelectionChangedHandler = std::function<void(size_t selectedIndex)>;
|
|
|
|
explicit TabControl(float headerHeight = 46.0f);
|
|
|
|
void AddTab(std::wstring title, std::shared_ptr<Visual> content);
|
|
void ClearTabs();
|
|
void SetSelectedIndex(size_t index);
|
|
size_t GetSelectedIndex() const;
|
|
void SetOnSelectionChanged(SelectionChangedHandler handler);
|
|
|
|
SizeF Measure(const SizeF &availableSize) override;
|
|
void Arrange(const RectF &finalRect) override;
|
|
void Render(DeviceResources &deviceResources) override;
|
|
void Attach(Window *window) override;
|
|
Visual *FindVisualAt(const PointF &point) override;
|
|
Visual *FindFocusableAt(const PointF &point) override;
|
|
Visual *FindFirstFocusableDescendant() override;
|
|
bool HitTest(const PointF &point) const override;
|
|
bool OnMouseDown(const POINT &point, WPARAM keyState) override;
|
|
bool OnMouseUp(const POINT &point, WPARAM keyState) override;
|
|
HCURSOR GetCursor() const override;
|
|
|
|
private:
|
|
size_t HitTestHeader(const PointF &point) const;
|
|
|
|
std::vector<Tab> tabs_;
|
|
std::vector<RectF> headerRects_;
|
|
float headerHeight_ = 46.0f;
|
|
size_t selectedIndex_ = 0;
|
|
bool pressed_ = false;
|
|
size_t pressedIndex_ = static_cast<size_t>(-1);
|
|
SelectionChangedHandler onSelectionChanged_;
|
|
};
|
|
|
|
class Accordion : public Visual
|
|
{
|
|
public:
|
|
struct Section
|
|
{
|
|
std::wstring title;
|
|
std::shared_ptr<Visual> content;
|
|
bool expanded = true;
|
|
};
|
|
|
|
explicit Accordion(float headerHeight = 48.0f);
|
|
|
|
void AddSection(std::wstring title, std::shared_ptr<Visual> content, bool expanded = true);
|
|
void ClearSections();
|
|
void SetAllowMultipleExpanded(bool allowMultipleExpanded);
|
|
|
|
SizeF Measure(const SizeF &availableSize) override;
|
|
void Arrange(const RectF &finalRect) override;
|
|
void Render(DeviceResources &deviceResources) override;
|
|
void Attach(Window *window) override;
|
|
Visual *FindVisualAt(const PointF &point) override;
|
|
Visual *FindFocusableAt(const PointF &point) override;
|
|
Visual *FindFirstFocusableDescendant() override;
|
|
bool HitTest(const PointF &point) const override;
|
|
bool OnMouseDown(const POINT &point, WPARAM keyState) override;
|
|
bool OnMouseUp(const POINT &point, WPARAM keyState) override;
|
|
HCURSOR GetCursor() const override;
|
|
|
|
private:
|
|
void CollapseOtherSections(size_t keepExpandedIndex);
|
|
size_t HitTestHeader(const PointF &point) const;
|
|
|
|
std::vector<Section> sections_;
|
|
std::vector<RectF> headerRects_;
|
|
std::vector<RectF> contentRects_;
|
|
float headerHeight_ = 48.0f;
|
|
bool allowMultipleExpanded_ = true;
|
|
bool pressed_ = false;
|
|
size_t pressedIndex_ = static_cast<size_t>(-1);
|
|
};
|
|
} // namespace msimeui
|