diff options
-rw-r--r-- | CruUI.vcxproj | 1 | ||||
-rw-r--r-- | CruUI.vcxproj.filters | 3 | ||||
-rw-r--r-- | src/main.cpp | 29 | ||||
-rw-r--r-- | src/ui/control.cpp | 7 | ||||
-rw-r--r-- | src/ui/controls/list_item.cpp | 58 | ||||
-rw-r--r-- | src/ui/controls/list_item.hpp | 46 | ||||
-rw-r--r-- | src/ui/convert_util.hpp | 13 | ||||
-rw-r--r-- | src/ui/layout_base.hpp | 39 | ||||
-rw-r--r-- | src/ui/ui_base.cpp | 5 | ||||
-rw-r--r-- | src/ui/ui_base.hpp | 45 | ||||
-rw-r--r-- | src/ui/ui_manager.cpp | 37 | ||||
-rw-r--r-- | src/ui/ui_manager.hpp | 9 |
12 files changed, 228 insertions, 64 deletions
diff --git a/CruUI.vcxproj b/CruUI.vcxproj index 66f068fc..0c0ba1b6 100644 --- a/CruUI.vcxproj +++ b/CruUI.vcxproj @@ -164,6 +164,7 @@ <ClInclude Include="src\ui\controls\text_box.hpp" /> <ClCompile Include="src\ui\controls\text_control.cpp" /> <ClInclude Include="src\ui\controls\toggle_button.hpp" /> + <ClInclude Include="src\ui\convert_util.hpp" /> <ClInclude Include="src\ui\cursor.hpp" /> <ClInclude Include="src\ui\events\ui_event.hpp" /> <ClInclude Include="src\ui\layout_base.hpp" /> diff --git a/CruUI.vcxproj.filters b/CruUI.vcxproj.filters index d6630408..7a84270a 100644 --- a/CruUI.vcxproj.filters +++ b/CruUI.vcxproj.filters @@ -167,6 +167,9 @@ <ClInclude Include="src\ui\controls\list_item.hpp"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="src\ui\convert_util.hpp"> + <Filter>Header Files</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="src\application.cpp"> diff --git a/src/main.cpp b/src/main.cpp index f9bc975c..269da573 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,10 +5,12 @@ #include "ui/controls/toggle_button.hpp" #include "ui/controls/button.hpp" #include "ui/controls/text_box.hpp" +#include "ui/controls/list_item.hpp" -using cru::ui::Rect; using cru::String; +using cru::StringView; using cru::Application; +using cru::ui::Rect; using cru::ui::Window; using cru::ui::Alignment; using cru::ui::LayoutSideParams; @@ -20,6 +22,7 @@ using cru::ui::controls::TextBlock; using cru::ui::controls::ToggleButton; using cru::ui::controls::Button; using cru::ui::controls::TextBox; +using cru::ui::controls::ListItem; int APIENTRY wWinMain( HINSTANCE hInstance, @@ -117,7 +120,29 @@ int APIENTRY wWinMain( button->AddChild(TextBlock::Create(L"Show popup window parenting this.")); button->mouse_click_event.AddHandler([window](auto) { - Window::CreatePopup(window)->Show(); + const auto popup = Window::CreatePopup(window); + + auto create_menu_item = [](const String& text) -> ListItem* + { + return CreateWithLayout<ListItem>( + LayoutSideParams::Content(Alignment::Start), + LayoutSideParams::Content(Alignment::Start), + ControlList{ TextBlock::Create(text) } + ); + }; + + const auto menu = LinearLayout::Create(LinearLayout::Orientation::Vertical, ControlList{ + create_menu_item(L"copy"), + create_menu_item(L"cut"), + create_menu_item(L"paste") + }); + + popup->AddChild(menu); + + popup->Relayout(); + popup->SetClientSize(menu->GetSize()); + + popup->Show(); }); layout->AddChild(button); } diff --git a/src/ui/control.cpp b/src/ui/control.cpp index 5d15e287..ed904de1 100644 --- a/src/ui/control.cpp +++ b/src/ui/control.cpp @@ -7,6 +7,7 @@ #include "graph/graph.hpp" #include "exception.hpp" #include "cru_debug.hpp" +#include "convert_util.hpp" #ifdef CRU_DEBUG_LAYOUT #include "ui_manager.hpp" @@ -376,12 +377,6 @@ namespace cru::ui window_ = nullptr; } - - inline D2D1_RECT_F Convert(const Rect& rect) - { - return D2D1::RectF(rect.left, rect.top, rect.left + rect.width, rect.top + rect.height); - } - void Control::OnDrawCore(ID2D1DeviceContext* device_context) { #ifdef CRU_DEBUG_LAYOUT diff --git a/src/ui/controls/list_item.cpp b/src/ui/controls/list_item.cpp index 25dd49a8..bdd44273 100644 --- a/src/ui/controls/list_item.cpp +++ b/src/ui/controls/list_item.cpp @@ -1,6 +1,62 @@ #include "list_item.hpp" +#include "ui/ui_manager.hpp" +#include "ui/convert_util.hpp" + namespace cru::ui::controls { - + ListItem::ListItem() : Control(true) + { + const auto predefine_resources = UiManager::GetInstance()->GetPredefineResources(); + + brushes_[State::Normal].border_brush = predefine_resources->list_item_normal_border_brush; + brushes_[State::Normal].fill_brush = predefine_resources->list_item_normal_fill_brush; + brushes_[State::Hover] .border_brush = predefine_resources->list_item_hover_border_brush; + brushes_[State::Hover] .fill_brush = predefine_resources->list_item_hover_fill_brush; + brushes_[State::Select].border_brush = predefine_resources->list_item_select_border_brush; + brushes_[State::Select].fill_brush = predefine_resources->list_item_select_fill_brush; + } + + StringView ListItem::GetControlType() const + { + return control_type; + } + + void ListItem::SetState(const State state) + { + state_ = state; + Repaint(); + } + + void ListItem::OnDrawForeground(ID2D1DeviceContext* device_context) + { + const auto rect = Rect(Point::Zero(), GetRect(RectRange::Padding).GetSize()); + device_context->FillRectangle(Convert(rect), brushes_[state_].fill_brush.Get()); + device_context->DrawRectangle(Convert(rect.Shrink(Thickness(0.5))), brushes_[state_].border_brush.Get(), 1); + } + + void ListItem::OnMouseEnterCore(events::MouseEventArgs& args) + { + if (GetState() == State::Select) + return; + + if (IsAnyMouseButtonDown()) + return; + + SetState(State::Hover); + } + + void ListItem::OnMouseLeaveCore(events::MouseEventArgs& args) + { + if (GetState() == State::Select) + return; + + SetState(State::Normal); + } + + void ListItem::OnMouseClickCore(events::MouseButtonEventArgs& args) + { + if (args.GetMouseButton() == MouseButton::Left) + SetState(State::Select); + } } diff --git a/src/ui/controls/list_item.hpp b/src/ui/controls/list_item.hpp index 8525e0e8..1de89b5f 100644 --- a/src/ui/controls/list_item.hpp +++ b/src/ui/controls/list_item.hpp @@ -1,5 +1,8 @@ #pragma once +#include <map> +#include <initializer_list> + #include "ui/control.hpp" namespace cru::ui::controls @@ -9,8 +12,29 @@ namespace cru::ui::controls public: static constexpr auto control_type = L"ListItem"; + enum class State + { + Normal, + Hover, + Select + }; + + private: + struct StateBrush + { + Microsoft::WRL::ComPtr<ID2D1Brush> border_brush; + Microsoft::WRL::ComPtr<ID2D1Brush> fill_brush; + }; + public: - static ListItem* Create(); + static ListItem* Create(const std::initializer_list<Control*>& children) + { + const auto list_item = new ListItem(); + for (auto control : children) + list_item->AddChild(control); + return list_item; + } + private: ListItem(); public: @@ -19,5 +43,25 @@ namespace cru::ui::controls ListItem& operator=(const ListItem& other) = delete; ListItem& operator=(ListItem&& other) = delete; ~ListItem() override = default; + + StringView GetControlType() const override; + + State GetState() const + { + return state_; + } + + void SetState(State state); + + protected: + void OnDrawForeground(ID2D1DeviceContext* device_context) override; + + void OnMouseEnterCore(events::MouseEventArgs& args) override final; + void OnMouseLeaveCore(events::MouseEventArgs& args) override final; + void OnMouseClickCore(events::MouseButtonEventArgs& args) override final; + + private: + State state_ = State::Normal; + std::map<State, StateBrush> brushes_{}; }; } diff --git a/src/ui/convert_util.hpp b/src/ui/convert_util.hpp new file mode 100644 index 00000000..1c18f59b --- /dev/null +++ b/src/ui/convert_util.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "system_headers.hpp" + +#include "ui_base.hpp" + +namespace cru::ui +{ + inline D2D1_RECT_F Convert(const Rect& rect) + { + return D2D1::RectF(rect.left, rect.top, rect.left + rect.width, rect.top + rect.height); + } +} diff --git a/src/ui/layout_base.hpp b/src/ui/layout_base.hpp index 512301b7..d8e4e2d1 100644 --- a/src/ui/layout_base.hpp +++ b/src/ui/layout_base.hpp @@ -33,45 +33,6 @@ namespace cru::ui Margin // including content, padding, border and margin }; - struct Thickness - { - constexpr static Thickness Zero() - { - return Thickness(0); - } - - constexpr Thickness() : Thickness(0) { } - - constexpr explicit Thickness(const float width) - : left(width), top(width), right(width), bottom(width) { } - - constexpr explicit Thickness(const float horizontal, const float vertical) - : left(horizontal), top(vertical), right(horizontal), bottom(vertical) { } - - constexpr Thickness(const float left, const float top, const float right, const float bottom) - : left(left), top(top), right(right), bottom(bottom) { } - - float GetHorizontalTotal() const - { - return left + right; - } - - float GetVerticalTotal() const - { - return top + bottom; - } - - float Validate() const - { - return left >= 0.0 && top >= 0.0 && right >= 0.0 && bottom >= 0.0; - } - - float left; - float top; - float right; - float bottom; - }; - struct LayoutSideParams final { constexpr static LayoutSideParams Exactly(const float length, const Alignment alignment = Alignment::Center) diff --git a/src/ui/ui_base.cpp b/src/ui/ui_base.cpp index b52694e7..c91fcd7b 100644 --- a/src/ui/ui_base.cpp +++ b/src/ui/ui_base.cpp @@ -15,4 +15,9 @@ namespace cru::ui const auto result = ::GetKeyState(virtual_code); return (static_cast<unsigned short>(result) & 1) != 0; } + + bool IsAnyMouseButtonDown() + { + return IsKeyDown(VK_LBUTTON) || IsKeyDown(VK_RBUTTON) || IsKeyDown(VK_MBUTTON); + } } diff --git a/src/ui/ui_base.hpp b/src/ui/ui_base.hpp index 8daa43d7..c20d44b6 100644 --- a/src/ui/ui_base.hpp +++ b/src/ui/ui_base.hpp @@ -63,6 +63,45 @@ namespace cru::ui return !(left == right); } + struct Thickness + { + constexpr static Thickness Zero() + { + return Thickness(0); + } + + constexpr Thickness() : Thickness(0) { } + + constexpr explicit Thickness(const float width) + : left(width), top(width), right(width), bottom(width) { } + + constexpr explicit Thickness(const float horizontal, const float vertical) + : left(horizontal), top(vertical), right(horizontal), bottom(vertical) { } + + constexpr Thickness(const float left, const float top, const float right, const float bottom) + : left(left), top(top), right(right), bottom(bottom) { } + + float GetHorizontalTotal() const + { + return left + right; + } + + float GetVerticalTotal() const + { + return top + bottom; + } + + float Validate() const + { + return left >= 0.0 && top >= 0.0 && right >= 0.0 && bottom >= 0.0; + } + + float left; + float top; + float right; + float bottom; + }; + struct Rect { constexpr Rect() = default; @@ -101,6 +140,11 @@ namespace cru::ui return Size(width, height); } + constexpr Rect Shrink(const Thickness& thickness) const + { + return Rect(left + thickness.left, top + thickness.top, width - thickness.GetHorizontalTotal(), height - thickness.GetVerticalTotal()); + } + constexpr bool IsPointInside(const Point& point) const { return @@ -154,4 +198,5 @@ namespace cru::ui bool IsKeyDown(int virtual_code); bool IsKeyToggled(int virtual_code); + bool IsAnyMouseButtonDown(); } diff --git a/src/ui/ui_manager.cpp b/src/ui/ui_manager.cpp index 3918b2d5..d803e0cb 100644 --- a/src/ui/ui_manager.cpp +++ b/src/ui/ui_manager.cpp @@ -52,29 +52,36 @@ namespace cru::ui //!!! never use default constructor of border at here, because it will recursively call this method! PredefineResources::PredefineResources(graph::GraphManager* graph_manager) : - border_property_brush{CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::Black))}, + border_property_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::Black))}, - button_normal_border{CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::RoyalBlue)), 2, 6, 6}, - button_press_border{CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::Blue)), 2, 6, 6}, + button_normal_border {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::RoyalBlue)), 2, 6, 6}, + button_press_border {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::Blue)), 2, 6, 6}, - text_control_selection_brush{CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::LightSkyBlue))}, + text_control_selection_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::LightSkyBlue))}, - text_box_border{CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::Black))}, - text_box_text_brush{CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::Black))}, - text_box_text_format{CreateDefaultTextFormat(graph_manager)}, - text_box_caret_brush{CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::Black))}, + text_box_border {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::Black))}, + text_box_text_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::Black))}, + text_box_text_format {CreateDefaultTextFormat(graph_manager)}, + text_box_caret_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::Black))}, - text_block_text_brush{CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::Black))}, - text_block_text_format{CreateDefaultTextFormat(graph_manager)}, + text_block_text_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::Black))}, + text_block_text_format {CreateDefaultTextFormat(graph_manager)}, - toggle_button_on_brush{CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::DeepSkyBlue))}, - toggle_button_off_brush{CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::LightGray))} + toggle_button_on_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::DeepSkyBlue))}, + toggle_button_off_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::LightGray))}, + + list_item_normal_border_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::White, 0))}, + list_item_normal_fill_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::White, 0))}, + list_item_hover_border_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::SkyBlue))}, + list_item_hover_fill_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::SkyBlue, 0.3))}, + list_item_select_border_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::MediumBlue))}, + list_item_select_fill_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::SkyBlue, 0.3))} #ifdef CRU_DEBUG_LAYOUT , - debug_layout_out_border_brush{CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::Crimson))}, - debug_layout_margin_brush{CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::LightCoral, 0.25f))}, - debug_layout_padding_brush{CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::SkyBlue, 0.25f))} + debug_layout_out_border_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::Crimson))}, + debug_layout_margin_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::LightCoral, 0.25f))}, + debug_layout_padding_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::SkyBlue, 0.25f))} #endif { diff --git a/src/ui/ui_manager.hpp b/src/ui/ui_manager.hpp index 753da907..6b368e12 100644 --- a/src/ui/ui_manager.hpp +++ b/src/ui/ui_manager.hpp @@ -53,6 +53,15 @@ namespace cru::ui Microsoft::WRL::ComPtr<ID2D1Brush> toggle_button_on_brush; Microsoft::WRL::ComPtr<ID2D1Brush> toggle_button_off_brush; + //region ListItem + Microsoft::WRL::ComPtr<ID2D1Brush> list_item_normal_border_brush; + Microsoft::WRL::ComPtr<ID2D1Brush> list_item_normal_fill_brush; + Microsoft::WRL::ComPtr<ID2D1Brush> list_item_hover_border_brush; + Microsoft::WRL::ComPtr<ID2D1Brush> list_item_hover_fill_brush; + Microsoft::WRL::ComPtr<ID2D1Brush> list_item_select_border_brush; + Microsoft::WRL::ComPtr<ID2D1Brush> list_item_select_fill_brush; + + #ifdef CRU_DEBUG_LAYOUT //region debug Microsoft::WRL::ComPtr<ID2D1Brush> debug_layout_out_border_brush; |