diff --git a/Import/GacUI.UnitTest.UI.cpp b/Import/GacUI.UnitTest.UI.cpp index 2269d6f0..8652b140 100644 --- a/Import/GacUI.UnitTest.UI.cpp +++ b/Import/GacUI.UnitTest.UI.cpp @@ -518,13 +518,49 @@ namespace gaclib_controls if (node && node->GetNodeType() == UnitTestSnapshotFileNodeType::File && frame) { - rootComposition = BuildRootComposition(GetRenderingTrace(node), GetRenderingFrame(frame)); - scRendering->GetContainerComposition()->AddChild(rootComposition); + auto loadDom = [=, this]() + { + labelLoading->SetVisible(false); + scRendering->SetVisible(true); - rootComposition->GetEventReceiver()->mouseEnter.AttachMethod(this, &UnitTestSnapshotViewerAppWindow::rootComposition_MouseEnter); - rootComposition->GetEventReceiver()->mouseLeave.AttachMethod(this, &UnitTestSnapshotViewerAppWindow::rootComopsition_MouseLeave); - rootComposition->GetEventReceiver()->mouseMove.AttachMethod(this, &UnitTestSnapshotViewerAppWindow::rootComposition_MouseMove); - rootComposition->GetEventReceiver()->leftButtonDown.AttachMethod(this, &UnitTestSnapshotViewerAppWindow::rootComposition_LeftButtonDown); + rootComposition = BuildRootComposition(GetRenderingTrace(node), GetRenderingFrame(frame)); + scRendering->GetContainerComposition()->AddChild(rootComposition); + + rootComposition->GetEventReceiver()->mouseEnter.AttachMethod(this, &UnitTestSnapshotViewerAppWindow::rootComposition_MouseEnter); + rootComposition->GetEventReceiver()->mouseLeave.AttachMethod(this, &UnitTestSnapshotViewerAppWindow::rootComopsition_MouseLeave); + rootComposition->GetEventReceiver()->mouseMove.AttachMethod(this, &UnitTestSnapshotViewerAppWindow::rootComposition_MouseMove); + rootComposition->GetEventReceiver()->leftButtonDown.AttachMethod(this, &UnitTestSnapshotViewerAppWindow::rootComposition_LeftButtonDown); + }; + + if (frame->GetDom()) + { + loadDom(); + } + else + { + labelLoading->SetVisible(true); + scRendering->SetVisible(false); + + struct LoadingContext + { + vint version; + Ptr handler; + }; + auto context = Ptr(new LoadingContext); + context->version = ++loadingFrameVersion; + context->handler = frame->DomChanged.Add([=, this]() + { + if (context->version == loadingFrameVersion) + { + loadDom(); + GetApplication()->InvokeInMainThread(GetApplication()->GetMainWindow(), [=]() + { + frame->DomChanged.Remove(context->handler); + context->handler = {}; + }); + } + }); + } } } @@ -720,6 +756,24 @@ Closures } void __vwsnf10_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_::operator()(const ::vl::reflection::description::Value& __vwsn_value_) const + { + auto __vwsn_old_ = ::vl::__vwsn::This(__vwsnthis_0->__vwsn_precompile_27)->GetText(); + auto __vwsn_new_ = ::vl::__vwsn::Unbox<::vl::WString>(__vwsn_value_); + if ((__vwsn_old_ == __vwsn_new_)) + { + return; + } + ::vl::__vwsn::This(__vwsnthis_0->__vwsn_precompile_27)->SetText(__vwsn_new_); + } + + //------------------------------------------------------------------- + + __vwsnf11_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_::__vwsnf11_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_(::gaclib_controls::UnitTestSnapshotViewerWindowConstructor* __vwsnctorthis_0) + :__vwsnthis_0(::vl::__vwsn::This(__vwsnctorthis_0)) + { + } + + void __vwsnf11_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_::operator()(const ::vl::reflection::description::Value& __vwsn_value_) const { auto __vwsn_old_ = ::vl::__vwsn::This(__vwsnthis_0->__vwsn_precompile_30)->GetText(); auto __vwsn_new_ = ::vl::__vwsn::Unbox<::vl::WString>(__vwsn_value_); @@ -732,12 +786,12 @@ Closures //------------------------------------------------------------------- - __vwsnf11_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_::__vwsnf11_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_(::gaclib_controls::UnitTestSnapshotViewerWindowConstructor* __vwsnctorthis_0) + __vwsnf12_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_::__vwsnf12_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_(::gaclib_controls::UnitTestSnapshotViewerWindowConstructor* __vwsnctorthis_0) :__vwsnthis_0(::vl::__vwsn::This(__vwsnctorthis_0)) { } - void __vwsnf11_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_::operator()(const ::vl::reflection::description::Value& __vwsn_value_) const + void __vwsnf12_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_::operator()(const ::vl::reflection::description::Value& __vwsn_value_) const { auto __vwsn_old_ = ::vl::__vwsn::This(__vwsnthis_0->self)->GetText(); auto __vwsn_new_ = ::vl::__vwsn::Unbox<::vl::WString>(__vwsn_value_); @@ -750,12 +804,12 @@ Closures //------------------------------------------------------------------- - __vwsnf12_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_::__vwsnf12_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_(::gaclib_controls::UnitTestSnapshotViewerWindowConstructor* __vwsnctorthis_0) + __vwsnf13_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_::__vwsnf13_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_(::gaclib_controls::UnitTestSnapshotViewerWindowConstructor* __vwsnctorthis_0) :__vwsnthis_0(::vl::__vwsn::This(__vwsnctorthis_0)) { } - void __vwsnf12_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_::operator()(const ::vl::reflection::description::Value& __vwsn_value_) const + void __vwsnf13_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_::operator()(const ::vl::reflection::description::Value& __vwsn_value_) const { auto __vwsn_old_ = ::vl::__vwsn::This(__vwsnthis_0->self)->GetStrings(); auto __vwsn_new_ = ::vl::__vwsn::Unbox<::vl::Ptr<::gaclib_controls::IUnitTestSnapshotViewerStringsStrings>>(__vwsn_value_); @@ -858,13 +912,13 @@ Closures void __vwsnf8_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_::operator()(const ::vl::reflection::description::Value& __vwsn_value_) const { - auto __vwsn_old_ = ::vl::__vwsn::This(__vwsnthis_0->treeViewDom)->GetItemSource(); - auto __vwsn_new_ = ::vl::__vwsn::Unbox<::vl::reflection::description::Value>(__vwsn_value_); + auto __vwsn_old_ = ::vl::__vwsn::This(__vwsnthis_0->labelLoading)->GetFont(); + auto __vwsn_new_ = ::vl::__vwsn::Unbox<::vl::Nullable<::vl::presentation::FontProperties>>(__vwsn_value_); if ((__vwsn_old_ == __vwsn_new_)) { return; } - ::vl::__vwsn::This(__vwsnthis_0->treeViewDom)->SetItemSource(__vwsn_new_); + ::vl::__vwsn::This(__vwsnthis_0->labelLoading)->SetFont(__vwsn_new_); } //------------------------------------------------------------------- @@ -876,13 +930,13 @@ Closures void __vwsnf9_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_::operator()(const ::vl::reflection::description::Value& __vwsn_value_) const { - auto __vwsn_old_ = ::vl::__vwsn::This(__vwsnthis_0->__vwsn_precompile_27)->GetText(); - auto __vwsn_new_ = ::vl::__vwsn::Unbox<::vl::WString>(__vwsn_value_); + auto __vwsn_old_ = ::vl::__vwsn::This(__vwsnthis_0->treeViewDom)->GetItemSource(); + auto __vwsn_new_ = ::vl::__vwsn::Unbox<::vl::reflection::description::Value>(__vwsn_value_); if ((__vwsn_old_ == __vwsn_new_)) { return; } - ::vl::__vwsn::This(__vwsnthis_0->__vwsn_precompile_27)->SetText(__vwsn_new_); + ::vl::__vwsn::This(__vwsnthis_0->treeViewDom)->SetItemSource(__vwsn_new_); } //------------------------------------------------------------------- @@ -963,16 +1017,18 @@ Closures __vwsnc2_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::__vwsnc2_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription(::gaclib_controls::UnitTestSnapshotViewerWindowConstructor* __vwsnctorthis_0) :__vwsnthis_0(::vl::__vwsn::This(__vwsnctorthis_0)) { - this->__vwsn_bind_cache_0 = static_cast<::vl::presentation::controls::GuiBindableTextList*>(nullptr); + this->__vwsn_bind_cache_0 = static_cast<::gaclib_controls::UnitTestSnapshotViewerWindow*>(nullptr); + this->__vwsn_bind_cache_1 = static_cast<::gaclib_controls::UnitTestSnapshotViewerWindow*>(nullptr); this->__vwsn_bind_handler_0_0 = ::vl::Ptr<::vl::reflection::description::IEventHandler>(); + this->__vwsn_bind_handler_1_0 = ::vl::Ptr<::vl::reflection::description::IEventHandler>(); this->__vwsn_bind_opened_ = false; this->__vwsn_bind_closed_ = false; } void __vwsnc2_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::__vwsn_bind_activator_() { - auto __vwsn_bind_activator_result_ = ::vl::__vwsn::Box([&](){ try{ return ::vl::__vwsn::This(::vl::__vwsn::Unbox<::vl::Ptr<::gaclib_controls::IUnitTestSnapshotFrame>>(::vl::__vwsn::This(__vwsn_bind_cache_0)->GetSelectedItem()).Obj())->GetDom(); } catch(...){ return ::vl::Ptr<::gaclib_controls::IUnitTestSnapshotDomNode>(); } }()); - ::vl::__vwsn::EventInvoke(this->ValueChanged)(__vwsn_bind_activator_result_); + auto __vwsn_bind_activator_result_ = [&](){ ::vl::presentation::FontProperties __vwsn_temp__; __vwsn_temp__.fontFamily = ::vl::__vwsn::This(__vwsn_bind_cache_0)->GetDisplayFont().fontFamily; __vwsn_temp__.size = ::vl::__vwsn::This(__vwsn_bind_cache_1)->GetDisplayFont().size; __vwsn_temp__.antialias = true; return __vwsn_temp__; }(); + ::vl::__vwsn::EventInvoke(this->ValueChanged)(::vl::__vwsn::Box(__vwsn_bind_activator_result_)); } void __vwsnc2_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::__vwsn_bind_callback_0_0(::vl::presentation::compositions::GuiGraphicsComposition* __vwsn_bind_callback_argument_0, ::vl::presentation::compositions::GuiEventArgs* __vwsn_bind_callback_argument_1) @@ -980,13 +1036,20 @@ Closures this->__vwsn_bind_activator_(); } + void __vwsnc2_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::__vwsn_bind_callback_1_0(::vl::presentation::compositions::GuiGraphicsComposition* __vwsn_bind_callback_argument_0, ::vl::presentation::compositions::GuiEventArgs* __vwsn_bind_callback_argument_1) + { + this->__vwsn_bind_activator_(); + } + bool __vwsnc2_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::Open() { if ((! __vwsn_bind_opened_)) { (__vwsn_bind_opened_ = true); - (__vwsn_bind_cache_0 = [&](){ try{ return __vwsnthis_0->textListFrames; } catch(...){ return static_cast<::vl::presentation::controls::GuiBindableTextList*>(nullptr); } }()); - (__vwsn_bind_handler_0_0 = [&](){ try{ return ::vl::__vwsn::EventAttach(::vl::__vwsn::This(__vwsn_bind_cache_0)->SelectionChanged, ::vl::Func(this, &__vwsnc2_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::__vwsn_bind_callback_0_0)); } catch(...){ return ::vl::Ptr<::vl::reflection::description::IEventHandler>(); } }()); + (__vwsn_bind_cache_0 = [&](){ try{ return __vwsnthis_0->self; } catch(...){ return static_cast<::gaclib_controls::UnitTestSnapshotViewerWindow*>(nullptr); } }()); + (__vwsn_bind_cache_1 = [&](){ try{ return __vwsnthis_0->self; } catch(...){ return static_cast<::gaclib_controls::UnitTestSnapshotViewerWindow*>(nullptr); } }()); + (__vwsn_bind_handler_0_0 = [&](){ try{ return ::vl::__vwsn::EventAttach(::vl::__vwsn::This(__vwsn_bind_cache_0)->DisplayFontChanged, ::vl::Func(this, &__vwsnc2_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::__vwsn_bind_callback_0_0)); } catch(...){ return ::vl::Ptr<::vl::reflection::description::IEventHandler>(); } }()); + (__vwsn_bind_handler_1_0 = [&](){ try{ return ::vl::__vwsn::EventAttach(::vl::__vwsn::This(__vwsn_bind_cache_1)->DisplayFontChanged, ::vl::Func(this, &__vwsnc2_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::__vwsn_bind_callback_1_0)); } catch(...){ return ::vl::Ptr<::vl::reflection::description::IEventHandler>(); } }()); return true; } return false; @@ -1009,11 +1072,18 @@ Closures (__vwsn_bind_closed_ = true); if (static_cast(__vwsn_bind_handler_0_0)) { - ::vl::__vwsn::EventDetach(::vl::__vwsn::This(__vwsn_bind_cache_0)->SelectionChanged, __vwsn_bind_handler_0_0); + ::vl::__vwsn::EventDetach(::vl::__vwsn::This(__vwsn_bind_cache_0)->DisplayFontChanged, __vwsn_bind_handler_0_0); (__vwsn_bind_handler_0_0 = ::vl::Ptr<::vl::reflection::description::IEventHandler>()); } - (__vwsn_bind_cache_0 = static_cast<::vl::presentation::controls::GuiBindableTextList*>(nullptr)); + if (static_cast(__vwsn_bind_handler_1_0)) + { + ::vl::__vwsn::EventDetach(::vl::__vwsn::This(__vwsn_bind_cache_1)->DisplayFontChanged, __vwsn_bind_handler_1_0); + (__vwsn_bind_handler_1_0 = ::vl::Ptr<::vl::reflection::description::IEventHandler>()); + } + (__vwsn_bind_cache_0 = static_cast<::gaclib_controls::UnitTestSnapshotViewerWindow*>(nullptr)); + (__vwsn_bind_cache_1 = static_cast<::gaclib_controls::UnitTestSnapshotViewerWindow*>(nullptr)); (__vwsn_bind_handler_0_0 = ::vl::Ptr<::vl::reflection::description::IEventHandler>()); + (__vwsn_bind_handler_1_0 = ::vl::Ptr<::vl::reflection::description::IEventHandler>()); return true; } return false; @@ -1024,19 +1094,33 @@ Closures __vwsnc3_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::__vwsnc3_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription(::gaclib_controls::UnitTestSnapshotViewerWindowConstructor* __vwsnctorthis_0) :__vwsnthis_0(::vl::__vwsn::This(__vwsnctorthis_0)) { - this->__vwsn_bind_cache_0 = static_cast<::vl::presentation::controls::GuiBindableTreeView*>(nullptr); + this->__vwsn_bind_cache_0 = static_cast<::vl::presentation::controls::GuiBindableTextList*>(nullptr); + this->__vwsn_bind_cache_1 = ::vl::Ptr<::gaclib_controls::IUnitTestSnapshotFrame>(); this->__vwsn_bind_handler_0_0 = ::vl::Ptr<::vl::reflection::description::IEventHandler>(); + this->__vwsn_bind_handler_1_0 = ::vl::Ptr<::vl::reflection::description::IEventHandler>(); this->__vwsn_bind_opened_ = false; this->__vwsn_bind_closed_ = false; } void __vwsnc3_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::__vwsn_bind_activator_() { - auto __vwsn_bind_activator_result_ = [&](){ try{ return ::vl::__vwsn::This(::vl::__vwsn::Unbox<::vl::Ptr<::gaclib_controls::IUnitTestSnapshotDomNode>>(::vl::__vwsn::This(__vwsn_bind_cache_0)->GetSelectedItem()).Obj())->GetDomAsJsonText(); } catch(...){ return ::vl::WString::Unmanaged(L""); } }(); - ::vl::__vwsn::EventInvoke(this->ValueChanged)(::vl::__vwsn::Box(__vwsn_bind_activator_result_)); + auto __vwsn_bind_activator_result_ = ::vl::__vwsn::Box([&](){ try{ return ::vl::__vwsn::This(__vwsn_bind_cache_1.Obj())->GetDom(); } catch(...){ return ::vl::Ptr<::gaclib_controls::IUnitTestSnapshotDomNode>(); } }()); + ::vl::__vwsn::EventInvoke(this->ValueChanged)(__vwsn_bind_activator_result_); } void __vwsnc3_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::__vwsn_bind_callback_0_0(::vl::presentation::compositions::GuiGraphicsComposition* __vwsn_bind_callback_argument_0, ::vl::presentation::compositions::GuiEventArgs* __vwsn_bind_callback_argument_1) + { + if (static_cast(__vwsn_bind_handler_1_0)) + { + ::vl::__vwsn::EventDetach(::vl::__vwsn::This(__vwsn_bind_cache_1.Obj())->DomChanged, __vwsn_bind_handler_1_0); + (__vwsn_bind_handler_1_0 = ::vl::Ptr<::vl::reflection::description::IEventHandler>()); + } + (__vwsn_bind_cache_1 = [&](){ try{ return ::vl::__vwsn::Unbox<::vl::Ptr<::gaclib_controls::IUnitTestSnapshotFrame>>(::vl::__vwsn::This(__vwsn_bind_cache_0)->GetSelectedItem()); } catch(...){ return ::vl::Ptr<::gaclib_controls::IUnitTestSnapshotFrame>(); } }()); + (__vwsn_bind_handler_1_0 = [&](){ try{ return ::vl::__vwsn::EventAttach(::vl::__vwsn::This(__vwsn_bind_cache_1.Obj())->DomChanged, ::vl::Func(this, &__vwsnc3_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::__vwsn_bind_callback_1_0)); } catch(...){ return ::vl::Ptr<::vl::reflection::description::IEventHandler>(); } }()); + this->__vwsn_bind_activator_(); + } + + void __vwsnc3_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::__vwsn_bind_callback_1_0() { this->__vwsn_bind_activator_(); } @@ -1046,8 +1130,10 @@ Closures if ((! __vwsn_bind_opened_)) { (__vwsn_bind_opened_ = true); - (__vwsn_bind_cache_0 = [&](){ try{ return __vwsnthis_0->treeViewDom; } catch(...){ return static_cast<::vl::presentation::controls::GuiBindableTreeView*>(nullptr); } }()); + (__vwsn_bind_cache_0 = [&](){ try{ return __vwsnthis_0->textListFrames; } catch(...){ return static_cast<::vl::presentation::controls::GuiBindableTextList*>(nullptr); } }()); + (__vwsn_bind_cache_1 = [&](){ try{ return ::vl::__vwsn::Unbox<::vl::Ptr<::gaclib_controls::IUnitTestSnapshotFrame>>(::vl::__vwsn::This(__vwsn_bind_cache_0)->GetSelectedItem()); } catch(...){ return ::vl::Ptr<::gaclib_controls::IUnitTestSnapshotFrame>(); } }()); (__vwsn_bind_handler_0_0 = [&](){ try{ return ::vl::__vwsn::EventAttach(::vl::__vwsn::This(__vwsn_bind_cache_0)->SelectionChanged, ::vl::Func(this, &__vwsnc3_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::__vwsn_bind_callback_0_0)); } catch(...){ return ::vl::Ptr<::vl::reflection::description::IEventHandler>(); } }()); + (__vwsn_bind_handler_1_0 = [&](){ try{ return ::vl::__vwsn::EventAttach(::vl::__vwsn::This(__vwsn_bind_cache_1.Obj())->DomChanged, ::vl::Func(this, &__vwsnc3_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::__vwsn_bind_callback_1_0)); } catch(...){ return ::vl::Ptr<::vl::reflection::description::IEventHandler>(); } }()); return true; } return false; @@ -1073,8 +1159,15 @@ Closures ::vl::__vwsn::EventDetach(::vl::__vwsn::This(__vwsn_bind_cache_0)->SelectionChanged, __vwsn_bind_handler_0_0); (__vwsn_bind_handler_0_0 = ::vl::Ptr<::vl::reflection::description::IEventHandler>()); } - (__vwsn_bind_cache_0 = static_cast<::vl::presentation::controls::GuiBindableTreeView*>(nullptr)); + if (static_cast(__vwsn_bind_handler_1_0)) + { + ::vl::__vwsn::EventDetach(::vl::__vwsn::This(__vwsn_bind_cache_1.Obj())->DomChanged, __vwsn_bind_handler_1_0); + (__vwsn_bind_handler_1_0 = ::vl::Ptr<::vl::reflection::description::IEventHandler>()); + } + (__vwsn_bind_cache_0 = static_cast<::vl::presentation::controls::GuiBindableTextList*>(nullptr)); + (__vwsn_bind_cache_1 = ::vl::Ptr<::gaclib_controls::IUnitTestSnapshotFrame>()); (__vwsn_bind_handler_0_0 = ::vl::Ptr<::vl::reflection::description::IEventHandler>()); + (__vwsn_bind_handler_1_0 = ::vl::Ptr<::vl::reflection::description::IEventHandler>()); return true; } return false; @@ -1093,7 +1186,7 @@ Closures void __vwsnc4_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::__vwsn_bind_activator_() { - auto __vwsn_bind_activator_result_ = [&](){ try{ return ::vl::__vwsn::This(::vl::__vwsn::Unbox<::vl::Ptr<::gaclib_controls::IUnitTestSnapshotDomNode>>(::vl::__vwsn::This(__vwsn_bind_cache_0)->GetSelectedItem()).Obj())->GetElementAsJsonText(); } catch(...){ return ::vl::WString::Unmanaged(L""); } }(); + auto __vwsn_bind_activator_result_ = [&](){ try{ return ::vl::__vwsn::This(::vl::__vwsn::Unbox<::vl::Ptr<::gaclib_controls::IUnitTestSnapshotDomNode>>(::vl::__vwsn::This(__vwsn_bind_cache_0)->GetSelectedItem()).Obj())->GetDomAsJsonText(); } catch(...){ return ::vl::WString::Unmanaged(L""); } }(); ::vl::__vwsn::EventInvoke(this->ValueChanged)(::vl::__vwsn::Box(__vwsn_bind_activator_result_)); } @@ -1143,11 +1236,10 @@ Closures //------------------------------------------------------------------- - __vwsnc5_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::__vwsnc5_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription(::gaclib_controls::UnitTestSnapshotViewerWindow* __vwsnctor___vwsn_this_, ::gaclib_controls::UnitTestSnapshotViewerWindowConstructor* __vwsnctorthis_0) - :__vwsn_this_(__vwsnctor___vwsn_this_) - , __vwsnthis_0(::vl::__vwsn::This(__vwsnctorthis_0)) + __vwsnc5_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::__vwsnc5_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription(::gaclib_controls::UnitTestSnapshotViewerWindowConstructor* __vwsnctorthis_0) + :__vwsnthis_0(::vl::__vwsn::This(__vwsnctorthis_0)) { - this->__vwsn_bind_cache_0 = static_cast<::gaclib_controls::UnitTestSnapshotViewerWindow*>(nullptr); + this->__vwsn_bind_cache_0 = static_cast<::vl::presentation::controls::GuiBindableTreeView*>(nullptr); this->__vwsn_bind_handler_0_0 = ::vl::Ptr<::vl::reflection::description::IEventHandler>(); this->__vwsn_bind_opened_ = false; this->__vwsn_bind_closed_ = false; @@ -1155,11 +1247,11 @@ Closures void __vwsnc5_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::__vwsn_bind_activator_() { - auto __vwsn_bind_activator_result_ = ::vl::__vwsn::This(::vl::__vwsn::This(__vwsn_bind_cache_0)->GetStrings().Obj())->WindowTitle(); + auto __vwsn_bind_activator_result_ = [&](){ try{ return ::vl::__vwsn::This(::vl::__vwsn::Unbox<::vl::Ptr<::gaclib_controls::IUnitTestSnapshotDomNode>>(::vl::__vwsn::This(__vwsn_bind_cache_0)->GetSelectedItem()).Obj())->GetElementAsJsonText(); } catch(...){ return ::vl::WString::Unmanaged(L""); } }(); ::vl::__vwsn::EventInvoke(this->ValueChanged)(::vl::__vwsn::Box(__vwsn_bind_activator_result_)); } - void __vwsnc5_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::__vwsn_bind_callback_0_0() + void __vwsnc5_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::__vwsn_bind_callback_0_0(::vl::presentation::compositions::GuiGraphicsComposition* __vwsn_bind_callback_argument_0, ::vl::presentation::compositions::GuiEventArgs* __vwsn_bind_callback_argument_1) { this->__vwsn_bind_activator_(); } @@ -1169,8 +1261,8 @@ Closures if ((! __vwsn_bind_opened_)) { (__vwsn_bind_opened_ = true); - (__vwsn_bind_cache_0 = [&](){ try{ return __vwsn_this_; } catch(...){ return static_cast<::gaclib_controls::UnitTestSnapshotViewerWindow*>(nullptr); } }()); - (__vwsn_bind_handler_0_0 = [&](){ try{ return ::vl::__vwsn::EventAttach(::vl::__vwsn::This(__vwsn_bind_cache_0)->StringsChanged, ::vl::Func(this, &__vwsnc5_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::__vwsn_bind_callback_0_0)); } catch(...){ return ::vl::Ptr<::vl::reflection::description::IEventHandler>(); } }()); + (__vwsn_bind_cache_0 = [&](){ try{ return __vwsnthis_0->treeViewDom; } catch(...){ return static_cast<::vl::presentation::controls::GuiBindableTreeView*>(nullptr); } }()); + (__vwsn_bind_handler_0_0 = [&](){ try{ return ::vl::__vwsn::EventAttach(::vl::__vwsn::This(__vwsn_bind_cache_0)->SelectionChanged, ::vl::Func(this, &__vwsnc5_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::__vwsn_bind_callback_0_0)); } catch(...){ return ::vl::Ptr<::vl::reflection::description::IEventHandler>(); } }()); return true; } return false; @@ -1193,10 +1285,10 @@ Closures (__vwsn_bind_closed_ = true); if (static_cast(__vwsn_bind_handler_0_0)) { - ::vl::__vwsn::EventDetach(::vl::__vwsn::This(__vwsn_bind_cache_0)->StringsChanged, __vwsn_bind_handler_0_0); + ::vl::__vwsn::EventDetach(::vl::__vwsn::This(__vwsn_bind_cache_0)->SelectionChanged, __vwsn_bind_handler_0_0); (__vwsn_bind_handler_0_0 = ::vl::Ptr<::vl::reflection::description::IEventHandler>()); } - (__vwsn_bind_cache_0 = static_cast<::gaclib_controls::UnitTestSnapshotViewerWindow*>(nullptr)); + (__vwsn_bind_cache_0 = static_cast<::vl::presentation::controls::GuiBindableTreeView*>(nullptr)); (__vwsn_bind_handler_0_0 = ::vl::Ptr<::vl::reflection::description::IEventHandler>()); return true; } @@ -1205,10 +1297,11 @@ Closures //------------------------------------------------------------------- - __vwsnc6_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::__vwsnc6_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription(::gaclib_controls::UnitTestSnapshotViewerWindowConstructor* __vwsnctorthis_0) - :__vwsnthis_0(::vl::__vwsn::This(__vwsnctorthis_0)) + __vwsnc6_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::__vwsnc6_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription(::gaclib_controls::UnitTestSnapshotViewerWindow* __vwsnctor___vwsn_this_, ::gaclib_controls::UnitTestSnapshotViewerWindowConstructor* __vwsnctorthis_0) + :__vwsn_this_(__vwsnctor___vwsn_this_) + , __vwsnthis_0(::vl::__vwsn::This(__vwsnctorthis_0)) { - this->__vwsn_bind_cache_0 = static_cast<::vl::presentation::controls::GuiApplication*>(nullptr); + this->__vwsn_bind_cache_0 = static_cast<::gaclib_controls::UnitTestSnapshotViewerWindow*>(nullptr); this->__vwsn_bind_handler_0_0 = ::vl::Ptr<::vl::reflection::description::IEventHandler>(); this->__vwsn_bind_opened_ = false; this->__vwsn_bind_closed_ = false; @@ -1216,7 +1309,7 @@ Closures void __vwsnc6_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::__vwsn_bind_activator_() { - auto __vwsn_bind_activator_result_ = ::gaclib_controls::UnitTestSnapshotViewerStrings::Get(::vl::__vwsn::This(__vwsn_bind_cache_0)->GetLocale()); + auto __vwsn_bind_activator_result_ = ::vl::__vwsn::This(::vl::__vwsn::This(__vwsn_bind_cache_0)->GetStrings().Obj())->WindowTitle(); ::vl::__vwsn::EventInvoke(this->ValueChanged)(::vl::__vwsn::Box(__vwsn_bind_activator_result_)); } @@ -1230,8 +1323,8 @@ Closures if ((! __vwsn_bind_opened_)) { (__vwsn_bind_opened_ = true); - (__vwsn_bind_cache_0 = [&](){ try{ return ::vl::presentation::controls::GetApplication(); } catch(...){ return static_cast<::vl::presentation::controls::GuiApplication*>(nullptr); } }()); - (__vwsn_bind_handler_0_0 = [&](){ try{ return ::vl::__vwsn::EventAttach(::vl::__vwsn::This(__vwsn_bind_cache_0)->LocaleChanged, ::vl::Func(this, &__vwsnc6_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::__vwsn_bind_callback_0_0)); } catch(...){ return ::vl::Ptr<::vl::reflection::description::IEventHandler>(); } }()); + (__vwsn_bind_cache_0 = [&](){ try{ return __vwsn_this_; } catch(...){ return static_cast<::gaclib_controls::UnitTestSnapshotViewerWindow*>(nullptr); } }()); + (__vwsn_bind_handler_0_0 = [&](){ try{ return ::vl::__vwsn::EventAttach(::vl::__vwsn::This(__vwsn_bind_cache_0)->StringsChanged, ::vl::Func(this, &__vwsnc6_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::__vwsn_bind_callback_0_0)); } catch(...){ return ::vl::Ptr<::vl::reflection::description::IEventHandler>(); } }()); return true; } return false; @@ -1248,6 +1341,67 @@ Closures } bool __vwsnc6_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::Close() + { + if ((! __vwsn_bind_closed_)) + { + (__vwsn_bind_closed_ = true); + if (static_cast(__vwsn_bind_handler_0_0)) + { + ::vl::__vwsn::EventDetach(::vl::__vwsn::This(__vwsn_bind_cache_0)->StringsChanged, __vwsn_bind_handler_0_0); + (__vwsn_bind_handler_0_0 = ::vl::Ptr<::vl::reflection::description::IEventHandler>()); + } + (__vwsn_bind_cache_0 = static_cast<::gaclib_controls::UnitTestSnapshotViewerWindow*>(nullptr)); + (__vwsn_bind_handler_0_0 = ::vl::Ptr<::vl::reflection::description::IEventHandler>()); + return true; + } + return false; + } + + //------------------------------------------------------------------- + + __vwsnc7_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::__vwsnc7_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription(::gaclib_controls::UnitTestSnapshotViewerWindowConstructor* __vwsnctorthis_0) + :__vwsnthis_0(::vl::__vwsn::This(__vwsnctorthis_0)) + { + this->__vwsn_bind_cache_0 = static_cast<::vl::presentation::controls::GuiApplication*>(nullptr); + this->__vwsn_bind_handler_0_0 = ::vl::Ptr<::vl::reflection::description::IEventHandler>(); + this->__vwsn_bind_opened_ = false; + this->__vwsn_bind_closed_ = false; + } + + void __vwsnc7_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::__vwsn_bind_activator_() + { + auto __vwsn_bind_activator_result_ = ::gaclib_controls::UnitTestSnapshotViewerStrings::Get(::vl::__vwsn::This(__vwsn_bind_cache_0)->GetLocale()); + ::vl::__vwsn::EventInvoke(this->ValueChanged)(::vl::__vwsn::Box(__vwsn_bind_activator_result_)); + } + + void __vwsnc7_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::__vwsn_bind_callback_0_0() + { + this->__vwsn_bind_activator_(); + } + + bool __vwsnc7_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::Open() + { + if ((! __vwsn_bind_opened_)) + { + (__vwsn_bind_opened_ = true); + (__vwsn_bind_cache_0 = [&](){ try{ return ::vl::presentation::controls::GetApplication(); } catch(...){ return static_cast<::vl::presentation::controls::GuiApplication*>(nullptr); } }()); + (__vwsn_bind_handler_0_0 = [&](){ try{ return ::vl::__vwsn::EventAttach(::vl::__vwsn::This(__vwsn_bind_cache_0)->LocaleChanged, ::vl::Func(this, &__vwsnc7_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::__vwsn_bind_callback_0_0)); } catch(...){ return ::vl::Ptr<::vl::reflection::description::IEventHandler>(); } }()); + return true; + } + return false; + } + + bool __vwsnc7_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::Update() + { + if ((__vwsn_bind_opened_ && (! __vwsn_bind_closed_))) + { + this->__vwsn_bind_activator_(); + return true; + } + return false; + } + + bool __vwsnc7_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription::Close() { if ((! __vwsn_bind_closed_)) { @@ -1266,11 +1420,11 @@ Closures //------------------------------------------------------------------- - __vwsnc7_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerStrings___vwsn_ls_en_US_BuildStrings__gaclib_controls_IUnitTestSnapshotViewerStringsStrings::__vwsnc7_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerStrings___vwsn_ls_en_US_BuildStrings__gaclib_controls_IUnitTestSnapshotViewerStringsStrings() + __vwsnc8_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerStrings___vwsn_ls_en_US_BuildStrings__gaclib_controls_IUnitTestSnapshotViewerStringsStrings::__vwsnc8_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerStrings___vwsn_ls_en_US_BuildStrings__gaclib_controls_IUnitTestSnapshotViewerStringsStrings() { } - ::vl::WString __vwsnc7_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerStrings___vwsn_ls_en_US_BuildStrings__gaclib_controls_IUnitTestSnapshotViewerStringsStrings::WindowTitle() + ::vl::WString __vwsnc8_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerStrings___vwsn_ls_en_US_BuildStrings__gaclib_controls_IUnitTestSnapshotViewerStringsStrings::WindowTitle() { return ::vl::WString::Unmanaged(L"Unit Test Snapshot Viewer"); } @@ -1305,7 +1459,7 @@ Class (::gaclib_controls::UnitTestSnapshotViewerStrings) ::vl::Ptr<::gaclib_controls::IUnitTestSnapshotViewerStringsStrings> UnitTestSnapshotViewerStrings::__vwsn_ls_en_US_BuildStrings(::vl::Locale __vwsn_ls_locale) { - return ::vl::Ptr<::gaclib_controls::IUnitTestSnapshotViewerStringsStrings>(new ::vl_workflow_global::__vwsnc7_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerStrings___vwsn_ls_en_US_BuildStrings__gaclib_controls_IUnitTestSnapshotViewerStringsStrings()); + return ::vl::Ptr<::gaclib_controls::IUnitTestSnapshotViewerStringsStrings>(new ::vl_workflow_global::__vwsnc8_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerStrings___vwsn_ls_en_US_BuildStrings__gaclib_controls_IUnitTestSnapshotViewerStringsStrings()); } void UnitTestSnapshotViewerStrings::Install(::vl::Locale __vwsn_ls_locale, ::vl::Ptr<::gaclib_controls::IUnitTestSnapshotViewerStringsStrings> __vwsn_ls_impl) @@ -1496,10 +1650,26 @@ Class (::gaclib_controls::UnitTestSnapshotViewerWindowConstructor) { ::vl::__vwsn::This(this->__vwsn_precompile_16)->SetSite(static_cast<::vl::vint>(0), static_cast<::vl::vint>(0), static_cast<::vl::vint>(1), static_cast<::vl::vint>(1)); } + { + (this->labelLoading = new ::vl::presentation::controls::GuiLabel(::vl::presentation::theme::ThemeName::Label)); + ::vl::__vwsn::This(__vwsn_this_)->SetNamedObject(::vl::WString::Unmanaged(L"labelLoading"), ::vl::__vwsn::Box(this->labelLoading)); + } + { + ::vl::__vwsn::This(this->labelLoading)->SetVisible(false); + } + { + ::vl::__vwsn::This(this->labelLoading)->SetText(::vl::WString::Unmanaged(L"Loading ...")); + } + { + ::vl::__vwsn::This(this->__vwsn_precompile_16)->AddChild(static_cast<::vl::presentation::compositions::GuiGraphicsComposition*>(::vl::__vwsn::This(this->labelLoading)->GetBoundsComposition())); + } { (this->scRendering = new ::vl::presentation::controls::GuiScrollContainer(::vl::presentation::theme::ThemeName::ScrollView)); ::vl::__vwsn::This(__vwsn_this_)->SetNamedObject(::vl::WString::Unmanaged(L"scRendering"), ::vl::__vwsn::Box(this->scRendering)); } + { + ::vl::__vwsn::This(this->scRendering)->SetVisible(false); + } { ::vl::__vwsn::This(this->scRendering)->SetHorizontalAlwaysVisible(false); } @@ -1705,15 +1875,20 @@ Class (::gaclib_controls::UnitTestSnapshotViewerWindowConstructor) ::vl::__vwsn::This(__vwsn_this_)->AddSubscription(__vwsn_created_subscription_); } { - auto __vwsn_created_subscription_ = ::vl::Ptr<::vl::reflection::description::IValueSubscription>(new ::vl_workflow_global::__vwsnc5_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription(__vwsn_this_, this)); + auto __vwsn_created_subscription_ = ::vl::Ptr<::vl::reflection::description::IValueSubscription>(new ::vl_workflow_global::__vwsnc5_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription(this)); ::vl::__vwsn::EventAttach(::vl::__vwsn::This(__vwsn_created_subscription_.Obj())->ValueChanged, vl::Func(::vl_workflow_global::__vwsnf11_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_(this))); ::vl::__vwsn::This(__vwsn_this_)->AddSubscription(__vwsn_created_subscription_); } { - auto __vwsn_created_subscription_ = ::vl::Ptr<::vl::reflection::description::IValueSubscription>(new ::vl_workflow_global::__vwsnc6_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription(this)); + auto __vwsn_created_subscription_ = ::vl::Ptr<::vl::reflection::description::IValueSubscription>(new ::vl_workflow_global::__vwsnc6_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription(__vwsn_this_, this)); ::vl::__vwsn::EventAttach(::vl::__vwsn::This(__vwsn_created_subscription_.Obj())->ValueChanged, vl::Func(::vl_workflow_global::__vwsnf12_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_(this))); ::vl::__vwsn::This(__vwsn_this_)->AddSubscription(__vwsn_created_subscription_); } + { + auto __vwsn_created_subscription_ = ::vl::Ptr<::vl::reflection::description::IValueSubscription>(new ::vl_workflow_global::__vwsnc7_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription(this)); + ::vl::__vwsn::EventAttach(::vl::__vwsn::This(__vwsn_created_subscription_.Obj())->ValueChanged, vl::Func(::vl_workflow_global::__vwsnf13_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_(this))); + ::vl::__vwsn::This(__vwsn_this_)->AddSubscription(__vwsn_created_subscription_); + } } UnitTestSnapshotViewerWindowConstructor::UnitTestSnapshotViewerWindowConstructor() @@ -1754,6 +1929,7 @@ Class (::gaclib_controls::UnitTestSnapshotViewerWindowConstructor) , __vwsn_precompile_32(static_cast<::vl::presentation::compositions::GuiBoundsComposition*>(nullptr)) , treeViewFileNodes(static_cast<::vl::presentation::controls::GuiBindableTreeView*>(nullptr)) , textListFrames(static_cast<::vl::presentation::controls::GuiBindableTextList*>(nullptr)) + , labelLoading(static_cast<::vl::presentation::controls::GuiLabel*>(nullptr)) , scRendering(static_cast<::vl::presentation::controls::GuiScrollContainer*>(nullptr)) , treeViewDom(static_cast<::vl::presentation::controls::GuiBindableTreeView*>(nullptr)) { @@ -1939,27 +2115,21 @@ UnitTestSnapshotDomNode class UnitTestSnapshotDomNode : public Object, public virtual IUnitTestSnapshotDomNode { protected: - UnitTest_RenderingTrace& trace; - UnitTest_RenderingFrame& frame; - Ptr renderingDom; - WString name; - WString dom; - WString element; - List> children; + const UnitTest_RenderingTrace& trace; + const UnitTest_RenderingFrame& frame; + Ptr renderingDom; + WString name; + WString dom; + WString element; + List> children; + bool childrenLoaded = false; public: - UnitTestSnapshotDomNode(UnitTest_RenderingTrace& _trace, UnitTest_RenderingFrame& _frame, Ptr _renderingDom) + UnitTestSnapshotDomNode(const UnitTest_RenderingTrace& _trace, const UnitTest_RenderingFrame& _frame, Ptr _renderingDom) : trace(_trace) , frame(_frame) , renderingDom(_renderingDom) { - if (renderingDom->children) - { - for (auto child : *renderingDom->children.Obj()) - { - children.Add(Ptr(new UnitTestSnapshotDomNode(trace, frame, child))); - } - } } WString GetName() override @@ -2030,6 +2200,17 @@ UnitTestSnapshotDomNode LazyList> GetChildren() override { + if (!childrenLoaded) + { + childrenLoaded = true; + if (renderingDom->children) + { + for (auto child : *renderingDom->children.Obj()) + { + children.Add(Ptr(new UnitTestSnapshotDomNode(trace, frame, child))); + } + } + } return From(children).Cast(); } }; @@ -2042,36 +2223,51 @@ UnitTestSnapshotFrame { friend const remoteprotocol::UnitTest_RenderingFrame& GetRenderingFrame(Ptr frame); protected: - vint index; - UnitTest_RenderingTrace& trace; - UnitTest_RenderingFrame frame; - Ptr domRoot; + vint index; + UnitTest_RenderingTrace& trace; + FilePath frameFilePath; + WString frameName; + + Nullable frame; + Ptr domRoot; + bool initiated = false; public: - UnitTestSnapshotFrame(vint _index, UnitTest_RenderingTrace& _trace, UnitTest_RenderingFrame _frame) + UnitTestSnapshotFrame(vint _index, Nullable _frameName, UnitTest_RenderingTrace& _trace, FilePath _frameFilePath) : index(_index) , trace(_trace) - , frame(_frame) + , frameFilePath(_frameFilePath) + , frameName(_frameName ? _frameName.Value() : itow(_index)) { } WString GetName() override { - if (frame.frameName) - { - return frame.frameName.Value(); - } - else - { - return itow(index); - } + return frameName; } Ptr GetDom() override { - if (!domRoot) + if (!initiated) { - domRoot = Ptr(new UnitTestSnapshotDomNode(trace, frame, frame.root)); + initiated = true; + controls::GetApplication()->InvokeAsync([this]() + { + WString jsonText = File(frameFilePath).ReadAllTextByBom(); + Ptr jsonNode; + { + glr::json::Parser parser; + jsonNode = JsonParse(jsonText, parser); + } + UnitTest_RenderingFrame loadedFrame; + ConvertJsonToCustomType(jsonNode, loadedFrame); + controls::GetApplication()->InvokeInMainThread(controls::GetApplication()->GetMainWindow(), [this, loadedFrame = std::move(loadedFrame)]() + { + frame = std::move(loadedFrame); + domRoot = Ptr(new UnitTestSnapshotDomNode(trace, frame.Value(), frame.Value().root)); + DomChanged(); + }); + }); } return domRoot; } @@ -2079,7 +2275,7 @@ UnitTestSnapshotFrame const remoteprotocol::UnitTest_RenderingFrame& GetRenderingFrame(Ptr frame) { - return frame.Cast()->frame; + return frame.Cast()->frame.Value(); } /*********************************************************************** @@ -2112,7 +2308,9 @@ UnitTestSnapshotFileNode { for (auto [frame, index] : indexed(*renderingTrace->frames.Obj())) { - frames.Add(Ptr(new UnitTestSnapshotFrame(index, *renderingTrace.Obj(), frame))); + auto filePath = file.GetFilePath().GetFullPath(); + auto framePath = FilePath(filePath.Left(filePath.Length() - 5)) / (L"frame_" + itow(index) + L".json"); + frames.Add(Ptr(new UnitTestSnapshotFrame(index, frame.frameName, *renderingTrace.Obj(), framePath))); } } } @@ -2197,7 +2395,8 @@ UnitTestSnapshotFolderNode List folders; folder.GetFolders(folders); for (auto name : From(folders) - .Select([](auto&& folder) {return folder.GetFilePath().GetName(); }) + .Where([](auto&& folder) { return !File(folder.GetFilePath().GetFullPath() + L".json").Exists(); }) + .Select([](auto&& folder) { return folder.GetFilePath().GetName(); }) .OrderBySelf()) { children->Add(Ptr(new UnitTestSnapshotFolderNode(folder.GetFilePath() / name))); diff --git a/Import/GacUI.UnitTest.UI.h b/Import/GacUI.UnitTest.UI.h index 8ca75f95..68386206 100644 --- a/Import/GacUI.UnitTest.UI.h +++ b/Import/GacUI.UnitTest.UI.h @@ -41,6 +41,7 @@ namespace vl_workflow_global struct __vwsnf10_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_; struct __vwsnf11_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_; struct __vwsnf12_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_; + struct __vwsnf13_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_; struct __vwsnf1_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_; struct __vwsnf2_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_; struct __vwsnf3_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_; @@ -56,7 +57,8 @@ namespace vl_workflow_global class __vwsnc4_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription; class __vwsnc5_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription; class __vwsnc6_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription; - class __vwsnc7_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerStrings___vwsn_ls_en_US_BuildStrings__gaclib_controls_IUnitTestSnapshotViewerStringsStrings; + class __vwsnc7_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription; + class __vwsnc8_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerStrings___vwsn_ls_en_US_BuildStrings__gaclib_controls_IUnitTestSnapshotViewerStringsStrings; } namespace __vwsn_enums @@ -117,6 +119,7 @@ namespace gaclib_controls public: virtual ::vl::WString GetName() = 0; virtual ::vl::Ptr<::gaclib_controls::IUnitTestSnapshotDomNode> GetDom() = 0; + ::vl::Event DomChanged; }; class IUnitTestSnapshotViewerStringsStrings : public virtual ::vl::reflection::IDescriptable, public ::vl::reflection::Description @@ -139,7 +142,7 @@ namespace gaclib_controls class UnitTestSnapshotViewerStrings : public ::vl::Object, public ::vl::reflection::Description { - friend class ::vl_workflow_global::__vwsnc7_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerStrings___vwsn_ls_en_US_BuildStrings__gaclib_controls_IUnitTestSnapshotViewerStringsStrings; + friend class ::vl_workflow_global::__vwsnc8_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerStrings___vwsn_ls_en_US_BuildStrings__gaclib_controls_IUnitTestSnapshotViewerStringsStrings; #ifdef VCZH_DESCRIPTABLEOBJECT_WITH_METADATA friend struct ::vl::reflection::description::CustomTypeDescriptorSelector; #endif @@ -158,9 +161,11 @@ namespace gaclib_controls friend class ::vl_workflow_global::__vwsnc4_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription; friend class ::vl_workflow_global::__vwsnc5_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription; friend class ::vl_workflow_global::__vwsnc6_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription; + friend class ::vl_workflow_global::__vwsnc7_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription; friend struct ::vl_workflow_global::__vwsnf10_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_; friend struct ::vl_workflow_global::__vwsnf11_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_; friend struct ::vl_workflow_global::__vwsnf12_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_; + friend struct ::vl_workflow_global::__vwsnf13_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_; friend struct ::vl_workflow_global::__vwsnf1_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_; friend struct ::vl_workflow_global::__vwsnf2_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_; friend struct ::vl_workflow_global::__vwsnf3_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_; @@ -211,6 +216,7 @@ namespace gaclib_controls ::vl::presentation::compositions::GuiBoundsComposition* __vwsn_precompile_32; ::vl::presentation::controls::GuiBindableTreeView* treeViewFileNodes; ::vl::presentation::controls::GuiBindableTextList* textListFrames; + ::vl::presentation::controls::GuiLabel* labelLoading; ::vl::presentation::controls::GuiScrollContainer* scRendering; ::vl::presentation::controls::GuiBindableTreeView* treeViewDom; void __vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize(::gaclib_controls::UnitTestSnapshotViewerWindow* __vwsn_this_); @@ -227,9 +233,11 @@ namespace gaclib_controls friend class ::vl_workflow_global::__vwsnc4_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription; friend class ::vl_workflow_global::__vwsnc5_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription; friend class ::vl_workflow_global::__vwsnc6_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription; + friend class ::vl_workflow_global::__vwsnc7_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription; friend struct ::vl_workflow_global::__vwsnf10_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_; friend struct ::vl_workflow_global::__vwsnf11_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_; friend struct ::vl_workflow_global::__vwsnf12_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_; + friend struct ::vl_workflow_global::__vwsnf13_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_; friend struct ::vl_workflow_global::__vwsnf1_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_; friend struct ::vl_workflow_global::__vwsnf2_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_; friend struct ::vl_workflow_global::__vwsnf3_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_; @@ -306,6 +314,15 @@ Closures void operator()(const ::vl::reflection::description::Value& __vwsn_value_) const; }; + struct __vwsnf13_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_ + { + ::gaclib_controls::UnitTestSnapshotViewerWindowConstructor* __vwsnthis_0; + + __vwsnf13_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_(::gaclib_controls::UnitTestSnapshotViewerWindowConstructor* __vwsnctorthis_0); + + void operator()(const ::vl::reflection::description::Value& __vwsn_value_) const; + }; + struct __vwsnf1_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize_ { ::gaclib_controls::UnitTestSnapshotViewerWindowConstructor* __vwsnthis_0; @@ -412,12 +429,15 @@ Closures __vwsnc2_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription(::gaclib_controls::UnitTestSnapshotViewerWindowConstructor* __vwsnctorthis_0); - ::vl::presentation::controls::GuiBindableTextList* __vwsn_bind_cache_0 = nullptr; + ::gaclib_controls::UnitTestSnapshotViewerWindow* __vwsn_bind_cache_0 = nullptr; + ::gaclib_controls::UnitTestSnapshotViewerWindow* __vwsn_bind_cache_1 = nullptr; ::vl::Ptr<::vl::reflection::description::IEventHandler> __vwsn_bind_handler_0_0; + ::vl::Ptr<::vl::reflection::description::IEventHandler> __vwsn_bind_handler_1_0; bool __vwsn_bind_opened_ = false; bool __vwsn_bind_closed_ = false; void __vwsn_bind_activator_(); void __vwsn_bind_callback_0_0(::vl::presentation::compositions::GuiGraphicsComposition* __vwsn_bind_callback_argument_0, ::vl::presentation::compositions::GuiEventArgs* __vwsn_bind_callback_argument_1); + void __vwsn_bind_callback_1_0(::vl::presentation::compositions::GuiGraphicsComposition* __vwsn_bind_callback_argument_0, ::vl::presentation::compositions::GuiEventArgs* __vwsn_bind_callback_argument_1); bool Open() override; bool Update() override; bool Close() override; @@ -430,12 +450,15 @@ Closures __vwsnc3_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription(::gaclib_controls::UnitTestSnapshotViewerWindowConstructor* __vwsnctorthis_0); - ::vl::presentation::controls::GuiBindableTreeView* __vwsn_bind_cache_0 = nullptr; + ::vl::presentation::controls::GuiBindableTextList* __vwsn_bind_cache_0 = nullptr; + ::vl::Ptr<::gaclib_controls::IUnitTestSnapshotFrame> __vwsn_bind_cache_1; ::vl::Ptr<::vl::reflection::description::IEventHandler> __vwsn_bind_handler_0_0; + ::vl::Ptr<::vl::reflection::description::IEventHandler> __vwsn_bind_handler_1_0; bool __vwsn_bind_opened_ = false; bool __vwsn_bind_closed_ = false; void __vwsn_bind_activator_(); void __vwsn_bind_callback_0_0(::vl::presentation::compositions::GuiGraphicsComposition* __vwsn_bind_callback_argument_0, ::vl::presentation::compositions::GuiEventArgs* __vwsn_bind_callback_argument_1); + void __vwsn_bind_callback_1_0(); bool Open() override; bool Update() override; bool Close() override; @@ -461,11 +484,29 @@ Closures class __vwsnc5_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription : public ::vl::Object, public virtual ::vl::reflection::description::IValueSubscription { + public: + ::gaclib_controls::UnitTestSnapshotViewerWindowConstructor* __vwsnthis_0; + + __vwsnc5_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription(::gaclib_controls::UnitTestSnapshotViewerWindowConstructor* __vwsnctorthis_0); + + ::vl::presentation::controls::GuiBindableTreeView* __vwsn_bind_cache_0 = nullptr; + ::vl::Ptr<::vl::reflection::description::IEventHandler> __vwsn_bind_handler_0_0; + bool __vwsn_bind_opened_ = false; + bool __vwsn_bind_closed_ = false; + void __vwsn_bind_activator_(); + void __vwsn_bind_callback_0_0(::vl::presentation::compositions::GuiGraphicsComposition* __vwsn_bind_callback_argument_0, ::vl::presentation::compositions::GuiEventArgs* __vwsn_bind_callback_argument_1); + bool Open() override; + bool Update() override; + bool Close() override; + }; + + class __vwsnc6_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription : public ::vl::Object, public virtual ::vl::reflection::description::IValueSubscription + { public: ::gaclib_controls::UnitTestSnapshotViewerWindow* __vwsn_this_; ::gaclib_controls::UnitTestSnapshotViewerWindowConstructor* __vwsnthis_0; - __vwsnc5_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription(::gaclib_controls::UnitTestSnapshotViewerWindow* __vwsnctor___vwsn_this_, ::gaclib_controls::UnitTestSnapshotViewerWindowConstructor* __vwsnctorthis_0); + __vwsnc6_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription(::gaclib_controls::UnitTestSnapshotViewerWindow* __vwsnctor___vwsn_this_, ::gaclib_controls::UnitTestSnapshotViewerWindowConstructor* __vwsnctorthis_0); ::gaclib_controls::UnitTestSnapshotViewerWindow* __vwsn_bind_cache_0 = nullptr; ::vl::Ptr<::vl::reflection::description::IEventHandler> __vwsn_bind_handler_0_0; @@ -478,12 +519,12 @@ Closures bool Close() override; }; - class __vwsnc6_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription : public ::vl::Object, public virtual ::vl::reflection::description::IValueSubscription + class __vwsnc7_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription : public ::vl::Object, public virtual ::vl::reflection::description::IValueSubscription { public: ::gaclib_controls::UnitTestSnapshotViewerWindowConstructor* __vwsnthis_0; - __vwsnc6_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription(::gaclib_controls::UnitTestSnapshotViewerWindowConstructor* __vwsnctorthis_0); + __vwsnc7_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerWindowConstructor___vwsn_gaclib_controls_UnitTestSnapshotViewerWindow_Initialize__vl_reflection_description_IValueSubscription(::gaclib_controls::UnitTestSnapshotViewerWindowConstructor* __vwsnctorthis_0); ::vl::presentation::controls::GuiApplication* __vwsn_bind_cache_0 = nullptr; ::vl::Ptr<::vl::reflection::description::IEventHandler> __vwsn_bind_handler_0_0; @@ -496,10 +537,10 @@ Closures bool Close() override; }; - class __vwsnc7_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerStrings___vwsn_ls_en_US_BuildStrings__gaclib_controls_IUnitTestSnapshotViewerStringsStrings : public ::vl::Object, public virtual ::gaclib_controls::IUnitTestSnapshotViewerStringsStrings + class __vwsnc8_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerStrings___vwsn_ls_en_US_BuildStrings__gaclib_controls_IUnitTestSnapshotViewerStringsStrings : public ::vl::Object, public virtual ::gaclib_controls::IUnitTestSnapshotViewerStringsStrings { public: - __vwsnc7_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerStrings___vwsn_ls_en_US_BuildStrings__gaclib_controls_IUnitTestSnapshotViewerStringsStrings(); + __vwsnc8_GuiUnitTestSnapshotViewer_gaclib_controls_UnitTestSnapshotViewerStrings___vwsn_ls_en_US_BuildStrings__gaclib_controls_IUnitTestSnapshotViewerStringsStrings(); ::vl::WString WindowTitle() override; }; @@ -578,6 +619,7 @@ namespace gaclib_controls vl::presentation::compositions::GuiBoundsComposition* rootComposition = nullptr; vl::presentation::compositions::GuiBoundsComposition* selectedComposition = nullptr; vl::presentation::compositions::GuiBoundsComposition* highlightComposition = nullptr; + vl::vint loadingFrameVersion = 0; void Highlight(vl::presentation::compositions::GuiBoundsComposition*& target, vl::presentation::remoteprotocol::RenderingDom& renderingDom, vl::presentation::Color color); void textListFrames_SelectionChanged(vl::presentation::compositions::GuiGraphicsComposition* sender, vl::presentation::compositions::GuiEventArgs& arguments); diff --git a/Import/GacUI.UnitTest.UIReflection.cpp b/Import/GacUI.UnitTest.UIReflection.cpp index 34f7ecd4..d5daa7e7 100644 --- a/Import/GacUI.UnitTest.UIReflection.cpp +++ b/Import/GacUI.UnitTest.UIReflection.cpp @@ -81,7 +81,8 @@ namespace vl CLASS_MEMBER_BASE(::vl::reflection::IDescriptable) CLASS_MEMBER_METHOD(GetDom, NO_PARAMETER) CLASS_MEMBER_METHOD(GetName, NO_PARAMETER) - CLASS_MEMBER_PROPERTY_READONLY(Dom, GetDom) + CLASS_MEMBER_EVENT(DomChanged) + CLASS_MEMBER_PROPERTY_EVENT_READONLY(Dom, GetDom, DomChanged) CLASS_MEMBER_PROPERTY_READONLY(Name, GetName) END_INTERFACE_MEMBER(::gaclib_controls::IUnitTestSnapshotFrame) @@ -164,6 +165,7 @@ namespace vl CLASS_MEMBER_FIELD(__vwsn_precompile_8) CLASS_MEMBER_FIELD(__vwsn_precompile_9) CLASS_MEMBER_FIELD(ViewModel) + CLASS_MEMBER_FIELD(labelLoading) CLASS_MEMBER_FIELD(scRendering) CLASS_MEMBER_FIELD(self) CLASS_MEMBER_FIELD(textListFrames) diff --git a/Import/GacUI.UnitTest.cpp b/Import/GacUI.UnitTest.cpp index 193a86f7..fdc778dc 100644 --- a/Import/GacUI.UnitTest.cpp +++ b/Import/GacUI.UnitTest.cpp @@ -4,747 +4,6 @@ DEVELOPER: Zihan Chen(vczh) ***********************************************************************/ #include "GacUI.UnitTest.h" -/*********************************************************************** -.\GUIUNITTESTPROTOCOL_RENDERING _DOCUMENT.CPP -***********************************************************************/ - -namespace vl::presentation::unittest -{ - using namespace collections; - using namespace remoteprotocol; - -/*********************************************************************** -Helper Functions for Document Paragraph -***********************************************************************/ - - vint GetFontSizeForPosition( - const DocumentParagraphState& state, - vint pos, - Nullable>& inlineProp) - { -#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::GetFontSizeForPosition(...)#" - inlineProp.Reset(); - - for (auto [range, prop] : state.mergedRuns) - { - if (pos >= range.caretBegin && pos < range.caretEnd) - { - if (auto textProp = prop.TryGet()) - { - return textProp->fontProperties.size; - } - if (auto objProp = prop.TryGet()) - { - inlineProp = { range.caretEnd - range.caretBegin,*objProp }; - return 0; - } - } - } - CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Every character is expected to have a font."); -#undef ERROR_MESSAGE_PREFIX - } - - double GetCharacterWidth(wchar_t c, vint fontSize) - { - return (c < 128 ? 0.6 : 1.0) * fontSize; - } - - void CalculateParagraphLayout(DocumentParagraphState& state) - { - state.characterLayouts.Clear(); - state.lines.Clear(); - state.cachedSize = Size(0, 16); - state.cachedInlineObjectBounds.Clear(); - - const WString& text = state.text; - - if (text.Length() == 0) - { - // Empty paragraph has default size - DocumentParagraphLineInfo line; - line.startPos = 0; - line.endPos = 0; - line.y = 0; - line.height = 16; // Default: 12 (font) + 4 - line.baseline = 12; - line.width = 0; - state.lines.Add(line); - return; - } - - // First pass: calculate per-character metrics - struct TempCharInfo - { - double x; - double width; - vint height; - Nullable inlineObjectProp; - }; - List tempChars; - List> lineRanges; // [start, end) for each line - - double currentX = 0; - vint currentLineStart = 0; - - for (vint i = 0; i < text.Length(); i++) - { - wchar_t c = text[i]; - TempCharInfo info = { currentX, 0, 0, {} }; - - // Handle \r - zero width, no line break - if (c == L'\r') - { - tempChars.Add(info); - continue; - } - - // Get character properties - Nullable> inlinePair; - vint fontSize = GetFontSizeForPosition(state, i, inlinePair); - - if (inlinePair) - { - auto& prop = inlinePair.Value().value; - info.width = (double)prop.size.x; - info.height = prop.size.y; - info.inlineObjectProp = inlinePair.Value().value; - } - else - { - if (fontSize <= 0) fontSize = 12; - info.width = GetCharacterWidth(c, fontSize); - info.height = fontSize; - } - - // Handle \n - always break line - if (c == L'\n') - { - info.width = 0; - tempChars.Add(info); - lineRanges.Add({ currentLineStart, i + 1 }); - currentLineStart = i + 1; - currentX = 0; - continue; - } - - // Check word wrap - if (state.wrapLine && state.maxWidth > 0 && currentX > 0) - { - if (currentX + info.width > state.maxWidth) - { - lineRanges.Add({ currentLineStart, i }); - currentLineStart = i; - currentX = 0; - } - } - - info.x = currentX; - tempChars.Add(info); - currentX += info.width; - - if(inlinePair) - { - i += inlinePair.Value().key - 1; - } - } - - // Add final line - if (currentLineStart <= text.Length()) - { - lineRanges.Add({ currentLineStart, text.Length() }); - } - - // Handle empty case - if (lineRanges.Count() == 0) - { - lineRanges.Add({ 0, 0 }); - } - - // Second pass: calculate line heights using baseline alignment - vint currentY = 0; - for (auto [lineStart, lineEnd] : lineRanges) - { - vint maxAboveBaseline = 0; - vint maxBelowBaseline = 0; - - for (vint i = lineStart; i < lineEnd && i < tempChars.Count(); i++) - { - auto& info = tempChars[i]; - if (info.inlineObjectProp) - { - auto&& prop = info.inlineObjectProp.Value(); - vint baseline = prop.baseline; - if (baseline == -1) - baseline = info.height; - vint above = baseline; - vint below = info.height - baseline; - if (above < 0) above = 0; - if (below < 0) below = 0; - if (maxAboveBaseline < above) - maxAboveBaseline = above; - if (maxBelowBaseline < below) - maxBelowBaseline = below; - } - else - { - if (maxAboveBaseline < info.height) - maxAboveBaseline = info.height; - } - } - - DocumentParagraphLineInfo line; - line.startPos = lineStart; - line.endPos = lineEnd; - line.y = currentY; - line.height = maxAboveBaseline + maxBelowBaseline + 4; - line.baseline = maxAboveBaseline; - - // Calculate line width - double lineWidth = 0; - for (vint i = lineStart; i < lineEnd && i < tempChars.Count(); i++) - { - double endX = tempChars[i].x + tempChars[i].width; - if (endX > lineWidth) lineWidth = endX; - } - line.width = (vint)lineWidth; - - // Fill inline object bounds - - for (vint i = lineStart; i < lineEnd && i < tempChars.Count(); i++) - { - auto& info = tempChars[i]; - if (info.inlineObjectProp) - { - auto&& prop = info.inlineObjectProp.Value(); - if (prop.callbackId != -1) - { - vint baseline = prop.baseline; - if (baseline == -1) - baseline = info.height; - vint y = line.y + 2 + line.baseline - baseline; - state.cachedInlineObjectBounds.Add(prop.callbackId, Rect(Point((vint)info.x, y), prop.size)); - } - } - } - - state.lines.Add(line); - currentY += line.height; - } - - // Third pass: create final character layouts with line indices - vint lineIdx = 0; - for (vint i = 0; i < tempChars.Count(); i++) - { - while (lineIdx < state.lines.Count() - 1 && i >= state.lines[lineIdx].endPos) - { - lineIdx++; - } - DocumentParagraphCharLayout cl; - cl.x = tempChars[i].x; - cl.width = tempChars[i].width; - cl.lineIndex = lineIdx; - cl.height = tempChars[i].height; - state.characterLayouts.Add(cl); - } - - // Calculate total size - vint maxWidth = 0; - for (auto&& line : state.lines) - { - if (line.width > maxWidth) maxWidth = line.width; - } - state.cachedSize = Size(maxWidth, currentY > 0 ? currentY : 16); - } - -/*********************************************************************** -IGuiRemoteProtocolMessages (Elements - Document) -***********************************************************************/ - - void UnitTestRemoteProtocol_Rendering::Impl_RendererUpdateElement_DocumentParagraph(vint id, const ElementDesc_DocumentParagraph& arguments) - { -#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_RendererUpdateElement_DocumentParagraph(vint, const ElementDesc_DocumentParagraph&)#" - vint index = paragraphStates.Keys().IndexOf(arguments.id); - CHECK_ERROR(index != -1, ERROR_MESSAGE_PREFIX L"Paragraph not created."); - auto state = paragraphStates.Values()[index]; - - // Apply text if provided (distinguish null vs empty string) - if (arguments.text) - { - state->text = arguments.text.Value(); - // Text changed - clear run maps since positions may be invalid - state->textRuns.Clear(); - state->inlineObjectRuns.Clear(); - } - - // Always update these - state->wrapLine = arguments.wrapLine; - state->maxWidth = arguments.maxWidth; - state->alignment = arguments.alignment; - - // Process removed inline objects first - if (arguments.removedInlineObjects) - { - for (auto callbackId : *arguments.removedInlineObjects.Obj()) - { - // Find the range for this inline object and reset it - for (auto [range, prop] : state->inlineObjectRuns) - { - if (prop.callbackId == callbackId) - { - elements::ResetInlineObjectRun(state->inlineObjectRuns, range); - break; - } - } - } - } - - // Apply runsDiff using helper functions - if (arguments.runsDiff) - { - for (auto run : *arguments.runsDiff.Obj()) - { - elements::CaretRange range{ run.caretBegin, run.caretEnd }; - - if (auto textProp = run.props.TryGet()) - { - elements::DocumentTextRunPropertyOverrides overrides; - overrides.textColor = textProp->textColor; - overrides.backgroundColor = textProp->backgroundColor; - overrides.fontFamily = textProp->fontProperties.fontFamily; - overrides.size = textProp->fontProperties.size; - // Convert bool flags back to TextStyle - elements::IGuiGraphicsParagraph::TextStyle style = (elements::IGuiGraphicsParagraph::TextStyle)0; - if (textProp->fontProperties.bold) style = (elements::IGuiGraphicsParagraph::TextStyle)((vint)style | (vint)elements::IGuiGraphicsParagraph::TextStyle::Bold); - if (textProp->fontProperties.italic) style = (elements::IGuiGraphicsParagraph::TextStyle)((vint)style | (vint)elements::IGuiGraphicsParagraph::TextStyle::Italic); - if (textProp->fontProperties.underline) style = (elements::IGuiGraphicsParagraph::TextStyle)((vint)style | (vint)elements::IGuiGraphicsParagraph::TextStyle::Underline); - if (textProp->fontProperties.strikeline) style = (elements::IGuiGraphicsParagraph::TextStyle)((vint)style | (vint)elements::IGuiGraphicsParagraph::TextStyle::Strikeline); - overrides.textStyle = style; - elements::AddTextRun(state->textRuns, range, overrides); - } - else if (auto inlineProp = run.props.TryGet()) - { - elements::AddInlineObjectRun(state->inlineObjectRuns, range, *inlineProp); - } - } - } - - // Merge runs to create final result - state->mergedRuns.Clear(); - elements::MergeRuns(state->textRuns, state->inlineObjectRuns, state->mergedRuns); - - // Recalculate layout - CalculateParagraphLayout(*state.Obj()); - - { - index = loggedTrace.createdElements->Keys().IndexOf(arguments.id); - CHECK_ERROR(index != -1, ERROR_MESSAGE_PREFIX L"Renderer with the specified id has not been created."); - - auto rendererType = loggedTrace.createdElements->Values()[index]; - CHECK_ERROR(rendererType == RendererType::DocumentParagraph, ERROR_MESSAGE_PREFIX L"Renderer with the specified id is not of the expected type."); - - index = lastElementDescs.Keys().IndexOf(arguments.id); - ElementDesc_DocumentParagraphFull element; - if (index != -1) - { - auto paragraphRef = lastElementDescs.Values()[index].TryGet(); - CHECK_ERROR(paragraphRef, ERROR_MESSAGE_PREFIX L"Renderer with the specified id is not of the expected type."); - element = *paragraphRef; - } - element.paragraph = arguments; - element.paragraph.text = state->text; - element.paragraph.createdInlineObjects = {}; - element.paragraph.removedInlineObjects = {}; - element.paragraph.runsDiff = Ptr(new List); - for (auto [range, props] : state->mergedRuns) - { - remoteprotocol::DocumentRun run; - run.caretBegin = range.caretBegin; - run.caretEnd = range.caretEnd; - run.props = props; - element.paragraph.runsDiff->Add(run); - } - lastElementDescs.Set(arguments.id, element); - } - - // Send response with calculated size and inline object bounds - UpdateElement_DocumentParagraphResponse response; - response.documentSize = state->cachedSize; - if (state->cachedInlineObjectBounds.Count() > 0) - { - response.inlineObjectBounds = Ptr(new Dictionary); - CopyFrom(*response.inlineObjectBounds.Obj(), state->cachedInlineObjectBounds); - } - - GetEvents()->RespondRendererUpdateElement_DocumentParagraph(id, response); -#undef ERROR_MESSAGE_PREFIX - } - - void UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_GetCaretBounds(vint id, const GetCaretBoundsRequest& arguments) - { -#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_GetCaretBounds(vint, const GetCaretBoundsRequest&)#" - vint index = paragraphStates.Keys().IndexOf(arguments.id); - CHECK_ERROR(index != -1, ERROR_MESSAGE_PREFIX L"No active paragraph."); - auto state = paragraphStates.Values()[index]; - - vint caret = arguments.caret; - - // Handle empty text - if (state->text.Length() == 0 || state->lines.Count() == 0) - { - auto& line = state->lines[0]; - GetEvents()->RespondDocumentParagraph_GetCaretBounds(id, Rect(Point(0, line.y), Size(0, line.height))); - return; - } - - // Clamp caret to valid range - if (caret < 0) caret = 0; - if (caret > state->text.Length()) caret = state->text.Length(); - - // Find which line the caret is on - vint lineIdx = 0; - for (vint i = 0; i < state->lines.Count(); i++) - { - if (caret >= state->lines[i].startPos && caret <= state->lines[i].endPos) - { - lineIdx = i; - break; - } - if (i == state->lines.Count() - 1) - { - lineIdx = i; - } - } - - auto& line = state->lines[lineIdx]; - - // Calculate x position - vint x = 0; - if (caret > 0 && caret <= state->characterLayouts.Count()) - { - // Caret is at the end of the previous character - auto& prevChar = state->characterLayouts[caret - 1]; - x = (vint)(prevChar.x + prevChar.width); - } - else if (caret < state->characterLayouts.Count()) - { - // Caret is at the start of this character - x = (vint)state->characterLayouts[caret].x; - } - - // Apply alignment offset - vint alignmentOffset = 0; - if (state->alignment == ElementHorizontalAlignment::Center) - { - alignmentOffset = (state->cachedSize.x - line.width) / 2; - } - else if (state->alignment == ElementHorizontalAlignment::Right) - { - alignmentOffset = state->cachedSize.x - line.width; - } - - Rect bounds(Point(x + alignmentOffset, line.y), Size(0, line.height)); - GetEvents()->RespondDocumentParagraph_GetCaretBounds(id, bounds); -#undef ERROR_MESSAGE_PREFIX - } - - void UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_GetCaret(vint id, const GetCaretRequest& arguments) - { -#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_GetCaret(vint, const GetCaretRequest&)#" - vint index = paragraphStates.Keys().IndexOf(arguments.id); - CHECK_ERROR(index != -1, ERROR_MESSAGE_PREFIX L"No active paragraph."); - auto state = paragraphStates.Values()[index]; - - vint caret = arguments.caret; - auto relPos = arguments.relativePosition; - GetCaretResponse response; - response.preferFrontSide = true; - - vint textLen = state->text.Length(); - - // Clamp caret - if (caret < 0) caret = 0; - if (caret > textLen) caret = textLen; - - // Find current line - vint lineIdx = 0; - for (vint i = 0; i < state->lines.Count(); i++) - { - if (caret >= state->lines[i].startPos && caret <= state->lines[i].endPos) - { - lineIdx = i; - break; - } - } - - using CRP = elements::IGuiGraphicsParagraph::CaretRelativePosition; - - switch (relPos) - { - case CRP::CaretFirst: - response.newCaret = 0; - break; - case CRP::CaretLast: - response.newCaret = textLen; - break; - case CRP::CaretLineFirst: - response.newCaret = state->lines[lineIdx].startPos; - break; - case CRP::CaretLineLast: - response.newCaret = state->lines[lineIdx].endPos; - if (response.newCaret > state->lines[lineIdx].startPos && response.newCaret > 0) - { - // Don't include CR/LF at end of line - while (response.newCaret > state->lines[lineIdx].startPos && response.newCaret > 0) - { - auto ch = state->text[response.newCaret - 1]; - if (ch == L'\r' || ch == L'\n') - { - response.newCaret--; - } - else - { - break; - } - } - } - break; - case CRP::CaretMoveLeft: - response.newCaret = caret > 0 ? caret - 1 : 0; - break; - case CRP::CaretMoveRight: - response.newCaret = caret < textLen ? caret + 1 : textLen; - break; - case CRP::CaretMoveUp: - if (lineIdx > 0) - { - // Calculate x offset in current line - vint xOffset = 0; - if (caret > 0 && caret <= state->characterLayouts.Count()) - { - auto& prevChar = state->characterLayouts[caret - 1]; - xOffset = (vint)(prevChar.x + prevChar.width); - } - // Find corresponding position in previous line - auto& prevLine = state->lines[lineIdx - 1]; - response.newCaret = prevLine.startPos; - for (vint i = prevLine.startPos; i < prevLine.endPos && i < state->characterLayouts.Count(); i++) - { - auto& ch = state->characterLayouts[i]; - if (ch.x + ch.width / 2 > xOffset) - break; - response.newCaret = i + 1; - } - } - else - { - response.newCaret = caret; - } - break; - case CRP::CaretMoveDown: - if (lineIdx < state->lines.Count() - 1) - { - // Calculate x offset in current line - vint xOffset = 0; - if (caret > 0 && caret <= state->characterLayouts.Count()) - { - auto& prevChar = state->characterLayouts[caret - 1]; - xOffset = (vint)(prevChar.x + prevChar.width); - } - // Find corresponding position in next line - auto& nextLine = state->lines[lineIdx + 1]; - response.newCaret = nextLine.startPos; - for (vint i = nextLine.startPos; i < nextLine.endPos && i < state->characterLayouts.Count(); i++) - { - auto& ch = state->characterLayouts[i]; - if (ch.x + ch.width / 2 > xOffset) - break; - response.newCaret = i + 1; - } - } - else - { - response.newCaret = caret; - } - break; - default: - response.newCaret = caret; - break; - } - - GetEvents()->RespondDocumentParagraph_GetCaret(id, response); -#undef ERROR_MESSAGE_PREFIX - } - - void UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_GetNearestCaretFromTextPos(vint id, const GetCaretBoundsRequest& arguments) - { -#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_GetNearestCaretFromTextPos(vint, const GetCaretBoundsRequest&)#" - vint index = paragraphStates.Keys().IndexOf(arguments.id); - CHECK_ERROR(index != -1, ERROR_MESSAGE_PREFIX L"No active paragraph."); - auto state = paragraphStates.Values()[index]; - - vint textPos = arguments.caret; - vint textLen = state->text.Length(); - - // Clamp to valid range - if (textPos < 0) textPos = 0; - if (textPos > textLen) textPos = textLen; - - // For simple implementation, text position equals caret position - GetEvents()->RespondDocumentParagraph_GetNearestCaretFromTextPos(id, textPos); -#undef ERROR_MESSAGE_PREFIX - } - - void UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_GetInlineObjectFromPoint(vint id, const GetInlineObjectFromPointRequest& arguments) - { -#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_GetInlineObjectFromPoint(vint, const GetInlineObjectFromPointRequest&)#" - vint index = paragraphStates.Keys().IndexOf(arguments.id); - CHECK_ERROR(index != -1, ERROR_MESSAGE_PREFIX L"No active paragraph."); - auto state = paragraphStates.Values()[index]; - - Point pt = arguments.point; - Nullable result; - - // Find the line containing the Y coordinate - vint lineIdx = -1; - for (vint i = 0; i < state->lines.Count(); i++) - { - if (pt.y >= state->lines[i].y && pt.y < state->lines[i].y + state->lines[i].height) - { - lineIdx = i; - break; - } - } - - if (lineIdx >= 0) - { - auto& line = state->lines[lineIdx]; - - // Apply alignment offset - vint alignmentOffset = 0; - if (state->alignment == ElementHorizontalAlignment::Center) - { - alignmentOffset = (state->cachedSize.x - line.width) / 2; - } - else if (state->alignment == ElementHorizontalAlignment::Right) - { - alignmentOffset = state->cachedSize.x - line.width; - } - vint relativeX = pt.x - alignmentOffset; - - // Check each character in the line - for (vint i = line.startPos; i < line.endPos && i < state->characterLayouts.Count(); i++) - { - auto& ch = state->characterLayouts[i]; - if (ch.isInlineObject && relativeX >= ch.x && relativeX < ch.x + ch.width) - { - // Found an inline object - look up its properties - for (auto [range, prop] : state->mergedRuns) - { - if (i >= range.caretBegin && i < range.caretEnd) - { - if (auto inlineProp = prop.TryGet()) - { - remoteprotocol::DocumentRun run; - run.caretBegin = range.caretBegin; - run.caretEnd = range.caretEnd; - run.props = *inlineProp; - result = run; - break; - } - } - } - break; - } - } - } - - GetEvents()->RespondDocumentParagraph_GetInlineObjectFromPoint(id, result); -#undef ERROR_MESSAGE_PREFIX - } - - void UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_IsValidCaret(vint id, const IsValidCaretRequest& arguments) - { -#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_IsValidCaret(vint, const IsValidCaretRequest&)#" - vint index = paragraphStates.Keys().IndexOf(arguments.id); - if (index == -1) - { - GetEvents()->RespondDocumentParagraph_IsValidCaret(id, false); - return; - } - auto state = paragraphStates.Values()[index]; - - vint caret = arguments.caret; - - // Check range: valid positions are 0 to text.Length() inclusive - if (caret < 0 || caret > state->text.Length()) - { - GetEvents()->RespondDocumentParagraph_IsValidCaret(id, false); - return; - } - - // Check if position is inside an inline object (not at the beginning) - // Inline objects occupy a range [caretBegin, caretEnd) - // Position at caretBegin is valid (cursor can be placed before the object) - // Positions inside (caretBegin < pos < caretEnd) are invalid - for (auto&& [range, _] : state->inlineObjectRuns) - { - if (caret > range.caretBegin && caret < range.caretEnd) - { - GetEvents()->RespondDocumentParagraph_IsValidCaret(id, false); - return; - } - } - - GetEvents()->RespondDocumentParagraph_IsValidCaret(id, true); -#undef ERROR_MESSAGE_PREFIX - } - - void UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_OpenCaret(const OpenCaretRequest& arguments) - { -#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_OpenCaret(const OpenCaretRequest&)#" - vint index = loggedTrace.createdElements->Keys().IndexOf(arguments.id); - CHECK_ERROR(index != -1, ERROR_MESSAGE_PREFIX L"Renderer with the specified id has not been created."); - - auto rendererType = loggedTrace.createdElements->Values()[index]; - CHECK_ERROR(rendererType == RendererType::DocumentParagraph, ERROR_MESSAGE_PREFIX L"Renderer with the specified id is not of the expected type."); - - index = lastElementDescs.Keys().IndexOf(arguments.id); - ElementDesc_DocumentParagraphFull element; - if (index != -1) - { - auto paragraphRef = lastElementDescs.Values()[index].TryGet(); - CHECK_ERROR(paragraphRef, ERROR_MESSAGE_PREFIX L"Renderer with the specified id is not of the expected type."); - element = *paragraphRef; - } - element.caret = arguments; - lastElementDescs.Set(arguments.id, element); -#undef ERROR_MESSAGE_PREFIX - } - - void UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_CloseCaret(const vint& arguments) - { -#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_CloseCaret(const vint&)#" - vint index = loggedTrace.createdElements->Keys().IndexOf(arguments); - CHECK_ERROR(index != -1, ERROR_MESSAGE_PREFIX L"Renderer with the specified id has not been created."); - - auto rendererType = loggedTrace.createdElements->Values()[index]; - CHECK_ERROR(rendererType == RendererType::DocumentParagraph, ERROR_MESSAGE_PREFIX L"Renderer with the specified id is not of the expected type."); - - index = lastElementDescs.Keys().IndexOf(arguments); - ElementDesc_DocumentParagraphFull element; - if (index != -1) - { - auto paragraphRef = lastElementDescs.Values()[index].TryGet(); - CHECK_ERROR(paragraphRef, ERROR_MESSAGE_PREFIX L"Renderer with the specified id is not of the expected type."); - element = *paragraphRef; - } - element.caret.Reset(); - lastElementDescs.Set(arguments, element); -#undef ERROR_MESSAGE_PREFIX - } -} - - /*********************************************************************** .\GUIUNITTESTPROTOCOL_RENDERING.CPP ***********************************************************************/ @@ -1404,6 +663,748 @@ IGuiRemoteProtocolMessages (Elements - Image) } +/*********************************************************************** +.\GUIUNITTESTPROTOCOL_RENDERING_DOCUMENT.CPP +***********************************************************************/ + +namespace vl::presentation::unittest +{ + using namespace collections; + using namespace remoteprotocol; + +/*********************************************************************** +Helper Functions for Document Paragraph +***********************************************************************/ + + vint GetFontSizeForPosition( + const DocumentParagraphState& state, + vint pos, + Nullable>& inlineProp) + { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::GetFontSizeForPosition(...)#" + inlineProp.Reset(); + + for (auto [range, prop] : state.mergedRuns) + { + if (pos >= range.caretBegin && pos < range.caretEnd) + { + if (auto textProp = prop.TryGet()) + { + return textProp->fontProperties.size; + } + if (auto objProp = prop.TryGet()) + { + inlineProp = { range.caretEnd - range.caretBegin,*objProp }; + return 0; + } + } + } + CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Every character is expected to have a font."); +#undef ERROR_MESSAGE_PREFIX + } + + double GetCharacterWidth(wchar_t c, vint fontSize) + { + return (c < 128 ? 0.6 : 1.0) * fontSize; + } + + void CalculateParagraphLayout(DocumentParagraphState& state) + { + state.characterLayouts.Clear(); + state.lines.Clear(); + state.cachedSize = Size(0, 16); + state.cachedInlineObjectBounds.Clear(); + + const WString& text = state.text; + + if (text.Length() == 0) + { + // Empty paragraph has default size + DocumentParagraphLineInfo line; + line.startPos = 0; + line.endPos = 0; + line.y = 0; + line.height = 16; // Default: 12 (font) + 4 + line.baseline = 12; + line.width = 0; + state.lines.Add(line); + return; + } + + // First pass: calculate per-character metrics + struct TempCharInfo + { + double x; + double width; + vint height; + Nullable inlineObjectProp; + }; + List tempChars; + List> lineRanges; // [start, end) for each line + + double currentX = 0; + vint currentLineStart = 0; + + for (vint i = 0; i < text.Length(); i++) + { + wchar_t c = text[i]; + TempCharInfo info = { currentX, 0, 0, {} }; + + // Handle \r - zero width, no line break + if (c == L'\r') + { + tempChars.Add(info); + continue; + } + + // Get character properties + Nullable> inlinePair; + vint fontSize = GetFontSizeForPosition(state, i, inlinePair); + + if (inlinePair) + { + auto& prop = inlinePair.Value().value; + info.width = (double)prop.size.x; + info.height = prop.size.y; + info.inlineObjectProp = inlinePair.Value().value; + } + else + { + if (fontSize <= 0) fontSize = 12; + info.width = GetCharacterWidth(c, fontSize); + info.height = fontSize; + } + + // Handle \n - always break line + if (c == L'\n') + { + info.width = 0; + tempChars.Add(info); + lineRanges.Add({ currentLineStart, i + 1 }); + currentLineStart = i + 1; + currentX = 0; + continue; + } + + // Check word wrap + if (state.wrapLine && state.maxWidth > 0 && currentX > 0) + { + if (currentX + info.width > state.maxWidth) + { + lineRanges.Add({ currentLineStart, i }); + currentLineStart = i; + currentX = 0; + } + } + + info.x = currentX; + tempChars.Add(info); + currentX += info.width; + + if(inlinePair) + { + i += inlinePair.Value().key - 1; + } + } + + // Add final line + if (currentLineStart <= text.Length()) + { + lineRanges.Add({ currentLineStart, text.Length() }); + } + + // Handle empty case + if (lineRanges.Count() == 0) + { + lineRanges.Add({ 0, 0 }); + } + + // Second pass: calculate line heights using baseline alignment + vint currentY = 0; + for (auto [lineStart, lineEnd] : lineRanges) + { + vint maxAboveBaseline = 0; + vint maxBelowBaseline = 0; + + for (vint i = lineStart; i < lineEnd && i < tempChars.Count(); i++) + { + auto& info = tempChars[i]; + if (info.inlineObjectProp) + { + auto&& prop = info.inlineObjectProp.Value(); + vint baseline = prop.baseline; + if (baseline == -1) + baseline = info.height; + vint above = baseline; + vint below = info.height - baseline; + if (above < 0) above = 0; + if (below < 0) below = 0; + if (maxAboveBaseline < above) + maxAboveBaseline = above; + if (maxBelowBaseline < below) + maxBelowBaseline = below; + } + else + { + if (maxAboveBaseline < info.height) + maxAboveBaseline = info.height; + } + } + + DocumentParagraphLineInfo line; + line.startPos = lineStart; + line.endPos = lineEnd; + line.y = currentY; + line.height = maxAboveBaseline + maxBelowBaseline + 4; + line.baseline = maxAboveBaseline; + + // Calculate line width + double lineWidth = 0; + for (vint i = lineStart; i < lineEnd && i < tempChars.Count(); i++) + { + double endX = tempChars[i].x + tempChars[i].width; + if (endX > lineWidth) lineWidth = endX; + } + line.width = (vint)lineWidth; + + // Fill inline object bounds + + for (vint i = lineStart; i < lineEnd && i < tempChars.Count(); i++) + { + auto& info = tempChars[i]; + if (info.inlineObjectProp) + { + auto&& prop = info.inlineObjectProp.Value(); + if (prop.callbackId != -1) + { + vint baseline = prop.baseline; + if (baseline == -1) + baseline = info.height; + vint y = line.y + 2 + line.baseline - baseline; + state.cachedInlineObjectBounds.Add(prop.callbackId, Rect(Point((vint)info.x, y), prop.size)); + } + } + } + + state.lines.Add(line); + currentY += line.height; + } + + // Third pass: create final character layouts with line indices + vint lineIdx = 0; + for (vint i = 0; i < tempChars.Count(); i++) + { + while (lineIdx < state.lines.Count() - 1 && i >= state.lines[lineIdx].endPos) + { + lineIdx++; + } + DocumentParagraphCharLayout cl; + cl.x = tempChars[i].x; + cl.width = tempChars[i].width; + cl.lineIndex = lineIdx; + cl.height = tempChars[i].height; + state.characterLayouts.Add(cl); + } + + // Calculate total size + vint maxWidth = 0; + for (auto&& line : state.lines) + { + if (line.width > maxWidth) maxWidth = line.width; + } + state.cachedSize = Size(maxWidth, currentY > 0 ? currentY : 16); + } + +/*********************************************************************** +IGuiRemoteProtocolMessages (Elements - Document) +***********************************************************************/ + + void UnitTestRemoteProtocol_Rendering::Impl_RendererUpdateElement_DocumentParagraph(vint id, const ElementDesc_DocumentParagraph& arguments) + { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_RendererUpdateElement_DocumentParagraph(vint, const ElementDesc_DocumentParagraph&)#" + vint index = paragraphStates.Keys().IndexOf(arguments.id); + CHECK_ERROR(index != -1, ERROR_MESSAGE_PREFIX L"Paragraph not created."); + auto state = paragraphStates.Values()[index]; + + // Apply text if provided (distinguish null vs empty string) + if (arguments.text) + { + state->text = arguments.text.Value(); + // Text changed - clear run maps since positions may be invalid + state->textRuns.Clear(); + state->inlineObjectRuns.Clear(); + } + + // Always update these + state->wrapLine = arguments.wrapLine; + state->maxWidth = arguments.maxWidth; + state->alignment = arguments.alignment; + + // Process removed inline objects first + if (arguments.removedInlineObjects) + { + for (auto callbackId : *arguments.removedInlineObjects.Obj()) + { + // Find the range for this inline object and reset it + for (auto [range, prop] : state->inlineObjectRuns) + { + if (prop.callbackId == callbackId) + { + elements::ResetInlineObjectRun(state->inlineObjectRuns, range); + break; + } + } + } + } + + // Apply runsDiff using helper functions + if (arguments.runsDiff) + { + for (auto run : *arguments.runsDiff.Obj()) + { + elements::CaretRange range{ run.caretBegin, run.caretEnd }; + + if (auto textProp = run.props.TryGet()) + { + elements::DocumentTextRunPropertyOverrides overrides; + overrides.textColor = textProp->textColor; + overrides.backgroundColor = textProp->backgroundColor; + overrides.fontFamily = textProp->fontProperties.fontFamily; + overrides.size = textProp->fontProperties.size; + // Convert bool flags back to TextStyle + elements::IGuiGraphicsParagraph::TextStyle style = (elements::IGuiGraphicsParagraph::TextStyle)0; + if (textProp->fontProperties.bold) style = (elements::IGuiGraphicsParagraph::TextStyle)((vint)style | (vint)elements::IGuiGraphicsParagraph::TextStyle::Bold); + if (textProp->fontProperties.italic) style = (elements::IGuiGraphicsParagraph::TextStyle)((vint)style | (vint)elements::IGuiGraphicsParagraph::TextStyle::Italic); + if (textProp->fontProperties.underline) style = (elements::IGuiGraphicsParagraph::TextStyle)((vint)style | (vint)elements::IGuiGraphicsParagraph::TextStyle::Underline); + if (textProp->fontProperties.strikeline) style = (elements::IGuiGraphicsParagraph::TextStyle)((vint)style | (vint)elements::IGuiGraphicsParagraph::TextStyle::Strikeline); + overrides.textStyle = style; + elements::AddTextRun(state->textRuns, range, overrides); + } + else if (auto inlineProp = run.props.TryGet()) + { + bool result = elements::AddInlineObjectRun(state->inlineObjectRuns, range, *inlineProp); + TEST_ASSERT(result); + } + } + } + + // Merge runs to create final result + state->mergedRuns.Clear(); + elements::MergeRuns(state->textRuns, state->inlineObjectRuns, state->mergedRuns); + + // Recalculate layout + CalculateParagraphLayout(*state.Obj()); + + { + index = loggedTrace.createdElements->Keys().IndexOf(arguments.id); + CHECK_ERROR(index != -1, ERROR_MESSAGE_PREFIX L"Renderer with the specified id has not been created."); + + auto rendererType = loggedTrace.createdElements->Values()[index]; + CHECK_ERROR(rendererType == RendererType::DocumentParagraph, ERROR_MESSAGE_PREFIX L"Renderer with the specified id is not of the expected type."); + + index = lastElementDescs.Keys().IndexOf(arguments.id); + ElementDesc_DocumentParagraphFull element; + if (index != -1) + { + auto paragraphRef = lastElementDescs.Values()[index].TryGet(); + CHECK_ERROR(paragraphRef, ERROR_MESSAGE_PREFIX L"Renderer with the specified id is not of the expected type."); + element = *paragraphRef; + } + element.paragraph = arguments; + element.paragraph.text = state->text; + element.paragraph.createdInlineObjects = {}; + element.paragraph.removedInlineObjects = {}; + element.paragraph.runsDiff = Ptr(new List); + for (auto [range, props] : state->mergedRuns) + { + remoteprotocol::DocumentRun run; + run.caretBegin = range.caretBegin; + run.caretEnd = range.caretEnd; + run.props = props; + element.paragraph.runsDiff->Add(run); + } + lastElementDescs.Set(arguments.id, element); + } + + // Send response with calculated size and inline object bounds + UpdateElement_DocumentParagraphResponse response; + response.documentSize = state->cachedSize; + if (state->cachedInlineObjectBounds.Count() > 0) + { + response.inlineObjectBounds = Ptr(new Dictionary); + CopyFrom(*response.inlineObjectBounds.Obj(), state->cachedInlineObjectBounds); + } + + GetEvents()->RespondRendererUpdateElement_DocumentParagraph(id, response); +#undef ERROR_MESSAGE_PREFIX + } + + void UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_GetCaretBounds(vint id, const GetCaretBoundsRequest& arguments) + { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_GetCaretBounds(vint, const GetCaretBoundsRequest&)#" + vint index = paragraphStates.Keys().IndexOf(arguments.id); + CHECK_ERROR(index != -1, ERROR_MESSAGE_PREFIX L"No active paragraph."); + auto state = paragraphStates.Values()[index]; + + vint caret = arguments.caret; + + // Handle empty text + if (state->text.Length() == 0 || state->lines.Count() == 0) + { + auto& line = state->lines[0]; + GetEvents()->RespondDocumentParagraph_GetCaretBounds(id, Rect(Point(0, line.y), Size(0, line.height))); + return; + } + + // Clamp caret to valid range + if (caret < 0) caret = 0; + if (caret > state->text.Length()) caret = state->text.Length(); + + // Find which line the caret is on + vint lineIdx = 0; + for (vint i = 0; i < state->lines.Count(); i++) + { + if (caret >= state->lines[i].startPos && caret <= state->lines[i].endPos) + { + lineIdx = i; + break; + } + if (i == state->lines.Count() - 1) + { + lineIdx = i; + } + } + + auto& line = state->lines[lineIdx]; + + // Calculate x position + vint x = 0; + if (caret > 0 && caret <= state->characterLayouts.Count()) + { + // Caret is at the end of the previous character + auto& prevChar = state->characterLayouts[caret - 1]; + x = (vint)(prevChar.x + prevChar.width); + } + else if (caret < state->characterLayouts.Count()) + { + // Caret is at the start of this character + x = (vint)state->characterLayouts[caret].x; + } + + // Apply alignment offset + vint alignmentOffset = 0; + if (state->alignment == ElementHorizontalAlignment::Center) + { + alignmentOffset = (state->cachedSize.x - line.width) / 2; + } + else if (state->alignment == ElementHorizontalAlignment::Right) + { + alignmentOffset = state->cachedSize.x - line.width; + } + + Rect bounds(Point(x + alignmentOffset, line.y), Size(0, line.height)); + GetEvents()->RespondDocumentParagraph_GetCaretBounds(id, bounds); +#undef ERROR_MESSAGE_PREFIX + } + + void UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_GetCaret(vint id, const GetCaretRequest& arguments) + { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_GetCaret(vint, const GetCaretRequest&)#" + vint index = paragraphStates.Keys().IndexOf(arguments.id); + CHECK_ERROR(index != -1, ERROR_MESSAGE_PREFIX L"No active paragraph."); + auto state = paragraphStates.Values()[index]; + + vint caret = arguments.caret; + auto relPos = arguments.relativePosition; + GetCaretResponse response; + response.preferFrontSide = true; + + vint textLen = state->text.Length(); + + // Clamp caret + if (caret < 0) caret = 0; + if (caret > textLen) caret = textLen; + + // Find current line + vint lineIdx = 0; + for (vint i = 0; i < state->lines.Count(); i++) + { + if (caret >= state->lines[i].startPos && caret <= state->lines[i].endPos) + { + lineIdx = i; + break; + } + } + + using CRP = elements::IGuiGraphicsParagraph::CaretRelativePosition; + + switch (relPos) + { + case CRP::CaretFirst: + response.newCaret = 0; + break; + case CRP::CaretLast: + response.newCaret = textLen; + break; + case CRP::CaretLineFirst: + response.newCaret = state->lines[lineIdx].startPos; + break; + case CRP::CaretLineLast: + response.newCaret = state->lines[lineIdx].endPos; + if (response.newCaret > state->lines[lineIdx].startPos && response.newCaret > 0) + { + // Don't include CR/LF at end of line + while (response.newCaret > state->lines[lineIdx].startPos && response.newCaret > 0) + { + auto ch = state->text[response.newCaret - 1]; + if (ch == L'\r' || ch == L'\n') + { + response.newCaret--; + } + else + { + break; + } + } + } + break; + case CRP::CaretMoveLeft: + response.newCaret = caret > 0 ? caret - 1 : 0; + break; + case CRP::CaretMoveRight: + response.newCaret = caret < textLen ? caret + 1 : textLen; + break; + case CRP::CaretMoveUp: + if (lineIdx > 0) + { + // Calculate x offset in current line + vint xOffset = 0; + if (caret > 0 && caret <= state->characterLayouts.Count()) + { + auto& prevChar = state->characterLayouts[caret - 1]; + xOffset = (vint)(prevChar.x + prevChar.width); + } + // Find corresponding position in previous line + auto& prevLine = state->lines[lineIdx - 1]; + response.newCaret = prevLine.startPos; + for (vint i = prevLine.startPos; i < prevLine.endPos && i < state->characterLayouts.Count(); i++) + { + auto& ch = state->characterLayouts[i]; + if (ch.x + ch.width / 2 > xOffset) + break; + response.newCaret = i + 1; + } + } + else + { + response.newCaret = caret; + } + break; + case CRP::CaretMoveDown: + if (lineIdx < state->lines.Count() - 1) + { + // Calculate x offset in current line + vint xOffset = 0; + if (caret > 0 && caret <= state->characterLayouts.Count()) + { + auto& prevChar = state->characterLayouts[caret - 1]; + xOffset = (vint)(prevChar.x + prevChar.width); + } + // Find corresponding position in next line + auto& nextLine = state->lines[lineIdx + 1]; + response.newCaret = nextLine.startPos; + for (vint i = nextLine.startPos; i < nextLine.endPos && i < state->characterLayouts.Count(); i++) + { + auto& ch = state->characterLayouts[i]; + if (ch.x + ch.width / 2 > xOffset) + break; + response.newCaret = i + 1; + } + } + else + { + response.newCaret = caret; + } + break; + default: + response.newCaret = caret; + break; + } + + GetEvents()->RespondDocumentParagraph_GetCaret(id, response); +#undef ERROR_MESSAGE_PREFIX + } + + void UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_GetNearestCaretFromTextPos(vint id, const GetCaretBoundsRequest& arguments) + { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_GetNearestCaretFromTextPos(vint, const GetCaretBoundsRequest&)#" + vint index = paragraphStates.Keys().IndexOf(arguments.id); + CHECK_ERROR(index != -1, ERROR_MESSAGE_PREFIX L"No active paragraph."); + auto state = paragraphStates.Values()[index]; + + vint textPos = arguments.caret; + vint textLen = state->text.Length(); + + // Clamp to valid range + if (textPos < 0) textPos = 0; + if (textPos > textLen) textPos = textLen; + + // For simple implementation, text position equals caret position + GetEvents()->RespondDocumentParagraph_GetNearestCaretFromTextPos(id, textPos); +#undef ERROR_MESSAGE_PREFIX + } + + void UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_GetInlineObjectFromPoint(vint id, const GetInlineObjectFromPointRequest& arguments) + { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_GetInlineObjectFromPoint(vint, const GetInlineObjectFromPointRequest&)#" + vint index = paragraphStates.Keys().IndexOf(arguments.id); + CHECK_ERROR(index != -1, ERROR_MESSAGE_PREFIX L"No active paragraph."); + auto state = paragraphStates.Values()[index]; + + Point pt = arguments.point; + Nullable result; + + // Find the line containing the Y coordinate + vint lineIdx = -1; + for (vint i = 0; i < state->lines.Count(); i++) + { + if (pt.y >= state->lines[i].y && pt.y < state->lines[i].y + state->lines[i].height) + { + lineIdx = i; + break; + } + } + + if (lineIdx >= 0) + { + auto& line = state->lines[lineIdx]; + + // Apply alignment offset + vint alignmentOffset = 0; + if (state->alignment == ElementHorizontalAlignment::Center) + { + alignmentOffset = (state->cachedSize.x - line.width) / 2; + } + else if (state->alignment == ElementHorizontalAlignment::Right) + { + alignmentOffset = state->cachedSize.x - line.width; + } + vint relativeX = pt.x - alignmentOffset; + + // Check each character in the line + for (vint i = line.startPos; i < line.endPos && i < state->characterLayouts.Count(); i++) + { + auto& ch = state->characterLayouts[i]; + if (ch.isInlineObject && relativeX >= ch.x && relativeX < ch.x + ch.width) + { + // Found an inline object - look up its properties + for (auto [range, prop] : state->mergedRuns) + { + if (i >= range.caretBegin && i < range.caretEnd) + { + if (auto inlineProp = prop.TryGet()) + { + remoteprotocol::DocumentRun run; + run.caretBegin = range.caretBegin; + run.caretEnd = range.caretEnd; + run.props = *inlineProp; + result = run; + break; + } + } + } + break; + } + } + } + + GetEvents()->RespondDocumentParagraph_GetInlineObjectFromPoint(id, result); +#undef ERROR_MESSAGE_PREFIX + } + + void UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_IsValidCaret(vint id, const IsValidCaretRequest& arguments) + { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_IsValidCaret(vint, const IsValidCaretRequest&)#" + vint index = paragraphStates.Keys().IndexOf(arguments.id); + if (index == -1) + { + GetEvents()->RespondDocumentParagraph_IsValidCaret(id, false); + return; + } + auto state = paragraphStates.Values()[index]; + + vint caret = arguments.caret; + + // Check range: valid positions are 0 to text.Length() inclusive + if (caret < 0 || caret > state->text.Length()) + { + GetEvents()->RespondDocumentParagraph_IsValidCaret(id, false); + return; + } + + // Check if position is inside an inline object (not at the beginning) + // Inline objects occupy a range [caretBegin, caretEnd) + // Position at caretBegin is valid (cursor can be placed before the object) + // Positions inside (caretBegin < pos < caretEnd) are invalid + for (auto&& [range, _] : state->inlineObjectRuns) + { + if (caret > range.caretBegin && caret < range.caretEnd) + { + GetEvents()->RespondDocumentParagraph_IsValidCaret(id, false); + return; + } + } + + GetEvents()->RespondDocumentParagraph_IsValidCaret(id, true); +#undef ERROR_MESSAGE_PREFIX + } + + void UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_OpenCaret(const OpenCaretRequest& arguments) + { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_OpenCaret(const OpenCaretRequest&)#" + vint index = loggedTrace.createdElements->Keys().IndexOf(arguments.id); + CHECK_ERROR(index != -1, ERROR_MESSAGE_PREFIX L"Renderer with the specified id has not been created."); + + auto rendererType = loggedTrace.createdElements->Values()[index]; + CHECK_ERROR(rendererType == RendererType::DocumentParagraph, ERROR_MESSAGE_PREFIX L"Renderer with the specified id is not of the expected type."); + + index = lastElementDescs.Keys().IndexOf(arguments.id); + ElementDesc_DocumentParagraphFull element; + if (index != -1) + { + auto paragraphRef = lastElementDescs.Values()[index].TryGet(); + CHECK_ERROR(paragraphRef, ERROR_MESSAGE_PREFIX L"Renderer with the specified id is not of the expected type."); + element = *paragraphRef; + } + element.caret = arguments; + lastElementDescs.Set(arguments.id, element); +#undef ERROR_MESSAGE_PREFIX + } + + void UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_CloseCaret(const vint& arguments) + { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_CloseCaret(const vint&)#" + vint index = loggedTrace.createdElements->Keys().IndexOf(arguments); + CHECK_ERROR(index != -1, ERROR_MESSAGE_PREFIX L"Renderer with the specified id has not been created."); + + auto rendererType = loggedTrace.createdElements->Values()[index]; + CHECK_ERROR(rendererType == RendererType::DocumentParagraph, ERROR_MESSAGE_PREFIX L"Renderer with the specified id is not of the expected type."); + + index = lastElementDescs.Keys().IndexOf(arguments); + ElementDesc_DocumentParagraphFull element; + if (index != -1) + { + auto paragraphRef = lastElementDescs.Values()[index].TryGet(); + CHECK_ERROR(paragraphRef, ERROR_MESSAGE_PREFIX L"Renderer with the specified id is not of the expected type."); + element = *paragraphRef; + } + element.caret.Reset(); + lastElementDescs.Set(arguments, element); +#undef ERROR_MESSAGE_PREFIX + } +} + + /*********************************************************************** .\GUIUNITTESTPROTOCOL_SHARED.CPP ***********************************************************************/ @@ -1552,6 +1553,22 @@ File GacUIUnitTest_PrepareSnapshotFile(const WString& appName, const WString& ex #undef ERROR_MESSAGE_PREFIX } +Folder GacUIUnitTest_PrepareSnapshotFramesFolder(const WString& appName) +{ +#define ERROR_MESSAGE_PREFIX L"GacUIUnitTest_PrepareSnapshotFile(const WString&, const WString&)#" + Folder snapshotRootFolder = GetUnitTestFrameworkConfig().snapshotFolder; + CHECK_ERROR(snapshotRootFolder.Exists(), ERROR_MESSAGE_PREFIX L"UnitTestFrameworkConfig::snapshotFolder does not point to an existing folder."); + + Folder snapshotFramesFolder = snapshotRootFolder.GetFilePath() / appName; + if (!snapshotFramesFolder.Exists()) + { + CHECK_ERROR(snapshotFramesFolder.Create(true), ERROR_MESSAGE_PREFIX L"Failed to create the folder containing frame snapshots."); + } + + return snapshotFramesFolder; +#undef ERROR_MESSAGE_PREFIX +} + void GacUIUnitTest_WriteSnapshotFileIfChanged(File& snapshotFile, const WString& textLog) { #define ERROR_MESSAGE_PREFIX L"GacUIUnitTest_WriteSnapshotFileIfChanged(File&, const WString&)#" @@ -1576,6 +1593,7 @@ void GacUIUnitTest_LogUI(const WString& appName, UnitTestRemoteProtocol& unitTes { #define ERROR_MESSAGE_PREFIX L"GacUIUnitTest_LogUI(const WString&, UnitTestRemoteProtocol&)#" File snapshotFile = GacUIUnitTest_PrepareSnapshotFile(appName, WString::Unmanaged(L".json")); + Folder snapshotFramesFolder = GacUIUnitTest_PrepareSnapshotFramesFolder(appName); JsonFormatting formatting; formatting.spaceAfterColon = true; @@ -1583,17 +1601,52 @@ void GacUIUnitTest_LogUI(const WString& appName, UnitTestRemoteProtocol& unitTes formatting.crlf = true; formatting.compact = true; + auto renderingTrace = unitTestProtocol.GetLoggedTrace(); + List frameTextLogs; + SortedList frameFileNames; + remoteprotocol::UnitTest_RenderingTrace deserialized; + auto jsonLog = remoteprotocol::ConvertCustomTypeToJson(unitTestProtocol.GetLoggedTrace()); - auto textLog = JsonToString(jsonLog, formatting); { - remoteprotocol::UnitTest_RenderingTrace deserialized; + auto textLog = JsonToString(jsonLog, formatting); remoteprotocol::ConvertJsonToCustomType(jsonLog, deserialized); auto jsonLog2 = remoteprotocol::ConvertCustomTypeToJson(deserialized); auto textLog2 = JsonToString(jsonLog2, formatting); CHECK_ERROR(textLog == textLog2, ERROR_MESSAGE_PREFIX L"Serialization and deserialization doesn't match."); } - GacUIUnitTest_WriteSnapshotFileIfChanged(snapshotFile, textLog); + if (renderingTrace.frames) + { + for (vint i = 0; i < renderingTrace.frames->Count(); i++) + { + auto&& frame = renderingTrace.frames->Get(i); + jsonLog = remoteprotocol::ConvertCustomTypeToJson(frame); + auto textLog = JsonToString(jsonLog, formatting); + + WString frameFileName = L"frame_" + itow(i) + L".json"; + frameFileNames.Add(frameFileName); + File snapshotFrameFile = snapshotFramesFolder.GetFilePath() / frameFileName; + GacUIUnitTest_WriteSnapshotFileIfChanged(snapshotFrameFile, textLog); + + renderingTrace.frames->Set(i, { .frameId = frame.frameId, .frameName = frame.frameName }); + } + } + + { + jsonLog = remoteprotocol::ConvertCustomTypeToJson(unitTestProtocol.GetLoggedTrace()); + auto textLog = JsonToString(jsonLog, formatting); + GacUIUnitTest_WriteSnapshotFileIfChanged(snapshotFile, textLog); + } + + List existingFrameFiles; + snapshotFramesFolder.GetFiles(existingFrameFiles); + for (auto&& file : existingFrameFiles) + { + if (!frameFileNames.Contains(file.GetFilePath().GetName())) + { + CHECK_ERROR(file.Delete(), ERROR_MESSAGE_PREFIX L"Failed to delete unnecessary frame file."); + } + } #undef ERROR_MESSAGE_PREFIX } @@ -1865,7 +1918,6 @@ void GacUIUnitTest_StartAsync(const WString& appName, Nullable config, const WString& resourceText) { #define ERROR_MESSAGE_PREFIX L"GacUIUnitTest_Start_WithResourceAsText(const WString&, Nullable, const WString&)#" - auto previousMainProxy = guiMainProxy; GacUIUnitTest_LinkGuiMainProxy([=](UnitTestRemoteProtocol* protocol, IUnitTestContext* context, const UnitTestMainFunc& previousMainProxy) { auto resource = GacUIUnitTest_CompileAndLoad(resourceText); diff --git a/Import/GacUI.UnitTest.h b/Import/GacUI.UnitTest.h index 8fa555ed..94d4eef9 100644 --- a/Import/GacUI.UnitTest.h +++ b/Import/GacUI.UnitTest.h @@ -655,6 +655,7 @@ IGuiRemoteProtocolMessages (Window) void Impl_WindowNotifySetTitleBar(const bool& arguments) { styleConfig.titleBar = arguments; this->GetEvents()->OnWindowBoundsUpdated(sizingConfig); } void Impl_WindowNotifyActivate() { styleConfig.activated = true; } void Impl_WindowNotifyMinSize(const NativeSize& arguments) {} + void Impl_WindowNotifySetCaret(const NativePoint& arguments) {} void Impl_WindowNotifyShow(const WindowShowing& arguments) { diff --git a/Import/GacUI.Windows.cpp b/Import/GacUI.Windows.cpp index 21faf20b..0837796e 100644 --- a/Import/GacUI.Windows.cpp +++ b/Import/GacUI.Windows.cpp @@ -2979,13 +2979,11 @@ WindowsDirect2DElementInlineObject public: WindowsDirect2DElementInlineObject( - const IGuiGraphicsParagraph::InlineObjectProperties& _properties, IRendererCallback* _rendererCallback, vint _start, vint _length ) :counter(1) - ,properties(_properties) ,rendererCallback(_rendererCallback) ,start(_start) ,length(_length) @@ -3019,6 +3017,11 @@ WindowsDirect2DElementInlineObject return properties; } + void SetProperties(const IGuiGraphicsParagraph::InlineObjectProperties& value) + { + properties=value; + } + Ptr GetElement() { return properties.backgroundImage; @@ -3597,38 +3600,50 @@ WindowsDirect2DParagraph (Formatting) bool SetInlineObject(vint start, vint length, const InlineObjectProperties& properties)override { - if(inlineElements.Keys().Contains(properties.backgroundImage.Obj())) - { - return false; - } + vint reuseIndex = -1; // TODO: (enumerable) foreach - for(vint i=0;i inlineObject=inlineElements.Values().Get(i); - if(startGetStart()+inlineObject->GetLength() && inlineObject->GetStart()GetStart() && length == inlineObject->GetLength()) { - return false; + auto&& inlineProps = inlineObject->GetProperties(); + if (inlineProps.callbackId != properties.callbackId) return false; + if (inlineProps.backgroundImage != properties.backgroundImage) return false; + reuseIndex = i; + } + else + { + if (inlineElements.Keys().Contains(properties.backgroundImage.Obj())) + { + return false; + } + if (start < inlineObject->GetStart() + inlineObject->GetLength() && inlineObject->GetStart() < start + length) + { + return false; + } } } - formatDataAvailable=false; + formatDataAvailable = false; - ComPtr inlineObject=new WindowsDirect2DElementInlineObject(properties, this, start, length); + auto inlineObject = reuseIndex != -1 ? inlineElements.Values().Get(reuseIndex) : ComPtr(new WindowsDirect2DElementInlineObject(this, start, length)); + inlineObject->SetProperties(properties); DWRITE_TEXT_RANGE range; - range.startPosition=(int)start; - range.length=(int)length; - HRESULT hr=textLayout->SetInlineObject(inlineObject.Obj(), range); - if(!FAILED(hr)) + range.startPosition = (int)start; + range.length = (int)length; + HRESULT hr = textLayout->SetInlineObject(inlineObject.Obj(), range); + if (!FAILED(hr)) { - if (properties.backgroundImage) + if (properties.backgroundImage && reuseIndex == -1) { - IGuiGraphicsRenderer* renderer=properties.backgroundImage->GetRenderer(); - if(renderer) + IGuiGraphicsRenderer* renderer = properties.backgroundImage->GetRenderer(); + if (renderer) { renderer->SetRenderTarget(renderTarget); } inlineElements.Add(properties.backgroundImage.Obj(), inlineObject); + SetMap(graphicsElements, start, length, properties.backgroundImage.Obj()); } - SetMap(graphicsElements, start, length, properties.backgroundImage.Obj()); return true; } else @@ -13906,9 +13921,9 @@ WindowsInputService WString WindowsInputService::GetKeyNameInternal(VKEY code) { if ((vint)code < 8) return L"?"; - wchar_t name[256]={0}; - vint scanCode=MapVirtualKey((int)code, MAPVK_VK_TO_VSC)<<16; - switch((vint)code) + wchar_t name[256] = { 0 }; + vint scanCode = MapVirtualKey((int)code, MAPVK_VK_TO_VSC) << 16; + switch ((vint)code) { case VK_INSERT: case VK_DELETE: @@ -13920,18 +13935,18 @@ WindowsInputService case VK_RIGHT: case VK_UP: case VK_DOWN: - scanCode|=1<<24; + scanCode |= 1 << 24; break; case VK_CLEAR: case VK_LSHIFT: - case VK_RSHIFT: + case VK_RSHIFT: case VK_LCONTROL: case VK_RCONTROL: case VK_LMENU: case VK_RMENU: return L"?"; } - GetKeyNameText((int)scanCode, name, sizeof(name)/sizeof(*name)); + GetKeyNameText((int)scanCode, name, sizeof(name) / sizeof(*name)); if (name[0]) { WString result = name; @@ -13946,6 +13961,13 @@ WindowsInputService void WindowsInputService::InitializeKeyNames() { +#define INITIALIZE_KEY_NAME(NAME, TEXT)\ + keyNames[(vint)VKEY::KEY_ ## NAME] = WString::Unmanaged(TEXT);\ + if (!predefinedKeys.Keys().Contains(WString::Unmanaged(TEXT))) predefinedKeys.Add(WString::Unmanaged(TEXT), VKEY::KEY_ ## NAME);\ + + GUI_DEFINE_KEYBOARD_WINDOWS_NAME(INITIALIZE_KEY_NAME) +#undef INITIALIZE_KEY_NAME + for (vint i = 0; i < keyNames.Count(); i++) { keyNames[i] = GetKeyNameInternal((VKEY)i); @@ -13961,13 +13983,6 @@ WindowsInputService ,isTimerEnabled(false) ,keyNames((vint)VKEY::KEY_MAXIMUM) { -#define INITIALIZE_KEY_NAME(NAME, TEXT)\ - keyNames[(vint)VKEY::KEY_ ## NAME] = WString::Unmanaged(TEXT);\ - if (!predefinedKeys.Keys().Contains(WString::Unmanaged(TEXT))) predefinedKeys.Add(WString::Unmanaged(TEXT), VKEY::KEY_ ## NAME);\ - - GUI_DEFINE_KEYBOARD_WINDOWS_NAME(INITIALIZE_KEY_NAME) -#undef INITIALIZE_KEY_NAME - InitializeKeyNames(); } diff --git a/Import/GacUI.cpp b/Import/GacUI.cpp index 278df89c..598d6857 100644 --- a/Import/GacUI.cpp +++ b/Import/GacUI.cpp @@ -29242,9 +29242,12 @@ GuiDocumentElement void GuiDocumentElement::SetDocument(Ptr value) { - document=value; - InvokeOnElementStateChanged(); - SetCaret(TextPos(), TextPos(), false); + if (document != value) + { + document = value; + InvokeOnElementStateChanged(); + SetCaret(TextPos(), TextPos(), false); + } } bool GuiDocumentElement::GetParagraphPadding() @@ -29272,8 +29275,11 @@ GuiDocumentElement void GuiDocumentElement::SetWrapLine(bool value) { - wrapLine = value; - InvokeOnElementStateChanged(); + if (wrapLine != value) + { + wrapLine = value; + InvokeOnElementStateChanged(); + } } wchar_t GuiDocumentElement::GetPasswordChar() @@ -29283,9 +29289,9 @@ GuiDocumentElement void GuiDocumentElement::SetPasswordChar(wchar_t value) { - if(passwordChar!=value) + if (passwordChar != value) { - passwordChar=value; + passwordChar = value; InvokeOnElementStateChanged(); } } @@ -29386,11 +29392,21 @@ GuiDocumentElement void GuiDocumentElement::NotifyParagraphUpdated(vint index, vint oldCount, vint newCount, bool updatedText) { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::elements::GuiDocumentElement::NotifyParagraphUpdated(vint, vint, vint, bool)#" if (auto elementRenderer = GetElementRenderer()) { - elementRenderer->NotifyParagraphUpdated(index, oldCount, newCount, updatedText); + if (updatedText) + { + elementRenderer->NotifyParagraphTextUpdated(index, oldCount, newCount); + } + else + { + CHECK_ERROR(oldCount == newCount, ERROR_MESSAGE_PREFIX L"updatedText must be true if oldCount is not equal to newCount"); + elementRenderer->NotifyParagraphStyleUpdated(index, oldCount); + } InvokeOnCompositionStateChanged(); } +#undef ERROR_MESSAGE_PREFIX } void GuiDocumentElement::EditRun(TextPos begin, TextPos end, Ptr model, bool copy) @@ -29407,7 +29423,7 @@ GuiDocumentElement { if (auto elementRenderer = GetElementRenderer()) { - elementRenderer->NotifyParagraphUpdated(begin.row, end.row - begin.row + 1, newRows, true); + elementRenderer->NotifyParagraphTextUpdated(begin.row, end.row - begin.row + 1, newRows); InvokeOnCompositionStateChanged(); } } @@ -29427,7 +29443,7 @@ GuiDocumentElement { if (auto elementRenderer = GetElementRenderer()) { - elementRenderer->NotifyParagraphUpdated(begin.row, end.row - begin.row + 1, newRows, true); + elementRenderer->NotifyParagraphTextUpdated(begin.row, end.row - begin.row + 1, newRows); InvokeOnCompositionStateChanged(); } } @@ -29446,7 +29462,7 @@ GuiDocumentElement { if (auto elementRenderer = GetElementRenderer()) { - elementRenderer->NotifyParagraphUpdated(begin.row, end.row - begin.row + 1, end.row - begin.row + 1, false); + elementRenderer->NotifyParagraphStyleUpdated(begin, end); InvokeOnCompositionStateChanged(); } } @@ -29465,7 +29481,7 @@ GuiDocumentElement { if (auto elementRenderer = GetElementRenderer()) { - elementRenderer->NotifyParagraphUpdated(begin.row, end.row - begin.row + 1, 1, true); + elementRenderer->NotifyParagraphTextUpdated(begin.row, end.row - begin.row + 1, 1); InvokeOnCompositionStateChanged(); } } @@ -29484,7 +29500,7 @@ GuiDocumentElement { if (auto elementRenderer = GetElementRenderer()) { - elementRenderer->NotifyParagraphUpdated(paragraphIndex, 1, 1, false); + elementRenderer->NotifyParagraphStyleUpdated({ paragraphIndex, begin }, { paragraphIndex,end }); InvokeOnCompositionStateChanged(); } } @@ -29503,7 +29519,7 @@ GuiDocumentElement { if (auto elementRenderer = GetElementRenderer()) { - elementRenderer->NotifyParagraphUpdated(paragraphIndex, 1, 1, false); + elementRenderer->NotifyParagraphStyleUpdated({ paragraphIndex, begin }, { paragraphIndex,end }); InvokeOnCompositionStateChanged(); } } @@ -29522,7 +29538,7 @@ GuiDocumentElement { if (auto elementRenderer = GetElementRenderer()) { - elementRenderer->NotifyParagraphUpdated(begin.row, end.row - begin.row + 1, end.row - begin.row + 1, false); + elementRenderer->NotifyParagraphStyleUpdated(begin, end); InvokeOnCompositionStateChanged(); } } @@ -29541,7 +29557,7 @@ GuiDocumentElement { if (auto elementRenderer = GetElementRenderer()) { - elementRenderer->NotifyParagraphUpdated(begin.row, end.row - begin.row + 1, end.row - begin.row + 1, false); + elementRenderer->NotifyParagraphStyleUpdated(begin, end); InvokeOnCompositionStateChanged(); } } @@ -29565,7 +29581,7 @@ GuiDocumentElement { if (auto elementRenderer = GetElementRenderer()) { - elementRenderer->NotifyParagraphUpdated(begin.row, end.row - begin.row + 1, end.row - begin.row + 1, false); + elementRenderer->NotifyParagraphStyleUpdated(begin, end); InvokeOnCompositionStateChanged(); } } @@ -29584,7 +29600,7 @@ GuiDocumentElement { if (auto elementRenderer = GetElementRenderer()) { - elementRenderer->NotifyParagraphUpdated(begin.row, end.row - begin.row + 1, end.row - begin.row + 1, false); + elementRenderer->NotifyParagraphStyleUpdated(begin, end); InvokeOnCompositionStateChanged(); } } @@ -29633,7 +29649,10 @@ GuiDocumentElement } if (auto elementRenderer = GetElementRenderer()) { - elementRenderer->NotifyParagraphUpdated(first, alignments.Count(), alignments.Count(), false); + for (vint i = first; i <= last; i++) + { + elementRenderer->NotifyParagraphStyleUpdated({ i,0 }, { i,0 }); + } InvokeOnCompositionStateChanged(); } } @@ -29664,7 +29683,7 @@ GuiDocumentElement } /*********************************************************************** -.\GRAPHICSELEMENT\GUIGRAPHICSDOCUMENTRENDERER.CPP +.\GRAPHICSELEMENT\GUIGRAPHICSDOCUMENTRENDERER_GUIDOCUMENTELEMENTRENDERER.CPP ***********************************************************************/ namespace vl @@ -29676,538 +29695,6 @@ namespace vl namespace elements { -/*********************************************************************** -SetPropertiesVisitor -***********************************************************************/ - - namespace visitors - { - class SetPropertiesVisitor : public Object, public DocumentRun::IVisitor - { - typedef DocumentModel::ResolvedStyle ResolvedStyle; - public: - vint start; - vint length; - vint selectionBegin; - vint selectionEnd; - List styles; - - DocumentModel* model; - GuiDocumentParagraphCache* paragraphCache; - Ptr cache; - IGuiGraphicsParagraph* paragraph; - - SetPropertiesVisitor(DocumentModel* _model, GuiDocumentParagraphCache* _paragraphCache, Ptr _cache, vint _selectionBegin, vint _selectionEnd) - : start(0) - , length(0) - , model(_model) - , paragraphCache(_paragraphCache) - , cache(_cache) - , paragraph(_cache->graphicsParagraph.Obj()) - , selectionBegin(_selectionBegin) - , selectionEnd(_selectionEnd) - { - ResolvedStyle style; - style = model->GetStyle(DocumentModel::DefaultStyleName, style); - styles.Add(style); - } - - void VisitContainer(DocumentContainerRun* run) - { - for (auto subRun : run->runs) - { - subRun->Accept(this); - } - } - - void ApplyStyle(vint start, vint length, const ResolvedStyle& style) - { - paragraph->SetFont(start, length, style.style.fontFamily); - paragraph->SetSize(start, length, style.style.size); - paragraph->SetStyle(start, length, - (IGuiGraphicsParagraph::TextStyle) - ((style.style.bold ? IGuiGraphicsParagraph::Bold : 0) - | (style.style.italic ? IGuiGraphicsParagraph::Italic : 0) - | (style.style.underline ? IGuiGraphicsParagraph::Underline : 0) - | (style.style.strikeline ? IGuiGraphicsParagraph::Strikeline : 0) - )); - } - - void ApplyColor(vint start, vint length, const ResolvedStyle& style) - { - paragraph->SetColor(start, length, style.color); - paragraph->SetBackgroundColor(start, length, style.backgroundColor); - } - - void Visit(DocumentTextRun* run)override - { - length = run->GetRepresentationText().Length(); - if (length > 0) - { - ResolvedStyle style = styles[styles.Count() - 1]; - ApplyStyle(start, length, style); - ApplyColor(start, length, style); - - vint styleStart = start; - vint styleEnd = styleStart + length; - if (styleStart < selectionEnd && selectionBegin < styleEnd) - { - vint s2 = styleStart > selectionBegin ? styleStart : selectionBegin; - vint s3 = selectionEnd < styleEnd ? selectionEnd : styleEnd; - - if (s2 < s3) - { - ResolvedStyle selectionStyle = model->GetStyle(DocumentModel::SelectionStyleName, style); - ApplyColor(s2, s3 - s2, selectionStyle); - } - } - } - start += length; - } - - void Visit(DocumentStylePropertiesRun* run)override - { - ResolvedStyle style = styles[styles.Count() - 1]; - style = model->GetStyle(run->style, style); - styles.Add(style); - VisitContainer(run); - styles.RemoveAt(styles.Count() - 1); - } - - void Visit(DocumentStyleApplicationRun* run)override - { - ResolvedStyle style = styles[styles.Count() - 1]; - style = model->GetStyle(run->styleName, style); - styles.Add(style); - VisitContainer(run); - styles.RemoveAt(styles.Count() - 1); - } - - void Visit(DocumentHyperlinkRun* run)override - { - ResolvedStyle style = styles[styles.Count() - 1]; - style = model->GetStyle(run->styleName, style); - styles.Add(style); - VisitContainer(run); - styles.RemoveAt(styles.Count() - 1); - } - - void Visit(DocumentImageRun* run)override - { - length = run->GetRepresentationText().Length(); - - auto element = Ptr(GuiImageFrameElement::Create()); - element->SetImage(run->image, run->frameIndex); - element->SetStretch(true); - - IGuiGraphicsParagraph::InlineObjectProperties properties; - properties.size = run->GetSize(); - properties.baseline = run->baseline; - properties.breakCondition = IGuiGraphicsParagraph::Alone; - properties.backgroundImage = element; - - paragraph->SetInlineObject(start, length, properties); - - if (start < selectionEnd && selectionBegin < start + length) - { - ResolvedStyle style = styles[styles.Count() - 1]; - ResolvedStyle selectionStyle = model->GetStyle(DocumentModel::SelectionStyleName, style); - ApplyColor(start, length, selectionStyle); - } - start += length; - } - - void Visit(DocumentEmbeddedObjectRun* run)override - { - length = run->GetRepresentationText().Length(); - - IGuiGraphicsParagraph::InlineObjectProperties properties; - properties.breakCondition = IGuiGraphicsParagraph::Alone; - - if (run->name != L"") - { - vint index = paragraphCache->nameCallbackIdMap.Keys().IndexOf(run->name); - if (index != -1) - { - auto id = paragraphCache->nameCallbackIdMap.Values()[index]; - index = cache->embeddedObjects.Keys().IndexOf(id); - if (index != -1) - { - auto eo = cache->embeddedObjects.Values()[index]; - if (eo->start == start) - { - properties.size = eo->size; - properties.callbackId = id; - } - } - } - else - { - auto eo = Ptr(new pg::EmbeddedObject); - eo->name = run->name; - eo->size = Size(0, 0); - eo->start = start; - - vint id = -1; - vint count = paragraphCache->freeCallbackIds.Count(); - if (count > 0) - { - id = paragraphCache->freeCallbackIds[count - 1]; - paragraphCache->freeCallbackIds.RemoveAt(count - 1); - } - else - { - id = paragraphCache->usedCallbackIds++; - } - - paragraphCache->nameCallbackIdMap.Add(eo->name, id); - cache->embeddedObjects.Add(id, eo); - properties.callbackId = id; - } - } - - paragraph->SetInlineObject(start, length, properties); - - if (start < selectionEnd && selectionBegin < start + length) - { - ResolvedStyle style = styles[styles.Count() - 1]; - ResolvedStyle selectionStyle = model->GetStyle(DocumentModel::SelectionStyleName, style); - ApplyColor(start, length, selectionStyle); - } - start += length; - } - - void Visit(DocumentParagraphRun* run)override - { - VisitContainer(run); - } - - static vint SetProperty(DocumentModel* model, GuiDocumentParagraphCache* paragraphCache, Ptr cache, Ptr run, vint selectionBegin, vint selectionEnd) - { - SetPropertiesVisitor visitor(model, paragraphCache, cache, selectionBegin, selectionEnd); - run->Accept(&visitor); - return visitor.length; - } - }; - } - using namespace visitors; - -/*********************************************************************** -GuiDocumentParagraphCache -***********************************************************************/ - - GuiDocumentParagraphCache::GuiDocumentParagraphCache(IGuiGraphicsParagraphCallback* _callback) - : callback(_callback) - , layoutProvider(GetGuiGraphicsResourceManager()->GetLayoutProvider()) - , defaultHeight(GetCurrentController()->ResourceService()->GetDefaultFont().size) - { - } - - GuiDocumentParagraphCache::~GuiDocumentParagraphCache() - { - } - - void GuiDocumentParagraphCache::Initialize(GuiDocumentElement* _element) - { - element = _element; - } - - void GuiDocumentParagraphCache::RenderTargetChanged(IGuiGraphicsRenderTarget* oldRenderTarget, IGuiGraphicsRenderTarget* newRenderTarget) - { - renderTarget = newRenderTarget; - // TODO: (enumerable) foreach - for (vint i = 0; i < paragraphCaches.Count(); i++) - { - if (auto cache = paragraphCaches[i].Obj()) - { - cache->graphicsParagraph = nullptr; - cache->outdatedStyles = true; - } - } - } - - vint GuiDocumentParagraphCache::GetParagraphCount() - { - return paragraphCaches.Count(); - } - - Ptr GuiDocumentParagraphCache::TryGetParagraphCache(vint paragraphIndex) - { - if (paragraphIndex < 0 || paragraphIndex >= paragraphCaches.Count()) return nullptr; - return paragraphCaches[paragraphIndex]; - } - - Ptr GuiDocumentParagraphCache::GetParagraphCache(vint paragraphIndex, bool requireParagraph) - { -#define ERROR_MESSAGE_PREFIX L"vl::presentation::elements::GuiDocumentParagraphCache::GetParagraphCache(vint)#" - auto cache = paragraphCaches[paragraphIndex]; - CHECK_ERROR(cache && (!requireParagraph || (cache->graphicsParagraph && !cache->outdatedStyles)), ERROR_MESSAGE_PREFIX L"The specified paragraph is not created."); - return cache; -#undef ERROR_MESSAGE_PREFIX - } - - Size GuiDocumentParagraphCache::GetParagraphSize(vint paragraphIndex) - { - return paragraphSizes[paragraphIndex].cachedSize; - } - - vint GuiDocumentParagraphCache::GetParagraphTopWithoutParagraphDistance(vint paragraphIndex) - { - if (paragraphIndex >= validCachedTops) - { - vint currentTop = 0; - if (validCachedTops > 0) - { - auto size = paragraphSizes[validCachedTops - 1]; - currentTop = size.cachedTopWithoutParagraphDistance + size.cachedSize.y; - } - - for (vint i = validCachedTops; i <= paragraphIndex; i++) - { - auto& size = paragraphSizes[i]; - size.cachedTopWithoutParagraphDistance = currentTop; - currentTop += size.cachedSize.y; - } - - validCachedTops = paragraphIndex + 1; - } - return paragraphSizes[paragraphIndex].cachedTopWithoutParagraphDistance; - } - - vint GuiDocumentParagraphCache::GetParagraphTop(vint paragraphIndex, vint paragraphDistance) - { - return GetParagraphTopWithoutParagraphDistance(paragraphIndex) + paragraphIndex * paragraphDistance; - } - - vint GuiDocumentParagraphCache::ResetCache() - { - nameCallbackIdMap.Clear(); - freeCallbackIds.Clear(); - usedCallbackIds = 0; - - auto document = element ? element->GetDocument() : nullptr; - if (document && document->paragraphs.Count() > 0) - { - paragraphCaches.Resize(0); - paragraphCaches.Resize(document->paragraphs.Count()); - paragraphSizes.Resize(document->paragraphs.Count()); - - for (vint i = 0; i < paragraphSizes.Count(); i++) - { - paragraphSizes[i] = { (i * defaultHeight),{0,defaultHeight}}; - } - - validCachedTops = document->paragraphs.Count(); - return document->paragraphs.Count() * defaultHeight; - } - else - { - paragraphCaches.Resize(0); - paragraphSizes.Resize(0); - validCachedTops = 0; - return 0; - } - } - - vint GuiDocumentParagraphCache::ResetCache(vint index, vint oldCount, vint newCount, bool updatedText) - { - if (updatedText) - { - for (vint i = 0; i < oldCount; i++) - { - if (auto cache = paragraphCaches[i + index]) - { - // TODO: (enumerable) foreach on dictionary - for (vint j = 0; j < cache->embeddedObjects.Count(); j++) - { - auto id = cache->embeddedObjects.Keys()[j]; - auto name = cache->embeddedObjects.Values()[j]->name; - nameCallbackIdMap.Remove(name); - freeCallbackIds.Add(id); - } - } - } - } - - if (oldCount == newCount) - { - for (vint i = 0; i < oldCount; i++) - { - if (updatedText) - { - paragraphCaches[index + i] = nullptr; - } - else if (auto cache = paragraphCaches[index + i]) - { - cache->outdatedStyles = true; - } - } - return 0; - } - else - { - pg::ParagraphCacheArray oldCaches; - pg::ParagraphSizeArray oldSizes; - - CopyFrom(oldCaches, paragraphCaches); - CopyFrom(oldSizes, paragraphSizes); - - vint paragraphCount = element->GetDocument()->paragraphs.Count(); - paragraphCaches.Resize(paragraphCount); - paragraphSizes.Resize(paragraphCount); - - vint paragraphTop = GetParagraphTopWithoutParagraphDistance(index); - for (vint i = 0; i < paragraphCount; i++) - { - if (i < index) - { - paragraphCaches[i] = oldCaches[i]; - paragraphSizes[i] = oldSizes[i]; - } - else if (i < index + newCount) - { - // updateText must be true, ensured in GuiDocumentElementRenderer::NotifyParagraphUpdated - paragraphCaches[i] = nullptr; - paragraphSizes[i] = { (paragraphTop + (i - index) * defaultHeight),{0,defaultHeight} }; - } - else - { - paragraphCaches[i] = oldCaches[i - (newCount - oldCount)]; - paragraphSizes[i] = oldSizes[i - (newCount - oldCount)]; - } - } - validCachedTops = index + newCount; - - vint oldUpdatedTotalHeight = 0; - for (vint i = 0; i < oldCount; i++) - { - oldUpdatedTotalHeight += oldSizes[index + i].cachedSize.y; - } - return newCount * defaultHeight - oldUpdatedTotalHeight; - } - } - - vint GuiDocumentParagraphCache::EnsureParagraph(vint paragraphIndex, vint maxWidth) - { - auto paragraph = element->GetDocument()->paragraphs[paragraphIndex]; - auto cache = paragraphCaches[paragraphIndex]; - if (!cache) - { - cache = Ptr(new pg::ParagraphCache); - cache->fullText = paragraph->GetTextForCaret(); - paragraphCaches[paragraphIndex] = cache; - } - - if (!cache->graphicsParagraph) - { - auto paragraphText = cache->fullText; - if (auto passwordChar = element->GetPasswordChar()) - { - Array passwordText(paragraphText.Length() + 1); - for (vint i = 0; i < paragraphText.Length(); i++) - { - passwordText[i] = passwordChar; - } - passwordText[paragraphText.Length()] = 0; - paragraphText = &passwordText[0]; - } - cache->graphicsParagraph = layoutProvider->CreateParagraph(paragraphText, renderTarget, callback); - cache->outdatedStyles = true; - } - - if (cache->outdatedStyles) - { - cache->outdatedStyles = false; - SetPropertiesVisitor::SetProperty(element->GetDocument().Obj(), this, cache, paragraph, cache->selectionBegin, cache->selectionEnd); - cache->graphicsParagraph->SetParagraphAlignment(paragraph->alignment ? paragraph->alignment.Value() : Alignment::Left); - cache->graphicsParagraph->SetWrapLine(element->GetWrapLine()); - cache->graphicsParagraph->SetMaxWidth(maxWidth); - } - - auto& cachedSize = paragraphSizes[paragraphIndex]; - Size oldSize = cachedSize.cachedSize; - Size newSize = cache->graphicsParagraph->GetSize(); - if(newSize.y < defaultHeight) - { - newSize.y = defaultHeight; - } - cachedSize.cachedSize = newSize; - if (oldSize.y != newSize.y && validCachedTops > paragraphIndex + 1) - { - validCachedTops = paragraphIndex + 1; - } - return newSize.y - oldSize.y; - } - - vint GuiDocumentParagraphCache::GetParagraphFromY(vint y, vint paragraphDistance) - { - auto document = element ? element->GetDocument() : nullptr; - if (!document || document->paragraphs.Count() == 0) return -1; - - vint start = 0; - vint end = paragraphSizes.Count() - 1; - - if (0 < validCachedTops && validCachedTops <= paragraphSizes.Count()) - { - vint index = validCachedTops - 1; - vint top = GetParagraphTop(index, paragraphDistance); - auto size = paragraphSizes[index].cachedSize; - if (y < top) - { - if (index < 1) return 0; - end = index - 1; - } - else if (y < top + size.y + paragraphDistance) - { - return index; - } - else - { - if (index >= paragraphSizes.Count() - 1) return paragraphSizes.Count() - 1; - start = validCachedTops; - } - } - - if (start >= end) return end; - while (true) - { - vint mid = (start + end) / 2; - vint top = GetParagraphTop(mid, paragraphDistance); - auto size = paragraphSizes[mid].cachedSize; - if (y < top) - { - end = mid - 1; - if (start >= end) return start; - } - else if (y < top + size.y + paragraphDistance) - { - return mid; - } - else - { - start = mid + 1; - if (start >= end) return end; - } - } - } - - void GuiDocumentParagraphCache::ReleaseParagraphs(vint index, vint count) - { - for (vint i = 0; i < count; i++) - { - vint paragraphIndex = index + i; - if (paragraphIndex >= 0 && paragraphIndex < paragraphCaches.Count()) - { - auto cache = paragraphCaches[paragraphIndex]; - if (cache) // Check if cache itself is null per UPDATE guidance - { - cache->graphicsParagraph = nullptr; // Release only the rendering object - // Preserve all other data: fullText, embeddedObjects, selectionBegin/selectionEnd - } - } - } - } - /*********************************************************************** GuiDocumentElementRenderer ***********************************************************************/ @@ -30233,6 +29720,7 @@ GuiDocumentElementRenderer void GuiDocumentElementRenderer::InitializeInternal() { pgCache.Initialize(element); + imageCache.Initialize(element); NotifyParagraphPaddingUpdated(element->GetParagraphPadding()); } @@ -30243,6 +29731,7 @@ GuiDocumentElementRenderer void GuiDocumentElementRenderer::RenderTargetChangedInternal(IGuiGraphicsRenderTarget* oldRenderTarget, IGuiGraphicsRenderTarget* newRenderTarget) { pgCache.RenderTargetChanged(oldRenderTarget, newRenderTarget); + imageCache.RenderTargetChanged(oldRenderTarget, newRenderTarget); } Ptr GuiDocumentElementRenderer::EnsureParagraph(vint paragraphIndex) @@ -30349,11 +29838,36 @@ GuiDocumentElementRenderer } } + void GuiDocumentElementRenderer::ApplyPropertiesOnParagraph(vint paragraphIndex, vint start, vint end, vint maxWidth) + { + auto cache = pgCache.GetParagraphCache(paragraphIndex, true); + auto paragraph = element->GetDocument()->paragraphs[paragraphIndex]; + visitors::SetProperties( + element->GetDocument().Obj(), + &pgCache, + &imageCache, + cache, + paragraphIndex, + paragraph, + cache->selectionBegin, + cache->selectionEnd, + start, + end + ); + cache->graphicsParagraph->SetParagraphAlignment(paragraph->alignment ? paragraph->alignment.Value() : Alignment::Left); + cache->graphicsParagraph->SetWrapLine(element->GetWrapLine()); + cache->graphicsParagraph->SetMaxWidth(maxWidth); + } + GuiDocumentElementRenderer::GuiDocumentElementRenderer() : pgCache(this) { } + GuiDocumentElementRenderer::~GuiDocumentElementRenderer() + { + } + void GuiDocumentElementRenderer::Render(Rect bounds) { List paragraphsToReset; @@ -30406,7 +29920,7 @@ GuiDocumentElementRenderer renderingParagraph = -1; bool resized = false; - for(auto eo: cache->embeddedObjects.Values()) + for (auto eo : cache->embeddedObjects.Values()) { if (eo->resized) { @@ -30433,12 +29947,12 @@ GuiDocumentElementRenderer // Calculate current visible range (currentBegin, currentCount) vint currentBegin = -1; vint currentCount = 0; - + // Determine currentBegin using existing GetParagraphFromY logic Rect clipper = renderTarget->GetClipper(); vint y1 = clipper.Top() - bounds.Top(); vint y2 = y1 + clipper.Height(); - + if (y1 < y2) // Only if there's visible area { currentBegin = pgCache.GetParagraphFromY(y1, paragraphDistance); @@ -30453,45 +29967,35 @@ GuiDocumentElementRenderer } } } - + UpdateRenderRangeAndCleanUp(currentBegin, currentCount); } FixMinSize(); - for(auto p:paragraphsToReset) + for (auto p : paragraphsToReset) { - NotifyParagraphUpdated(p, 1, 1, false); + NotifyParagraphStyleUpdated(p, 1); } } void GuiDocumentElementRenderer::NotifyParagraphPaddingUpdated(bool value) { - vint defaultHeight = GetCurrentController()->ResourceService()->GetDefaultFont().size; + vint defaultHeight = GuiDocumentParagraphCache::GetDefaultHeight(); paragraphDistance = element->GetParagraphPadding() ? defaultHeight : 0; } void GuiDocumentElementRenderer::OnElementStateChanged() { + imageCache.ResetCache(); lastTotalWidth = 0; lastTotalHeightWithoutParagraphDistance = pgCache.ResetCache(); FixMinSize(); } - void GuiDocumentElementRenderer::NotifyParagraphUpdated(vint index, vint oldCount, vint newCount, bool updatedText) + void GuiDocumentElementRenderer::NotifyParagraphUpdateLastTotalWidth(vint index, vint count) { -#define ERROR_MESSAGE_PREFIX L"vl::presentation::elements::GuiDocumentElementRenderer::NotifyParagraphUpdated(vint, vint, vint, bool)#" - vint oldParagraphCount = pgCache.GetParagraphCount(); - vint newParagraphCount = element->GetDocument()->paragraphs.Count(); - - CHECK_ERROR(oldCount >= 0, ERROR_MESSAGE_PREFIX L"oldCount cannot be negative."); - CHECK_ERROR(newCount >= 0, ERROR_MESSAGE_PREFIX L"newCount cannot be negative."); - CHECK_ERROR(0 <= index && index + oldCount <= oldParagraphCount, ERROR_MESSAGE_PREFIX L"index + oldCount is out of range."); - CHECK_ERROR(0 <= index && index + newCount <= newParagraphCount, ERROR_MESSAGE_PREFIX L"index + newCount is out of range."); - CHECK_ERROR(updatedText || oldCount == newCount, ERROR_MESSAGE_PREFIX L"updatedText must be true if oldCount is not equal to newCount."); - CHECK_ERROR(newParagraphCount - oldParagraphCount == newCount - oldCount, ERROR_MESSAGE_PREFIX L"newCount - oldCount does not reflect the actual paragraph count changing."); - - for (vint i = 0; i < oldCount; i++) + for (vint i = 0; i < count; i++) { vint width = pgCache.GetParagraphSize(index + i).x; if (lastTotalWidth == width) @@ -30500,15 +30004,56 @@ GuiDocumentElementRenderer break; } } - lastTotalHeightWithoutParagraphDistance += pgCache.ResetCache(index, oldCount, newCount, updatedText); + } + + void GuiDocumentElementRenderer::NotifyParagraphTextUpdated(vint index, vint oldCount, vint newCount) + { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::elements::GuiDocumentElementRenderer::NotifyParagraphTextUpdated(vint, vint, vint)#" + vint oldParagraphCount = pgCache.GetParagraphCount(); + vint newParagraphCount = element->GetDocument()->paragraphs.Count(); + + CHECK_ERROR(oldCount >= 0, ERROR_MESSAGE_PREFIX L"oldCount cannot be negative."); + CHECK_ERROR(newCount >= 0, ERROR_MESSAGE_PREFIX L"newCount cannot be negative."); + CHECK_ERROR(0 <= index && index + oldCount <= oldParagraphCount, ERROR_MESSAGE_PREFIX L"index + oldCount is out of range."); + CHECK_ERROR(0 <= index && index + newCount <= newParagraphCount, ERROR_MESSAGE_PREFIX L"index + newCount is out of range."); + CHECK_ERROR(newParagraphCount - oldParagraphCount == newCount - oldCount, ERROR_MESSAGE_PREFIX L"newCount - oldCount does not reflect the actual paragraph count changing."); + + imageCache.ResetTextCache(index, oldCount, newCount); + NotifyParagraphUpdateLastTotalWidth(index, oldCount); + lastTotalHeightWithoutParagraphDistance += pgCache.ResetTextCache(index, oldCount, newCount); + FixMinSize(); + UpdateRenderRange(index, oldCount, newCount); + +#undef ERROR_MESSAGE_PREFIX + } + + void GuiDocumentElementRenderer::NotifyParagraphStyleUpdated(TextPos begin, TextPos end) + { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::elements::GuiDocumentElementRenderer::NotifyParagraphStyleUpdated(vint, TextPos, TextPos)#" + vint paragraphCount = pgCache.GetParagraphCount(); + CHECK_ERROR(paragraphCount == element->GetDocument()->paragraphs.Count(), ERROR_MESSAGE_PREFIX L"This function can only be called when only text style is updated."); + CHECK_ERROR(0 <= begin.row && begin.row < paragraphCount, ERROR_MESSAGE_PREFIX L"begin.row is out of range."); + CHECK_ERROR(0 <= end.row && end.row < paragraphCount, ERROR_MESSAGE_PREFIX L"end.row is out of range."); + + vint count = end.row - begin.row + 1; + NotifyParagraphUpdateLastTotalWidth(begin.row, count); + lastTotalHeightWithoutParagraphDistance += pgCache.ResetStyleCache(begin, end); FixMinSize(); - // Update render range if text was actually updated - if (updatedText) - { - UpdateRenderRange(index, oldCount, newCount); - } +#undef ERROR_MESSAGE_PREFIX + } + void GuiDocumentElementRenderer::NotifyParagraphStyleUpdated(vint index, vint count) + { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::elements::GuiDocumentElementRenderer::NotifyParagraphTextUpdated(vint, vint, vint)#" + vint oldParagraphCount = pgCache.GetParagraphCount(); + vint newParagraphCount = element->GetDocument()->paragraphs.Count(); + + CHECK_ERROR(count >= 0, ERROR_MESSAGE_PREFIX L"count cannot be negative."); + CHECK_ERROR(0 <= index && index + count <= newParagraphCount, ERROR_MESSAGE_PREFIX L"index + count is out of range."); + NotifyParagraphUpdateLastTotalWidth(index, count); + lastTotalHeightWithoutParagraphDistance += pgCache.ResetStyleCache(index, count); + FixMinSize(); #undef ERROR_MESSAGE_PREFIX } @@ -30587,11 +30132,15 @@ GuiDocumentElementRenderer if (cache->selectionBegin != newBegin || cache->selectionEnd != newEnd) { + vint selectionBegin = cache->selectionBegin; + vint selectionEnd = cache->selectionEnd; + if (newBegin < selectionBegin || selectionBegin == -1) selectionBegin = newBegin; + if (newEnd > selectionEnd || selectionEnd == -1) selectionEnd = newEnd; cache->selectionBegin = newBegin; cache->selectionEnd = newEnd; if (cache->graphicsParagraph) { - NotifyParagraphUpdated(i, 1, 1, false); + NotifyParagraphStyleUpdated({ i,selectionBegin }, { i,selectionEnd }); } } } @@ -30602,11 +30151,13 @@ GuiDocumentElementRenderer { if (cache->selectionBegin != -1 || cache->selectionEnd != -1) { + vint selectionBegin = cache->selectionBegin; + vint selectionEnd = cache->selectionEnd; cache->selectionBegin = -1; cache->selectionEnd = -1; if (cache->graphicsParagraph) { - NotifyParagraphUpdated(i, 1, 1, false); + NotifyParagraphStyleUpdated({ i,selectionBegin }, { i,selectionEnd }); } } } @@ -30744,6 +30295,749 @@ GuiDocumentElementRenderer } } +/*********************************************************************** +.\GRAPHICSELEMENT\GUIGRAPHICSDOCUMENTRENDERER_GUIDOCUMENTIMAGECACHE.CPP +***********************************************************************/ + +namespace vl +{ + using namespace collections; + + namespace presentation + { + namespace elements + { + +/*********************************************************************** +GuiDocumentImageCache +***********************************************************************/ + + GuiDocumentImageCache::GuiDocumentImageCache() + { + } + + GuiDocumentImageCache::~GuiDocumentImageCache() + { + } + + void GuiDocumentImageCache::Initialize(GuiDocumentElement* _element) + { + element = _element; + } + + void GuiDocumentImageCache::RenderTargetChanged(IGuiGraphicsRenderTarget* oldRenderTarget, IGuiGraphicsRenderTarget* newRenderTarget) + { + } + + void GuiDocumentImageCache::ResetCache() + { + caches.Resize(0); + if (auto document = element->GetDocument()) + { + caches.Resize(document->paragraphs.Count()); + } + } + + void GuiDocumentImageCache::ResetTextCache(vint index, vint oldCount, vint newCount) + { + pg::ParagraphImageCacheArray oldCaches; + CopyFrom(oldCaches, caches); + + vint paragraphCount = element->GetDocument()->paragraphs.Count(); + caches.Resize(paragraphCount); + + for (vint i = 0; i < paragraphCount; i++) + { + if (i < index) + { + caches[i] = oldCaches[i]; + } + else if (i < index + newCount) + { + caches[i] = {}; + } + else + { + caches[i] = oldCaches[i - (newCount - oldCount)]; + } + } + } + + Ptr GuiDocumentImageCache::GetImageElement(Ptr image, vint frameIndex, vint paragraphIndex, vint start) + { + auto cache = caches[paragraphIndex]; + if (!cache) + { + cache = Ptr(new pg::ParagraphImageCache); + caches[paragraphIndex] = cache; + } + + auto key = Tuple(image.Obj(), frameIndex, start); + vint index = cache->elements.Keys().IndexOf(key); + if (index == -1) + { + auto element = Ptr(GuiImageFrameElement::Create()); + element->SetImage(image, frameIndex); + element->SetStretch(true); + cache->elements.Add(key, element); + return element; + } + else + { + return cache->elements.Values()[index]; + } + } + } + } +} + +/*********************************************************************** +.\GRAPHICSELEMENT\GUIGRAPHICSDOCUMENTRENDERER_GUIDOCUMENTPARAGRAPHCACHE.CPP +***********************************************************************/ + +namespace vl +{ + using namespace collections; + + namespace presentation + { + namespace elements + { + +/*********************************************************************** +GuiDocumentParagraphCache +***********************************************************************/ + + GuiDocumentParagraphCache::GuiDocumentParagraphCache(GuiDocumentElementRenderer* _renderer) + : renderer(_renderer) + , callback(_renderer) + , layoutProvider(GetGuiGraphicsResourceManager()->GetLayoutProvider()) + { + } + + GuiDocumentParagraphCache::~GuiDocumentParagraphCache() + { + } + + vint GuiDocumentParagraphCache::GetDefaultHeight() + { + vint defaultHeight = GetCurrentController()->ResourceService()->GetDefaultFont().size; + if (defaultHeight < 8) defaultHeight = 8; + return defaultHeight; + } + + void GuiDocumentParagraphCache::Initialize(GuiDocumentElement* _element) + { + element = _element; + } + + void GuiDocumentParagraphCache::RenderTargetChanged(IGuiGraphicsRenderTarget* oldRenderTarget, IGuiGraphicsRenderTarget* newRenderTarget) + { + renderTarget = newRenderTarget; + // TODO: (enumerable) foreach + for (vint i = 0; i < paragraphCaches.Count(); i++) + { + if (auto cache = paragraphCaches[i].Obj()) + { + cache->graphicsParagraph = nullptr; + cache->invalidation = true; + } + } + } + + vint GuiDocumentParagraphCache::GetParagraphCount() + { + return paragraphCaches.Count(); + } + + Ptr GuiDocumentParagraphCache::TryGetParagraphCache(vint paragraphIndex) + { + if (paragraphIndex < 0 || paragraphIndex >= paragraphCaches.Count()) return nullptr; + return paragraphCaches[paragraphIndex]; + } + + Ptr GuiDocumentParagraphCache::GetParagraphCache(vint paragraphIndex, bool requireParagraph) + { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::elements::GuiDocumentParagraphCache::GetParagraphCache(vint)#" + auto cache = paragraphCaches[paragraphIndex]; + if (requireParagraph) + { + CHECK_ERROR(cache && cache->graphicsParagraph && cache->invalidation.TryGet() && cache->invalidation.Get() == false, ERROR_MESSAGE_PREFIX L"The specified paragraph is not created."); + } + return cache; +#undef ERROR_MESSAGE_PREFIX + } + + Size GuiDocumentParagraphCache::GetParagraphSize(vint paragraphIndex) + { + return paragraphSizes[paragraphIndex].cachedSize; + } + + vint GuiDocumentParagraphCache::GetParagraphTopWithoutParagraphDistance(vint paragraphIndex) + { + if (paragraphIndex >= validCachedTops) + { + vint currentTop = 0; + if (validCachedTops > 0) + { + auto size = paragraphSizes[validCachedTops - 1]; + currentTop = size.cachedTopWithoutParagraphDistance + size.cachedSize.y; + } + + for (vint i = validCachedTops; i <= paragraphIndex; i++) + { + auto& size = paragraphSizes[i]; + size.cachedTopWithoutParagraphDistance = currentTop; + currentTop += size.cachedSize.y; + } + + validCachedTops = paragraphIndex + 1; + } + return paragraphSizes[paragraphIndex].cachedTopWithoutParagraphDistance; + } + + vint GuiDocumentParagraphCache::GetParagraphTop(vint paragraphIndex, vint paragraphDistance) + { + return GetParagraphTopWithoutParagraphDistance(paragraphIndex) + paragraphIndex * paragraphDistance; + } + + vint GuiDocumentParagraphCache::ResetCache() + { + nameCallbackIdMap.Clear(); + freeCallbackIds.Clear(); + usedCallbackIds = 0; + + auto document = element ? element->GetDocument() : nullptr; + if (document && document->paragraphs.Count() > 0) + { + paragraphCaches.Resize(0); + paragraphCaches.Resize(document->paragraphs.Count()); + paragraphSizes.Resize(document->paragraphs.Count()); + + vint defaultHeight = GetDefaultHeight(); + for (vint i = 0; i < paragraphSizes.Count(); i++) + { + paragraphSizes[i] = { (i * defaultHeight),{0,defaultHeight}}; + } + + validCachedTops = document->paragraphs.Count(); + return document->paragraphs.Count() * defaultHeight; + } + else + { + paragraphCaches.Resize(0); + paragraphSizes.Resize(0); + validCachedTops = 0; + return 0; + } + } + + vint GuiDocumentParagraphCache::ResetTextCache(vint index, vint oldCount, vint newCount) + { + for (vint i = 0; i < oldCount; i++) + { + if (auto cache = paragraphCaches[i + index]) + { + // TODO: (enumerable) foreach on dictionary + for (vint j = 0; j < cache->embeddedObjects.Count(); j++) + { + auto id = cache->embeddedObjects.Keys()[j]; + auto name = cache->embeddedObjects.Values()[j]->name; + nameCallbackIdMap.Remove(name); + freeCallbackIds.Add(id); + } + } + } + + if (oldCount == newCount) + { + for (vint i = 0; i < oldCount; i++) + { + paragraphCaches[index + i] = nullptr; + } + return 0; + } + else + { + vint defaultHeight = GetDefaultHeight(); + pg::ParagraphCacheArray oldCaches; + pg::ParagraphSizeArray oldSizes; + + CopyFrom(oldCaches, paragraphCaches); + CopyFrom(oldSizes, paragraphSizes); + + vint paragraphCount = element->GetDocument()->paragraphs.Count(); + paragraphCaches.Resize(paragraphCount); + paragraphSizes.Resize(paragraphCount); + + vint paragraphTop = GetParagraphTopWithoutParagraphDistance(index); + for (vint i = 0; i < paragraphCount; i++) + { + if (i < index) + { + paragraphCaches[i] = oldCaches[i]; + paragraphSizes[i] = oldSizes[i]; + } + else if (i < index + newCount) + { + // updateText must be true, ensured in GuiDocumentElementRenderer::NotifyParagraphUpdated + paragraphCaches[i] = nullptr; + paragraphSizes[i] = { (paragraphTop + (i - index) * defaultHeight),{0,defaultHeight} }; + } + else + { + paragraphCaches[i] = oldCaches[i - (newCount - oldCount)]; + paragraphSizes[i] = oldSizes[i - (newCount - oldCount)]; + } + } + validCachedTops = index + newCount; + + vint oldUpdatedTotalHeight = 0; + for (vint i = 0; i < oldCount; i++) + { + oldUpdatedTotalHeight += oldSizes[index + i].cachedSize.y; + } + return newCount * defaultHeight - oldUpdatedTotalHeight; + } + } + + vint GuiDocumentParagraphCache::ResetStyleCache(TextPos begin, TextPos end) + { + for (vint i = begin.row; i <= end.row; i++) + { + if (auto cache = paragraphCaches[i]) + { + vint rangeBegin = i == begin.row ? begin.column : 0; + vint rangeEnd = i == end.row ? end.column : cache->fullText.Length(); + cache->invalidation.Apply(Overloading( + [&, this](bool value) + { + if (!value) + { + cache->invalidation = Pair(rangeBegin, rangeEnd); + } + }, + [&, this](collections::Pair range) + { + if (rangeBegin > range.key) rangeBegin = range.key; + if (rangeEnd < range.value) rangeEnd = range.value; + cache->invalidation = Pair(rangeBegin, rangeEnd); + } + )); + } + } + return 0; + } + + vint GuiDocumentParagraphCache::ResetStyleCache(vint index, vint count) + { + for (vint i = 0; i < count; i++) + { + if (auto cache = paragraphCaches[index + i]) + { + cache->invalidation = true; + } + } + return 0; + } + + vint GuiDocumentParagraphCache::EnsureParagraph(vint paragraphIndex, vint maxWidth) + { + auto paragraph = element->GetDocument()->paragraphs[paragraphIndex]; + auto cache = paragraphCaches[paragraphIndex]; + if (!cache) + { + cache = Ptr(new pg::ParagraphCache); + cache->fullText = paragraph->GetTextForCaret(); + paragraphCaches[paragraphIndex] = cache; + } + + if (!cache->graphicsParagraph) + { + auto paragraphText = cache->fullText; + if (auto passwordChar = element->GetPasswordChar()) + { + Array passwordText(paragraphText.Length() + 1); + for (vint i = 0; i < paragraphText.Length(); i++) + { + passwordText[i] = passwordChar; + } + passwordText[paragraphText.Length()] = 0; + paragraphText = &passwordText[0]; + } + cache->graphicsParagraph = layoutProvider->CreateParagraph(paragraphText, renderTarget, callback); + cache->invalidation = true; + } + + cache->invalidation.Apply(Overloading( + [&, this](bool value) + { + if (value) + { + cache->invalidation = false; + renderer->ApplyPropertiesOnParagraph(paragraphIndex, 0, cache->fullText.Length(), maxWidth); + } + }, + [&, this](collections::Pair range) + { + cache->invalidation = false; + renderer->ApplyPropertiesOnParagraph(paragraphIndex, range.key, range.value, maxWidth); + } + )); + + auto& cachedSize = paragraphSizes[paragraphIndex]; + Size oldSize = cachedSize.cachedSize; + Size newSize = cache->graphicsParagraph->GetSize(); + + vint defaultHeight = GetDefaultHeight(); + if(newSize.y < defaultHeight) + { + newSize.y = defaultHeight; + } + cachedSize.cachedSize = newSize; + if (oldSize.y != newSize.y && validCachedTops > paragraphIndex + 1) + { + validCachedTops = paragraphIndex + 1; + } + return newSize.y - oldSize.y; + } + + vint GuiDocumentParagraphCache::GetParagraphFromY(vint y, vint paragraphDistance) + { + auto document = element ? element->GetDocument() : nullptr; + if (!document || document->paragraphs.Count() == 0) return -1; + + vint start = 0; + vint end = paragraphSizes.Count() - 1; + + if (0 < validCachedTops && validCachedTops <= paragraphSizes.Count()) + { + vint index = validCachedTops - 1; + vint top = GetParagraphTop(index, paragraphDistance); + auto size = paragraphSizes[index].cachedSize; + if (y < top) + { + if (index < 1) return 0; + end = index - 1; + } + else if (y < top + size.y + paragraphDistance) + { + return index; + } + else + { + if (index >= paragraphSizes.Count() - 1) return paragraphSizes.Count() - 1; + start = validCachedTops; + } + } + + if (start >= end) return end; + while (true) + { + vint mid = (start + end) / 2; + vint top = GetParagraphTop(mid, paragraphDistance); + auto size = paragraphSizes[mid].cachedSize; + if (y < top) + { + end = mid - 1; + if (start >= end) return start; + } + else if (y < top + size.y + paragraphDistance) + { + return mid; + } + else + { + start = mid + 1; + if (start >= end) return end; + } + } + } + + void GuiDocumentParagraphCache::ReleaseParagraphs(vint index, vint count) + { + for (vint i = 0; i < count; i++) + { + vint paragraphIndex = index + i; + if (paragraphIndex >= 0 && paragraphIndex < paragraphCaches.Count()) + { + auto cache = paragraphCaches[paragraphIndex]; + if (cache) // Check if cache itself is null per UPDATE guidance + { + cache->graphicsParagraph = nullptr; // Release only the rendering object + // Preserve all other data: fullText, embeddedObjects, selectionBegin/selectionEnd + } + } + } + } + } + } +} + +/*********************************************************************** +.\GRAPHICSELEMENT\GUIGRAPHICSDOCUMENTRENDERER_SETPROPERTIESVISITOR.CPP +***********************************************************************/ + +namespace vl +{ + using namespace collections; + + namespace presentation + { + namespace elements + { + +/*********************************************************************** +SetPropertiesVisitor +***********************************************************************/ + + namespace visitors + { + class SetPropertiesVisitor : public Object, public DocumentRun::IVisitor + { + typedef DocumentModel::ResolvedStyle ResolvedStyle; + public: + vint start = 0; + vint length = 0; + vint selectionBegin; + vint selectionEnd; + vint rangeBegin; + vint rangeEnd; + List styles; + + DocumentModel* model; + GuiDocumentParagraphCache* paragraphCache; + GuiDocumentImageCache* imageCache; + Ptr cache; + IGuiGraphicsParagraph* paragraph; + vint paragraphIndex; + + SetPropertiesVisitor( + DocumentModel* _model, + GuiDocumentParagraphCache* _paragraphCache, + GuiDocumentImageCache* _imageCache, + Ptr _cache, + vint _paragraphIndex, + vint _selectionBegin, + vint _selectionEnd, + vint _rangeBegin, + vint _rangeEnd + ) + : model(_model) + , paragraphCache(_paragraphCache) + , imageCache(_imageCache) + , cache(_cache) + , paragraphIndex(_paragraphIndex) + , paragraph(_cache->graphicsParagraph.Obj()) + , selectionBegin(_selectionBegin) + , selectionEnd(_selectionEnd) + , rangeBegin(_rangeBegin) + , rangeEnd(_rangeEnd) + { + ResolvedStyle style; + style = model->GetStyle(DocumentModel::DefaultStyleName, style); + styles.Add(style); + } + + void VisitContainer(DocumentContainerRun* run) + { + for (auto subRun : run->runs) + { + subRun->Accept(this); + } + } + + void ApplyStyle(vint start, vint length, const ResolvedStyle& style) + { + paragraph->SetFont(start, length, style.style.fontFamily); + paragraph->SetSize(start, length, style.style.size); + paragraph->SetStyle(start, length, + (IGuiGraphicsParagraph::TextStyle) + ((style.style.bold ? IGuiGraphicsParagraph::Bold : 0) + | (style.style.italic ? IGuiGraphicsParagraph::Italic : 0) + | (style.style.underline ? IGuiGraphicsParagraph::Underline : 0) + | (style.style.strikeline ? IGuiGraphicsParagraph::Strikeline : 0) + )); + } + + void ApplyColor(vint start, vint length, const ResolvedStyle& style) + { + paragraph->SetColor(start, length, style.color); + paragraph->SetBackgroundColor(start, length, style.backgroundColor); + } + + void Visit(DocumentTextRun* run)override + { + length = run->GetRepresentationText().Length(); + if (length > 0 && start < rangeEnd && rangeBegin < start + length) + { + ResolvedStyle style = styles[styles.Count() - 1]; + ApplyStyle(start, length, style); + ApplyColor(start, length, style); + + vint styleStart = start; + vint styleEnd = styleStart + length; + if (styleStart < selectionEnd && selectionBegin < styleEnd) + { + vint s2 = styleStart > selectionBegin ? styleStart : selectionBegin; + vint s3 = selectionEnd < styleEnd ? selectionEnd : styleEnd; + + if (s2 < s3) + { + ResolvedStyle selectionStyle = model->GetStyle(DocumentModel::SelectionStyleName, style); + ApplyColor(s2, s3 - s2, selectionStyle); + } + } + } + start += length; + } + + void Visit(DocumentStylePropertiesRun* run)override + { + ResolvedStyle style = styles[styles.Count() - 1]; + style = model->GetStyle(run->style, style); + styles.Add(style); + VisitContainer(run); + styles.RemoveAt(styles.Count() - 1); + } + + void Visit(DocumentStyleApplicationRun* run)override + { + ResolvedStyle style = styles[styles.Count() - 1]; + style = model->GetStyle(run->styleName, style); + styles.Add(style); + VisitContainer(run); + styles.RemoveAt(styles.Count() - 1); + } + + void Visit(DocumentHyperlinkRun* run)override + { + ResolvedStyle style = styles[styles.Count() - 1]; + style = model->GetStyle(run->styleName, style); + styles.Add(style); + VisitContainer(run); + styles.RemoveAt(styles.Count() - 1); + } + + void Visit(DocumentImageRun* run)override + { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::elements::visitors::SetPropertiesVisitor::Visit(DocumentImageRun*)#" + length = run->GetRepresentationText().Length(); + if (start < rangeEnd && rangeBegin < start + length) + { + IGuiGraphicsParagraph::InlineObjectProperties properties; + properties.size = run->GetSize(); + properties.baseline = run->baseline; + properties.breakCondition = IGuiGraphicsParagraph::Alone; + properties.backgroundImage = imageCache->GetImageElement(run->image, run->frameIndex, paragraphIndex, start); + + bool result = paragraph->SetInlineObject(start, length, properties); + CHECK_ERROR(result, ERROR_MESSAGE_PREFIX L"The specified range has already been occupied by another inline object."); + + ResolvedStyle style = styles[styles.Count() - 1]; + if (start < selectionEnd && selectionBegin < start + length) + { + style = model->GetStyle(DocumentModel::SelectionStyleName, style); + } + ApplyColor(start, length, style); + } + start += length; +#undef ERROR_MESSAGE_PREFIX + } + + void Visit(DocumentEmbeddedObjectRun* run)override + { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::elements::visitors::SetPropertiesVisitor::Visit(DocumentEmbeddedObjectRun*)#" + length = run->GetRepresentationText().Length(); + if (start < rangeEnd && rangeBegin < start + length) + { + IGuiGraphicsParagraph::InlineObjectProperties properties; + properties.breakCondition = IGuiGraphicsParagraph::Alone; + + if (run->name != L"") + { + vint index = paragraphCache->nameCallbackIdMap.Keys().IndexOf(run->name); + if (index != -1) + { + auto id = paragraphCache->nameCallbackIdMap.Values()[index]; + index = cache->embeddedObjects.Keys().IndexOf(id); + if (index != -1) + { + auto eo = cache->embeddedObjects.Values()[index]; + if (eo->start == start) + { + properties.size = eo->size; + properties.callbackId = id; + } + } + } + else + { + auto eo = Ptr(new pg::EmbeddedObject); + eo->name = run->name; + eo->size = Size(0, 0); + eo->start = start; + + vint id = -1; + vint count = paragraphCache->freeCallbackIds.Count(); + if (count > 0) + { + id = paragraphCache->freeCallbackIds[count - 1]; + paragraphCache->freeCallbackIds.RemoveAt(count - 1); + } + else + { + id = paragraphCache->usedCallbackIds++; + } + + paragraphCache->nameCallbackIdMap.Add(eo->name, id); + cache->embeddedObjects.Add(id, eo); + properties.callbackId = id; + } + } + + bool result = paragraph->SetInlineObject(start, length, properties); + CHECK_ERROR(result, ERROR_MESSAGE_PREFIX L"The specified range has already been occupied by another inline object."); + + ResolvedStyle style = styles[styles.Count() - 1]; + if (start < selectionEnd && selectionBegin < start + length) + { + style = model->GetStyle(DocumentModel::SelectionStyleName, style); + } + ApplyColor(start, length, style); + } + start += length; +#undef ERROR_MESSAGE_PREFIX + } + + void Visit(DocumentParagraphRun* run)override + { + VisitContainer(run); + } + }; + + vint SetProperties( + DocumentModel* model, + GuiDocumentParagraphCache* paragraphCache, + GuiDocumentImageCache* imageCache, + Ptr cache, + vint paragraphIndex, + Ptr run, + vint selectionBegin, + vint selectionEnd, + vint rangeBegin, + vint rangeEnd + ) + { + SetPropertiesVisitor visitor(model, paragraphCache, imageCache, cache, paragraphIndex, selectionBegin, selectionEnd, rangeBegin, rangeEnd); + run->Accept(&visitor); + return visitor.length; + } + } + } + } +} + /*********************************************************************** .\GRAPHICSELEMENT\GUIGRAPHICSELEMENT.CPP ***********************************************************************/ @@ -35980,9 +36274,9 @@ GuiRemoteProtocolElementRenderer RENDERER_TEMPLATE_HEADER void RENDERER_CLASS_TYPE::FinalizeInternal() { - if (this->renderTarget && id != -1) + if (GetGuiGraphicsResourceManager() && id != -1) { - this->renderTarget->UnregisterRenderer(this); + remoteRenderTarget->UnregisterRenderer(this); id = -1; } } @@ -35990,19 +36284,27 @@ GuiRemoteProtocolElementRenderer RENDERER_TEMPLATE_HEADER void RENDERER_CLASS_TYPE::RenderTargetChangedInternal(GuiRemoteGraphicsRenderTarget* oldRenderTarget, GuiRemoteGraphicsRenderTarget* newRenderTarget) { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::elements_remoteprotocol::GuiRemoteProtocolElementRenderer::RenderTargetChangedInternal(GuiRemoteGraphicsRenderTarget*, GuiRemoteGraphicsRenderTarget*)#" if (oldRenderTarget == newRenderTarget) return; - if (oldRenderTarget && id != -1) - { - oldRenderTarget->UnregisterRenderer(this); - id = -1; - } if (newRenderTarget) { - id = newRenderTarget->AllocateNewElementId(); - newRenderTarget->RegisterRenderer(this); + if (!remoteRenderTarget) + { + remoteRenderTarget = newRenderTarget; + } + else + { + CHECK_ERROR(remoteRenderTarget == newRenderTarget, ERROR_MESSAGE_PREFIX L"There should be only one global GuiRemoteGraphicsRenderTarget."); + } + if (id == -1) + { + id = newRenderTarget->AllocateNewElementId(); + newRenderTarget->RegisterRenderer(this); + } updated = true; renderTargetChanged = true; } +#undef ERROR_MESSAGE_PREFIX } RENDERER_TEMPLATE_HEADER @@ -36795,7 +37097,10 @@ DiffRuns if (firstOverlap != -1) { if (map.Keys()[firstOverlap] != range) return false; - const_cast(map.Values()[firstOverlap]) = property; + auto&& inlinedProps = const_cast(map.Values()[firstOverlap]); + if (inlinedProps.callbackId != property.callbackId) return false; + if (inlinedProps.backgroundElementId != property.backgroundElementId) return false; + inlinedProps = property; return true; } } @@ -43160,6 +43465,11 @@ namespace vl::presentation::remote_renderer suggestedMinSize.x = arguments.x + size.x - clientSize.x; suggestedMinSize.y = arguments.y + size.y - clientSize.y; } + + void GuiRemoteRendererSingle::RequestWindowNotifySetCaret(const NativePoint& arguments) + { + window->SetCaretPoint(arguments); + } } /*********************************************************************** @@ -43238,6 +43548,9 @@ namespace vl::presentation::remote_renderer case RendererType::ImageFrame: element = Ptr(GuiImageFrameElement::Create()); break; + case RendererType::DocumentParagraph: + element = CreateRemoteDocumentParagraphElement(); + break; default:; } @@ -43281,315 +43594,6 @@ namespace vl::presentation::remote_renderer fontHeightMeasurings.Clear(); } -/*********************************************************************** -* Rendering (Elemnents) -***********************************************************************/ - - void GuiRemoteRendererSingle::RequestRendererUpdateElement_SolidBorder(const remoteprotocol::ElementDesc_SolidBorder& arguments) - { - vint index = availableElements.Keys().IndexOf(arguments.id); - if (index == -1) return; - auto element = availableElements.Values()[index].Cast(); - if (!element) return; - - element->SetColor(arguments.borderColor); - element->SetShape(arguments.shape); - } - - void GuiRemoteRendererSingle::RequestRendererUpdateElement_SinkBorder(const remoteprotocol::ElementDesc_SinkBorder& arguments) - { - vint index = availableElements.Keys().IndexOf(arguments.id); - if (index == -1) return; - auto element = availableElements.Values()[index].Cast(); - if (!element) return; - - element->SetColors(arguments.leftTopColor, arguments.rightBottomColor); - } - - void GuiRemoteRendererSingle::RequestRendererUpdateElement_SinkSplitter(const remoteprotocol::ElementDesc_SinkSplitter& arguments) - { - vint index = availableElements.Keys().IndexOf(arguments.id); - if (index == -1) return; - auto element = availableElements.Values()[index].Cast(); - if (!element) return; - - element->SetColors(arguments.leftTopColor, arguments.rightBottomColor); - element->SetDirection(arguments.direction); - } - - void GuiRemoteRendererSingle::RequestRendererUpdateElement_SolidBackground(const remoteprotocol::ElementDesc_SolidBackground& arguments) - { - vint index = availableElements.Keys().IndexOf(arguments.id); - if (index == -1) return; - auto element = availableElements.Values()[index].Cast(); - if (!element) return; - - element->SetColor(arguments.backgroundColor); - element->SetShape(arguments.shape); - } - - void GuiRemoteRendererSingle::RequestRendererUpdateElement_GradientBackground(const remoteprotocol::ElementDesc_GradientBackground& arguments) - { - vint index = availableElements.Keys().IndexOf(arguments.id); - if (index == -1) return; - auto element = availableElements.Values()[index].Cast(); - if (!element) return; - - element->SetColors(arguments.leftTopColor, arguments.rightBottomColor); - element->SetDirection(arguments.direction); - element->SetShape(arguments.shape); - } - - void GuiRemoteRendererSingle::RequestRendererUpdateElement_InnerShadow(const remoteprotocol::ElementDesc_InnerShadow& arguments) - { - vint index = availableElements.Keys().IndexOf(arguments.id); - if (index == -1) return; - auto element = availableElements.Values()[index].Cast(); - if (!element) return; - - element->SetColor(arguments.shadowColor); - element->SetThickness(arguments.thickness); - } - - void GuiRemoteRendererSingle::RequestRendererUpdateElement_Polygon(const remoteprotocol::ElementDesc_Polygon& arguments) - { - vint index = availableElements.Keys().IndexOf(arguments.id); - if (index == -1) return; - auto element = availableElements.Values()[index].Cast(); - if (!element) return; - - element->SetSize(arguments.size); - element->SetBorderColor(arguments.borderColor); - element->SetBackgroundColor(arguments.backgroundColor); - - if (arguments.points && arguments.points->Count() > 0) - { - element->SetPoints(&arguments.points->Get(0), arguments.points->Count()); - } - } - -/*********************************************************************** -* Rendering (Elemnents -- Label) -***********************************************************************/ - - void GuiRemoteRendererSingle::StoreLabelMeasuring(vint id, remoteprotocol::ElementSolidLabelMeasuringRequest request, Ptr solidLabel, Size minSize) - { - switch (request) - { - case ElementSolidLabelMeasuringRequest::FontHeight: - { - Pair key = { solidLabel->GetFont().fontFamily,solidLabel->GetFont().size }; - if (fontHeightMeasurings.Contains(key)) return; - fontHeightMeasurings.Add(key); - - ElementMeasuring_FontHeight response; - response.fontFamily = key.key; - response.fontSize = key.value; - response.height = minSize.y; - - if (!elementMeasurings.fontHeights) - { - elementMeasurings.fontHeights = Ptr(new List); - } - elementMeasurings.fontHeights->Add(response); - } - break; - case ElementSolidLabelMeasuringRequest::TotalSize: - { - ElementMeasuring_ElementMinSize response; - response.id = id; - response.minSize = minSize; - - if (!elementMeasurings.minSizes) - { - elementMeasurings.minSizes = Ptr(new List); - } - elementMeasurings.minSizes->Add(response); - } - break; - } - } - - void GuiRemoteRendererSingle::RequestRendererUpdateElement_SolidLabel(const remoteprotocol::ElementDesc_SolidLabel& arguments) - { - vint index = availableElements.Keys().IndexOf(arguments.id); - if (index == -1) return; - auto element = availableElements.Values()[index].Cast(); - if (!element) return; - - element->SetColor(arguments.textColor); - element->SetAlignments(GetAlignment(arguments.horizontalAlignment), GetAlignment(arguments.verticalAlignment)); - element->SetWrapLine(arguments.wrapLine); - element->SetWrapLineHeightCalculation(arguments.wrapLineHeightCalculation); - element->SetEllipse(arguments.ellipse); - element->SetMultiline(arguments.multiline); - - if (arguments.font) - { - element->SetFont(arguments.font.Value()); - } - if (arguments.text) - { - element->SetText(arguments.text.Value()); - } - - if (arguments.measuringRequest) - { - SolidLabelMeasuring measuring; - measuring.request = arguments.measuringRequest.Value(); - index = solidLabelMeasurings.Keys().IndexOf(arguments.id); - if (solidLabelMeasurings.Keys().Contains(arguments.id)) - { - solidLabelMeasurings.Set(arguments.id, measuring); - } - else - { - solidLabelMeasurings.Add(arguments.id, measuring); - } - - StoreLabelMeasuring(arguments.id, measuring.request, element, element->GetRenderer()->GetMinSize()); - } - } - -/*********************************************************************** -* Rendering (Elements -- Image) -***********************************************************************/ - - remoteprotocol::ImageMetadata GuiRemoteRendererSingle::CreateImageMetadata(vint id, INativeImage* image) - { - ImageMetadata response; - response.id = id; - response.format = image->GetFormat(); - response.frames = Ptr(new List); - for (vint i = 0; i < image->GetFrameCount(); i++) - { - auto frame = image->GetFrame(i); - response.frames->Add({ frame->GetSize() }); - } - - return response; - } - - remoteprotocol::ImageMetadata GuiRemoteRendererSingle::CreateImage(const remoteprotocol::ImageCreation& arguments) - { - arguments.imageData->SeekFromBegin(0); - auto image = GetCurrentController()->ImageService()->CreateImageFromStream(*arguments.imageData.Obj()); - if (availableImages.Keys().Contains(arguments.id)) - { - availableImages.Set(arguments.id, image); - } - else - { - availableImages.Add(arguments.id, image); - } - return CreateImageMetadata(arguments.id, image.Obj()); - } - - void GuiRemoteRendererSingle::RequestImageCreated(vint id, const remoteprotocol::ImageCreation& arguments) - { -#define ERROR_MESSAGE_PREFIX L"vl::presentation::remote_renderer::GuiRemoteRendererSingle::RequestImageCreated(const ImageCreation&)#" - CHECK_ERROR(!arguments.imageDataOmitted && arguments.imageData, ERROR_MESSAGE_PREFIX L"Binary content of the image is missing."); - - events->RespondImageCreated(id, CreateImage(arguments)); -#undef ERROR_MESSAGE_PREFIX - } - - void GuiRemoteRendererSingle::RequestImageDestroyed(const vint& arguments) - { - availableImages.Remove(arguments); - } - - void GuiRemoteRendererSingle::RequestRendererUpdateElement_ImageFrame(const remoteprotocol::ElementDesc_ImageFrame& arguments) - { -#define ERROR_MESSAGE_PREFIX L"vl::presentation::remote_renderer::GuiRemoteRendererSingle::RequestRendererUpdateElement_ImageFrame(const arguments&)#" - - vint index = availableElements.Keys().IndexOf(arguments.id); - if (index == -1) return; - auto element = availableElements.Values()[index].Cast(); - if (!element) return; - - element->SetAlignments(GetAlignment(arguments.horizontalAlignment), GetAlignment(arguments.verticalAlignment)); - element->SetStretch(arguments.stretch); - element->SetEnabled(arguments.enabled); - - if (arguments.imageId && arguments.imageCreation) - { - CHECK_ERROR(arguments.imageId.Value() == arguments.imageCreation.Value().id, ERROR_MESSAGE_PREFIX L"imageId and imageCreation.id must be identical."); - } - - if (arguments.imageId) - { - if (arguments.imageCreation && !elementMeasurings.createdImages) - { - elementMeasurings.createdImages = Ptr(new List); - } - - vint index = availableImages.Keys().IndexOf(arguments.imageId.Value()); - if (index == -1) - { - CHECK_ERROR(arguments.imageCreation && !arguments.imageCreation.Value().imageDataOmitted && arguments.imageCreation.Value().imageData, ERROR_MESSAGE_PREFIX L"Binary content of the image is missing."); - - auto response = CreateImage(arguments.imageCreation.Value()); - element->SetImage(availableImages[response.id], arguments.imageFrame); - elementMeasurings.createdImages->Add(response); - } - else - { - auto image = availableImages.Values()[index]; - element->SetImage(image, arguments.imageFrame); - if (arguments.imageCreation) - { - elementMeasurings.createdImages->Add(CreateImageMetadata(arguments.imageId.Value(), image.Obj())); - } - } - } -#undef ERROR_MESSAGE_PREFIX - } - -/*********************************************************************** -* Rendering (Elements -- Document) -***********************************************************************/ - - void GuiRemoteRendererSingle::RequestRendererUpdateElement_DocumentParagraph(vint id, const remoteprotocol::ElementDesc_DocumentParagraph& arguments) - { - CHECK_FAIL(L"Not implemented."); - } - - void GuiRemoteRendererSingle::RequestDocumentParagraph_GetCaret(vint id, const remoteprotocol::GetCaretRequest& arguments) - { - CHECK_FAIL(L"Not implemented."); - } - - void GuiRemoteRendererSingle::RequestDocumentParagraph_GetCaretBounds(vint id, const remoteprotocol::GetCaretBoundsRequest& arguments) - { - CHECK_FAIL(L"Not implemented."); - } - - void GuiRemoteRendererSingle::RequestDocumentParagraph_GetInlineObjectFromPoint(vint id, const remoteprotocol::GetInlineObjectFromPointRequest& arguments) - { - CHECK_FAIL(L"Not implemented."); - } - - void GuiRemoteRendererSingle::RequestDocumentParagraph_GetNearestCaretFromTextPos(vint id, const remoteprotocol::GetCaretBoundsRequest& arguments) - { - CHECK_FAIL(L"Not implemented."); - } - - void GuiRemoteRendererSingle::RequestDocumentParagraph_IsValidCaret(vint id, const remoteprotocol::IsValidCaretRequest& arguments) - { - CHECK_FAIL(L"Not implemented."); - } - - void GuiRemoteRendererSingle::RequestDocumentParagraph_OpenCaret(const remoteprotocol::OpenCaretRequest& arguments) - { - CHECK_FAIL(L"Not implemented."); - } - - void GuiRemoteRendererSingle::RequestDocumentParagraph_CloseCaret(const vint& arguments) - { - CHECK_FAIL(L"Not implemented."); - } - /*********************************************************************** * Rendering (Dom) ***********************************************************************/ @@ -43802,6 +43806,797 @@ namespace vl::presentation::remote_renderer } } + +/*********************************************************************** +.\PLATFORMPROVIDERS\REMOTERENDERER\GUIREMOTERENDERERSINGLE_RENDERING_DOCUMENT.CPP +***********************************************************************/ + +namespace vl::presentation::remote_renderer +{ + using namespace collections; + using namespace elements; + using namespace remoteprotocol; + +/*********************************************************************** +* Rendering (Elements -- Document) +***********************************************************************/ + + class GuiRemoteDocumentParagraphElement + : public Object + , public IGuiGraphicsElement + , protected IGuiGraphicsRenderer + , protected IGuiGraphicsRendererFactory + , protected IGuiGraphicsParagraphCallback + { + protected: + GuiRemoteRendererSingle* owner = nullptr; + compositions::GuiGraphicsComposition* ownerComposition = nullptr; + IGuiGraphicsRenderTarget* renderTarget = nullptr; + + Ptr paragraph; + Nullable text; + bool wrapLine = false; + vint maxWidth = 1; + remoteprotocol::ElementHorizontalAlignment alignment = remoteprotocol::ElementHorizontalAlignment::Left; + Nullable caret; + + elements::DocumentTextRunPropertyMap textRuns; + elements::DocumentInlineObjectRunPropertyMap inlineObjectRuns; + elements::DocumentRunPropertyMap mergedRuns; + + Dictionary inlineObjectBounds; + Dictionary inlineObjectProps; + Dictionary inlineObjectRanges; + + void SetOwnerComposition(compositions::GuiGraphicsComposition* composition) override + { + ownerComposition = composition; + } + + public: + GuiRemoteDocumentParagraphElement(GuiRemoteRendererSingle* _owner) + : owner(_owner) + { + } + + ~GuiRemoteDocumentParagraphElement() + { + } + + IGuiGraphicsParagraph* GetParagraph() const + { + return paragraph.Obj(); + } + + // ===== IGuiGraphicsElement ===== + + IGuiGraphicsRenderer* GetRenderer() override + { + return this; + } + + compositions::GuiGraphicsComposition* GetOwnerComposition() override + { + return ownerComposition; + } + + protected: + + // ===== IGuiGraphicsRenderer ===== + + IGuiGraphicsRendererFactory* GetFactory() override + { + return this; + } + + void Initialize(IGuiGraphicsElement* element) override + { + } + + void Finalize() override + { + } + + void SetRenderTarget(IGuiGraphicsRenderTarget* _renderTarget) override + { + if (renderTarget != _renderTarget) + { + paragraph = nullptr; + } + renderTarget = _renderTarget; + paragraph = nullptr; + if (renderTarget) + { + TryRecreateParagraph(); + } + } + + void Render(Rect bounds) override + { + if (paragraph) + { + paragraph->Render(bounds); + } + } + + void OnElementStateChanged() override + { + } + + Size GetMinSize() override + { + return {}; + } + + // ===== IGuiGraphicsRendererFactory ===== + + IGuiGraphicsRenderer* Create() override + { + CHECK_FAIL(L"vl::presentation::remote_renderer::GuiRemoteDocumentParagraphElement::Create()#Not supported."); + } + + // ===== IGuiGraphicsParagraphCallback ===== + + Size OnRenderInlineObject(vint callbackId, Rect location) override + { + inlineObjectBounds.Set(callbackId, location); + vint index = inlineObjectProps.Keys().IndexOf(callbackId); + if (index == -1) return {}; + return inlineObjectProps.Values()[index].size; + } + + protected: + + void ApplyProps() + { + paragraph->SetWrapLine(wrapLine); + paragraph->SetMaxWidth(maxWidth); + paragraph->SetParagraphAlignment(owner->GetAlignment(alignment)); + } + + void ApplyCaret() + { + if (caret) + { + auto& c = caret.Value(); + paragraph->OpenCaret(c.caret, c.caretColor, c.frontSide); + } + else + { + paragraph->CloseCaret(); + } + } + + void ApplyTextRun(elements::CaretRange range, const remoteprotocol::DocumentTextRunProperty& textProp) + { + auto start = range.caretBegin; + auto length = range.caretEnd - range.caretBegin; + if (length <= 0) return; + + paragraph->SetFont(start, length, textProp.fontProperties.fontFamily); + paragraph->SetSize(start, length, textProp.fontProperties.size); + + IGuiGraphicsParagraph::TextStyle style = (IGuiGraphicsParagraph::TextStyle)0; + if (textProp.fontProperties.bold) style = (IGuiGraphicsParagraph::TextStyle)((vint)style | (vint)IGuiGraphicsParagraph::TextStyle::Bold); + if (textProp.fontProperties.italic) style = (IGuiGraphicsParagraph::TextStyle)((vint)style | (vint)IGuiGraphicsParagraph::TextStyle::Italic); + if (textProp.fontProperties.underline) style = (IGuiGraphicsParagraph::TextStyle)((vint)style | (vint)IGuiGraphicsParagraph::TextStyle::Underline); + if (textProp.fontProperties.strikeline) style = (IGuiGraphicsParagraph::TextStyle)((vint)style | (vint)IGuiGraphicsParagraph::TextStyle::Strikeline); + paragraph->SetStyle(start, length, style); + + paragraph->SetColor(start, length, textProp.textColor); + paragraph->SetBackgroundColor(start, length, textProp.backgroundColor); + } + + void ApplyInlineObjectRun(elements::CaretRange range, const remoteprotocol::DocumentInlineObjectRunProperty& inlineProp) + { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::remote_renderer::GuiRemoteDocumentParagraphElement::ApplyInlineObjectRun(CaretRange, const DocumentInlineObjectRunProperty&)#" + auto start = range.caretBegin; + auto length = range.caretEnd - range.caretBegin; + if (length <= 0) return; + + Ptr background; + if (inlineProp.backgroundElementId != -1) + { + vint index = owner->availableElements.Keys().IndexOf(inlineProp.backgroundElementId); + CHECK_ERROR(index != -1, ERROR_MESSAGE_PREFIX L"backgroundElementId not found."); + background = owner->availableElements.Values()[index]; + } + + IGuiGraphicsParagraph::InlineObjectProperties props; + props.size = inlineProp.size; + props.baseline = inlineProp.baseline; + props.breakCondition = (IGuiGraphicsParagraph::BreakCondition)inlineProp.breakCondition; + props.callbackId = inlineProp.callbackId; + props.backgroundImage = background; + CHECK_ERROR(paragraph->SetInlineObject(start, length, props), ERROR_MESSAGE_PREFIX L"SetInlineObject failed."); +#undef ERROR_MESSAGE_PREFIX + } + + void ApplyRuns() + { + auto&& mergedKeys = mergedRuns.Keys(); + for (vint i = 0; i < mergedKeys.Count(); i++) + { + auto range = mergedKeys[i]; + auto props = mergedRuns.Values()[i]; + + props.Apply(Overloading( + [&](const remoteprotocol::DocumentTextRunProperty& textProp) + { + ApplyTextRun(range, textProp); + }, + [&](const remoteprotocol::DocumentInlineObjectRunProperty& inlineProp) + { + ApplyInlineObjectRun(range, inlineProp); + } + )); + } + } + + void TryRecreateParagraph() + { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::remote_renderer::GuiRemoteDocumentParagraphElement::TryRecreateParagraph()#" + if (!renderTarget) return; + if (!text) return; + + auto resourceManager = GetGuiGraphicsResourceManager(); + CHECK_ERROR(resourceManager != nullptr, ERROR_MESSAGE_PREFIX L"GetGuiGraphicsResourceManager() returns null."); + paragraph = resourceManager->GetLayoutProvider()->CreateParagraph(text.Value(), renderTarget, this); + ApplyProps(); + ApplyRuns(); + ApplyCaret(); +#undef ERROR_MESSAGE_PREFIX + } + + public: + + bool TryGetInlineObjectRunProperty(vint callbackId, remoteprotocol::DocumentInlineObjectRunProperty& outProp) const + { + vint index = inlineObjectProps.Keys().IndexOf(callbackId); + if (index == -1) return false; + outProp = inlineObjectProps.Values()[index]; + return true; + } + + void ApplyUpdateAndFillResponse(const remoteprotocol::ElementDesc_DocumentParagraph& arguments, remoteprotocol::UpdateElement_DocumentParagraphResponse& response) + { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::remote_renderer::GuiRemoteDocumentParagraphElement::ApplyUpdateAndFillResponse(const remoteprotocol::ElementDesc_DocumentParagraph&, remoteprotocol::UpdateElement_DocumentParagraphResponse&)#" + + wrapLine = arguments.wrapLine; + maxWidth = arguments.maxWidth; + alignment = arguments.alignment; + + if (!paragraph) + { + CHECK_ERROR(arguments.text, ERROR_MESSAGE_PREFIX L"First update must contain text."); + text = arguments.text; + + // These should be unnecessary but keep them here so far. + textRuns.Clear(); + inlineObjectRuns.Clear(); + mergedRuns.Clear(); + inlineObjectBounds.Clear(); + inlineObjectProps.Clear(); + inlineObjectRanges.Clear(); + + if (renderTarget) + { + TryRecreateParagraph(); + } + } + else + { + CHECK_ERROR(!arguments.text, ERROR_MESSAGE_PREFIX L"Text is only allowed on first update."); + ApplyProps(); + } + + if (arguments.removedInlineObjects) + { + for (auto callbackId : *arguments.removedInlineObjects.Obj()) + { + auto&& inlineKeys = inlineObjectRuns.Keys(); + for (vint i = 0; i < inlineKeys.Count(); i++) + { + auto range = inlineKeys[i]; + auto prop = inlineObjectRuns.Values()[i]; + if (prop.callbackId == callbackId) + { + CHECK_ERROR(elements::ResetInlineObjectRun(inlineObjectRuns, range), ERROR_MESSAGE_PREFIX L"ResetInlineObjectRun failed."); + break; + } + } + + vint index = inlineObjectRanges.Keys().IndexOf(callbackId); + if (index != -1 && paragraph) + { + auto range = inlineObjectRanges.Values()[index]; + paragraph->ResetInlineObject(range.caretBegin, range.caretEnd - range.caretBegin); + } + inlineObjectBounds.Remove(callbackId); + inlineObjectProps.Remove(callbackId); + inlineObjectRanges.Remove(callbackId); + } + } + + if (arguments.runsDiff) + { + for (auto run : *arguments.runsDiff.Obj()) + { + elements::CaretRange range{ run.caretBegin, run.caretEnd }; + run.props.Apply(Overloading( + [&](const remoteprotocol::DocumentTextRunProperty& textProp) + { + elements::DocumentTextRunPropertyOverrides overrides; + overrides.textColor = textProp.textColor; + overrides.backgroundColor = textProp.backgroundColor; + overrides.fontFamily = textProp.fontProperties.fontFamily; + overrides.size = textProp.fontProperties.size; + // Convert bool flags back to TextStyle + elements::IGuiGraphicsParagraph::TextStyle style = (elements::IGuiGraphicsParagraph::TextStyle)0; + if (textProp.fontProperties.bold) style = (elements::IGuiGraphicsParagraph::TextStyle)((vint)style | (vint)elements::IGuiGraphicsParagraph::TextStyle::Bold); + if (textProp.fontProperties.italic) style = (elements::IGuiGraphicsParagraph::TextStyle)((vint)style | (vint)elements::IGuiGraphicsParagraph::TextStyle::Italic); + if (textProp.fontProperties.underline) style = (elements::IGuiGraphicsParagraph::TextStyle)((vint)style | (vint)elements::IGuiGraphicsParagraph::TextStyle::Underline); + if (textProp.fontProperties.strikeline) style = (elements::IGuiGraphicsParagraph::TextStyle)((vint)style | (vint)elements::IGuiGraphicsParagraph::TextStyle::Strikeline); + overrides.textStyle = style; + elements::AddTextRun(textRuns, range, overrides); + }, + [&](const remoteprotocol::DocumentInlineObjectRunProperty& inlineProp) + { + CHECK_ERROR(elements::AddInlineObjectRun(inlineObjectRuns, range, inlineProp), ERROR_MESSAGE_PREFIX L"arguments.runsDiff updated an inline object run incorrectly."); + } + )); + } + } + + mergedRuns.Clear(); + elements::MergeRuns(textRuns, inlineObjectRuns, mergedRuns); + + if (arguments.runsDiff && paragraph) + { + for (auto run : *arguments.runsDiff.Obj()) + { + elements::CaretRange range{ run.caretBegin, run.caretEnd }; + + run.props.Apply(Overloading( + [&](const remoteprotocol::DocumentTextRunProperty& textProp) + { + ApplyTextRun(range, textProp); + }, + [&](const remoteprotocol::DocumentInlineObjectRunProperty& inlineProp) + { + ApplyInlineObjectRun(range, inlineProp); + } + )); + } + } + + response.documentSize = paragraph->GetSize(); + if (inlineObjectRuns.Count() > 0) + { + renderTarget->StartRendering(); + paragraph->Render(Rect(Point(0, 0), response.documentSize)); + auto failure = renderTarget->StopRendering(); + (void)failure; + + if (inlineObjectBounds.Count() > 0) + { + response.inlineObjectBounds = Ptr(new Dictionary); + CopyFrom(*response.inlineObjectBounds.Obj(), inlineObjectBounds); + } + } +#undef ERROR_MESSAGE_PREFIX + } + + void OpenCaretAndStore(const remoteprotocol::OpenCaretRequest& arguments) + { + caret = arguments; + if (paragraph) + { + paragraph->OpenCaret(arguments.caret, arguments.caretColor, arguments.frontSide); + } + } + + void CloseCaretAndStore() + { + caret.Reset(); + if (paragraph) + { + paragraph->CloseCaret(); + } + } + }; + + Ptr GuiRemoteRendererSingle::CreateRemoteDocumentParagraphElement() + { + return Ptr(new GuiRemoteDocumentParagraphElement(this)); + } + +#define PREPARE_DOCUMENT_WRAPPER_RAW(WRAPPER_NAME, ELEMENT_ID) \ + vint index = availableElements.Keys().IndexOf(ELEMENT_ID); \ + CHECK_ERROR(index != -1, L"GuiRemoteRendererSingle::Request*()#Failed to find IGuiGraphicsParagraph from element id."); \ + auto WRAPPER_NAME = availableElements.Values()[index].Cast(); \ + CHECK_ERROR(WRAPPER_NAME, L"GuiRemoteRendererSingle::Request*()#Failed to find IGuiGraphicsParagraph from element id.") \ + +#define PREPARE_DOCUMENT_WRAPPER(WRAPPER_NAME, ELEMENT_ID) \ + PREPARE_DOCUMENT_WRAPPER_RAW(WRAPPER_NAME, ELEMENT_ID); \ + CHECK_ERROR(WRAPPER_NAME->GetParagraph(), L"GuiRemoteRendererSingle::Request*()#The IGuiGraphicsParagraph is not created yet.") \ + + void GuiRemoteRendererSingle::RequestRendererUpdateElement_DocumentParagraph(vint id, const remoteprotocol::ElementDesc_DocumentParagraph& arguments) + { + PREPARE_DOCUMENT_WRAPPER_RAW(wrapper, arguments.id); + UpdateElement_DocumentParagraphResponse response; + wrapper->ApplyUpdateAndFillResponse(arguments, response); + events->RespondRendererUpdateElement_DocumentParagraph(id, response); + } + + void GuiRemoteRendererSingle::RequestDocumentParagraph_GetCaret(vint id, const remoteprotocol::GetCaretRequest& arguments) + { + PREPARE_DOCUMENT_WRAPPER(wrapper, arguments.id); + GetCaretResponse response; + response.preferFrontSide = false; + response.newCaret = wrapper->GetParagraph()->GetCaret(arguments.caret, arguments.relativePosition, response.preferFrontSide); + events->RespondDocumentParagraph_GetCaret(id, response); + } + + void GuiRemoteRendererSingle::RequestDocumentParagraph_GetCaretBounds(vint id, const remoteprotocol::GetCaretBoundsRequest& arguments) + { + PREPARE_DOCUMENT_WRAPPER(wrapper, arguments.id); + auto bounds = wrapper->GetParagraph()->GetCaretBounds(arguments.caret, arguments.frontSide); + events->RespondDocumentParagraph_GetCaretBounds(id, bounds); + } + + void GuiRemoteRendererSingle::RequestDocumentParagraph_GetInlineObjectFromPoint(vint id, const remoteprotocol::GetInlineObjectFromPointRequest& arguments) + { + PREPARE_DOCUMENT_WRAPPER(wrapper, arguments.id); + Nullable result; + vint start = 0; + vint length = 0; + auto props = wrapper->GetParagraph()->GetInlineObjectFromPoint(arguments.point, start, length); + if (props) + { + vint cb = props.Value().callbackId; + remoteprotocol::DocumentInlineObjectRunProperty inlineProp; + if (wrapper->TryGetInlineObjectRunProperty(cb, inlineProp)) + { + remoteprotocol::DocumentRun run; + run.caretBegin = start; + run.caretEnd = start + length; + run.props = inlineProp; + result = run; + } + } + events->RespondDocumentParagraph_GetInlineObjectFromPoint(id, result); + } + + void GuiRemoteRendererSingle::RequestDocumentParagraph_GetNearestCaretFromTextPos(vint id, const remoteprotocol::GetCaretBoundsRequest& arguments) + { + PREPARE_DOCUMENT_WRAPPER(wrapper, arguments.id); + auto result = wrapper->GetParagraph()->GetNearestCaretFromTextPos(arguments.caret, arguments.frontSide); + events->RespondDocumentParagraph_GetNearestCaretFromTextPos(id, result); + } + + void GuiRemoteRendererSingle::RequestDocumentParagraph_IsValidCaret(vint id, const remoteprotocol::IsValidCaretRequest& arguments) + { + PREPARE_DOCUMENT_WRAPPER(wrapper, arguments.id); + auto result = wrapper->GetParagraph()->IsValidCaret(arguments.caret); + events->RespondDocumentParagraph_IsValidCaret(id, result); + } + + void GuiRemoteRendererSingle::RequestDocumentParagraph_OpenCaret(const remoteprotocol::OpenCaretRequest& arguments) + { + PREPARE_DOCUMENT_WRAPPER(wrapper, arguments.id); + wrapper->OpenCaretAndStore(arguments); + } + + void GuiRemoteRendererSingle::RequestDocumentParagraph_CloseCaret(const vint& arguments) + { + PREPARE_DOCUMENT_WRAPPER(wrapper, arguments); + wrapper->CloseCaretAndStore(); + } + +#undef PREPARE_DOCUMENT_WRAPPER +#undef PREPARE_DOCUMENT_WRAPPER_RAW +} + + +/*********************************************************************** +.\PLATFORMPROVIDERS\REMOTERENDERER\GUIREMOTERENDERERSINGLE_RENDERING_ELEMENTS.CPP +***********************************************************************/ + +namespace vl::presentation::remote_renderer +{ + using namespace collections; + using namespace elements; + using namespace remoteprotocol; + +/*********************************************************************** +* Rendering (Elemnents) +***********************************************************************/ + + void GuiRemoteRendererSingle::RequestRendererUpdateElement_SolidBorder(const remoteprotocol::ElementDesc_SolidBorder& arguments) + { + vint index = availableElements.Keys().IndexOf(arguments.id); + if (index == -1) return; + auto element = availableElements.Values()[index].Cast(); + if (!element) return; + + element->SetColor(arguments.borderColor); + element->SetShape(arguments.shape); + } + + void GuiRemoteRendererSingle::RequestRendererUpdateElement_SinkBorder(const remoteprotocol::ElementDesc_SinkBorder& arguments) + { + vint index = availableElements.Keys().IndexOf(arguments.id); + if (index == -1) return; + auto element = availableElements.Values()[index].Cast(); + if (!element) return; + + element->SetColors(arguments.leftTopColor, arguments.rightBottomColor); + } + + void GuiRemoteRendererSingle::RequestRendererUpdateElement_SinkSplitter(const remoteprotocol::ElementDesc_SinkSplitter& arguments) + { + vint index = availableElements.Keys().IndexOf(arguments.id); + if (index == -1) return; + auto element = availableElements.Values()[index].Cast(); + if (!element) return; + + element->SetColors(arguments.leftTopColor, arguments.rightBottomColor); + element->SetDirection(arguments.direction); + } + + void GuiRemoteRendererSingle::RequestRendererUpdateElement_SolidBackground(const remoteprotocol::ElementDesc_SolidBackground& arguments) + { + vint index = availableElements.Keys().IndexOf(arguments.id); + if (index == -1) return; + auto element = availableElements.Values()[index].Cast(); + if (!element) return; + + element->SetColor(arguments.backgroundColor); + element->SetShape(arguments.shape); + } + + void GuiRemoteRendererSingle::RequestRendererUpdateElement_GradientBackground(const remoteprotocol::ElementDesc_GradientBackground& arguments) + { + vint index = availableElements.Keys().IndexOf(arguments.id); + if (index == -1) return; + auto element = availableElements.Values()[index].Cast(); + if (!element) return; + + element->SetColors(arguments.leftTopColor, arguments.rightBottomColor); + element->SetDirection(arguments.direction); + element->SetShape(arguments.shape); + } + + void GuiRemoteRendererSingle::RequestRendererUpdateElement_InnerShadow(const remoteprotocol::ElementDesc_InnerShadow& arguments) + { + vint index = availableElements.Keys().IndexOf(arguments.id); + if (index == -1) return; + auto element = availableElements.Values()[index].Cast(); + if (!element) return; + + element->SetColor(arguments.shadowColor); + element->SetThickness(arguments.thickness); + } + + void GuiRemoteRendererSingle::RequestRendererUpdateElement_Polygon(const remoteprotocol::ElementDesc_Polygon& arguments) + { + vint index = availableElements.Keys().IndexOf(arguments.id); + if (index == -1) return; + auto element = availableElements.Values()[index].Cast(); + if (!element) return; + + element->SetSize(arguments.size); + element->SetBorderColor(arguments.borderColor); + element->SetBackgroundColor(arguments.backgroundColor); + + if (arguments.points && arguments.points->Count() > 0) + { + element->SetPoints(&arguments.points->Get(0), arguments.points->Count()); + } + } +} + +/*********************************************************************** +.\PLATFORMPROVIDERS\REMOTERENDERER\GUIREMOTERENDERERSINGLE_RENDERING_IMAGE.CPP +***********************************************************************/ + +namespace vl::presentation::remote_renderer +{ + using namespace collections; + using namespace elements; + using namespace remoteprotocol; + +/*********************************************************************** +* Rendering (Elements -- Image) +***********************************************************************/ + + remoteprotocol::ImageMetadata GuiRemoteRendererSingle::CreateImageMetadata(vint id, INativeImage* image) + { + ImageMetadata response; + response.id = id; + response.format = image->GetFormat(); + response.frames = Ptr(new List); + for (vint i = 0; i < image->GetFrameCount(); i++) + { + auto frame = image->GetFrame(i); + response.frames->Add({ frame->GetSize() }); + } + + return response; + } + + remoteprotocol::ImageMetadata GuiRemoteRendererSingle::CreateImage(const remoteprotocol::ImageCreation& arguments) + { + arguments.imageData->SeekFromBegin(0); + auto image = GetCurrentController()->ImageService()->CreateImageFromStream(*arguments.imageData.Obj()); + if (availableImages.Keys().Contains(arguments.id)) + { + availableImages.Set(arguments.id, image); + } + else + { + availableImages.Add(arguments.id, image); + } + return CreateImageMetadata(arguments.id, image.Obj()); + } + + void GuiRemoteRendererSingle::RequestImageCreated(vint id, const remoteprotocol::ImageCreation& arguments) + { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::remote_renderer::GuiRemoteRendererSingle::RequestImageCreated(const ImageCreation&)#" + CHECK_ERROR(!arguments.imageDataOmitted && arguments.imageData, ERROR_MESSAGE_PREFIX L"Binary content of the image is missing."); + + events->RespondImageCreated(id, CreateImage(arguments)); +#undef ERROR_MESSAGE_PREFIX + } + + void GuiRemoteRendererSingle::RequestImageDestroyed(const vint& arguments) + { + availableImages.Remove(arguments); + } + + void GuiRemoteRendererSingle::RequestRendererUpdateElement_ImageFrame(const remoteprotocol::ElementDesc_ImageFrame& arguments) + { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::remote_renderer::GuiRemoteRendererSingle::RequestRendererUpdateElement_ImageFrame(const arguments&)#" + + vint index = availableElements.Keys().IndexOf(arguments.id); + if (index == -1) return; + auto element = availableElements.Values()[index].Cast(); + if (!element) return; + + element->SetAlignments(GetAlignment(arguments.horizontalAlignment), GetAlignment(arguments.verticalAlignment)); + element->SetStretch(arguments.stretch); + element->SetEnabled(arguments.enabled); + + if (arguments.imageId && arguments.imageCreation) + { + CHECK_ERROR(arguments.imageId.Value() == arguments.imageCreation.Value().id, ERROR_MESSAGE_PREFIX L"imageId and imageCreation.id must be identical."); + } + + if (arguments.imageId) + { + if (arguments.imageCreation && !elementMeasurings.createdImages) + { + elementMeasurings.createdImages = Ptr(new List); + } + + vint index = availableImages.Keys().IndexOf(arguments.imageId.Value()); + if (index == -1) + { + CHECK_ERROR(arguments.imageCreation && !arguments.imageCreation.Value().imageDataOmitted && arguments.imageCreation.Value().imageData, ERROR_MESSAGE_PREFIX L"Binary content of the image is missing."); + + auto response = CreateImage(arguments.imageCreation.Value()); + element->SetImage(availableImages[response.id], arguments.imageFrame); + elementMeasurings.createdImages->Add(response); + } + else + { + auto image = availableImages.Values()[index]; + element->SetImage(image, arguments.imageFrame); + if (arguments.imageCreation) + { + elementMeasurings.createdImages->Add(CreateImageMetadata(arguments.imageId.Value(), image.Obj())); + } + } + } +#undef ERROR_MESSAGE_PREFIX + } +} + +/*********************************************************************** +.\PLATFORMPROVIDERS\REMOTERENDERER\GUIREMOTERENDERERSINGLE_RENDERING_LABEL.CPP +***********************************************************************/ + +namespace vl::presentation::remote_renderer +{ + using namespace collections; + using namespace elements; + using namespace remoteprotocol; + +/*********************************************************************** +* Rendering (Elemnents -- Label) +***********************************************************************/ + + void GuiRemoteRendererSingle::StoreLabelMeasuring(vint id, remoteprotocol::ElementSolidLabelMeasuringRequest request, Ptr solidLabel, Size minSize) + { + switch (request) + { + case ElementSolidLabelMeasuringRequest::FontHeight: + { + Pair key = { solidLabel->GetFont().fontFamily,solidLabel->GetFont().size }; + if (fontHeightMeasurings.Contains(key)) return; + fontHeightMeasurings.Add(key); + + ElementMeasuring_FontHeight response; + response.fontFamily = key.key; + response.fontSize = key.value; + response.height = minSize.y; + + if (!elementMeasurings.fontHeights) + { + elementMeasurings.fontHeights = Ptr(new List); + } + elementMeasurings.fontHeights->Add(response); + } + break; + case ElementSolidLabelMeasuringRequest::TotalSize: + { + ElementMeasuring_ElementMinSize response; + response.id = id; + response.minSize = minSize; + + if (!elementMeasurings.minSizes) + { + elementMeasurings.minSizes = Ptr(new List); + } + elementMeasurings.minSizes->Add(response); + } + break; + } + } + + void GuiRemoteRendererSingle::RequestRendererUpdateElement_SolidLabel(const remoteprotocol::ElementDesc_SolidLabel& arguments) + { + vint index = availableElements.Keys().IndexOf(arguments.id); + if (index == -1) return; + auto element = availableElements.Values()[index].Cast(); + if (!element) return; + + element->SetColor(arguments.textColor); + element->SetAlignments(GetAlignment(arguments.horizontalAlignment), GetAlignment(arguments.verticalAlignment)); + element->SetWrapLine(arguments.wrapLine); + element->SetWrapLineHeightCalculation(arguments.wrapLineHeightCalculation); + element->SetEllipse(arguments.ellipse); + element->SetMultiline(arguments.multiline); + + if (arguments.font) + { + element->SetFont(arguments.font.Value()); + } + if (arguments.text) + { + element->SetText(arguments.text.Value()); + } + + if (arguments.measuringRequest) + { + SolidLabelMeasuring measuring; + measuring.request = arguments.measuringRequest.Value(); + index = solidLabelMeasurings.Keys().IndexOf(arguments.id); + if (solidLabelMeasurings.Keys().Contains(arguments.id)) + { + solidLabelMeasurings.Set(arguments.id, measuring); + } + else + { + solidLabelMeasurings.Add(arguments.id, measuring); + } + + StoreLabelMeasuring(arguments.id, measuring.request, element, element->GetRenderer()->GetMinSize()); + } + } +} + /*********************************************************************** .\RESOURCES\GUIDOCUMENT.CPP ***********************************************************************/ diff --git a/Import/GacUI.h b/Import/GacUI.h index 4f389ad1..da54cc8f 100644 --- a/Import/GacUI.h +++ b/Import/GacUI.h @@ -12135,7 +12135,9 @@ IGuiDocumentElementRenderer { public: virtual void NotifyParagraphPaddingUpdated(bool value) = 0; - virtual void NotifyParagraphUpdated(vint index, vint oldCount, vint newCount, bool updatedText) = 0; + virtual void NotifyParagraphTextUpdated(vint index, vint oldCount, vint newCount) = 0; + virtual void NotifyParagraphStyleUpdated(TextPos begin, TextPos end) = 0; + virtual void NotifyParagraphStyleUpdated(vint index, vint count) = 0; virtual Ptr GetHyperlinkFromPoint(Point point) = 0; virtual void OpenCaret(TextPos caret, Color color, bool frontSide) = 0; virtual void CloseCaret(TextPos caret) = 0; @@ -20299,6 +20301,7 @@ namespace vl { class SetPropertiesVisitor; } + class GuiDocumentElementRenderer; /*********************************************************************** GuiDocumentParagraphCache @@ -20317,11 +20320,12 @@ GuiDocumentParagraphCache typedef collections::Dictionary NameIdMap; typedef collections::List FreeIdList; typedef collections::Dictionary> IdEmbeddedObjectMap; + typedef Variant> CacheInvalidation; struct ParagraphCache { Ptr graphicsParagraph; - bool outdatedStyles = true; + CacheInvalidation invalidation = false; WString fullText; IdEmbeddedObjectMap embeddedObjects; @@ -20340,15 +20344,18 @@ GuiDocumentParagraphCache typedef collections::Array ParagraphSizeArray; } + /// + /// Maintain cached for each paragraph in the document as well as their rendering positions. + /// class GuiDocumentParagraphCache : public Object { friend class visitors::SetPropertiesVisitor; protected: + GuiDocumentElementRenderer* renderer = nullptr; IGuiGraphicsParagraphCallback* callback = nullptr; GuiDocumentElement* element = nullptr; IGuiGraphicsRenderTarget* renderTarget = nullptr; IGuiGraphicsLayoutProvider* layoutProvider = nullptr; - vint defaultHeight = 0; pg::ParagraphCacheArray paragraphCaches; pg::ParagraphSizeArray paragraphSizes; @@ -20359,9 +20366,11 @@ GuiDocumentParagraphCache vint usedCallbackIds = 0; public: - GuiDocumentParagraphCache(IGuiGraphicsParagraphCallback* _callback); + GuiDocumentParagraphCache(GuiDocumentElementRenderer* _renderer); ~GuiDocumentParagraphCache(); + static vint GetDefaultHeight(); + void Initialize(GuiDocumentElement* _element); void RenderTargetChanged(IGuiGraphicsRenderTarget* oldRenderTarget, IGuiGraphicsRenderTarget* newRenderTarget); @@ -20372,13 +20381,73 @@ GuiDocumentParagraphCache vint GetParagraphTopWithoutParagraphDistance(vint paragraphIndex); vint GetParagraphTop(vint paragraphIndex, vint paragraphDistance); - vint ResetCache(); // returns total height - vint ResetCache(vint index, vint oldCount, vint newCount, bool updatedText); // returns the diff of total height - vint EnsureParagraph(vint paragraphIndex, vint maxWidth); // returns the diff of total height + vint ResetCache(); // returns total height + vint ResetTextCache(vint index, vint oldCount, vint newCount); // returns the diff of total height + vint ResetStyleCache(TextPos begin, TextPos end); // returns the diff of total height + vint ResetStyleCache(vint index, vint count); // returns the diff of total height + vint EnsureParagraph(vint paragraphIndex, vint maxWidth); // returns the diff of total height vint GetParagraphFromY(vint y, vint paragraphDistance); void ReleaseParagraphs(vint index, vint count); }; +/*********************************************************************** +GuiDocumentImageCache +***********************************************************************/ + + namespace pg + { + using ImageKey = Tuple; + using ImageElementMap = collections::Dictionary>; + + struct ParagraphImageCache + { + ImageElementMap elements; + }; + + using ParagraphImageCacheArray = collections::Array>; + } + + /// + /// Manage the life-cycle of for each occurances of images in the document. + /// The same element should be used until the is removed from the document. + /// + class GuiDocumentImageCache : public Object + { + protected: + GuiDocumentElement* element = nullptr; + pg::ParagraphImageCacheArray caches; + + public: + GuiDocumentImageCache(); + ~GuiDocumentImageCache(); + + void Initialize(GuiDocumentElement* _element); + void RenderTargetChanged(IGuiGraphicsRenderTarget* oldRenderTarget, IGuiGraphicsRenderTarget* newRenderTarget); + void ResetCache(); + void ResetTextCache(vint index, vint oldCount, vint newCount); + Ptr GetImageElement(Ptr image, vint frameIndex, vint paragraphIndex, vint start); + }; + +/*********************************************************************** +SetPropertiesVisitor +***********************************************************************/ + + namespace visitors + { + extern vint SetProperties( + DocumentModel* model, + GuiDocumentParagraphCache* paragraphCache, + GuiDocumentImageCache* imageCache, + Ptr cache, + vint paragraphIndex, + Ptr run, + vint selectionBegin, + vint selectionEnd, + vint rangeBegin, + vint rangeEnd + ); + } + /*********************************************************************** GuiDocumentElementRenderer ***********************************************************************/ @@ -20388,8 +20457,7 @@ GuiDocumentElementRenderer , private virtual IGuiGraphicsParagraphCallback { friend class GuiElementRendererBase; - protected: - + friend class GuiDocumentParagraphCache; private: Size OnRenderInlineObject(vint callbackId, Rect location)override; @@ -20399,6 +20467,7 @@ GuiDocumentElementRenderer vint lastTotalWidth = 0; vint lastTotalHeightWithoutParagraphDistance = 0; GuiDocumentParagraphCache pgCache; + GuiDocumentImageCache imageCache; vint previousRenderBegin = -1; // -1 indicates invalid/uninitialized range vint previousRenderCount = 0; // Invalid when begin == -1 @@ -20417,14 +20486,22 @@ GuiDocumentElementRenderer void FixMinSize(); void UpdateRenderRange(vint index, vint oldCount, vint newCount); void UpdateRenderRangeAndCleanUp(vint currentBegin, vint currentCount); + void NotifyParagraphUpdateLastTotalWidth(vint index, vint count); + void ApplyPropertiesOnParagraph(vint paragraphIndex, vint start, vint end, vint maxWidth); public: GuiDocumentElementRenderer(); + ~GuiDocumentElementRenderer(); + void Render(Rect bounds) override; void OnElementStateChanged() override; + void NotifyParagraphPaddingUpdated(bool value) override; - void NotifyParagraphUpdated(vint index, vint oldCount, vint newCount, bool updatedText) override; + void NotifyParagraphTextUpdated(vint index, vint oldCount, vint newCount) override; + void NotifyParagraphStyleUpdated(TextPos begin, TextPos end) override; + void NotifyParagraphStyleUpdated(vint index, vint count) override; + Ptr GetHyperlinkFromPoint(Point point) override; void OpenCaret(TextPos caret, Color color, bool frontSide) override; @@ -21523,6 +21600,7 @@ namespace vl::presentation::remoteprotocol HANDLER(WindowNotifyActivate, void, void, NOREQ, NORES, DROPREP)\ HANDLER(WindowNotifyShow, ::vl::presentation::remoteprotocol::WindowShowing, void, REQ, NORES, DROPREP)\ HANDLER(WindowNotifyMinSize, ::vl::presentation::NativeSize, void, REQ, NORES, DROPREP)\ + HANDLER(WindowNotifySetCaret, ::vl::presentation::NativePoint, void, REQ, NORES, DROPREP)\ HANDLER(IOUpdateGlobalShortcutKey, ::vl::Ptr<::vl::collections::List<::vl::presentation::remoteprotocol::GlobalShortcutKey>>, void, REQ, NORES, NODROP)\ HANDLER(IORequireCapture, void, void, NOREQ, NORES, NODROP)\ HANDLER(IOReleaseCapture, void, void, NOREQ, NORES, NODROP)\ @@ -21584,6 +21662,7 @@ namespace vl::presentation::remoteprotocol HANDLER(::vl::Ptr<::vl::collections::List<::vl::vint>>)\ HANDLER(::vl::Ptr<::vl::presentation::remoteprotocol::RenderingDom>)\ HANDLER(::vl::WString)\ + HANDLER(::vl::presentation::NativePoint)\ HANDLER(::vl::presentation::NativeRect)\ HANDLER(::vl::presentation::NativeSize)\ HANDLER(::vl::presentation::VKEY)\ @@ -21689,25 +21768,60 @@ DiffRuns using DocumentInlineObjectRunPropertyMap = collections::Dictionary; using DocumentRunPropertyMap = collections::Dictionary; + /// + /// Updates style properties of a text run. Ranges will be splitted or merged accordingly. + /// + /// Current text runs + /// Range of the text run to update + /// Properties to override extern void AddTextRun( DocumentTextRunPropertyMap& map, CaretRange range, const DocumentTextRunPropertyOverrides& propertyOverrides); + /// + /// Adds an inline object run. + /// The function succeeds if the target range has no inline object or has an exactly same inline object. + /// Inline object runs cannot be splitted or merged. + /// To replace an inline object, call to remove it first. + /// + /// Current inline object runs + /// Range of the inline object run to add + /// Properties of the inline object run + /// Returns true if the inline object run was added successfully. extern bool AddInlineObjectRun( DocumentInlineObjectRunPropertyMap& map, CaretRange range, const remoteprotocol::DocumentInlineObjectRunProperty& property); + /// + /// Removes an inline object run. The function succeeds if the target range exactly matches an existing inline object run. + /// + /// Current inline object runs + /// Range of the inline object run to remove + /// Returns true if the inline object run was removed successfully. extern bool ResetInlineObjectRun( DocumentInlineObjectRunPropertyMap& map, CaretRange range); + /// + /// Merge text runs and inline object runs into a single run map. + /// Inline object runs has a higher priority, if a text run and inline object run overlapps, only unoverlapping part of the text run survives. + /// + /// Current text runs + /// Current inline object runs + /// Resulting merged run map extern void MergeRuns( const DocumentTextRunPropertyMap& textRuns, const DocumentInlineObjectRunPropertyMap& inlineObjectRuns, DocumentRunPropertyMap& result); + /// + /// Build diff from two run maps. + /// + /// Old run maps + /// New run maps + /// Resulting diff extern void DiffRuns( const DocumentRunPropertyMap& oldRuns, const DocumentRunPropertyMap& newRuns, @@ -22112,6 +22226,7 @@ namespace vl::presentation::elements_remoteprotocol { protected: vint id = -1; + GuiRemoteGraphicsRenderTarget* remoteRenderTarget = nullptr; vuint64_t renderingBatchId = 0; bool updated = true; bool renderTargetChanged = false; @@ -23782,6 +23897,8 @@ namespace vl::presentation::remote_renderer , protected virtual INativeWindowListener , protected virtual INativeControllerListener { + friend class GuiRemoteDocumentParagraphElement; + protected: INativeWindow* window = nullptr; INativeScreen* screen = nullptr; @@ -23844,6 +23961,7 @@ namespace vl::presentation::remote_renderer remoteprotocol::ImageMetadata CreateImageMetadata(vint id, INativeImage* image); remoteprotocol::ImageMetadata CreateImage(const remoteprotocol::ImageCreation& arguments); void CheckDom(); + Ptr CreateRemoteDocumentParagraphElement(); protected: bool supressPaint = false; @@ -23908,6 +24026,7 @@ namespace vl::presentation::remote_renderer #endif + /*********************************************************************** .\PLATFORMPROVIDERS\REMOTE\GUIREMOTEPROTOCOL_DOMDIFF.H ***********************************************************************/