Files
msime-windows/ui/include/msimeui/Controls.h
T

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