diff options
-rw-r--r-- | CruUI/CruUI.vcxproj | 6 | ||||
-rw-r--r-- | CruUI/main.cpp | 20 | ||||
-rw-r--r-- | CruUI/ui/control.cpp | 47 | ||||
-rw-r--r-- | CruUI/ui/control.h | 2 | ||||
-rw-r--r-- | CruUI/ui/controls/linear_layout.cpp | 8 | ||||
-rw-r--r-- | CruUI/ui/controls/linear_layout.h | 6 | ||||
-rw-r--r-- | CruUI/ui/controls/text_block.cpp | 15 | ||||
-rw-r--r-- | CruUI/ui/controls/text_block.h | 11 | ||||
-rw-r--r-- | CruUI/ui/window.cpp | 12 | ||||
-rw-r--r-- | CruUI/ui/window.h | 404 |
10 files changed, 294 insertions, 237 deletions
diff --git a/CruUI/CruUI.vcxproj b/CruUI/CruUI.vcxproj index ba46d963..62b23165 100644 --- a/CruUI/CruUI.vcxproj +++ b/CruUI/CruUI.vcxproj @@ -134,14 +134,16 @@ </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <ClCompile> - <PrecompiledHeader>Use</PrecompiledHeader> + <PrecompiledHeader>NotUsing</PrecompiledHeader> <WarningLevel>Level3</WarningLevel> <Optimization>MaxSpeed</Optimization> <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> <SDLCheck>true</SDLCheck> - <PreprocessorDefinitions>NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>_SILENCE_CXX17_OLD_ALLOCATOR_MEMBERS_DEPRECATION_WARNING;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions> <ConformanceMode>true</ConformanceMode> + <AdditionalIncludeDirectories>$(ProjectDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <LanguageStandard>stdcpplatest</LanguageStandard> </ClCompile> <Link> <SubSystem>Windows</SubSystem> diff --git a/CruUI/main.cpp b/CruUI/main.cpp index bd0d3247..99f9ded6 100644 --- a/CruUI/main.cpp +++ b/CruUI/main.cpp @@ -66,19 +66,13 @@ int APIENTRY wWinMain( //test 2 - LinearLayout layout; - TextBlock text_block_1; - text_block_1.SetText(L"Hello World!!!"); - TextBlock text_block_2; - text_block_2.SetText(L"This is a very very very very very long sentence!!!"); - TextBlock text_block_3; - text_block_3.SetText(L"By crupest!!!"); - - layout.AddChild(&text_block_1); - layout.AddChild(&text_block_2); - layout.AddChild(&text_block_3); - - window.AddChild(&layout); + const auto layout = LinearLayout::Create(); + + layout->AddChild(TextBlock::Create(L"Hello World!!!")); + layout->AddChild(TextBlock::Create(L"This is a very very very very very long sentence!!!")); + layout->AddChild(TextBlock::Create(L"By crupest!!!")); + + window.AddChild(layout); window.Show(); diff --git a/CruUI/ui/control.cpp b/CruUI/ui/control.cpp index ee6db284..507beee8 100644 --- a/CruUI/ui/control.cpp +++ b/CruUI/ui/control.cpp @@ -1,6 +1,8 @@ #include "control.h" +#include <string> #include <algorithm> +#include <chrono> #include "window.h" #include "timer.h" @@ -25,19 +27,29 @@ namespace cru { } + Control::~Control() + { + ForeachChild([](auto control) + { + delete control; + }); + } + void Control::ForeachChild(Action<Control*>&& predicate) const { - for (const auto child : children_) - predicate(child); + if (is_container_) + for (const auto child : children_) + predicate(child); } void Control::ForeachChild(FlowControlAction<Control*>&& predicate) const { - for (const auto child : children_) - { - if (predicate(child) == FlowControl::Break) - break; - } + if (is_container_) + for (const auto child : children_) + { + if (predicate(child) == FlowControl::Break) + break; + } } void AddChildCheck(Control* control) @@ -128,7 +140,8 @@ namespace cru { void Control::TraverseDescendants(Action<Control*>&& predicate) { - TraverseDescendantsInternal(this, predicate); + if (is_container_) + TraverseDescendantsInternal(this, predicate); } Point Control::GetPositionRelative() @@ -138,13 +151,16 @@ namespace cru { void Control::SetPositionRelative(const Point & position) { - if (old_position_ == position) // if cache has been refreshed and no pending notify - old_position_ = position_; - position_ = position; - if (auto window = GetWindow()) + if (position != position_) { - window->GetLayoutManager()->InvalidateControlPositionCache(this); - window->Repaint(); + if (old_position_ == position) // if cache has been refreshed and no pending notify + old_position_ = position_; + position_ = position; + if (auto window = GetWindow()) + { + window->GetLayoutManager()->InvalidateControlPositionCache(this); + window->Repaint(); + } } } @@ -241,9 +257,12 @@ namespace cru { void Control::Layout(const Rect& rect) { + auto before = std::chrono::steady_clock::now(); SetPositionRelative(rect.GetLeftTop()); SetSize(rect.GetSize()); OnLayout(rect); + auto after = std::chrono::steady_clock::now(); + OutputDebugStringW((L"Layout time duration:" + std::to_wstring(std::chrono::duration_cast<std::chrono::milliseconds>(after - before).count()) + L"\n").c_str()); } Size Control::GetDesiredSize() const diff --git a/CruUI/ui/control.h b/CruUI/ui/control.h index f4302e80..41401008 100644 --- a/CruUI/ui/control.h +++ b/CruUI/ui/control.h @@ -36,7 +36,7 @@ namespace cru Control(Control&& other) = delete; Control& operator=(const Control& other) = delete; Control& operator=(Control&& other) = delete; - ~Control() override = default; + ~Control() override; public: diff --git a/CruUI/ui/controls/linear_layout.cpp b/CruUI/ui/controls/linear_layout.cpp index f639720d..1d88a4e1 100644 --- a/CruUI/ui/controls/linear_layout.cpp +++ b/CruUI/ui/controls/linear_layout.cpp @@ -44,9 +44,17 @@ namespace cru::ui::controls { control->Measure(rest_available_size_for_children); if (orientation_ == Orientation::Horizontal) + { rest_available_size_for_children.width -= control->GetDesiredSize().width; + if (rest_available_size_for_children.width < 0) + rest_available_size_for_children.width = 0; + } else + { rest_available_size_for_children.height -= control->GetDesiredSize().height; + if (rest_available_size_for_children.height < 0) + rest_available_size_for_children.height = 0; + } }); auto actual_size_for_children = total_available_size_for_children - rest_available_size_for_children; diff --git a/CruUI/ui/controls/linear_layout.h b/CruUI/ui/controls/linear_layout.h index b8722b6f..74c504cb 100644 --- a/CruUI/ui/controls/linear_layout.h +++ b/CruUI/ui/controls/linear_layout.h @@ -13,6 +13,12 @@ namespace cru::ui::controls Vertical }; + static LinearLayout* Create(const Orientation orientation = Orientation::Vertical) + { + return new LinearLayout(orientation); + } + + private: explicit LinearLayout(Orientation orientation = Orientation::Vertical); protected: diff --git a/CruUI/ui/controls/text_block.cpp b/CruUI/ui/controls/text_block.cpp index 3649aea6..d15bd681 100644 --- a/CruUI/ui/controls/text_block.cpp +++ b/CruUI/ui/controls/text_block.cpp @@ -1,5 +1,7 @@ #include "text_block.h" +#include <chrono> + #include "ui/window.h" #include "graph/graph.h" #include "exception.h" @@ -27,9 +29,12 @@ namespace cru void TextBlock::SetText(const String& text) { - const auto old_text = text_; - text_ = text; - OnTextChangedCore(old_text, text); + if (text_ != text) + { + const auto old_text = text_; + text_ = text; + OnTextChangedCore(old_text, text); + } } void TextBlock::SetBrush(const Microsoft::WRL::ComPtr<ID2D1Brush>& brush) @@ -47,8 +52,12 @@ namespace cru void TextBlock::OnSizeChangedCore(events::SizeChangedEventArgs& args) { + auto before = std::chrono::steady_clock::now(); RecreateTextLayout(); Repaint(); + auto after = std::chrono::steady_clock::now(); + OutputDebugStringW((L"TextBlock OnSizeChangedCore time duration:" + std::to_wstring(std::chrono::duration_cast<std::chrono::milliseconds>(after - before).count()) + L"\n").c_str()); + } void TextBlock::OnDraw(ID2D1DeviceContext* device_context) diff --git a/CruUI/ui/controls/text_block.h b/CruUI/ui/controls/text_block.h index abf77112..04347a0c 100644 --- a/CruUI/ui/controls/text_block.h +++ b/CruUI/ui/controls/text_block.h @@ -15,11 +15,22 @@ namespace cru public: using TextLayoutHandler = Action<Microsoft::WRL::ComPtr<IDWriteTextLayout>>; + static TextBlock* Create( + const String& text = L"", + const Microsoft::WRL::ComPtr<IDWriteTextFormat>& init_text_format = nullptr, + const Microsoft::WRL::ComPtr<ID2D1Brush>& init_brush = nullptr) + { + const auto text_block = new TextBlock(init_text_format, init_brush); + text_block->SetText(text); + return text_block; + } + private: explicit TextBlock( const Microsoft::WRL::ComPtr<IDWriteTextFormat>& init_text_format = nullptr, const Microsoft::WRL::ComPtr<ID2D1Brush>& init_brush = nullptr ); + public: TextBlock(const TextBlock& other) = delete; TextBlock(TextBlock&& other) = delete; TextBlock& operator=(const TextBlock& other) = delete; diff --git a/CruUI/ui/window.cpp b/CruUI/ui/window.cpp index 2c731a1e..392bca87 100644 --- a/CruUI/ui/window.cpp +++ b/CruUI/ui/window.cpp @@ -97,9 +97,9 @@ namespace cru RefreshInvalidControlPositionCache(); // first refresh position cache. for (const auto i : cache_invalid_controls_) // traverse all descendants of position-invalid controls and notify position change event i->TraverseDescendants([](Control* control) - { - control->CheckAndNotifyPositionChanged(); - }); + { + control->CheckAndNotifyPositionChanged(); + }); cache_invalid_controls_.clear(); // after update and notify, clear the set. }); } @@ -401,6 +401,12 @@ namespace cru return rect; } + bool Window::IsMessageInQueue(UINT message) + { + MSG msg; + return ::PeekMessageW(&msg, hwnd_, message, message, PM_NOREMOVE) != 0; + } + void Window::OnDestroyInternal() { Application::GetInstance()->GetWindowManager()->UnregisterWindow(hwnd_); hwnd_ = nullptr; diff --git a/CruUI/ui/window.h b/CruUI/ui/window.h index 7ecea456..9ffa4dc6 100644 --- a/CruUI/ui/window.h +++ b/CruUI/ui/window.h @@ -9,265 +9,267 @@ #include "control.h" namespace cru { - namespace graph { - class WindowRenderTarget; - } - - namespace ui { - class WindowClass : public Object - { - public: - WindowClass(const String& name, WNDPROC window_proc, HINSTANCE h_instance); - WindowClass(const WindowClass& other) = delete; - WindowClass(WindowClass&& other) = delete; - WindowClass& operator=(const WindowClass& other) = delete; - WindowClass& operator=(WindowClass&& other) = delete; - ~WindowClass() override = default; - - - const wchar_t* GetName() const - { - return name_.c_str(); - } - - ATOM GetAtom() const - { - return atom_; - } - - private: - String name_; - ATOM atom_; - }; - - class WindowManager : public Object - { - public: - WindowManager(); - WindowManager(const WindowManager& other) = delete; - WindowManager(WindowManager&& other) = delete; - WindowManager& operator=(const WindowManager& other) = delete; - WindowManager& operator=(WindowManager&& other) = delete; - ~WindowManager() override = default; - - - //Get the general window class for creating ordinary window. - WindowClass* GetGeneralWindowClass() const - { + namespace graph { + class WindowRenderTarget; + } + + namespace ui { + class WindowClass : public Object + { + public: + WindowClass(const String& name, WNDPROC window_proc, HINSTANCE h_instance); + WindowClass(const WindowClass& other) = delete; + WindowClass(WindowClass&& other) = delete; + WindowClass& operator=(const WindowClass& other) = delete; + WindowClass& operator=(WindowClass&& other) = delete; + ~WindowClass() override = default; + + + const wchar_t* GetName() const + { + return name_.c_str(); + } + + ATOM GetAtom() const + { + return atom_; + } + + private: + String name_; + ATOM atom_; + }; + + class WindowManager : public Object + { + public: + WindowManager(); + WindowManager(const WindowManager& other) = delete; + WindowManager(WindowManager&& other) = delete; + WindowManager& operator=(const WindowManager& other) = delete; + WindowManager& operator=(WindowManager&& other) = delete; + ~WindowManager() override = default; + + + //Get the general window class for creating ordinary window. + WindowClass* GetGeneralWindowClass() const + { return general_window_class_.get(); - } + } - //Register a window newly created. - //This function adds the hwnd to hwnd-window map. - //It should be called immediately after a window was created. - void RegisterWindow(HWND hwnd, Window* window); + //Register a window newly created. + //This function adds the hwnd to hwnd-window map. + //It should be called immediately after a window was created. + void RegisterWindow(HWND hwnd, Window* window); - //Unregister a window that is going to be destroyed. - //This function removes the hwnd from the hwnd-window map. - //It should be called immediately before a window is going to be destroyed, - void UnregisterWindow(HWND hwnd); + //Unregister a window that is going to be destroyed. + //This function removes the hwnd from the hwnd-window map. + //It should be called immediately before a window is going to be destroyed, + void UnregisterWindow(HWND hwnd); - //Return a pointer to the Window object related to the HWND or nullptr if the hwnd is not in the map. - Window* FromHandle(HWND hwnd); + //Return a pointer to the Window object related to the HWND or nullptr if the hwnd is not in the map. + Window* FromHandle(HWND hwnd); - private: - std::unique_ptr<WindowClass> general_window_class_; - std::map<HWND, Window*> window_map_; - }; + private: + std::unique_ptr<WindowClass> general_window_class_; + std::map<HWND, Window*> window_map_; + }; - class WindowLayoutManager : public Object - { - public: - WindowLayoutManager() = default; - WindowLayoutManager(const WindowLayoutManager& other) = delete; - WindowLayoutManager(WindowLayoutManager&& other) = delete; - WindowLayoutManager& operator=(const WindowLayoutManager& other) = delete; - WindowLayoutManager& operator=(WindowLayoutManager&& other) = delete; - ~WindowLayoutManager() override = default; + class WindowLayoutManager : public Object + { + public: + WindowLayoutManager() = default; + WindowLayoutManager(const WindowLayoutManager& other) = delete; + WindowLayoutManager(WindowLayoutManager&& other) = delete; + WindowLayoutManager& operator=(const WindowLayoutManager& other) = delete; + WindowLayoutManager& operator=(WindowLayoutManager&& other) = delete; + ~WindowLayoutManager() override = default; - //Mark position cache of the control and its descendants invalid, - //(which is saved as an auto-managed list internal) - //and send a message to refresh them. - void InvalidateControlPositionCache(Control* control); + //Mark position cache of the control and its descendants invalid, + //(which is saved as an auto-managed list internal) + //and send a message to refresh them. + void InvalidateControlPositionCache(Control* control); - //Refresh position cache of the control and its descendants whose cache - //has been marked as invalid. - void RefreshInvalidControlPositionCache(); + //Refresh position cache of the control and its descendants whose cache + //has been marked as invalid. + void RefreshInvalidControlPositionCache(); - //Refresh position cache of the control and its descendants immediately. - static void RefreshControlPositionCache(Control* control); + //Refresh position cache of the control and its descendants immediately. + static void RefreshControlPositionCache(Control* control); - private: - static void RefreshControlPositionCacheInternal(Control* control, const Point& parent_lefttop_absolute); + private: + static void RefreshControlPositionCacheInternal(Control* control, const Point& parent_lefttop_absolute); - private: - std::set<Control*> cache_invalid_controls_; - }; + private: + std::set<Control*> cache_invalid_controls_; + }; - class Window : public Control - { - friend class WindowManager; - public: - Window(); - Window(const Window& other) = delete; - Window(Window&& other) = delete; - Window& operator=(const Window& other) = delete; - Window& operator=(Window&& other) = delete; - ~Window() override; + class Window : public Control + { + friend class WindowManager; + public: + Window(); + Window(const Window& other) = delete; + Window(Window&& other) = delete; + Window& operator=(const Window& other) = delete; + Window& operator=(Window&& other) = delete; + ~Window() override; - public: - //*************** region: managers *************** - WindowLayoutManager* GetLayoutManager() const - { - return layout_manager_.get(); - } + public: + //*************** region: managers *************** + WindowLayoutManager* GetLayoutManager() const + { + return layout_manager_.get(); + } - //*************** region: handle *************** + //*************** region: handle *************** - //Get the handle of the window. Return null if window is invalid. - HWND GetWindowHandle() const - { - return hwnd_; - } + //Get the handle of the window. Return null if window is invalid. + HWND GetWindowHandle() const + { + return hwnd_; + } - //Return if the window is still valid, that is, hasn't been closed or destroyed. - bool IsWindowValid() const - { - return hwnd_ != nullptr; - } + //Return if the window is still valid, that is, hasn't been closed or destroyed. + bool IsWindowValid() const + { + return hwnd_ != nullptr; + } - //*************** region: window operations *************** + //*************** region: window operations *************** - //Close and destroy the window if the window is valid. - void Close(); + //Close and destroy the window if the window is valid. + void Close(); - //Send a repaint message to the window's message queue which may make the window repaint. - void Repaint() override; + //Send a repaint message to the window's message queue which may make the window repaint. + void Repaint() override; - //Show the window. - void Show(); + //Show the window. + void Show(); - //Hide thw window. - void Hide(); + //Hide thw window. + void Hide(); - //Get the client size. - Size GetClientSize(); + //Get the client size. + Size GetClientSize(); - //Set the client size and repaint. - void SetClientSize(const Size& size); + //Set the client size and repaint. + void SetClientSize(const Size& size); - //Get the rect of the window containing frame. - //The lefttop of the rect is relative to screen lefttop. - Rect GetWindowRect(); + //Get the rect of the window containing frame. + //The lefttop of the rect is relative to screen lefttop. + Rect GetWindowRect(); - //Set the rect of the window containing frame. - //The lefttop of the rect is relative to screen lefttop. - void SetWindowRect(const Rect& rect); + //Set the rect of the window containing frame. + //The lefttop of the rect is relative to screen lefttop. + void SetWindowRect(const Rect& rect); - //Handle the raw window message. - //Return true if the message is handled and get the result through "result" argument. - //Return false if the message is not handled. - bool HandleWindowMessage(HWND hwnd, int msg, WPARAM w_param, LPARAM l_param, LRESULT& result); + //Handle the raw window message. + //Return true if the message is handled and get the result through "result" argument. + //Return false if the message is not handled. + bool HandleWindowMessage(HWND hwnd, int msg, WPARAM w_param, LPARAM l_param, LRESULT& result); - //*************** region: position and size *************** + //*************** region: position and size *************** - //Always return (0, 0) for a window. - Point GetPositionRelative() override final; + //Always return (0, 0) for a window. + Point GetPositionRelative() override final; - //This method has no effect for a window. - void SetPositionRelative(const Point& position) override final; + //This method has no effect for a window. + void SetPositionRelative(const Point& position) override final; - //Get the size of client area for a window. - Size GetSize() override final; + //Get the size of client area for a window. + Size GetSize() override final; - //This method has no effect for a window. Use SetClientSize instead. - void SetSize(const Size& size) override final; + //This method has no effect for a window. Use SetClientSize instead. + void SetSize(const Size& size) override final; - //*************** region: features *************** + //*************** region: features *************** - //Refresh control list. - //It should be invoked every time a control is added or removed from the tree. - void RefreshControlList(); + //Refresh control list. + //It should be invoked every time a control is added or removed from the tree. + void RefreshControlList(); - //Get the most top control at "point". - Control* HitTest(const Point& point); + //Get the most top control at "point". + Control* HitTest(const Point& point); - - //*************** region: focus *************** - //Request focus for specified control. - bool RequestFocusFor(Control* control); + //*************** region: focus *************** - //Get the control that has focus. - Control* GetFocusControl(); + //Request focus for specified control. + bool RequestFocusFor(Control* control); + //Get the control that has focus. + Control* GetFocusControl(); - private: - //*************** region: native operations *************** - //Get the client rect in pixel. - RECT GetClientRectPixel(); + private: + //*************** region: native operations *************** + //Get the client rect in pixel. + RECT GetClientRectPixel(); - //*************** region: native messages *************** + bool IsMessageInQueue(UINT message); - void OnDestroyInternal(); - void OnPaintInternal(); - void OnResizeInternal(int new_width, int new_height); - void OnSetFocusInternal(); - void OnKillFocusInternal(); + //*************** region: native messages *************** - void OnMouseMoveInternal(POINT point); - void OnMouseLeaveInternal(); - void OnMouseDownInternal(MouseButton button, POINT point); - void OnMouseUpInternal(MouseButton button, POINT point); + void OnDestroyInternal(); + void OnPaintInternal(); + void OnResizeInternal(int new_width, int new_height); + void OnSetFocusInternal(); + void OnKillFocusInternal(); + void OnMouseMoveInternal(POINT point); + void OnMouseLeaveInternal(); + void OnMouseDownInternal(MouseButton button, POINT point); + void OnMouseUpInternal(MouseButton button, POINT point); - //*************** region: event dispatcher helper *************** - template<typename EventArgs> - using EventMethod = void (Control::*)(EventArgs&); - // Dispatch the event. - // - // This will invoke the "event_method" of the control and its parent and parent's - // parent ... (until "last_receiver" if it's not nullptr) with appropriate args. - // - // Args is of type "EventArgs". The first init argument is "sender", which is - // automatically bound to each receiving control. The second init argument is - // "original_sender", which is unchanged. And "args" will be perfectly forwarded - // as the rest arguments. - template<typename EventArgs, typename... Args> - void DispatchEvent(Control* original_sender, EventMethod<EventArgs> event_method, Control* last_receiver, Args&&... args) - { - auto control = original_sender; - while (control != nullptr && control != last_receiver) - { - EventArgs event_args(control, original_sender, std::forward<Args>(args)...); - (control->*event_method)(event_args); - control = control->GetParent(); - } - } + //*************** region: event dispatcher helper *************** - private: - std::unique_ptr<WindowLayoutManager> layout_manager_; + template<typename EventArgs> + using EventMethod = void (Control::*)(EventArgs&); - HWND hwnd_ = nullptr; - std::shared_ptr<graph::WindowRenderTarget> render_target_{}; + // Dispatch the event. + // + // This will invoke the "event_method" of the control and its parent and parent's + // parent ... (until "last_receiver" if it's not nullptr) with appropriate args. + // + // Args is of type "EventArgs". The first init argument is "sender", which is + // automatically bound to each receiving control. The second init argument is + // "original_sender", which is unchanged. And "args" will be perfectly forwarded + // as the rest arguments. + template<typename EventArgs, typename... Args> + void DispatchEvent(Control* original_sender, EventMethod<EventArgs> event_method, Control* last_receiver, Args&&... args) + { + auto control = original_sender; + while (control != nullptr && control != last_receiver) + { + EventArgs event_args(control, original_sender, std::forward<Args>(args)...); + (control->*event_method)(event_args); + control = control->GetParent(); + } + } - std::list<Control*> control_list_{}; + private: + std::unique_ptr<WindowLayoutManager> layout_manager_; - Control* mouse_hover_control_ = nullptr; + HWND hwnd_ = nullptr; + std::shared_ptr<graph::WindowRenderTarget> render_target_{}; - bool window_focus_ = false; - Control* focus_control_ = this; // "focus_control_" can't be nullptr - }; - } + std::list<Control*> control_list_{}; + + Control* mouse_hover_control_ = nullptr; + + bool window_focus_ = false; + Control* focus_control_ = this; // "focus_control_" can't be nullptr + }; + } } |