diff options
-rw-r--r-- | include/cru/platform/gui/UiApplication.hpp | 4 | ||||
-rw-r--r-- | include/cru/ui/host/LayoutPaintCycler.hpp | 39 | ||||
-rw-r--r-- | include/cru/ui/host/WindowHost.hpp | 8 | ||||
-rw-r--r-- | src/ui/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/ui/host/LayoutPaintCycler.cpp | 35 | ||||
-rw-r--r-- | src/ui/host/WindowHost.cpp | 33 |
6 files changed, 105 insertions, 16 deletions
diff --git a/include/cru/platform/gui/UiApplication.hpp b/include/cru/platform/gui/UiApplication.hpp index 6a2eb067..ff947dfc 100644 --- a/include/cru/platform/gui/UiApplication.hpp +++ b/include/cru/platform/gui/UiApplication.hpp @@ -67,6 +67,10 @@ class TimerAutoCanceler { return *this; } + TimerAutoCanceler& operator=(long long other) { + return this->operator=(TimerAutoCanceler(other)); + } + ~TimerAutoCanceler() { Reset(); } long long Release() { diff --git a/include/cru/ui/host/LayoutPaintCycler.hpp b/include/cru/ui/host/LayoutPaintCycler.hpp new file mode 100644 index 00000000..ed543afa --- /dev/null +++ b/include/cru/ui/host/LayoutPaintCycler.hpp @@ -0,0 +1,39 @@ +#pragma once +#include "../Base.hpp" + +#include "cru/platform/gui/UiApplication.hpp" + +#include <chrono> + +namespace cru::ui::host { +class LayoutPaintCycler { + public: + explicit LayoutPaintCycler(WindowHost* host); + + CRU_DELETE_COPY(LayoutPaintCycler) + CRU_DELETE_MOVE(LayoutPaintCycler) + + ~LayoutPaintCycler(); + + public: + void InvalidateLayout(); + void InvalidatePaint(); + + bool IsLayoutDirty() { return layout_dirty_; } + + private: + void OnCycle(); + + private: + WindowHost* host_; + + platform::gui::TimerAutoCanceler timer_canceler_; + + bool layout_dirty_ = true; + bool paint_dirty_ = true; + + std::chrono::steady_clock::time_point last_cycle_time_; + std::chrono::steady_clock::duration cycle_threshold_ = + std::chrono::milliseconds(1000) / 144; +}; +} // namespace cru::ui::host diff --git a/include/cru/ui/host/WindowHost.hpp b/include/cru/ui/host/WindowHost.hpp index 77ed937f..81eabb52 100644 --- a/include/cru/ui/host/WindowHost.hpp +++ b/include/cru/ui/host/WindowHost.hpp @@ -7,8 +7,11 @@ #include "../render/Base.hpp" #include <functional> +#include <memory> namespace cru::ui::host { +class LayoutPaintCycler; + struct AfterLayoutEventArgs {}; // The bridge between control tree and native window. @@ -43,6 +46,8 @@ class WindowHost : public Object { void Relayout(); void Relayout(const Size& available_size); + void Repaint(); + // Is layout is invalid, wait for relayout and then run the action. Otherwist // run it right now. void RunAfterLayoutStable(std::function<void()> action); @@ -125,7 +130,8 @@ class WindowHost : public Object { platform::gui::INativeWindow* native_window_ = nullptr; - bool need_layout_ = false; + std::unique_ptr<LayoutPaintCycler> layout_paint_cycler_; + Event<AfterLayoutEventArgs> after_layout_event_; std::vector<std::function<void()> > after_layout_stable_action_; diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index d7b924cb..449fb4d9 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -21,6 +21,7 @@ add_library(cru_ui STATIC controls/TextBlock.cpp controls/TextBox.cpp controls/TextControlService.hpp + host/LayoutPaintCycler.cpp host/WindowHost.cpp render/BorderRenderObject.cpp render/CanvasRenderObject.cpp @@ -50,6 +51,7 @@ target_sources(cru_ui PUBLIC ${CRU_UI_INCLUDE_DIR}/controls/StackLayout.hpp ${CRU_UI_INCLUDE_DIR}/controls/TextBox.hpp ${CRU_UI_INCLUDE_DIR}/controls/TextBlock.hpp + ${CRU_UI_INCLUDE_DIR}/host/LayoutPaintCycler.hpp ${CRU_UI_INCLUDE_DIR}/host/WindowHost.hpp ${CRU_UI_INCLUDE_DIR}/render/Base.hpp ${CRU_UI_INCLUDE_DIR}/render/BorderRenderObject.hpp diff --git a/src/ui/host/LayoutPaintCycler.cpp b/src/ui/host/LayoutPaintCycler.cpp new file mode 100644 index 00000000..9b319da4 --- /dev/null +++ b/src/ui/host/LayoutPaintCycler.cpp @@ -0,0 +1,35 @@ +#include "cru/ui/host/LayoutPaintCycler.hpp" +#include <chrono> + +#include "../Helper.hpp" +#include "cru/ui/Base.hpp" +#include "cru/ui/host/WindowHost.hpp" + +namespace cru::ui::host { +LayoutPaintCycler::LayoutPaintCycler(WindowHost* host) : host_(host) { + timer_canceler_ = GetUiApplication()->SetInterval( + std::chrono::duration_cast<std::chrono::milliseconds>( + this->cycle_threshold_), + [this] { OnCycle(); }); +} + +LayoutPaintCycler::~LayoutPaintCycler() = default; + +void LayoutPaintCycler::InvalidateLayout() { layout_dirty_ = true; } + +void LayoutPaintCycler::InvalidatePaint() { paint_dirty_ = true; } + +void LayoutPaintCycler::OnCycle() { + last_cycle_time_ = std::chrono::steady_clock::now(); + if (layout_dirty_) { + host_->Relayout(); + host_->Repaint(); + } else { + if (paint_dirty_) { + host_->Repaint(); + } + } + layout_dirty_ = true; + paint_dirty_ = true; +} +} // namespace cru::ui::host diff --git a/src/ui/host/WindowHost.cpp b/src/ui/host/WindowHost.cpp index eac247c1..e04fdf31 100644 --- a/src/ui/host/WindowHost.cpp +++ b/src/ui/host/WindowHost.cpp @@ -1,6 +1,7 @@ #include "cru/ui/host/WindowHost.hpp" #include "RoutedEventDispatch.hpp" +#include "cru/common/Base.hpp" #include "cru/common/Logger.hpp" #include "cru/platform/graphics/Painter.hpp" #include "cru/platform/gui/InputMethod.hpp" @@ -8,10 +9,12 @@ #include "cru/platform/gui/Window.hpp" #include "cru/ui/DebugFlags.hpp" #include "cru/ui/Window.hpp" +#include "cru/ui/host/LayoutPaintCycler.hpp" #include "cru/ui/render/MeasureRequirement.hpp" #include "cru/ui/render/RenderObject.hpp" #include <cstddef> +#include <memory> namespace cru::ui::host { using platform::gui::INativeWindow; @@ -113,6 +116,8 @@ WindowHost::WindowHost(Control* root_control) root_render_object_ = root_control->GetRenderObject(); root_render_object_->SetWindowHostRecursive(this); + this->layout_paint_cycler_ = std::make_unique<LayoutPaintCycler>(this); + BindNativeEvent(this, native_window, native_window->DestroyEvent(), &WindowHost::OnNativeDestroy, event_revoker_guards_); BindNativeEvent(this, native_window, native_window->PaintEvent(), @@ -141,13 +146,10 @@ WindowHost::~WindowHost() { } } -void WindowHost::InvalidatePaint() { - if (native_window_) native_window_->RequestRepaint(); -} +void WindowHost::InvalidatePaint() { layout_paint_cycler_->InvalidatePaint(); } void WindowHost::InvalidateLayout() { - need_layout_ = true; - this->InvalidatePaint(); + layout_paint_cycler_->InvalidateLayout(); } bool WindowHost::IsLayoutPreferToFillWindow() const { @@ -186,6 +188,13 @@ void WindowHost::Relayout(const Size& available_size) { log::TagDebug(log_tag, u"A relayout is finished."); } +void WindowHost::Repaint() { + auto painter = native_window_->BeginPaint(); + painter->Clear(colors::white); + root_render_object_->Draw(painter.get()); + painter->EndDraw(); +} + Control* WindowHost::GetFocusControl() { return focus_control_; } void WindowHost::SetFocusControl(Control* control) { @@ -236,7 +245,7 @@ Control* WindowHost::GetMouseCaptureControl() { } void WindowHost::RunAfterLayoutStable(std::function<void()> action) { - if (need_layout_) { + if (layout_paint_cycler_->IsLayoutDirty()) { after_layout_stable_action_.push_back(std::move(action)); } else { action(); @@ -249,14 +258,8 @@ void WindowHost::OnNativeDestroy(INativeWindow* window, std::nullptr_t) { } void WindowHost::OnNativePaint(INativeWindow* window, std::nullptr_t) { - if (need_layout_) { - Relayout(); - need_layout_ = false; - } - auto painter = window->BeginPaint(); - painter->Clear(colors::white); - root_render_object_->Draw(painter.get()); - painter->EndDraw(); + CRU_UNUSED(window) + layout_paint_cycler_->InvalidatePaint(); } void WindowHost::OnNativeResize(INativeWindow* window, const Size& size) { @@ -397,4 +400,4 @@ Control* WindowHost::HitTest(const Point& point) { } return root_control_; } -} // namespace cru::ui +} // namespace cru::ui::host |