/*********************************************************************** THIS FILE IS AUTOMATICALLY GENERATED. DO NOT MODIFY DEVELOPER: Zihan Chen(vczh) ***********************************************************************/ #include "GacUI.h" #ifndef VCZH_DEBUG_NO_REFLECTION #include "GacUIReflection.h" #endif /*********************************************************************** .\GACUIREFLECTIONHELPER.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace helper_types { /*********************************************************************** LocalizedStrings ***********************************************************************/ WString LocalizedStrings::FirstOrEmpty(const collections::LazyList& formats) { return formats.First(WString::Empty); } } } namespace reflection { namespace description { using namespace glr::xml; using namespace stream; using namespace collections; using namespace presentation; using namespace presentation::elements; using namespace presentation::compositions; using namespace presentation::controls; using namespace presentation::theme; using namespace presentation::templates; /*********************************************************************** Serialization (Color) ***********************************************************************/ Color TypedValueSerializerProvider::GetDefaultValue() { return Color(); } bool TypedValueSerializerProvider::Serialize(const Color& input, WString& output) { output = input.ToString(); return true; } bool TypedValueSerializerProvider::Deserialize(const WString& input, Color& output) { output = Color::Parse(input); return true; } /*********************************************************************** Serialization (DocumentFontSize) ***********************************************************************/ DocumentFontSize TypedValueSerializerProvider::GetDefaultValue() { return DocumentFontSize(); } bool TypedValueSerializerProvider::Serialize(const DocumentFontSize& input, WString& output) { output = input.ToString(); return true; } bool TypedValueSerializerProvider::Deserialize(const WString& input, DocumentFontSize& output) { output = DocumentFontSize::Parse(input); return true; } /*********************************************************************** Serialization (GlobalStringKey) ***********************************************************************/ GlobalStringKey TypedValueSerializerProvider::GetDefaultValue() { return GlobalStringKey(); } bool TypedValueSerializerProvider::Serialize(const GlobalStringKey& input, WString& output) { output = input.ToString(); return true; } bool TypedValueSerializerProvider::Deserialize(const WString& input, GlobalStringKey& output) { output = GlobalStringKey::Get(input); return true; } /*********************************************************************** External Functions (Basic) ***********************************************************************/ Ptr INativeImage_Constructor(const WString& path) { return GetCurrentController()->ImageService()->CreateImageFromFile(path); } INativeCursor* INativeCursor_Constructor1() { return GetCurrentController()->ResourceService()->GetDefaultSystemCursor(); } INativeCursor* INativeCursor_Constructor2(INativeCursor::SystemCursorType type) { return GetCurrentController()->ResourceService()->GetSystemCursor(type); } /*********************************************************************** External Functions (Elements) ***********************************************************************/ text::TextLines* GuiColorizedTextElement_GetLines(GuiColorizedTextElement* thisObject) { return &thisObject->GetLines(); } /*********************************************************************** External Functions (Compositions) ***********************************************************************/ void GuiTableComposition_SetRows(GuiTableComposition* thisObject, vint value) { vint columns = thisObject->GetColumns(); if (columns <= 0) columns = 1; thisObject->SetRowsAndColumns(value, columns); } void GuiTableComposition_SetColumns(GuiTableComposition* thisObject, vint value) { vint row = thisObject->GetRows(); if (row <= 0) row = 1; thisObject->SetRowsAndColumns(row, value); } void IGuiAltActionHost_CollectAltActions(IGuiAltActionHost* host, List& actions) { Group group; host->CollectAltActions(group); // TODO: (enumerable) Linq:SelectMany for (vint i = 0; i < group.Count(); i++) { CopyFrom(actions, group.GetByIndex(i), true); } } } } } /*********************************************************************** .\APPLICATION\CONTROLS\GUIAPPLICATION.CPP ***********************************************************************/ extern void GuiMain(); namespace vl { namespace presentation { extern void GuiInitializeUtilities(); extern void GuiFinalizeUtilities(); namespace controls { using namespace collections; using namespace compositions; using namespace theme; using namespace description; /*********************************************************************** GuiGlobalShortcutKeyManager ***********************************************************************/ class GuiGlobalShortcutKeyManager : public GuiShortcutKeyManager { protected: Dictionary idToItemsMap; Dictionary itemToIdsMap; bool IsGlobal() override { return true; } bool OnCreatingShortcut(GuiShortcutKeyItem* item) override { bool ctrl, shift, alt; VKEY key; item->ReadKeyConfig(ctrl, shift, alt, key); vint id = GetCurrentController()->InputService()->RegisterGlobalShortcutKey(ctrl, shift, alt, key); if (id < (vint)NativeGlobalShortcutKeyResult::ValidIdBegins) return false; idToItemsMap.Add(id, item); itemToIdsMap.Add(item, id); return true; } void OnDestroyingShortcut(GuiShortcutKeyItem* item) override { vint id = itemToIdsMap[item]; idToItemsMap.Remove(id); itemToIdsMap.Remove(item); GetCurrentController()->InputService()->UnregisterGlobalShortcutKey(id); } public: GuiShortcutKeyItem* TryGetItemFromId(vint id) { vint index = idToItemsMap.Keys().IndexOf(id); return index == -1 ? nullptr : idToItemsMap.Values()[index]; } }; /*********************************************************************** GuiApplication ***********************************************************************/ void GuiApplication::InvokeClipboardNotify(compositions::GuiGraphicsComposition* composition, compositions::GuiEventArgs& arguments) { if(composition->HasEventReceiver()) { composition->GetEventReceiver()->clipboardNotify.Execute(arguments); } for (auto subComposition : composition->Children()) { InvokeClipboardNotify(subComposition, arguments); } } void GuiApplication::ClipboardUpdated() { for(vint i=0;iGetNotifyEventArguments(); windows[i]->ClipboardUpdated.Execute(arguments); InvokeClipboardNotify(windows[i]->GetBoundsComposition(), arguments); } } void GuiApplication::GlobalShortcutKeyActivated(vint id) { auto manager = dynamic_cast(globalShortcutKeyManager.Obj()); if (auto item = manager->TryGetItemFromId(id)) { item->Execute(); } } GuiApplication::GuiApplication() :locale(Locale::UserDefault()) { globalShortcutKeyManager = Ptr(new GuiGlobalShortcutKeyManager); GetCurrentController()->CallbackService()->InstallListener(this); } GuiApplication::~GuiApplication() { if(sharedTooltipControl) { delete sharedTooltipControl; sharedTooltipControl=0; } GetCurrentController()->CallbackService()->UninstallListener(this); } INativeWindow* GuiApplication::GetThreadContextNativeWindow(GuiControlHost* controlHost) { return nullptr; } void GuiApplication::RegisterWindow(GuiWindow* window) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::controls::GuiApplication::RegisterWindow(GuiWindow*)#" CHECK_ERROR(!window->registeredInApplication, ERROR_MESSAGE_PREFIX L"The window has been registered"); window->registeredInApplication = true; windows.Add(window); if (auto nativeWindow = window->GetNativeWindow()) { windowMap.Add(nativeWindow, window); } #undef ERROR_MESSAGE_PREFIX } void GuiApplication::UnregisterWindow(GuiWindow* window) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::controls::GuiApplication::UnregisterWindow(GuiWindow*)#" CHECK_ERROR(window->registeredInApplication, ERROR_MESSAGE_PREFIX L"The window has not been registered"); window->registeredInApplication = false; if (auto nativeWindow = window->GetNativeWindow()) { windowMap.Remove(nativeWindow); } windows.Remove(window); #undef ERROR_MESSAGE_PREFIX } void GuiApplication::NotifyNativeWindowChanged(GuiControlHost* controlHost, INativeWindow* previousNativeWindow) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::controls::GuiApplication::NotifyNativeWindowChanged(GuiControlsHost*, INativeWindow*)#" if (auto window = dynamic_cast(controlHost)) { if (!window->registeredInApplication) return; if (previousNativeWindow) { CHECK_ERROR(windowMap[previousNativeWindow] == window, ERROR_MESSAGE_PREFIX L"Unpaired arguments."); windowMap.Remove(previousNativeWindow); } if (auto nativeWindow = window->GetNativeWindow()) { windowMap.Add(nativeWindow, window); } } #undef ERROR_MESSAGE_PREFIX } void GuiApplication::RegisterPopupOpened(GuiPopup* popup) { vint index=openingPopups.IndexOf(popup); if(index==-1) { openingPopups.Add(popup); } } void GuiApplication::RegisterPopupClosed(GuiPopup* popup) { if(openingPopups.Remove(popup)) { } } void GuiApplication::TooltipMouseEnter(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { sharedTooltipHovering=true; } void GuiApplication::TooltipMouseLeave(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { sharedTooltipHovering=false; if(sharedTooltipClosing) { CloseTooltip(); } } Locale GuiApplication::GetLocale() { return locale; } void GuiApplication::SetLocale(Locale value) { if (locale != value) { locale = value; LocaleChanged(); } } void GuiApplication::Run(GuiWindow* _mainWindow) { if (!mainWindow) { mainWindow = _mainWindow; GetCurrentController()->WindowService()->Run(mainWindow->GetNativeWindow()); mainWindow = nullptr; } } bool GuiApplication::RunOneCycle() { return GetCurrentController()->WindowService()->RunOneCycle(); } GuiWindow* GuiApplication::GetMainWindow() { return mainWindow; } const collections::List& GuiApplication::GetWindows() { return windows; } GuiWindow* GuiApplication::GetWindow(NativePoint location) { INativeWindow* nativeWindow = GetCurrentController()->WindowService()->GetWindow(location); if (nativeWindow) { // TODO: (enumerable) foreach for (vint i = 0; i < windows.Count(); i++) { GuiWindow* window = windows[i]; if (window->GetNativeWindow() == nativeWindow) { return window; } } } return 0; } GuiWindow* GuiApplication::GetWindowFromNative(INativeWindow* nativeWindow) { vint index = windowMap.Keys().IndexOf(nativeWindow); return index == -1 ? nullptr : windowMap.Values()[index]; } void GuiApplication::ShowTooltip(GuiControl* owner, GuiControl* tooltip, vint preferredContentWidth, Point location) { GuiWindow* ownerWindow = dynamic_cast(owner->GetRelatedControlHost()); if (sharedTooltipOwnerWindow != ownerWindow) { delete sharedTooltipControl; sharedTooltipControl = 0; } if(!sharedTooltipControl) { sharedTooltipControl = new GuiTooltip(theme::ThemeName::Tooltip); if (ownerWindow) { if (auto tooltipStyle = ownerWindow->TypedControlTemplateObject(true)->GetTooltipTemplate()) { sharedTooltipControl->SetControlTemplate(tooltipStyle); } } sharedTooltipControl->GetBoundsComposition()->GetEventReceiver()->mouseEnter.AttachMethod(this, &GuiApplication::TooltipMouseEnter); sharedTooltipControl->GetBoundsComposition()->GetEventReceiver()->mouseLeave.AttachMethod(this, &GuiApplication::TooltipMouseLeave); } sharedTooltipHovering=false; sharedTooltipClosing=false; sharedTooltipOwnerWindow = ownerWindow; sharedTooltipOwner=owner; sharedTooltipControl->SetTemporaryContentControl(tooltip); sharedTooltipControl->SetPreferredContentWidth(preferredContentWidth); sharedTooltipControl->SetClientSize(Size(10, 10)); sharedTooltipControl->ShowPopup(owner, location); } void GuiApplication::CloseTooltip() { if(sharedTooltipControl) { if(sharedTooltipHovering) { sharedTooltipClosing=true; } else { sharedTooltipClosing=false; sharedTooltipControl->Close(); } } } GuiControl* GuiApplication::GetTooltipOwner() { if(!sharedTooltipControl) return 0; if(!sharedTooltipControl->GetTemporaryContentControl()) return 0; return sharedTooltipOwner; } compositions::IGuiShortcutKeyManager* GuiApplication::GetGlobalShortcutKeyManager() { return globalShortcutKeyManager.Obj(); } WString GuiApplication::GetExecutablePath() { return GetCurrentController()->GetExecutablePath(); } WString GuiApplication::GetExecutableFolder() { WString path=GetExecutablePath(); for(vint i=path.Length()-1;i>=0;i--) { if(path[i]==L'\\' || path[i]==L'/') { return path.Sub(0, i+1); } } return L""; } bool GuiApplication::IsInMainThread(GuiControlHost* controlHost) { return GetCurrentController()->AsyncService()->IsInMainThread(GetThreadContextNativeWindow(controlHost)); } void GuiApplication::InvokeAsync(const Func& proc) { GetCurrentController()->AsyncService()->InvokeAsync(proc); } void GuiApplication::InvokeInMainThread(GuiControlHost* controlHost, const Func& proc) { GetCurrentController()->AsyncService()->InvokeInMainThread(GetThreadContextNativeWindow(controlHost), proc); } bool GuiApplication::InvokeInMainThreadAndWait(GuiControlHost* controlHost, const Func& proc, vint milliseconds) { CHECK_ERROR(!IsInMainThread(controlHost), L"GuiApplication::InvokeInMainThreadAndWait(GuiControlHost*, const Func&, vint)#This function cannot be called in UI thread."); return GetCurrentController()->AsyncService()->InvokeInMainThreadAndWait(GetThreadContextNativeWindow(controlHost), proc, milliseconds); } Ptr GuiApplication::DelayExecute(const Func& proc, vint milliseconds) { return GetCurrentController()->AsyncService()->DelayExecute(proc, milliseconds); } Ptr GuiApplication::DelayExecuteInMainThread(const Func& proc, vint milliseconds) { return GetCurrentController()->AsyncService()->DelayExecuteInMainThread(proc, milliseconds); } void GuiApplication::RunGuiTask(GuiControlHost* controlHost, const Func& proc) { if (IsInMainThread(controlHost)) { proc(); } else { InvokeInMainThreadAndWait(controlHost, [&proc]() { proc(); }); } } /*********************************************************************** Helpers ***********************************************************************/ GuiApplication* application = nullptr; bool GACUI_UNITTEST_ONLY_SKIP_THREAD_LOCAL_STORAGE_DISPOSE_STORAGES = false; bool GACUI_UNITTEST_ONLY_SKIP_TYPE_AND_PLUGIN_LOAD_UNLOAD = false; GuiApplication* GetApplication() { return application; } /*********************************************************************** GuiApplicationMain ***********************************************************************/ class UIThreadAsyncScheduler :public Object, public IAsyncScheduler, public Description { public: void Execute(const Func& callback)override { GetApplication()->InvokeInMainThread(GetApplication()->GetMainWindow(), callback); } void ExecuteInBackground(const Func& callback)override { GetApplication()->InvokeAsync(callback); } void DelayExecute(const Func& callback, vint milliseconds)override { GetApplication()->DelayExecuteInMainThread(callback, milliseconds); } }; class OtherThreadAsyncScheduler :public Object, public IAsyncScheduler, public Description { public: void Execute(const Func& callback)override { GetApplication()->InvokeAsync(callback); } void ExecuteInBackground(const Func& callback)override { GetApplication()->InvokeAsync(callback); } void DelayExecute(const Func& callback, vint milliseconds)override { GetApplication()->DelayExecute(callback, milliseconds); } }; void GuiApplicationInitialize() { theme::InitializeTheme(); if (!GACUI_UNITTEST_ONLY_SKIP_TYPE_AND_PLUGIN_LOAD_UNLOAD) { #ifndef VCZH_DEBUG_NO_REFLECTION GetGlobalTypeManager()->Load(); #endif GetPluginManager()->Load(true, true); } else { GetPluginManager()->Load(false, true); } GetCurrentController()->InputService()->StartTimer(); { GuiApplication app; application = &app; IAsyncScheduler::RegisterSchedulerForCurrentThread(Ptr(new UIThreadAsyncScheduler)); IAsyncScheduler::RegisterDefaultScheduler(Ptr(new OtherThreadAsyncScheduler)); GuiInitializeUtilities(); GuiMain(); GuiFinalizeUtilities(); IAsyncScheduler::UnregisterDefaultScheduler(); IAsyncScheduler::UnregisterSchedulerForCurrentThread(); application = nullptr; } GetCurrentController()->InputService()->StopTimer(); theme::FinalizeTheme(); FinalizeGlobalStorage(); if (!GACUI_UNITTEST_ONLY_SKIP_TYPE_AND_PLUGIN_LOAD_UNLOAD) { GetPluginManager()->Unload(true, true); DestroyPluginManager(); #ifndef VCZH_DEBUG_NO_REFLECTION ResetGlobalTypeManager(); #endif } else { GetPluginManager()->Unload(false, true); } if (!GACUI_UNITTEST_ONLY_SKIP_THREAD_LOCAL_STORAGE_DISPOSE_STORAGES) { ThreadLocalStorage::DisposeStorages(); } } void GuiRawInitialize() { if (!GACUI_UNITTEST_ONLY_SKIP_TYPE_AND_PLUGIN_LOAD_UNLOAD) { #ifndef VCZH_DEBUG_NO_REFLECTION GetGlobalTypeManager()->Load(); #endif GetPluginManager()->Load(true, true); } else { GetPluginManager()->Load(false, true); } GetCurrentController()->InputService()->StartTimer(); { IAsyncScheduler::RegisterSchedulerForCurrentThread(Ptr(new UIThreadAsyncScheduler)); IAsyncScheduler::RegisterDefaultScheduler(Ptr(new OtherThreadAsyncScheduler)); GuiMain(); IAsyncScheduler::UnregisterDefaultScheduler(); IAsyncScheduler::UnregisterSchedulerForCurrentThread(); } GetCurrentController()->InputService()->StopTimer(); FinalizeGlobalStorage(); if (!GACUI_UNITTEST_ONLY_SKIP_TYPE_AND_PLUGIN_LOAD_UNLOAD) { GetPluginManager()->Unload(true, true); DestroyPluginManager(); #ifndef VCZH_DEBUG_NO_REFLECTION ResetGlobalTypeManager(); #endif } else { GetPluginManager()->Unload(false, true); } if (!GACUI_UNITTEST_ONLY_SKIP_THREAD_LOCAL_STORAGE_DISPOSE_STORAGES) { ThreadLocalStorage::DisposeStorages(); } } } } } void GuiRawMain() { vl::presentation::controls::GuiRawInitialize(); } void GuiApplicationMain() { vl::presentation::controls::GuiApplicationInitialize(); } /*********************************************************************** .\APPLICATION\CONTROLS\GUIBASICCONTROLS.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { using namespace elements; using namespace compositions; using namespace collections; using namespace reflection::description; /*********************************************************************** GuiDisposedFlag ***********************************************************************/ void GuiDisposedFlag::SetDisposed() { disposed = true; } GuiDisposedFlag::GuiDisposedFlag(GuiControl* _owner) :owner(_owner) { } GuiDisposedFlag::~GuiDisposedFlag() { } bool GuiDisposedFlag::IsDisposed() { return disposed; } /*********************************************************************** GuiControl ***********************************************************************/ Ptr GuiControl::GetDisposedFlag() { if (!disposedFlag) { disposedFlag = Ptr(new GuiDisposedFlag(this)); } return disposedFlag; } void GuiControl::BeforeControlTemplateUninstalled() { } void GuiControl::AfterControlTemplateInstalled(bool initialize) { controlTemplateObject->SetText(text); controlTemplateObject->SetFont(displayFont); controlTemplateObject->SetContext(context); controlTemplateObject->SetVisuallyEnabled(isVisuallyEnabled); controlTemplateObject->SetFocusableComposition(focusableComposition); controlTemplateObject->SetFocused(isFocused); } void GuiControl::CheckAndStoreControlTemplate(templates::GuiControlTemplate* value) { controlTemplateObject = value; } void GuiControl::EnsureControlTemplateExists() { if (!controlTemplateObject) { RebuildControlTemplate(); } } void GuiControl::RebuildControlTemplate() { bool initialize = controlTemplateObject == nullptr; if (controlTemplateObject) { BeforeControlTemplateUninstalled(); containerComposition->GetParent()->RemoveChild(containerComposition); boundsComposition->AddChild(containerComposition); SafeDeleteComposition(controlTemplateObject); controlTemplateObject = nullptr; } if (controlTemplate) { CheckAndStoreControlTemplate(controlTemplate({})); } else { CheckAndStoreControlTemplate(theme::GetCurrentTheme()->CreateStyle(controlThemeName)({})); } if (controlTemplateObject) { controlTemplateObject->SetAlignmentToParent(Margin(0, 0, 0, 0)); containerComposition->GetParent()->RemoveChild(containerComposition); boundsComposition->AddChild(controlTemplateObject); controlTemplateObject->GetContainerComposition()->AddChild(containerComposition); AfterControlTemplateInstalled(initialize); } } void GuiControl::OnChildInserted(GuiControl* control) { GuiControl* oldParent=control->parent; children.Add(control); control->parent=this; control->OnParentChanged(oldParent, control->parent); control->UpdateVisuallyEnabled(); control->UpdateDisplayFont(); if (auto host = boundsComposition->GetRelatedGraphicsHost()) { host->InvalidateTabOrderCache(); } } void GuiControl::OnChildRemoved(GuiControl* control) { GuiControl* oldParent=control->parent; control->parent=0; children.Remove(control); control->OnParentChanged(oldParent, control->parent); if (auto host = boundsComposition->GetRelatedGraphicsHost()) { host->InvalidateTabOrderCache(); } } void GuiControl::OnParentChanged(GuiControl* oldParent, GuiControl* newParent) { OnParentLineChanged(); } void GuiControl::OnParentLineChanged() { { GuiControlSignalEventArgs arguments(boundsComposition); arguments.controlSignal = ControlSignal::ParentLineChanged; ControlSignalTrigerred.Execute(arguments); } // TODO: (enumerable) foreach for(vint i=0;iOnParentLineChanged(); } } void GuiControl::OnServiceAdded() { { GuiControlSignalEventArgs arguments(boundsComposition); arguments.controlSignal = ControlSignal::ServiceAdded; ControlSignalTrigerred.Execute(arguments); } // TODO: (enumerable) foreach for(vint i=0;iOnParentLineChanged(); } } void GuiControl::OnRenderTargetChanged(elements::IGuiGraphicsRenderTarget* renderTarget) { GuiControlSignalEventArgs arguments(boundsComposition); arguments.controlSignal = ControlSignal::RenderTargetChanged; ControlSignalTrigerred.Execute(arguments); } void GuiControl::OnBeforeReleaseGraphicsHost() { // TODO: (enumerable) foreach for(vint i=0;iOnBeforeReleaseGraphicsHost(); } } void GuiControl::UpdateVisuallyEnabled() { bool newValue = isEnabled && (parent == 0 ? true : parent->GetVisuallyEnabled()); if (isVisuallyEnabled != newValue) { isVisuallyEnabled = newValue; if (!isVisuallyEnabled && isFocused) { GuiControl* selectedControl = nullptr; auto current = GetParent(); while (current) { if (current->GetFocusableComposition() && current->GetVisuallyEnabled()) { selectedControl = current; break; } current = current->GetParent(); } if (selectedControl) { selectedControl->SetFocused(); } else if(auto host = focusableComposition->GetRelatedGraphicsHost()) { host->ClearFocus(); } } if (controlTemplateObject) { controlTemplateObject->SetVisuallyEnabled(isVisuallyEnabled); } VisuallyEnabledChanged.Execute(GetNotifyEventArguments()); // TODO: (enumerable) foreach for (vint i = 0; i < children.Count(); i++) { children[i]->UpdateVisuallyEnabled(); } } } void GuiControl::UpdateDisplayFont() { auto newValue = font ? font.Value() : parent ? parent->GetDisplayFont() : GetCurrentController()->ResourceService()->GetDefaultFont(); if (displayFont != newValue) { displayFont = newValue; if (controlTemplateObject) { controlTemplateObject->SetFont(displayFont); } DisplayFontChanged.Execute(GetNotifyEventArguments()); // TODO: (enumerable) foreach for (vint i = 0; i < children.Count(); i++) { children[i]->UpdateDisplayFont(); } } } void GuiControl::OnGotFocus(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if (!isFocused) { isFocused = true; if (controlTemplateObject) { controlTemplateObject->SetFocused(true); } FocusedChanged.Execute(GetNotifyEventArguments()); } } void GuiControl::OnLostFocus(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if (isFocused) { isFocused = false; if (controlTemplateObject) { controlTemplateObject->SetFocused(false); } FocusedChanged.Execute(GetNotifyEventArguments()); } } void GuiControl::SetFocusableComposition(compositions::GuiGraphicsComposition* value) { if (focusableComposition != value) { if (focusableComposition) { focusableComposition->GetEventReceiver()->gotFocus.Detach(gotFocusHandler); focusableComposition->GetEventReceiver()->lostFocus.Detach(lostFocusHandler); gotFocusHandler = nullptr; lostFocusHandler = nullptr; } focusableComposition = value; if (controlTemplateObject) { controlTemplateObject->SetFocusableComposition(focusableComposition); } if (focusableComposition) { gotFocusHandler = focusableComposition->GetEventReceiver()->gotFocus.AttachMethod(this, &GuiControl::OnGotFocus); lostFocusHandler = focusableComposition->GetEventReceiver()->lostFocus.AttachMethod(this, &GuiControl::OnLostFocus); } else { GuiEventArgs arguments(boundsComposition); OnLostFocus(boundsComposition, arguments); } } } bool GuiControl::IsControlVisibleAndEnabled() { GuiControl* control = this; while (control) { if (!control->GetVisible() || !control->GetEnabled()) { return false; } control = control->GetParent(); } return true; } bool GuiControl::IsAltEnabled() { return IsControlVisibleAndEnabled(); } bool GuiControl::IsAltAvailable() { return focusableComposition != nullptr && alt != L""; } compositions::GuiGraphicsComposition* GuiControl::GetAltComposition() { return boundsComposition; } compositions::IGuiAltActionHost* GuiControl::GetActivatingAltHost() { return activatingAltHost; } void GuiControl::OnActiveAlt() { SetFocused(); } bool GuiControl::IsTabEnabled() { return IsControlVisibleAndEnabled(); } bool GuiControl::IsTabAvailable() { return focusableComposition != nullptr; } bool GuiControl::SharedPtrDestructorProc(DescriptableObject* obj, bool forceDisposing) { GuiControl* value=dynamic_cast(obj); if(value->GetBoundsComposition()->GetParent()) { if (!forceDisposing) return false; } SafeDeleteControl(value); return true; } GuiControl::GuiControl(theme::ThemeName themeName) :controlThemeName(themeName) { if (auto controller = GetCurrentController()) { displayFont = controller->ResourceService()->GetDefaultFont(); } { boundsComposition = new GuiBoundsComposition; boundsComposition->SetAssociatedControl(this); boundsComposition->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); containerComposition = new GuiBoundsComposition; containerComposition->SetTransparentToMouse(true); containerComposition->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); containerComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); boundsComposition->AddChild(containerComposition); } { ControlThemeNameChanged.SetAssociatedComposition(boundsComposition); ControlTemplateChanged.SetAssociatedComposition(boundsComposition); ControlSignalTrigerred.SetAssociatedComposition(boundsComposition); VisibleChanged.SetAssociatedComposition(boundsComposition); EnabledChanged.SetAssociatedComposition(boundsComposition); FocusedChanged.SetAssociatedComposition(boundsComposition); VisuallyEnabledChanged.SetAssociatedComposition(boundsComposition); DisplayFontChanged.SetAssociatedComposition(boundsComposition); AltChanged.SetAssociatedComposition(boundsComposition); TextChanged.SetAssociatedComposition(boundsComposition); FontChanged.SetAssociatedComposition(boundsComposition); ContextChanged.SetAssociatedComposition(boundsComposition); } sharedPtrDestructorProc = &GuiControl::SharedPtrDestructorProc; } GuiControl::~GuiControl() { if (disposedFlag) { disposedFlag->SetDisposed(); } // prevent a root bounds composition from notifying its dead controls if (!parent) { NotifyFinalizeInstance(boundsComposition); } if (tooltipControl) { // the only legal parent is the GuiApplication::sharedTooltipWindow if (tooltipControl->GetBoundsComposition()->GetParent()) { tooltipControl->GetBoundsComposition()->GetParent()->RemoveChild(tooltipControl->GetBoundsComposition()); } delete tooltipControl; } // TODO: (enumerable) foreach for (vint i = 0; i < children.Count(); i++) { delete children[i]; } children.Clear(); // let the root control of a control tree delete the whole composition tree if (!parent) { delete boundsComposition; } } void GuiControl::TryDelayExecuteIfNotDeleted(Func proc) { if (auto controlHost = GetRelatedControlHost()) { auto flag = GetDisposedFlag(); GetApplication()->InvokeInMainThread(controlHost, [=]() { if (!flag->IsDisposed()) { proc(); } }); } else { proc(); } } compositions::GuiEventArgs GuiControl::GetNotifyEventArguments() { return GuiEventArgs(boundsComposition); } theme::ThemeName GuiControl::GetControlThemeName() { return controlThemeName; } void GuiControl::SetControlThemeName(theme::ThemeName value) { SetControlThemeNameAndTemplate(value, controlTemplate); } GuiControl::ControlTemplatePropertyType GuiControl::GetControlTemplate() { return controlTemplate; } void GuiControl::SetControlTemplate(const ControlTemplatePropertyType& value) { SetControlThemeNameAndTemplate(controlThemeName, value); } void GuiControl::SetControlThemeNameAndTemplate(theme::ThemeName themeNameValue, const ControlTemplatePropertyType& controlTemplateValue) { bool themeChanged = (controlThemeName != themeNameValue); bool templateChanged = (controlTemplate || controlTemplateValue); if (themeChanged || templateChanged) { controlThemeName = themeNameValue; controlTemplate = controlTemplateValue; RebuildControlTemplate(); if (themeChanged) { ControlThemeNameChanged.Execute(GetNotifyEventArguments()); } if (templateChanged) { ControlTemplateChanged.Execute(GetNotifyEventArguments()); } } } templates::GuiControlTemplate* GuiControl::GetControlTemplateObject() { EnsureControlTemplateExists(); return controlTemplateObject; } compositions::GuiBoundsComposition* GuiControl::GetBoundsComposition() { EnsureControlTemplateExists(); return boundsComposition; } compositions::GuiGraphicsComposition* GuiControl::GetContainerComposition() { EnsureControlTemplateExists(); return containerComposition; } compositions::GuiGraphicsComposition* GuiControl::GetFocusableComposition() { EnsureControlTemplateExists(); return focusableComposition; } GuiControl* GuiControl::GetParent() { return parent; } vint GuiControl::GetChildrenCount() { return children.Count(); } GuiControl* GuiControl::GetChild(vint index) { return children[index]; } bool GuiControl::AddChild(GuiControl* control) { return GetContainerComposition()->AddChild(control->GetBoundsComposition()); } bool GuiControl::HasChild(GuiControl* control) { return children.Contains(control); } GuiControlHost* GuiControl::GetRelatedControlHost() { return parent?parent->GetRelatedControlHost():0; } bool GuiControl::GetVisuallyEnabled() { return isVisuallyEnabled; } bool GuiControl::GetFocused() { return isFocused; } void GuiControl::SetFocused() { if (!focusableComposition) return; if (!isVisuallyEnabled) return; if (!focusableComposition->GetEventuallyVisible()) return; if (auto host = focusableComposition->GetRelatedGraphicsHost()) { host->SetFocus(focusableComposition); } } bool GuiControl::GetAcceptTabInput() { return acceptTabInput; } void GuiControl::SetAcceptTabInput(bool value) { acceptTabInput = value; } vint GuiControl::GetTabPriority() { return tabPriority; } void GuiControl::SetTabPriority(vint value) { vint newTabPriority = value < 0 ? -1 : value; if (tabPriority != newTabPriority) { tabPriority = newTabPriority; if (auto host = boundsComposition->GetRelatedGraphicsHost()) { host->InvalidateTabOrderCache(); } } } bool GuiControl::GetEnabled() { return isEnabled; } void GuiControl::SetEnabled(bool value) { if(isEnabled!=value) { isEnabled=value; EnabledChanged.Execute(GetNotifyEventArguments()); UpdateVisuallyEnabled(); } } bool GuiControl::GetVisible() { return isVisible; } void GuiControl::SetVisible(bool value) { boundsComposition->SetVisible(value); if(isVisible!=value) { isVisible=value; VisibleChanged.Execute(GetNotifyEventArguments()); } } const WString& GuiControl::GetAlt() { return alt; } bool GuiControl::SetAlt(const WString& value) { if (!IGuiAltAction::IsLegalAlt(value)) return false; if (alt != value) { alt = value; AltChanged.Execute(GetNotifyEventArguments()); } return true; } void GuiControl::SetActivatingAltHost(compositions::IGuiAltActionHost* host) { activatingAltHost = host; } const WString& GuiControl::GetText() { return text; } void GuiControl::SetText(const WString& value) { if (text != value) { text = value; if (controlTemplateObject) { controlTemplateObject->SetText(text); } TextChanged.Execute(GetNotifyEventArguments()); } } const Nullable& GuiControl::GetFont() { return font; } void GuiControl::SetFont(const Nullable& value) { if (font != value) { font = value; FontChanged.Execute(GetNotifyEventArguments()); UpdateDisplayFont(); } } const FontProperties& GuiControl::GetDisplayFont() { return displayFont; } description::Value GuiControl::GetContext() { return context; } void GuiControl::SetContext(const description::Value& value) { if (context != value) { context = value; if (controlTemplateObject) { controlTemplateObject->SetContext(context); } ContextChanged.Execute(GetNotifyEventArguments()); } } description::Value GuiControl::GetTag() { return tag; } void GuiControl::SetTag(const description::Value& value) { tag=value; } GuiControl* GuiControl::GetTooltipControl() { return tooltipControl; } GuiControl* GuiControl::SetTooltipControl(GuiControl* value) { GuiControl* oldTooltipControl=tooltipControl; tooltipControl=value; return oldTooltipControl; } vint GuiControl::GetTooltipWidth() { return tooltipWidth; } void GuiControl::SetTooltipWidth(vint value) { tooltipWidth=value; } bool GuiControl::DisplayTooltip(Point location) { if(!tooltipControl) return false; GetApplication()->ShowTooltip(this, tooltipControl, tooltipWidth, location); return true; } void GuiControl::CloseTooltip() { if(GetApplication()->GetTooltipOwner()==this) { GetApplication()->CloseTooltip(); } } IDescriptable* GuiControl::QueryService(const WString& identifier) { if (identifier == IGuiAltAction::Identifier) { return (IGuiAltAction*)this; } else if (identifier == IGuiAltActionContainer::Identifier) { return nullptr; } else if (identifier == IGuiTabAction::Identifier) { return (IGuiTabAction*)this; } else { vint index = controlServices.Keys().IndexOf(identifier); if (index != -1) { return controlServices.Values()[index].Obj(); } if (parent) { return parent->QueryService(identifier); } } return nullptr; } bool GuiControl::AddService(const WString& identifier, Ptr value) { if (controlServices.Keys().Contains(identifier)) { return false; } controlServices.Add(identifier, value); OnServiceAdded(); return true; } /*********************************************************************** GuiCustomControl ***********************************************************************/ controls::GuiControlHost* GuiCustomControl::GetControlHostForInstance() { return GetRelatedControlHost(); } void GuiCustomControl::OnParentLineChanged() { GuiControl::OnParentLineChanged(); OnControlHostForInstanceChanged(); } GuiCustomControl::GuiCustomControl(theme::ThemeName themeName) :GuiControl(themeName) { } GuiCustomControl::~GuiCustomControl() { FinalizeAggregation(); FinalizeInstanceRecursively(this); } } } } /*********************************************************************** .\APPLICATION\CONTROLS\GUIINSTANCEROOTOBJECT.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { using namespace reflection::description; using namespace compositions; /*********************************************************************** GuiComponent ***********************************************************************/ GuiComponent::GuiComponent() { } GuiComponent::~GuiComponent() { } void GuiComponent::Attach(GuiInstanceRootObject* rootObject) { } void GuiComponent::Detach(GuiInstanceRootObject* rootObject) { } /*********************************************************************** GuiInstanceRootObject ***********************************************************************/ class RootObjectTimerCallback : public Object, public IGuiGraphicsTimerCallback { public: GuiControlHost* controlHost; GuiInstanceRootObject* rootObject; bool alive = true; RootObjectTimerCallback(GuiInstanceRootObject* _rootObject, GuiControlHost* _controlHost) :rootObject(_rootObject) , controlHost(_controlHost) { } bool Play()override { if (alive) { // TODO: (enumerable) foreach:indexed(alterable(reversed)) for (vint i = rootObject->runningAnimations.Count() - 1; i >= 0; i--) { auto animation = rootObject->runningAnimations[i]; animation->Run(); if (animation->GetStopped()) { rootObject->runningAnimations.RemoveAt(i); } } if (rootObject->runningAnimations.Count() == 0) { rootObject->UninstallTimerCallback(nullptr); return false; } } return alive; } }; void GuiInstanceRootObject::InstallTimerCallback(controls::GuiControlHost* controlHost) { if (!timerCallback) { timerCallback = Ptr(new RootObjectTimerCallback(this, controlHost)); controlHost->GetTimerManager()->AddCallback(timerCallback); } } bool GuiInstanceRootObject::UninstallTimerCallback(controls::GuiControlHost* controlHost) { if (timerCallback && timerCallback->controlHost != controlHost) { timerCallback->alive = false; timerCallback = nullptr; return true; } return false; } void GuiInstanceRootObject::OnControlHostForInstanceChanged() { auto controlHost = GetControlHostForInstance(); if (UninstallTimerCallback(controlHost)) { for (auto animation : runningAnimations) { animation->Pause(); } } if (controlHost) { InstallTimerCallback(controlHost); for (auto animation : runningAnimations) { animation->Resume(); } StartPendingAnimations(); } } void GuiInstanceRootObject::StartPendingAnimations() { for (auto animation : pendingAnimations) { animation->Start(); } CopyFrom(runningAnimations, pendingAnimations, true); pendingAnimations.Clear(); } GuiInstanceRootObject::GuiInstanceRootObject() { } GuiInstanceRootObject::~GuiInstanceRootObject() { UninstallTimerCallback(nullptr); } void GuiInstanceRootObject::FinalizeInstance() { if (!finalized) { finalized = true; for (auto subscription : subscriptions) { subscription->Close(); } for (auto component : components) { component->Detach(this); } subscriptions.Clear(); // TODO: (enumerable) foreach for (vint i = 0; i resolver) { resourceResolver = resolver; } Ptr GuiInstanceRootObject::ResolveResource(const WString& protocol, const WString& path, bool ensureExist) { Ptr object; if (resourceResolver) { object = resourceResolver->ResolveResource(protocol, path); } if (ensureExist && !object) { throw ArgumentException(L"Resource \"" + protocol + L"://" + path + L"\" does not exist."); } return object; } Ptr GuiInstanceRootObject::AddSubscription(Ptr subscription) { CHECK_ERROR(finalized == false, L"GuiInstanceRootObject::AddSubscription(Ptr)#Cannot add subscription after finalizing."); if (subscriptions.Contains(subscription.Obj())) { return nullptr; } else { subscriptions.Add(subscription); subscription->Open(); subscription->Update(); return subscription; } } void GuiInstanceRootObject::UpdateSubscriptions() { for (auto subscription : subscriptions) { subscription->Update(); } } bool GuiInstanceRootObject::AddComponent(GuiComponent* component) { CHECK_ERROR(finalized == false, L"GuiInstanceRootObject::AddComponent(GuiComponent*)#Cannot add component after finalizing."); if(components.Contains(component)) { return false; } else { components.Add(component); component->Attach(this); return true; } } bool GuiInstanceRootObject::AddControlHostComponent(GuiControlHost* controlHost) { return AddComponent(new GuiObjectComponent(Ptr(controlHost))); } bool GuiInstanceRootObject::AddAnimation(Ptr animation) { CHECK_ERROR(finalized == false, L"GuiInstanceRootObject::AddAnimation(Ptr)#Cannot add animation after finalizing."); if (runningAnimations.Contains(animation.Obj()) || pendingAnimations.Contains(animation.Obj())) { return false; } else { pendingAnimations.Add(animation); if (auto controlHost = GetControlHostForInstance()) { InstallTimerCallback(controlHost); StartPendingAnimations(); } return true; } } bool GuiInstanceRootObject::KillAnimation(Ptr animation) { if (!animation) return false; if (runningAnimations.Contains(animation.Obj())) { runningAnimations.Remove(animation.Obj()); return true; } if (pendingAnimations.Contains(animation.Obj())) { pendingAnimations.Remove(animation.Obj()); return true; } return false; } reflection::description::Value GuiInstanceRootObject::GetNamedObject(const WString& name) { vint index = namedObjects.Keys().IndexOf(name); if (index == -1) { return {}; } else { return namedObjects.Values()[index]; } } void GuiInstanceRootObject::SetNamedObject(const WString& name, const reflection::description::Value& namedObject) { namedObjects.Set(name, namedObject); } } } } /*********************************************************************** .\APPLICATION\CONTROLS\GUILABELCONTROLS.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { using namespace elements; using namespace compositions; using namespace collections; using namespace reflection::description; /*********************************************************************** GuiLabel ***********************************************************************/ void GuiLabel::BeforeControlTemplateUninstalled_() { auto ct = TypedControlTemplateObject(false); if (!ct) return; textColorConsisted = (textColor == ct->GetDefaultTextColor()); } void GuiLabel::AfterControlTemplateInstalled_(bool initialize) { auto ct = TypedControlTemplateObject(true); if (initialize || textColorConsisted) { SetTextColor(ct->GetDefaultTextColor()); } else { ct->SetTextColor(textColor); } } GuiLabel::GuiLabel(theme::ThemeName themeName) :GuiControl(themeName) { } GuiLabel::~GuiLabel() { } Color GuiLabel::GetTextColor() { return textColor; } void GuiLabel::SetTextColor(Color value) { if (textColor != value) { textColor = value; TypedControlTemplateObject(true)->SetTextColor(textColor); } } } } } /*********************************************************************** .\APPLICATION\CONTROLS\GUITHEMEMANAGER.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace templates { /*********************************************************************** GuiTemplate ***********************************************************************/ GuiTemplate_PROPERTIES(GUI_TEMPLATE_PROPERTY_IMPL) controls::GuiControlHost* GuiTemplate::GetControlHostForInstance() { return GetRelatedControlHost(); } void GuiTemplate::OnParentLineChanged() { GuiBoundsComposition::OnParentLineChanged(); OnControlHostForInstanceChanged(); } GuiTemplate::GuiTemplate() { GuiTemplate_PROPERTIES(GUI_TEMPLATE_PROPERTY_EVENT_INIT) } GuiTemplate::~GuiTemplate() { FinalizeInstanceRecursively(this); } /*********************************************************************** Template Declarations ***********************************************************************/ GUI_CORE_CONTROL_TEMPLATE_DECL(GUI_TEMPLATE_CLASS_IMPL) } } } /*********************************************************************** .\APPLICATION\CONTROLS\GUIWINDOWCONTROLS.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { using namespace elements; using namespace compositions; using namespace collections; using namespace reflection::description; /*********************************************************************** GuiControlHost ***********************************************************************/ void GuiControlHost::DeleteThis() { auto callback = callbackAfterDeleteThis; delete this; if (callback) { callback(); } } void GuiControlHost::OnNativeWindowChanged() { } void GuiControlHost::OnVisualStatusChanged() { } controls::GuiControlHost* GuiControlHost::GetControlHostForInstance() { return this; } GuiControl* GuiControlHost::GetTooltipOwner(Point location) { GuiGraphicsComposition* composition=this->GetBoundsComposition()->FindVisibleComposition(location, true); if(composition) { GuiControl* control=composition->GetRelatedControl(); while(control) { if(control->GetTooltipControl()) { return control; } control=control->GetParent(); } } return nullptr; } void GuiControlHost::MoveIntoTooltipControl(GuiControl* tooltipControl, Point location) { if (tooltipLocation != location) { tooltipLocation = location; { GuiControl* currentOwner = GetApplication()->GetTooltipOwner(); if (currentOwner && currentOwner != tooltipControl) { if (tooltipCloseDelay) { tooltipCloseDelay->Cancel(); tooltipCloseDelay = 0; } GetApplication()->DelayExecuteInMainThread([=]() { currentOwner->CloseTooltip(); }, TooltipDelayCloseTime); } } if (!tooltipControl) { if (tooltipOpenDelay) { tooltipOpenDelay->Cancel(); tooltipOpenDelay = 0; } } else if (tooltipOpenDelay) { tooltipOpenDelay->Delay(TooltipDelayOpenTime); } else if (GetApplication()->GetTooltipOwner() != tooltipControl) { tooltipOpenDelay = GetApplication()->DelayExecuteInMainThread([this]() { GuiControl* owner = GetTooltipOwner(tooltipLocation); if (owner) { Point offset = owner->GetBoundsComposition()->GetGlobalBounds().LeftTop(); Point p(tooltipLocation.x - offset.x, tooltipLocation.y - offset.y + 24); owner->DisplayTooltip(p); tooltipOpenDelay = 0; tooltipCloseDelay = GetApplication()->DelayExecuteInMainThread([this, owner]() { owner->CloseTooltip(); }, TooltipDelayLifeTime); } }, TooltipDelayOpenTime); } } } void GuiControlHost::MouseMoving(const NativeWindowMouseInfo& info) { if (!info.left && !info.middle && !info.right) { GuiControl* tooltipControl = GetTooltipOwner(tooltipLocation); MoveIntoTooltipControl(tooltipControl, Point(host->GetNativeWindow()->Convert(NativePoint(info.x, info.y)))); } } void GuiControlHost::MouseLeaved() { MoveIntoTooltipControl(0, Point(-1, -1)); } void GuiControlHost::Moved() { OnVisualStatusChanged(); } void GuiControlHost::Enabled() { GuiControl::SetEnabled(true); OnVisualStatusChanged(); } void GuiControlHost::Disabled() { GuiControl::SetEnabled(false); OnVisualStatusChanged(); } void GuiControlHost::GotFocus() { WindowGotFocus.Execute(GetNotifyEventArguments()); OnVisualStatusChanged(); } void GuiControlHost::LostFocus() { WindowLostFocus.Execute(GetNotifyEventArguments()); OnVisualStatusChanged(); } void GuiControlHost::RenderingAsActivated() { WindowActivated.Execute(GetNotifyEventArguments()); OnVisualStatusChanged(); } void GuiControlHost::RenderingAsDeactivated() { WindowDeactivated.Execute(GetNotifyEventArguments()); OnVisualStatusChanged(); } void GuiControlHost::Opened() { WindowOpened.Execute(GetNotifyEventArguments()); } void GuiControlHost::BeforeClosing(bool& cancel) { GuiRequestEventArgs arguments(boundsComposition); arguments.cancel=cancel; WindowClosing.Execute(arguments); if(!arguments.handled) { cancel=arguments.cancel; } } void GuiControlHost::AfterClosing() { WindowReadyToClose.Execute(GetNotifyEventArguments()); } void GuiControlHost::Closed() { WindowClosed.Execute(GetNotifyEventArguments()); } void GuiControlHost::Destroying() { WindowDestroying.Execute(GetNotifyEventArguments()); calledDestroyed = true; if (deleteWhenDestroyed) { GetApplication()->InvokeInMainThread(this, [=]() { DeleteThis(); }); } SetNativeWindow(nullptr); } void GuiControlHost::UpdateClientSize(Size value, bool updateNativeWindowOnly) { if (auto window = host->GetNativeWindow()) { host->GetNativeWindow()->SetClientSize(window->Convert(value)); if (!updateNativeWindowOnly) { host->RequestUpdateSizeFromNativeWindow(); } } } void GuiControlHost::UpdateClientSizeAfterRendering(Size preferredSize, Size clientSize) { auto size = GetClientSize(); if (size != clientSize) { UpdateClientSize(clientSize, true); } } GuiControlHost::GuiControlHost(theme::ThemeName themeName, INativeWindow::WindowMode mode) :GuiControl(themeName) , windowMode(mode) { boundsComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); WindowGotFocus.SetAssociatedComposition(boundsComposition); WindowLostFocus.SetAssociatedComposition(boundsComposition); WindowActivated.SetAssociatedComposition(boundsComposition); WindowDeactivated.SetAssociatedComposition(boundsComposition); WindowOpened.SetAssociatedComposition(boundsComposition); WindowClosing.SetAssociatedComposition(boundsComposition); WindowReadyToClose.SetAssociatedComposition(boundsComposition); WindowClosed.SetAssociatedComposition(boundsComposition); WindowDestroying.SetAssociatedComposition(boundsComposition); host=new GuiGraphicsHost(this, boundsComposition); sharedPtrDestructorProc = 0; } GuiControlHost::~GuiControlHost() { FinalizeInstanceRecursively(this); OnBeforeReleaseGraphicsHost(); delete host; } void GuiControlHost::DeleteAfterProcessingAllEvents(const Func& callback) { CHECK_ERROR(!deleteWhenDestroyed, L"vl::presentation::controls::GuiControlHost::DeleteAfterProcessingAllEvents()#This function cannot be called twice."); deleteWhenDestroyed = true; callbackAfterDeleteThis = callback; auto window = host->GetNativeWindow(); if (calledDestroyed || !window) { DeleteThis(); } else { GetApplication()->InvokeInMainThread(this, [window]() { GetCurrentController()->WindowService()->DestroyNativeWindow(window); }); } } compositions::GuiGraphicsHost* GuiControlHost::GetGraphicsHost() { return host; } compositions::GuiGraphicsComposition* GuiControlHost::GetMainComposition() { return host->GetMainComposition(); } INativeWindow* GuiControlHost::GetNativeWindow() { return host->GetNativeWindow(); } void GuiControlHost::SetNativeWindow(INativeWindow* window) { auto previousNativeWindow = host->GetNativeWindow(); if(previousNativeWindow) { host->GetNativeWindow()->UninstallListener(this); } if (window) { if (windowMode != window->GetWindowMode()) { CHECK_FAIL(L"GuiControlHost::SetNativeWindow(INativeWindow*)#Window mode does not match."); } } host->SetNativeWindow(window); if(host->GetNativeWindow()) { host->GetNativeWindow()->InstallListener(this); } GetApplication()->NotifyNativeWindowChanged(this, previousNativeWindow); OnNativeWindowChanged(); } void GuiControlHost::ForceCalculateSizeImmediately() { auto size = GetClientSize(); boundsComposition->ForceCalculateSizeImmediately(); SetClientSize(size); } bool GuiControlHost::GetEnabled() { if(host->GetNativeWindow()) { return host->GetNativeWindow()->IsEnabled(); } else { return false; } } void GuiControlHost::SetEnabled(bool value) { if(host->GetNativeWindow()) { if(value) { host->GetNativeWindow()->Enable(); } else { host->GetNativeWindow()->Disable(); } } } bool GuiControlHost::GetFocused() { if(host->GetNativeWindow()) { return host->GetNativeWindow()->IsActivated(); } else { return false; } } void GuiControlHost::SetFocused() { if(host->GetNativeWindow()) { host->GetNativeWindow()->SetActivate(); } } bool GuiControlHost::GetRenderingAsActivated() { if(host->GetNativeWindow()) { return host->GetNativeWindow()->IsRenderingAsActivated(); } else { return false; } } bool GuiControlHost::GetShowInTaskBar() { if(host->GetNativeWindow()) { return host->GetNativeWindow()->IsAppearedInTaskBar(); } else { return false; } } void GuiControlHost::SetShowInTaskBar(bool value) { if(host->GetNativeWindow()) { if(value) { host->GetNativeWindow()->ShowInTaskBar(); } else { host->GetNativeWindow()->HideInTaskBar(); } } } bool GuiControlHost::GetEnabledActivate() { if(host->GetNativeWindow()) { return host->GetNativeWindow()->IsEnabledActivate(); } else { return false; } } void GuiControlHost::SetEnabledActivate(bool value) { if(host->GetNativeWindow()) { if(value) { host->GetNativeWindow()->EnableActivate(); } else { host->GetNativeWindow()->DisableActivate(); } } } bool GuiControlHost::GetTopMost() { if(host->GetNativeWindow()) { return host->GetNativeWindow()->GetTopMost(); } else { return false; } } void GuiControlHost::SetTopMost(bool topmost) { if(host->GetNativeWindow()) { host->GetNativeWindow()->SetTopMost(topmost); } } compositions::IGuiShortcutKeyManager* GuiControlHost::GetShortcutKeyManager() { return host->GetShortcutKeyManager(); } void GuiControlHost::SetShortcutKeyManager(compositions::IGuiShortcutKeyManager* value) { host->SetShortcutKeyManager(value); } compositions::GuiGraphicsTimerManager* GuiControlHost::GetTimerManager() { return host->GetTimerManager(); } Size GuiControlHost::GetClientSize() { if (auto window = host->GetNativeWindow()) { return window->Convert(window->GetClientSize()); } else { return Size(0, 0); } } void GuiControlHost::SetClientSize(Size value) { UpdateClientSize(value, false); } NativePoint GuiControlHost::GetLocation() { if(auto window = host->GetNativeWindow()) { return window->GetBounds().LeftTop(); } else { return NativePoint(); } } void GuiControlHost::SetLocation(NativePoint value) { if (auto window = host->GetNativeWindow()) { auto bounds = window->GetBounds(); window->SetBounds(NativeRect(value, bounds.GetSize())); } } void GuiControlHost::SetBounds(NativePoint location, Size size) { if (auto window = host->GetNativeWindow()) { window->SetBounds(NativeRect(location, window->Convert(size))); } } GuiControlHost* GuiControlHost::GetRelatedControlHost() { return this; } const WString& GuiControlHost::GetText() { WString result; if(host->GetNativeWindow()) { result=host->GetNativeWindow()->GetTitle(); } if(result!=GuiControl::GetText()) { GuiControl::SetText(result); } return GuiControl::GetText(); } void GuiControlHost::SetText(const WString& value) { if(host->GetNativeWindow()) { host->GetNativeWindow()->SetTitle(value); GuiControl::SetText(value); } } INativeScreen* GuiControlHost::GetRelatedScreen() { if(host->GetNativeWindow()) { return GetCurrentController()->ScreenService()->GetScreen(host->GetNativeWindow()); } else { return 0; } } void GuiControlHost::Show() { if(host->GetNativeWindow()) { host->GetNativeWindow()->Show(); } } void GuiControlHost::ShowDeactivated() { if(host->GetNativeWindow()) { host->GetNativeWindow()->ShowDeactivated(); } } void GuiControlHost::ShowRestored() { if(host->GetNativeWindow()) { host->GetNativeWindow()->ShowRestored(); } } void GuiControlHost::ShowMaximized() { if(host->GetNativeWindow()) { host->GetNativeWindow()->ShowMaximized(); } } void GuiControlHost::ShowMinimized() { if(host->GetNativeWindow()) { host->GetNativeWindow()->ShowMinimized(); } } void GuiControlHost::Hide() { if (auto window = host->GetNativeWindow()) { window->Hide(false); } } void GuiControlHost::Close() { if (auto window = host->GetNativeWindow()) { window->Hide(true); // auto mainWindow = GetCurrentController()->WindowService()->GetMainWindow(); // if (mainWindow == window) // { // SetNativeWindow(nullptr); // GetCurrentController()->WindowService()->DestroyNativeWindow(window); // } // else // { // window->Hide(true); // } } } bool GuiControlHost::GetOpening() { INativeWindow* window=host->GetNativeWindow(); if(window) { return window->IsVisible(); } return false; } /*********************************************************************** GuiWindow ***********************************************************************/ void GuiWindow::BeforeControlTemplateUninstalled_() { } void GuiWindow::AfterControlTemplateInstalled_(bool initialize) { ApplyFrameConfig(); auto ct = TypedControlTemplateObject(true); auto window = GetNativeWindow(); SetControlTemplateProperties(); UpdateIcon(window, ct); UpdateCustomFramePadding(window, ct); if (window) { window->SetIcon(icon); } SetNativeWindowFrameProperties(); } void GuiWindow::UpdateIcon(INativeWindow* window, templates::GuiWindowTemplate* ct) { ct->SetIcon(icon ? icon : window ? window->GetIcon() : nullptr); } void GuiWindow::UpdateCustomFramePadding(INativeWindow* window, templates::GuiWindowTemplate* ct) { if (window) { ct->SetCustomFramePadding(window->Convert(window->GetCustomFramePadding())); } else { ct->SetCustomFramePadding({8, 8, 8, 8}); } } bool GuiWindow::IsRenderedAsMaximized() { auto nativeWindow = GetNativeWindow(); if (nativeWindow && GetApplication()->GetMainWindow() == this) { if (auto hostedApp = GetHostedApplication()) { nativeWindow = hostedApp->GetNativeWindowHost(); } } return nativeWindow ? nativeWindow->GetSizeState() == INativeWindow::Maximized : false; } void GuiWindow::SetControlTemplateProperties() { if (auto ct = TypedControlTemplateObject(false)) { ct->SetMaximizedBox(hasMaximizedBox); ct->SetMinimizedBox(hasMinimizedBox); ct->SetBorder(hasBorder); ct->SetSizeBox(hasSizeBox); ct->SetIconVisible(isIconVisible); ct->SetTitleBar(hasTitleBar); ct->SetMaximized(IsRenderedAsMaximized()); ct->SetActivated(GetRenderingAsActivated()); } } void GuiWindow::SetNativeWindowFrameProperties() { if (auto window = GetNativeWindow()) { if (TypedControlTemplateObject(true)->GetCustomFrameEnabled()) { window->EnableCustomFrameMode(); window->SetBorder(false); } else { window->DisableCustomFrameMode(); window->SetBorder(hasBorder); } window->SetMaximizedBox(hasMaximizedBox); window->SetMinimizedBox(hasMinimizedBox); window->SetSizeBox(hasSizeBox); window->SetIconVisible(isIconVisible); window->SetTitleBar(hasTitleBar); } } bool GuiWindow::ApplyFrameConfigOnVariable(BoolOption frameConfig, BoolOption templateConfig, bool& variable) { if (frameConfig == BoolOption::AlwaysTrue && templateConfig == BoolOption::AlwaysFalse)return false; if (frameConfig == BoolOption::AlwaysFalse && templateConfig == BoolOption::AlwaysTrue) return false; if (frameConfig == BoolOption::AlwaysTrue || templateConfig == BoolOption::AlwaysTrue) { variable = true; } else if (frameConfig == BoolOption::AlwaysFalse || templateConfig == BoolOption::AlwaysFalse) { variable = false; } return true; } void GuiWindow::ApplyFrameConfig() { #define ERROR_MESSAGE_PREFIX L"vl::presentation::controls::GuiWindow::CheckCustomFrameAndControlTemplateCompatibility#" #define FIX_WINDOW_PROPERTY(VARIABLE, NAME)\ CHECK_ERROR(\ ApplyFrameConfigOnVariable(\ (frameConfig ? frameConfig->NAME ## Option : BoolOption::Customizable),\ (ct ? ct->Get ## NAME ## Option() : BoolOption::Customizable),\ VARIABLE\ ),\ ERROR_MESSAGE_PREFIX L"Frame configuration and control template are not compatible on property \"" L ## #NAME L"\"."\ );\ auto ct = TypedControlTemplateObject(false); if (frameConfig && ct) { if ( (frameConfig->CustomFrameEnabled == BoolOption::AlwaysTrue && !ct->GetCustomFrameEnabled()) || (frameConfig->CustomFrameEnabled == BoolOption::AlwaysFalse && ct->GetCustomFrameEnabled()) ) { CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Frame configuration and control template are not compatible on property \"CustomFrameEnabled\"."); } } FIX_WINDOW_PROPERTY(hasMaximizedBox, MaximizedBox) FIX_WINDOW_PROPERTY(hasMinimizedBox, MinimizedBox) FIX_WINDOW_PROPERTY(hasBorder, Border) FIX_WINDOW_PROPERTY(hasSizeBox, SizeBox) FIX_WINDOW_PROPERTY(isIconVisible, IconVisible) FIX_WINDOW_PROPERTY(hasTitleBar, TitleBar) auto window = GetNativeWindow(); SetControlTemplateProperties(); UpdateCustomFramePadding(window, ct); SetNativeWindowFrameProperties(); #undef FIX_WINDOW_PROPERTY #undef ERROR_MESSAGE_PREFIX } void GuiWindow::Moved() { GuiControlHost::Moved(); TypedControlTemplateObject(true)->SetMaximized(IsRenderedAsMaximized()); } void GuiWindow::DpiChanged(bool preparing) { if (!preparing) { if (auto ct = TypedControlTemplateObject(false)) { UpdateCustomFramePadding(GetNativeWindow(), ct); } } } void GuiWindow::Opened() { GuiControlHost::Opened(); if (auto ct = TypedControlTemplateObject(false)) { UpdateIcon(GetNativeWindow(), ct); } } void GuiWindow::BeforeClosing(bool& cancel) { if (GetHostedApplication() && this == GetApplication()->GetMainWindow()) { GuiWindow* pickedWindow = nullptr; if (showModalRecord) { pickedWindow = showModalRecord->current; } else { for (auto window : From(GetApplication()->GetWindows())) { if (window->GetVisible() && window->showModalRecord) { pickedWindow = window->showModalRecord->current; break; } } } if (pickedWindow && pickedWindow != this) { if (pickedWindow->GetFocused()) { pickedWindow->Hide(); } else { pickedWindow->SetFocused(); } cancel = true; return; } } GuiControlHost::BeforeClosing(cancel); } void GuiWindow::AssignFrameConfig(const NativeWindowFrameConfig& config) { frameConfig = &config; FrameConfigChanged.Execute(GetNotifyEventArguments()); ApplyFrameConfig(); } void GuiWindow::OnNativeWindowChanged() { SetNativeWindowFrameProperties(); GuiControlHost::OnNativeWindowChanged(); } void GuiWindow::OnVisualStatusChanged() { GuiControlHost::OnVisualStatusChanged(); } void GuiWindow::OnWindowActivated(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if (auto ct = TypedControlTemplateObject(false)) { ct->SetActivated(true); } } void GuiWindow::OnWindowDeactivated(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if (auto ct = TypedControlTemplateObject(false)) { ct->SetActivated(false); } } GuiWindow::GuiWindow(theme::ThemeName themeName, INativeWindow::WindowMode mode) :GuiControlHost(themeName, mode) { SetAltComposition(boundsComposition); SetAltControl(this, true); INativeWindow* window = GetCurrentController()->WindowService()->CreateNativeWindow(windowMode); SetNativeWindow(window); GetApplication()->RegisterWindow(this); ClipboardUpdated.SetAssociatedComposition(boundsComposition); FrameConfigChanged.SetAssociatedComposition(boundsComposition); WindowActivated.AttachMethod(this, &GuiWindow::OnWindowActivated); WindowDeactivated.AttachMethod(this, &GuiWindow::OnWindowDeactivated); } GuiWindow::GuiWindow(theme::ThemeName themeName) :GuiWindow(themeName, INativeWindow::Normal) { } GuiWindow::~GuiWindow() { FinalizeAggregation(); GetApplication()->UnregisterWindow(this); INativeWindow* window=host->GetNativeWindow(); if(window) { SetNativeWindow(nullptr); GetCurrentController()->WindowService()->DestroyNativeWindow(window); } } IDescriptable* GuiWindow::QueryService(const WString& identifier) { if (identifier == IGuiAltActionHost::Identifier) { return (IGuiAltActionHost*)this; } else { return GuiControlHost::QueryService(identifier); } } void GuiWindow::MoveToScreenCenter() { MoveToScreenCenter(GetRelatedScreen()); } void GuiWindow::MoveToScreenCenter(INativeScreen* screen) { if (screen) { if (auto window = host->GetNativeWindow()) { NativeRect screenBounds = screen->GetClientBounds(); NativeSize windowSize = window->GetBounds().GetSize(); SetLocation( NativePoint( screenBounds.Left() + (screenBounds.Width() - windowSize.x) / 2, screenBounds.Top() + (screenBounds.Height() - windowSize.y) / 2 ) ); } } } const NativeWindowFrameConfig& GuiWindow::GetFrameConfig() { return frameConfig ? *frameConfig : NativeWindowFrameConfig::Default; } #define IMPL_WINDOW_PROPERTY(VARIABLE, NAME, CONDITION_BREAK) \ bool GuiWindow::Get ## NAME() \ { \ return VARIABLE; \ } \ void GuiWindow::Set ## NAME(bool visible) \ { \ auto ct = TypedControlTemplateObject(true); \ if (ct->Get ## NAME ## Option() == BoolOption::Customizable) \ { \ VARIABLE = visible; \ ct->Set ## NAME(visible); \ auto window = GetNativeWindow(); \ if (window) \ { \ CONDITION_BREAK \ window->Set ## NAME(visible); \ } \ UpdateCustomFramePadding(window, ct); \ } \ } \ #define IMPL_WINDOW_PROPERTY_EMPTY_CONDITION #define IMPL_WINDOW_PROPERTY_BORDER_CONDITION if (!ct->GetCustomFrameEnabled()) IMPL_WINDOW_PROPERTY(hasMaximizedBox, MaximizedBox, IMPL_WINDOW_PROPERTY_EMPTY_CONDITION) IMPL_WINDOW_PROPERTY(hasMinimizedBox, MinimizedBox, IMPL_WINDOW_PROPERTY_EMPTY_CONDITION) IMPL_WINDOW_PROPERTY(hasBorder, Border, IMPL_WINDOW_PROPERTY_BORDER_CONDITION) IMPL_WINDOW_PROPERTY(hasSizeBox, SizeBox, IMPL_WINDOW_PROPERTY_EMPTY_CONDITION) IMPL_WINDOW_PROPERTY(isIconVisible, IconVisible, IMPL_WINDOW_PROPERTY_EMPTY_CONDITION) IMPL_WINDOW_PROPERTY(hasTitleBar, TitleBar, IMPL_WINDOW_PROPERTY_EMPTY_CONDITION) Ptr GuiWindow::GetIcon() { return icon; } void GuiWindow::SetIcon(Ptr value) { if (icon != value) { icon = value; auto window = GetNativeWindow(); if (window) { window->SetIcon(icon); } if (auto ct = TypedControlTemplateObject(false)) { UpdateIcon(window, ct); } } } #undef IMPL_WINDOW_PROPERTY_BORDER_CONDITION #undef IMPL_WINDOW_PROPERTY_EMPTY_CONDITION #undef IMPL_WINDOW_PROPERTY void GuiWindow::ShowWithOwner(GuiWindow* owner) { auto ownerNativeWindow = owner->GetNativeWindow(); auto nativeWindow = GetNativeWindow(); auto previousParent = nativeWindow->GetParent(); if (ownerNativeWindow != previousParent) { nativeWindow->SetParent(ownerNativeWindow); WindowReadyToClose.AttachLambda([=](GuiGraphicsComposition* sender, GuiEventArgs& arguments) { nativeWindow->SetParent(previousParent); }); } Show(); } void GuiWindow::ShowModal(GuiWindow* owner, const Func& callback) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::controls::GuiWindow::ShowModal(GuiWindow*, const Func&)#" CHECK_ERROR(!showModalRecord, ERROR_MESSAGE_PREFIX L"Cannot call this function nestedly."); CHECK_ERROR(owner && owner->GetEnabled(), ERROR_MESSAGE_PREFIX L"The owner should not have been disabled."); if (!owner->showModalRecord) { owner->showModalRecord = Ptr(new ShowModalRecord{ owner,owner }); } showModalRecord = owner->showModalRecord; showModalRecord->current = this; owner->SetEnabled(false); GetNativeWindow()->SetParent(owner->GetNativeWindow()); auto container = Ptr(new IGuiGraphicsEventHandler::Container); auto disposeFlag = GetDisposedFlag(); container->handler = WindowReadyToClose.AttachLambda([=](GuiGraphicsComposition* sender, GuiEventArgs& arguments) { callback(); GetNativeWindow()->SetParent(nullptr); owner->SetEnabled(true); owner->SetFocused(); showModalRecord = nullptr; owner->showModalRecord->current = owner; if (owner->showModalRecord->current == owner->showModalRecord->origin) { owner->showModalRecord = nullptr; } GetApplication()->InvokeInMainThread(this, [=]() { if (!disposeFlag->IsDisposed()) { WindowReadyToClose.Detach(container->handler); } container->handler = nullptr; }); }); Show(); #undef ERROR_MESSAGE_PREFIX } void GuiWindow::ShowModalAndDelete(GuiWindow* owner, const Func& callback) { ShowModal(owner, [=]() { callback(); DeleteAfterProcessingAllEvents({}); }); } void GuiWindow::ShowModalAndDelete(GuiWindow* owner, const Func& callbackClosed, const Func& callbackDeleted) { ShowModal(owner, [=]() { callbackClosed(); DeleteAfterProcessingAllEvents(callbackDeleted); }); } Ptr GuiWindow::ShowModalAsync(GuiWindow* owner) { auto future = IFuture::Create(); ShowModal(owner, [promise = future->GetPromise()]() { promise->SendResult({}); }); return future; } /*********************************************************************** GuiPopup ***********************************************************************/ void GuiPopup::UpdateClientSizeAfterRendering(Size preferredSize, Size clientSize) { if (popupType == -1) { GuiWindow::UpdateClientSizeAfterRendering(preferredSize, clientSize); } else { auto window = host->GetNativeWindow(); auto currentClientSize = window->GetClientSize(); auto currentWindowSize = window->GetBounds().GetSize(); auto offsetX = currentWindowSize.x - currentClientSize.x; auto offsetY = currentWindowSize.y - currentClientSize.y; auto nativeClientSize = window->Convert(clientSize); auto position = CalculatePopupPosition(NativeSize(nativeClientSize.x + offsetX, nativeClientSize.y + offsetY), popupType, popupInfo); if (position != GetLocation() || clientSize != GetClientSize()) { SetBounds(position, clientSize); } } } void GuiPopup::PopupOpened(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { GetApplication()->RegisterPopupOpened(this); } void GuiPopup::PopupClosed(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { popupType = -1; GetApplication()->RegisterPopupClosed(this); if(auto window = GetNativeWindow()) { window->SetParent(nullptr); } } void GuiPopup::OnKeyDown(compositions::GuiGraphicsComposition* sender, compositions::GuiKeyEventArgs& arguments) { if (!arguments.handled) { Hide(); } } bool GuiPopup::IsClippedByScreen(NativeSize size, NativePoint location, INativeScreen* screen) { NativeRect screenBounds = screen->GetClientBounds(); NativeRect windowBounds(location, size); return !screenBounds.Contains(windowBounds.LeftTop()) || !screenBounds.Contains(windowBounds.RightBottom()); } NativePoint GuiPopup::CalculatePopupPosition(NativeSize windowSize, NativePoint location, INativeScreen* screen) { NativeRect screenBounds = screen->GetClientBounds(); if (location.x < screenBounds.x1) { location.x = screenBounds.x1; } else if (location.x + windowSize.x > screenBounds.x2) { location.x = screenBounds.x2 - windowSize.x; } if (location.y < screenBounds.y1) { location.y = screenBounds.y1; } else if (location.y + windowSize.y > screenBounds.y2) { location.y = screenBounds.y2 - windowSize.y; } return location; } NativePoint GuiPopup::CalculatePopupPosition(NativeSize windowSize, GuiControl* control, INativeWindow* controlWindow, Rect bounds, bool preferredTopBottomSide) { NativePoint controlClientOffset = controlWindow->Convert(control->GetBoundsComposition()->GetGlobalBounds().LeftTop()); NativePoint controlWindowOffset = controlWindow->GetClientBoundsInScreen().LeftTop(); NativeRect targetBounds(controlWindow->Convert(bounds.LeftTop()), controlWindow->Convert(bounds.GetSize())); targetBounds.x1 += controlClientOffset.x + controlWindowOffset.x; targetBounds.x2 += controlClientOffset.x + controlWindowOffset.x; targetBounds.y1 += controlClientOffset.y + controlWindowOffset.y; targetBounds.y2 += controlClientOffset.y + controlWindowOffset.y; NativePoint locations[4]; if (preferredTopBottomSide) { locations[0] = NativePoint(targetBounds.x1, targetBounds.y2); locations[1] = NativePoint(targetBounds.x2 - windowSize.x, targetBounds.y2); locations[2] = NativePoint(targetBounds.x1, targetBounds.y1 - windowSize.y); locations[3] = NativePoint(targetBounds.x2 - windowSize.x, targetBounds.y1 - windowSize.y); } else { locations[0] = NativePoint(targetBounds.x2, targetBounds.y1); locations[1] = NativePoint(targetBounds.x2, targetBounds.y2 - windowSize.y); locations[2] = NativePoint(targetBounds.x1 - windowSize.x, targetBounds.y1); locations[3] = NativePoint(targetBounds.x1 - windowSize.x, targetBounds.y2 - windowSize.y); } auto screen = GetCurrentController()->ScreenService()->GetScreen(controlWindow); for (vint i = 0; i < 4; i++) { if (!IsClippedByScreen(windowSize, locations[i], screen)) { return CalculatePopupPosition(windowSize, locations[i], screen); } } return CalculatePopupPosition(windowSize, locations[0], screen); } NativePoint GuiPopup::CalculatePopupPosition(NativeSize windowSize, GuiControl* control, INativeWindow* controlWindow, Point location) { NativePoint controlClientOffset = controlWindow->Convert(control->GetBoundsComposition()->GetGlobalBounds().LeftTop()); NativePoint controlWindowOffset = controlWindow->GetClientBoundsInScreen().LeftTop(); NativePoint targetLocation = controlWindow->Convert(location); NativeCoordinate x = controlClientOffset.x + controlWindowOffset.x + targetLocation.x; NativeCoordinate y = controlClientOffset.y + controlWindowOffset.y + targetLocation.y; return CalculatePopupPosition(windowSize, NativePoint(x, y), GetCurrentController()->ScreenService()->GetScreen(controlWindow)); } NativePoint GuiPopup::CalculatePopupPosition(NativeSize windowSize, GuiControl* control, INativeWindow* controlWindow, bool preferredTopBottomSide) { Rect bounds(Point(0, 0), control->GetBoundsComposition()->GetCachedBounds().GetSize()); return CalculatePopupPosition(windowSize, control, controlWindow, bounds, preferredTopBottomSide); } NativePoint GuiPopup::CalculatePopupPosition(NativeSize windowSize, vint popupType, const PopupInfo& popupInfo) { switch (popupType) { case 1: return CalculatePopupPosition(windowSize, popupInfo._1.location, popupInfo._1.screen); case 2: return CalculatePopupPosition(windowSize, popupInfo._2.control, popupInfo._2.controlWindow, popupInfo._2.bounds, popupInfo._2.preferredTopBottomSide); case 3: return CalculatePopupPosition(windowSize, popupInfo._3.control, popupInfo._3.controlWindow, popupInfo._3.location); case 4: return CalculatePopupPosition(windowSize, popupInfo._4.control, popupInfo._4.controlWindow, popupInfo._4.preferredTopBottomSide); default: CHECK_FAIL(L"vl::presentation::controls::GuiPopup::CalculatePopupPosition(Size, const PopupInfo&)#Internal error."); } } void GuiPopup::ShowPopupInternal() { auto window = GetNativeWindow(); auto clientSize = window->Convert(window->GetClientSize()); UpdateClientSizeAfterRendering(clientSize, clientSize); INativeWindow* controlWindow = nullptr; switch (popupType) { case 2: controlWindow = popupInfo._2.controlWindow; break; case 3: controlWindow = popupInfo._3.controlWindow; break; case 4: controlWindow = popupInfo._4.controlWindow; break; } if (controlWindow) { window->SetParent(controlWindow); SetTopMost(controlWindow->GetTopMost()); } else { SetTopMost(true); } SetEnabledActivate(false); ShowDeactivated(); } GuiPopup::GuiPopup(theme::ThemeName themeName, INativeWindow::WindowMode mode) :GuiWindow(themeName, mode) { SetMinimizedBox(false); SetMaximizedBox(false); SetBorder(false); SetSizeBox(false); SetIconVisible(false); SetTitleBar(false); SetShowInTaskBar(false); WindowOpened.AttachMethod(this, &GuiPopup::PopupOpened); WindowClosed.AttachMethod(this, &GuiPopup::PopupClosed); boundsComposition->GetEventReceiver()->keyDown.AttachMethod(this, &GuiPopup::OnKeyDown); } GuiPopup::GuiPopup(theme::ThemeName themeName) :GuiPopup(themeName, INativeWindow::Popup) { } GuiPopup::~GuiPopup() { GetApplication()->RegisterPopupClosed(this); } void GuiPopup::ShowPopup(NativePoint location, INativeScreen* screen) { if (auto window = GetNativeWindow()) { if (!screen) { SetBounds(location, GetClientSize()); screen = GetCurrentController()->ScreenService()->GetScreen(window); } popupType = 1; popupInfo._1.location = location; popupInfo._1.screen = screen; ShowPopupInternal(); } } void GuiPopup::ShowPopup(GuiControl* control, Rect bounds, bool preferredTopBottomSide) { if (auto window = GetNativeWindow()) { if (auto controlHost = control->GetBoundsComposition()->GetRelatedControlHost()) { if (auto controlWindow = controlHost->GetNativeWindow()) { popupType = 2; popupInfo._2.control = control; popupInfo._2.controlWindow = controlWindow; popupInfo._2.bounds = bounds; popupInfo._2.preferredTopBottomSide = preferredTopBottomSide; ShowPopupInternal(); } } } } void GuiPopup::ShowPopup(GuiControl* control, Point location) { if (auto window = GetNativeWindow()) { if (auto controlHost = control->GetBoundsComposition()->GetRelatedControlHost()) { if (auto controlWindow = controlHost->GetNativeWindow()) { popupType = 3; popupInfo._3.control = control; popupInfo._3.controlWindow = controlWindow; popupInfo._3.location = location; ShowPopupInternal(); } } } } void GuiPopup::ShowPopup(GuiControl* control, bool preferredTopBottomSide) { if (auto window = GetNativeWindow()) { if (auto controlHost = control->GetBoundsComposition()->GetRelatedControlHost()) { if (auto controlWindow = controlHost->GetNativeWindow()) { popupType = 4; popupInfo._4.control = control; popupInfo._4.controlWindow = controlWindow; popupInfo._4.preferredTopBottomSide = preferredTopBottomSide; ShowPopupInternal(); } } } } /*********************************************************************** GuiPopup ***********************************************************************/ void GuiTooltip::GlobalTimer() { SetClientSize(GetClientSize()); } void GuiTooltip::TooltipOpened(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { } void GuiTooltip::TooltipClosed(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { SetTemporaryContentControl(0); } GuiTooltip::GuiTooltip(theme::ThemeName themeName) : GuiPopup(themeName, INativeWindow::Tooltip) { containerComposition->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); containerComposition->SetPreferredMinSize(Size(20, 10)); GetCurrentController()->CallbackService()->InstallListener(this); WindowOpened.AttachMethod(this, &GuiTooltip::TooltipOpened); WindowClosed.AttachMethod(this, &GuiTooltip::TooltipClosed); } GuiTooltip::~GuiTooltip() { GetCurrentController()->CallbackService()->UninstallListener(this); } vint GuiTooltip::GetPreferredContentWidth() { return containerComposition->GetPreferredMinSize().x; } void GuiTooltip::SetPreferredContentWidth(vint value) { containerComposition->SetPreferredMinSize(Size(value, 10)); } GuiControl* GuiTooltip::GetTemporaryContentControl() { return temporaryContentControl; } void GuiTooltip::SetTemporaryContentControl(GuiControl* control) { if(temporaryContentControl && HasChild(temporaryContentControl)) { containerComposition->RemoveChild(temporaryContentControl->GetBoundsComposition()); temporaryContentControl=0; } temporaryContentControl=control; if(control) { control->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); AddChild(control); } } } } } /*********************************************************************** .\APPLICATION\GRAPHICSCOMPOSITIONS\GUIGRAPHICSBOUNDSCOMPOSITION.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace compositions { using namespace collections; using namespace elements; /*********************************************************************** GuiBoundsComposition ***********************************************************************/ Size GuiBoundsComposition::Layout_CalculateMinSize() { Size minSize = Layout_CalculateMinSizeHelper(); if (minSize.x < expectedBounds.Width()) minSize.x = expectedBounds.Width(); if (minSize.y < expectedBounds.Height()) minSize.y = expectedBounds.Height(); return minSize; } Size GuiBoundsComposition::Layout_CalculateMinClientSizeForParent(Margin parentInternalMargin) { vint offsetW = 0; vint offsetH = 0; if (alignmentToParent.left == -1 && alignmentToParent.right == -1) { offsetW = expectedBounds.x1; } else { if (alignmentToParent.left != -1) { offsetW += alignmentToParent.left - parentInternalMargin.left; } if (alignmentToParent.right != -1) { offsetW += alignmentToParent.right - parentInternalMargin.right; } } if (alignmentToParent.top == -1 && alignmentToParent.bottom == -1) { offsetH = expectedBounds.y1; } else { if (alignmentToParent.top != -1) { offsetH += alignmentToParent.top - parentInternalMargin.top; } if (alignmentToParent.bottom != -1) { offsetH += alignmentToParent.bottom - parentInternalMargin.bottom; } } return { cachedMinSize.x + offsetW,cachedMinSize.y + offsetH }; } Rect GuiBoundsComposition::Layout_CalculateBounds(Size parentSize) { if (auto parent = GetParent()) { Rect result; Rect parentBounds({}, parentSize); Margin parentInternalMargin = parent->GetInternalMargin(); if (alignmentToParent.left != -1 && alignmentToParent.right != -1) { result.x1 = parentBounds.x1 + alignmentToParent.left; result.x2 = parentBounds.x2 - alignmentToParent.right; } else if (alignmentToParent.left != -1) { result.x1 = parentBounds.x1 + alignmentToParent.left; result.x2 = result.x1 + cachedMinSize.x; } else if (alignmentToParent.right != -1) { result.x2 = parentBounds.x2 - alignmentToParent.right; result.x1 = result.x2 - cachedMinSize.x; } else { result.x1 = expectedBounds.x1 + parentInternalMargin.left; result.x2 = result.x1 + cachedMinSize.x; } if (alignmentToParent.top != -1 && alignmentToParent.bottom != -1) { result.y1 = parentBounds.y1 + alignmentToParent.top; result.y2 = parentBounds.y2 - alignmentToParent.bottom; } else if (alignmentToParent.top != -1) { result.y1 = parentBounds.y1 + alignmentToParent.top; result.y2 = result.y1 + cachedMinSize.y; } else if (alignmentToParent.bottom != -1) { result.y2 = parentBounds.y2 - alignmentToParent.bottom; result.y1 = result.y2 - cachedMinSize.y; } else { result.y1 = expectedBounds.y1 + parentInternalMargin.top; result.y2 = result.y1 + cachedMinSize.y; } return result; } else { return Rect(expectedBounds.LeftTop(), cachedMinSize); } } Rect GuiBoundsComposition::GetExpectedBounds() { return expectedBounds; } void GuiBoundsComposition::SetExpectedBounds(Rect value) { if (value.x2 < value.x1) value.x2 = value.x1; if (value.y2 < value.y1) value.y2 = value.y1; if (expectedBounds != value) { expectedBounds = value; InvokeOnCompositionStateChanged(); } } Margin GuiBoundsComposition::GetAlignmentToParent() { return alignmentToParent; } void GuiBoundsComposition::SetAlignmentToParent(Margin value) { if (alignmentToParent != value) { alignmentToParent = value; InvokeOnCompositionStateChanged(); } } bool GuiBoundsComposition::IsAlignedToParent() { return alignmentToParent != Margin(-1, -1, -1, -1); } } } } /*********************************************************************** .\APPLICATION\GRAPHICSCOMPOSITIONS\GUIGRAPHICSCOMPOSITION.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace compositions { using namespace elements; void InvokeOnCompositionStateChanged(compositions::GuiGraphicsComposition* composition) { composition->InvokeOnCompositionStateChanged(); } /*********************************************************************** GuiGraphicsComposition ***********************************************************************/ void GuiGraphicsComposition::OnControlParentChanged(controls::GuiControl* control) { if (associatedControl && associatedControl != control) { if (associatedControl->GetParent()) { associatedControl->GetParent()->OnChildRemoved(associatedControl); } if (control) { control->OnChildInserted(associatedControl); } } else { for (auto child : children) { child->OnControlParentChanged(control); } } } void GuiGraphicsComposition::OnChildInserted(GuiGraphicsComposition* child) { child->OnControlParentChanged(GetRelatedControl()); } void GuiGraphicsComposition::OnChildRemoved(GuiGraphicsComposition* child) { child->OnControlParentChanged(nullptr); } void GuiGraphicsComposition::OnParentChanged(GuiGraphicsComposition* oldParent, GuiGraphicsComposition* newParent) { OnParentLineChanged(); } void GuiGraphicsComposition::OnParentLineChanged() { for (auto child : children) { child->OnParentLineChanged(); } } void GuiGraphicsComposition::OnCompositionStateChanged() { } void GuiGraphicsComposition::OnRenderContextChanged() { } void GuiGraphicsComposition::UpdateRelatedHostRecord(GraphicsHostRecord* record) { relatedHostRecord = record; auto renderTarget = GetRenderTarget(); if (ownedElement) { if (auto renderer = ownedElement->GetRenderer()) { renderer->SetRenderTarget(renderTarget); } } for (auto child : children) { child->UpdateRelatedHostRecord(record); } if (HasEventReceiver()) { GetEventReceiver()->renderTargetChanged.Execute(GuiEventArgs(this)); } if (associatedControl) { associatedControl->OnRenderTargetChanged(renderTarget); } OnRenderContextChanged(); } void GuiGraphicsComposition::SetAssociatedControl(controls::GuiControl* control) { if (associatedControl) { for (auto child : children) { child->OnControlParentChanged(nullptr); } } associatedControl = control; if (associatedControl) { for (auto child : children) { child->OnControlParentChanged(associatedControl); } } } void GuiGraphicsComposition::InvokeOnCompositionStateChanged(bool forceRequestRender) { OnCompositionStateChanged(); if (relatedHostRecord && (forceRequestRender || GetEventuallyVisible())) { relatedHostRecord->host->RequestRender(); } } bool GuiGraphicsComposition::SharedPtrDestructorProc(DescriptableObject* obj, bool forceDisposing) { GuiGraphicsComposition* value=dynamic_cast(obj); if(value->parent) { if (!forceDisposing) return false; } SafeDeleteComposition(value); return true; } GuiGraphicsComposition::GuiGraphicsComposition() { sharedPtrDestructorProc = &GuiGraphicsComposition::SharedPtrDestructorProc; CachedMinSizeChanged.SetAssociatedComposition(this); CachedBoundsChanged.SetAssociatedComposition(this); } GuiGraphicsComposition::~GuiGraphicsComposition() { for (auto child : children) { delete child; } } bool GuiGraphicsComposition::IsRendering() { return isRendering; } GuiGraphicsComposition* GuiGraphicsComposition::GetParent() { return parent; } const GuiGraphicsComposition::CompositionList& GuiGraphicsComposition::Children() { return children; } bool GuiGraphicsComposition::AddChild(GuiGraphicsComposition* child) { return InsertChild(children.Count(), child); } bool GuiGraphicsComposition::InsertChild(vint index, GuiGraphicsComposition* child) { CHECK_ERROR(!isRendering, L"GuiGraphicsComposition::InsertChild(vint, GuiGraphicsComposition*)#Cannot modify composition tree during rendering."); if (!child) return false; if (child->GetParent()) return false; children.Insert(index, child); // composition parent changed -> control parent changed -> related host changed child->parent = this; child->OnParentChanged(nullptr, this); OnChildInserted(child); child->UpdateRelatedHostRecord(relatedHostRecord); InvokeOnCompositionStateChanged(); return true; } bool GuiGraphicsComposition::RemoveChild(GuiGraphicsComposition* child) { CHECK_ERROR(!isRendering, L"GuiGraphicsComposition::InsertChild(vint, GuiGraphicsComposition*)#Cannot modify composition tree during rendering."); if (!child) return false; vint index = children.IndexOf(child); if (index == -1) return false; // composition parent changed -> control parent changed -> related host changed child->parent = nullptr; child->OnParentChanged(this, nullptr); OnChildRemoved(child); child->UpdateRelatedHostRecord(nullptr); GuiGraphicsHost* host = GetRelatedGraphicsHost(); if (host) { host->DisconnectComposition(child); } children.RemoveAt(index); InvokeOnCompositionStateChanged(); return true; } bool GuiGraphicsComposition::MoveChild(GuiGraphicsComposition* child, vint newIndex) { if(!child) return false; vint index=children.IndexOf(child); if(index==-1) return false; if(index==newIndex) return true; children.RemoveAt(index); children.Insert(newIndex, child); InvokeOnCompositionStateChanged(); return true; } Ptr GuiGraphicsComposition::GetOwnedElement() { return ownedElement; } void GuiGraphicsComposition::SetOwnedElement(Ptr element) { if (ownedElement != element) { if (ownedElement) { if (auto renderer = ownedElement->GetRenderer()) { renderer->SetRenderTarget(nullptr); } ownedElement->SetOwnerComposition(nullptr); } ownedElement = element; if (ownedElement) { if (auto renderer = ownedElement->GetRenderer()) { renderer->SetRenderTarget(GetRenderTarget()); } ownedElement->SetOwnerComposition(this); } InvokeOnCompositionStateChanged(); } } bool GuiGraphicsComposition::GetVisible() { return visible; } void GuiGraphicsComposition::SetVisible(bool value) { if (visible != value) { visible = value; InvokeOnCompositionStateChanged(true); } } bool GuiGraphicsComposition::GetEventuallyVisible() { auto current = this; while (current) { if (!current->visible) return false; current = current->parent; } return true; } GuiGraphicsComposition::MinSizeLimitation GuiGraphicsComposition::GetMinSizeLimitation() { return minSizeLimitation; } void GuiGraphicsComposition::SetMinSizeLimitation(MinSizeLimitation value) { if (minSizeLimitation != value) { minSizeLimitation = value; InvokeOnCompositionStateChanged(); } } elements::IGuiGraphicsRenderTarget* GuiGraphicsComposition::GetRenderTarget() { return relatedHostRecord ? relatedHostRecord->renderTarget : nullptr; } void GuiGraphicsComposition::Render(Size offset) { auto renderTarget = GetRenderTarget(); if (visible && renderTarget && !renderTarget->IsClipperCoverWholeTarget()) { Rect bounds = GetCachedBounds(); if (bounds.x1 <= bounds.x2 && bounds.y1 <= bounds.y2) { bounds.x1 += offset.x; bounds.x2 += offset.x; bounds.y1 += offset.y; bounds.y2 += offset.y; isRendering = true; if (ownedElement) { IGuiGraphicsRenderer* renderer = ownedElement->GetRenderer(); if (renderer) { renderer->Render(bounds); } } if (children.Count() > 0 || associatedHitTestResult != INativeWindowListener::NoDecision || associatedCursor) { renderTarget->PushClipper(bounds, this); if (!renderTarget->IsClipperCoverWholeTarget()) { for (auto child : children) { child->Render(Size(bounds.x1, bounds.y1)); } } renderTarget->PopClipper(this); } isRendering = false; } } } GuiGraphicsEventReceiver* GuiGraphicsComposition::GetEventReceiver() { if(!eventReceiver) { eventReceiver=Ptr(new GuiGraphicsEventReceiver(this)); } return eventReceiver.Obj(); } bool GuiGraphicsComposition::HasEventReceiver() { return eventReceiver; } GuiGraphicsComposition* GuiGraphicsComposition::FindVisibleComposition(Point location, bool forMouseEvent) { if (!visible) return 0; Rect bounds = GetCachedBounds(); Rect relativeBounds = Rect(Point(0, 0), bounds.GetSize()); if (relativeBounds.Contains(location)) { // TODO: (enumerable) foreach:reversed for (vint i = children.Count() - 1; i >= 0; i--) { GuiGraphicsComposition* child = children[i]; Rect childBounds = child->GetCachedBounds(); Point newLocation = location - Size(childBounds.x1, childBounds.y1); GuiGraphicsComposition* childResult = child->FindVisibleComposition(newLocation, forMouseEvent); if (childResult) { return childResult; } } if (!forMouseEvent || !transparentToMouse) { return this; } } return nullptr; } bool GuiGraphicsComposition::GetTransparentToMouse() { return transparentToMouse; } void GuiGraphicsComposition::SetTransparentToMouse(bool value) { transparentToMouse = value; } controls::GuiControl* GuiGraphicsComposition::GetAssociatedControl() { return associatedControl; } GuiGraphicsHost* GuiGraphicsComposition::GetAssociatedHost() { if (relatedHostRecord && relatedHostRecord->host->GetMainComposition() == this) { return relatedHostRecord->host; } else { return nullptr; } } INativeCursor* GuiGraphicsComposition::GetAssociatedCursor() { return associatedCursor; } void GuiGraphicsComposition::SetAssociatedCursor(INativeCursor* cursor) { associatedCursor = cursor; } INativeWindowListener::HitTestResult GuiGraphicsComposition::GetAssociatedHitTestResult() { return associatedHitTestResult; } void GuiGraphicsComposition::SetAssociatedHitTestResult(INativeWindowListener::HitTestResult value) { associatedHitTestResult = value; } controls::GuiControl* GuiGraphicsComposition::GetRelatedControl() { GuiGraphicsComposition* composition = this; while (composition) { if (composition->GetAssociatedControl()) { return composition->GetAssociatedControl(); } else { composition = composition->GetParent(); } } return nullptr; } GuiGraphicsHost* GuiGraphicsComposition::GetRelatedGraphicsHost() { return relatedHostRecord ? relatedHostRecord->host : nullptr; } controls::GuiControlHost* GuiGraphicsComposition::GetRelatedControlHost() { if (auto control = GetRelatedControl()) { return control->GetRelatedControlHost(); } return nullptr; } INativeCursor* GuiGraphicsComposition::GetRelatedCursor() { GuiGraphicsComposition* composition = this; while (composition) { if (composition->GetAssociatedCursor()) { return composition->GetAssociatedCursor(); } else { composition = composition->GetParent(); } } return nullptr; } INativeWindowListener::HitTestResult GuiGraphicsComposition::GetRelatedHitTestResult() { GuiGraphicsComposition* composition = this; while (composition) { INativeWindowListener::HitTestResult result = composition->GetAssociatedHitTestResult(); if (result == INativeWindowListener::NoDecision) { composition = composition->GetParent(); } else { return result; } } return INativeWindowListener::NoDecision; } Margin GuiGraphicsComposition::GetInternalMargin() { return internalMargin; } void GuiGraphicsComposition::SetInternalMargin(Margin value) { if (value.left < 0) value.left = 0; if (value.top < 0) value.top = 0; if (value.right < 0) value.right = 0; if (value.bottom < 0) value.bottom = 0; if (internalMargin != value) { internalMargin = value; InvokeOnCompositionStateChanged(); } } Size GuiGraphicsComposition::GetPreferredMinSize() { return preferredMinSize; } void GuiGraphicsComposition::SetPreferredMinSize(Size value) { if (value.x < 0) value.x = 0; if (value.y < 0) value.y = 0; if (preferredMinSize != value) { preferredMinSize = value; InvokeOnCompositionStateChanged(); } } } } } /*********************************************************************** .\APPLICATION\GRAPHICSCOMPOSITIONS\GUIGRAPHICSCOMPOSITION_HELPERS.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace compositions { using namespace controls; /*********************************************************************** Helper Functions ***********************************************************************/ void NotifyFinalizeInstance(controls::GuiControl* value) { if (value) { NotifyFinalizeInstance(value->GetBoundsComposition()); } } void NotifyFinalizeInstance(GuiGraphicsComposition* value) { if (value) { bool finalized = false; if (auto root = dynamic_cast(value)) { if (root->IsFinalized()) { finalized = true; } else { root->FinalizeInstance(); } } if (auto control = value->GetAssociatedControl()) { if (auto root = dynamic_cast(control)) { if (root->IsFinalized()) { finalized = true; } else { root->FinalizeInstance(); } } } if (!finalized) { for (auto child : value->Children()) { NotifyFinalizeInstance(child); } } } } void SafeDeleteControlInternal(controls::GuiControl* value) { if(value) { if (value->GetRelatedControlHost() != value) { GuiGraphicsComposition* bounds = value->GetBoundsComposition(); if (bounds->GetParent()) { bounds->GetParent()->RemoveChild(bounds); } } delete value; } } void SafeDeleteCompositionInternal(GuiGraphicsComposition* value) { if (value) { if (value->GetParent()) { value->GetParent()->RemoveChild(value); } if (value->GetAssociatedControl()) { SafeDeleteControlInternal(value->GetAssociatedControl()); } else { // TODO: (enumerable) foreach:reversed for (vint i = value->Children().Count() - 1; i >= 0; i--) { SafeDeleteCompositionInternal(value->Children().Get(i)); } delete value; } } } void SafeDeleteControl(controls::GuiControl* value) { if (auto controlHost = dynamic_cast(value)) { controlHost->DeleteAfterProcessingAllEvents({}); } else { NotifyFinalizeInstance(value); SafeDeleteControlInternal(value); } } void SafeDeleteComposition(GuiGraphicsComposition* value) { NotifyFinalizeInstance(value); SafeDeleteCompositionInternal(value); } } } } /*********************************************************************** .\APPLICATION\GRAPHICSCOMPOSITIONS\GUIGRAPHICSCOMPOSITION_LAYOUT.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace compositions { using namespace elements; /*********************************************************************** GuiGraphicsComposition ***********************************************************************/ Size GuiGraphicsComposition::Layout_CalculateMinSizeHelper() { Size minSize; if (minSize.x < preferredMinSize.x) minSize.x = preferredMinSize.x; if (minSize.y < preferredMinSize.y) minSize.y = preferredMinSize.y; if (minSizeLimitation != GuiGraphicsComposition::NoLimit) { if (ownedElement) { IGuiGraphicsRenderer* renderer = ownedElement->GetRenderer(); if (renderer) { auto elementSize = renderer->GetMinSize(); if (minSize.x < elementSize.x) minSize.x = elementSize.x; if (minSize.y < elementSize.y) minSize.y = elementSize.y; } } } vint offsetW = internalMargin.left + internalMargin.right; vint offsetH = internalMargin.top + internalMargin.bottom; minSize.x += offsetW; minSize.y += offsetH; for (auto child : children) { child->Layout_UpdateMinSize(); } if (minSizeLimitation == GuiGraphicsComposition::LimitToElementAndChildren) { for (auto child : children) { Size minClientSize = child->Layout_CalculateMinClientSizeForParent(internalMargin); Size minBoundsSize(minClientSize.x + offsetW, minClientSize.y + offsetH); if (minSize.x < minBoundsSize.x) minSize.x = minBoundsSize.x; if (minSize.y < minBoundsSize.y) minSize.y = minBoundsSize.y; } } return minSize; } void GuiGraphicsComposition::Layout_SetCachedMinSize(Size value) { if (value.x < 0) value.x = 0; if (value.y < 0) value.y = 0; if (cachedMinSize != value) { cachedMinSize = value; CachedMinSizeChanged.Execute(GuiEventArgs(this)); InvokeOnCompositionStateChanged(); } } void GuiGraphicsComposition::Layout_SetCachedBounds(Rect value) { if (value.x2 < value.x1) value.x2 = value.x1; if (value.y2 < value.y1) value.y2 = value.y1; if (cachedBounds != value) { cachedBounds = value; CachedBoundsChanged.Execute(GuiEventArgs(this)); InvokeOnCompositionStateChanged(); } } void GuiGraphicsComposition::Layout_UpdateMinSize() { Layout_SetCachedMinSize(Layout_CalculateMinSize()); } void GuiGraphicsComposition::Layout_UpdateBounds(Size parentSize) { Layout_SetCachedBounds(Layout_CalculateBounds(parentSize)); for (auto child : children) { child->Layout_UpdateBounds(cachedBounds.GetSize()); } } Size GuiGraphicsComposition::GetCachedMinSize() { return cachedMinSize; } Size GuiGraphicsComposition::GetCachedMinClientSize() { auto minSize = cachedMinSize; minSize.x -= internalMargin.left + internalMargin.right; minSize.y -= internalMargin.top + internalMargin.bottom; return minSize; } Rect GuiGraphicsComposition::GetCachedBounds() { return cachedBounds; } Rect GuiGraphicsComposition::GetCachedClientArea() { Rect bounds = cachedBounds; bounds.x1 += internalMargin.left; bounds.y1 += internalMargin.top; bounds.x2 -= internalMargin.right; bounds.y2 -= internalMargin.bottom; if (bounds.x2 < bounds.x1) bounds.x2 = bounds.x1; if (bounds.y2 < bounds.y1) bounds.y2 = bounds.y1; return bounds; } Rect GuiGraphicsComposition::GetGlobalBounds() { Rect bounds = cachedBounds; GuiGraphicsComposition* composition = parent; while (composition) { Point offset = composition->cachedBounds.LeftTop(); bounds.x1 += offset.x; bounds.x2 += offset.x; bounds.y1 += offset.y; bounds.y2 += offset.y; composition = composition->parent; } return bounds; } void GuiGraphicsComposition::ForceCalculateSizeImmediately() { Size parentSize; if (parent) { parentSize = parent->cachedBounds.GetSize(); } Layout_UpdateMinSize(); Layout_UpdateBounds(parentSize); } } } } /*********************************************************************** .\APPLICATION\GRAPHICSCOMPOSITIONS\GUIGRAPHICSEVENTRECEIVER.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace compositions { /*********************************************************************** Event Receiver ***********************************************************************/ GuiGraphicsEventReceiver::GuiGraphicsEventReceiver(GuiGraphicsComposition* _sender) :sender(_sender) ,leftButtonDown(_sender) ,leftButtonUp(_sender) ,leftButtonDoubleClick(_sender) ,middleButtonDown(_sender) ,middleButtonUp(_sender) ,middleButtonDoubleClick(_sender) ,rightButtonDown(_sender) ,rightButtonUp(_sender) ,rightButtonDoubleClick(_sender) ,horizontalWheel(_sender) ,verticalWheel(_sender) ,mouseMove(_sender) ,mouseEnter(_sender) ,mouseLeave(_sender) ,previewKey(_sender) ,keyDown(_sender) ,keyUp(_sender) ,previewCharInput(_sender) ,charInput(_sender) ,gotFocus(_sender) ,lostFocus(_sender) ,caretNotify(_sender) ,clipboardNotify(_sender) ,renderTargetChanged(_sender) { } GuiGraphicsEventReceiver::~GuiGraphicsEventReceiver() { } GuiGraphicsComposition* GuiGraphicsEventReceiver::GetAssociatedComposition() { return sender; } } } } /*********************************************************************** .\APPLICATION\GRAPHICSCOMPOSITIONS\GUIGRAPHICSWINDOWCOMPOSITION.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace compositions { /*********************************************************************** GuiWindowComposition ***********************************************************************/ Rect GuiWindowComposition::Layout_CalculateBounds(Size parentSize) { Rect bounds; if (relatedHostRecord) { if (auto window = relatedHostRecord->host->GetNativeWindow()) { bounds = Rect(Point(0, 0), window->Convert(window->GetClientSize())); } } return bounds; } } } } /*********************************************************************** .\APPLICATION\GRAPHICSHOST\GUIGRAPHICSHOST.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace compositions { using namespace collections; using namespace controls; using namespace elements; /*********************************************************************** GuiGraphicsTimerManager ***********************************************************************/ GuiGraphicsTimerManager::GuiGraphicsTimerManager() { } GuiGraphicsTimerManager::~GuiGraphicsTimerManager() { } void GuiGraphicsTimerManager::AddCallback(Ptr callback) { callbacks.Add(callback); } void GuiGraphicsTimerManager::Play() { // TODO: (enumerable) foreach:indexed(alterable(reversed)) for (vint i = callbacks.Count() - 1; i >= 0; i--) { auto callback = callbacks[i]; if (!callback->Play()) { callbacks.RemoveAt(i); } } } /*********************************************************************** GuiGraphicsHost ***********************************************************************/ void GuiGraphicsHost::RefreshRelatedHostRecord(INativeWindow* nativeWindow) { hostRecord.nativeWindow = nativeWindow; hostRecord.renderTarget = nativeWindow ? GetGuiGraphicsResourceManager()->GetRenderTarget(nativeWindow) : nullptr; windowComposition->UpdateRelatedHostRecord(&hostRecord); } void GuiGraphicsHost::SetFocusInternal(GuiGraphicsComposition* composition) { if (focusedComposition && focusedComposition->HasEventReceiver()) { GuiEventArgs arguments; arguments.compositionSource = focusedComposition; arguments.eventSource = focusedComposition; focusedComposition->GetEventReceiver()->lostFocus.Execute(arguments); } focusedComposition = composition; SetCaretPoint(Point(0, 0)); if (focusedComposition && focusedComposition->HasEventReceiver()) { GuiEventArgs arguments; arguments.compositionSource = focusedComposition; arguments.eventSource = focusedComposition; focusedComposition->GetEventReceiver()->gotFocus.Execute(arguments); } } void GuiGraphicsHost::DisconnectCompositionInternal(GuiGraphicsComposition* composition) { // TODO: (enumerable) foreach for(vint i=0;iChildren().Count();i++) { DisconnectCompositionInternal(composition->Children().Get(i)); } if(mouseCaptureComposition==composition) { if(hostRecord.nativeWindow) { hostRecord.nativeWindow->ReleaseCapture(); } mouseCaptureComposition=0; } if(focusedComposition==composition) { focusedComposition=0; } mouseEnterCompositions.Remove(composition); } void GuiGraphicsHost::MouseCapture(const NativeWindowMouseInfo& info) { if (hostRecord.nativeWindow && (info.left || info.middle || info.right)) { if (!hostRecord.nativeWindow->IsCapturing() && !info.nonClient) { hostRecord.nativeWindow->RequireCapture(); auto point = hostRecord.nativeWindow->Convert(NativePoint(info.x, info.y)); mouseCaptureComposition = windowComposition->FindVisibleComposition(point, true); } } } void GuiGraphicsHost::MouseUncapture(const NativeWindowMouseInfo& info) { if(hostRecord.nativeWindow && !(info.left || info.middle || info.right)) { hostRecord.nativeWindow->ReleaseCapture(); mouseCaptureComposition=0; } } void GuiGraphicsHost::OnCharInput(const NativeWindowCharInfo& info, GuiGraphicsComposition* composition, GuiCharEvent GuiGraphicsEventReceiver::* eventReceiverEvent) { List compositions; GuiCharEventArgs arguments(composition); (NativeWindowCharInfo&)arguments = info; while (composition) { if (composition->HasEventReceiver()) { if (!arguments.eventSource) { arguments.eventSource = composition; } compositions.Add(composition); } composition = composition->GetParent(); } // TODO: (enumerable) foreach:reversed for(vint i=compositions.Count()-1;i>=0;i--) { compositions[i]->GetEventReceiver()->previewCharInput.Execute(arguments); if(arguments.handled) { return; } } // TODO: (enumerable) foreach for(vint i=0;iGetEventReceiver()->*eventReceiverEvent).Execute(arguments); if(arguments.handled) { return; } } } void GuiGraphicsHost::OnKeyInput(const NativeWindowKeyInfo& info, GuiGraphicsComposition* composition, GuiKeyEvent GuiGraphicsEventReceiver::* eventReceiverEvent) { List compositions; GuiKeyEventArgs arguments(composition); (NativeWindowKeyInfo&)arguments = info; while (composition) { if (composition->HasEventReceiver()) { if (!arguments.eventSource) { arguments.eventSource = composition; } compositions.Add(composition); } composition = composition->GetParent(); } // TODO: (enumerable) foreach:reversed for (vint i = compositions.Count() - 1; i >= 0; i--) { compositions[i]->GetEventReceiver()->previewKey.Execute(arguments); if (arguments.handled) { return; } } // TODO: (enumerable) foreach for (vint i = 0; i < compositions.Count(); i++) { (compositions[i]->GetEventReceiver()->*eventReceiverEvent).Execute(arguments); if (arguments.handled) { return; } } } void GuiGraphicsHost::RaiseMouseEvent(GuiMouseEventArgs& arguments, GuiGraphicsComposition* composition, GuiMouseEvent GuiGraphicsEventReceiver::* eventReceiverEvent) { arguments.compositionSource=composition; arguments.eventSource=0; vint x=arguments.x; vint y=arguments.y; while(composition) { if(composition->HasEventReceiver()) { if(!arguments.eventSource) { arguments.eventSource=composition; } GuiGraphicsEventReceiver* eventReceiver=composition->GetEventReceiver(); (eventReceiver->*eventReceiverEvent).Execute(arguments); if(arguments.handled) { break; } } GuiGraphicsComposition* parent=composition->GetParent(); if(parent) { Rect parentBounds=parent->GetCachedBounds(); Rect clientArea=parent->GetCachedClientArea(); Rect childBounds=composition->GetCachedBounds(); x+=childBounds.x1+(clientArea.x1-parentBounds.x1); y+=childBounds.y1+(clientArea.y1-parentBounds.y1); arguments.x=x; arguments.y=y; } composition=parent; } } void GuiGraphicsHost::OnMouseInput(const NativeWindowMouseInfo& info, bool capture, bool release, GuiMouseEvent GuiGraphicsEventReceiver::* eventReceiverEvent) { if (capture) MouseCapture(info); GuiGraphicsComposition* composition = 0; if (mouseCaptureComposition) { composition = mouseCaptureComposition; } else { auto point = hostRecord.nativeWindow->Convert(NativePoint(info.x, info.y)); composition = windowComposition->FindVisibleComposition(point, true); } if (release) MouseUncapture(info); if (composition) { Rect bounds = composition->GetGlobalBounds(); Point point = hostRecord.nativeWindow->Convert(NativePoint(info.x, info.y)); GuiMouseEventArgs arguments; arguments.ctrl = info.ctrl; arguments.shift = info.shift; arguments.left = info.left; arguments.middle = info.middle; arguments.right = info.right; arguments.wheel = info.wheel; arguments.nonClient = info.nonClient; arguments.x = point.x - bounds.x1; arguments.y = point.y - bounds.y1; RaiseMouseEvent(arguments, composition, eventReceiverEvent); } } void GuiGraphicsHost::ResetRenderTarget() { windowComposition->UpdateRelatedHostRecord(nullptr); } void GuiGraphicsHost::CreateRenderTarget() { GetGuiGraphicsResourceManager()->RecreateRenderTarget(hostRecord.nativeWindow); RefreshRelatedHostRecord(hostRecord.nativeWindow); } INativeWindowListener::HitTestResult GuiGraphicsHost::HitTest(NativePoint location) { NativeRect bounds = hostRecord.nativeWindow->GetBounds(); NativeRect clientBounds = hostRecord.nativeWindow->GetClientBoundsInScreen(); NativePoint clientLocation(location.x + bounds.x1 - clientBounds.x1, location.y + bounds.y1 - clientBounds.y1); auto point = hostRecord.nativeWindow->Convert(clientLocation); if (auto hitComposition = windowComposition->FindVisibleComposition(point, true)) { return hitComposition->GetRelatedHitTestResult(); } return INativeWindowListener::NoDecision; } void GuiGraphicsHost::Moving(NativeRect& bounds, bool fixSizeOnly, bool draggingBorder) { NativeRect oldBounds = hostRecord.nativeWindow->GetBounds(); minSize = windowComposition->GetCachedMinSize(); NativeSize minWindowSize = hostRecord.nativeWindow->Convert(minSize) + (oldBounds.GetSize() - hostRecord.nativeWindow->GetClientSize()); if (bounds.Width() < minWindowSize.x) { if (fixSizeOnly) { if (bounds.Width() < minWindowSize.x) { bounds.x2 = bounds.x1 + minWindowSize.x; } } else if (oldBounds.x1 != bounds.x1) { bounds.x1 = oldBounds.x2 - minWindowSize.x; } else if (oldBounds.x2 != bounds.x2) { bounds.x2 = oldBounds.x1 + minWindowSize.x; } } if (bounds.Height() < minWindowSize.y) { if (fixSizeOnly) { if (bounds.Height() < minWindowSize.y) { bounds.y2 = bounds.y1 + minWindowSize.y; } } else if (oldBounds.y1 != bounds.y1) { bounds.y1 = oldBounds.y2 - minWindowSize.y; } else if (oldBounds.y2 != bounds.y2) { bounds.y2 = oldBounds.y1 + minWindowSize.y; } } } void GuiGraphicsHost::Moved() { RequestUpdateSizeFromNativeWindow(); } void GuiGraphicsHost::DpiChanged(bool preparing) { if (preparing) { ResetRenderTarget(); } else { CreateRenderTarget(); needRender = true; } } void GuiGraphicsHost::Paint() { if (!supressPaint) { needRender = true; } } void GuiGraphicsHost::LeftButtonDown(const NativeWindowMouseInfo& info) { altActionManager->CloseAltHost(); OnMouseInput(info, true, false, &GuiGraphicsEventReceiver::leftButtonDown); } void GuiGraphicsHost::LeftButtonUp(const NativeWindowMouseInfo& info) { OnMouseInput(info, false, true, &GuiGraphicsEventReceiver::leftButtonUp); } void GuiGraphicsHost::LeftButtonDoubleClick(const NativeWindowMouseInfo& info) { altActionManager->CloseAltHost(); OnMouseInput(info, false, false, &GuiGraphicsEventReceiver::leftButtonDown); OnMouseInput(info, false, false, &GuiGraphicsEventReceiver::leftButtonDoubleClick); } void GuiGraphicsHost::RightButtonDown(const NativeWindowMouseInfo& info) { altActionManager->CloseAltHost(); OnMouseInput(info, true, false, &GuiGraphicsEventReceiver::rightButtonDown); } void GuiGraphicsHost::RightButtonUp(const NativeWindowMouseInfo& info) { OnMouseInput(info, false, true, &GuiGraphicsEventReceiver::rightButtonUp); } void GuiGraphicsHost::RightButtonDoubleClick(const NativeWindowMouseInfo& info) { altActionManager->CloseAltHost(); OnMouseInput(info, false, false, &GuiGraphicsEventReceiver::rightButtonDown); OnMouseInput(info, false, false, &GuiGraphicsEventReceiver::rightButtonDoubleClick); } void GuiGraphicsHost::MiddleButtonDown(const NativeWindowMouseInfo& info) { altActionManager->CloseAltHost(); OnMouseInput(info, true, false, &GuiGraphicsEventReceiver::middleButtonDown); } void GuiGraphicsHost::MiddleButtonUp(const NativeWindowMouseInfo& info) { OnMouseInput(info, false, true, &GuiGraphicsEventReceiver::middleButtonUp); } void GuiGraphicsHost::MiddleButtonDoubleClick(const NativeWindowMouseInfo& info) { altActionManager->CloseAltHost(); OnMouseInput(info, false, false, &GuiGraphicsEventReceiver::middleButtonDown); OnMouseInput(info, false, false, &GuiGraphicsEventReceiver::middleButtonDoubleClick); } void GuiGraphicsHost::HorizontalWheel(const NativeWindowMouseInfo& info) { OnMouseInput(info, false, false, &GuiGraphicsEventReceiver::horizontalWheel); } void GuiGraphicsHost::VerticalWheel(const NativeWindowMouseInfo& info) { OnMouseInput(info, false, false, &GuiGraphicsEventReceiver::verticalWheel); } void GuiGraphicsHost::MouseMoving(const NativeWindowMouseInfo& info) { CompositionList newCompositions; { auto point = hostRecord.nativeWindow->Convert(NativePoint(info.x, info.y)); GuiGraphicsComposition* composition = windowComposition->FindVisibleComposition(point, true); while (composition) { newCompositions.Insert(0, composition); composition = composition->GetParent(); } } vint firstDifferentIndex = mouseEnterCompositions.Count(); // TODO: (enumerable) foreach:indexed for (vint i = 0; i < mouseEnterCompositions.Count(); i++) { if (i == newCompositions.Count()) { firstDifferentIndex = newCompositions.Count(); break; } if (mouseEnterCompositions[i] != newCompositions[i]) { firstDifferentIndex = i; break; } } // TODO: (enumerable) foreach:reversed Linq:Take for (vint i = mouseEnterCompositions.Count() - 1; i >= firstDifferentIndex; i--) { GuiGraphicsComposition* composition = mouseEnterCompositions[i]; if (composition->HasEventReceiver()) { composition->GetEventReceiver()->mouseLeave.Execute(GuiEventArgs(composition)); } } CopyFrom(mouseEnterCompositions, newCompositions); // TODO: (enumerable) foreach Linq:Skip for (vint i = firstDifferentIndex; i < mouseEnterCompositions.Count(); i++) { GuiGraphicsComposition* composition = mouseEnterCompositions[i]; if (composition->HasEventReceiver()) { composition->GetEventReceiver()->mouseEnter.Execute(GuiEventArgs(composition)); } } INativeCursor* cursor = 0; if (newCompositions.Count() > 0) { cursor = newCompositions[newCompositions.Count() - 1]->GetRelatedCursor(); } if (cursor) { hostRecord.nativeWindow->SetWindowCursor(cursor); } else { hostRecord.nativeWindow->SetWindowCursor(GetCurrentController()->ResourceService()->GetDefaultSystemCursor()); } OnMouseInput(info, false, false, &GuiGraphicsEventReceiver::mouseMove); } void GuiGraphicsHost::MouseEntered() { } void GuiGraphicsHost::MouseLeaved() { // TODO: (enumerable) foreach:reversed for (vint i = mouseEnterCompositions.Count() - 1; i >= 0; i--) { GuiGraphicsComposition* composition = mouseEnterCompositions[i]; if (composition->HasEventReceiver()) { composition->GetEventReceiver()->mouseLeave.Execute(GuiEventArgs(composition)); } } mouseEnterCompositions.Clear(); } void GuiGraphicsHost::KeyDown(const NativeWindowKeyInfo& info) { if (altActionManager->KeyDown(info)) { return; } if (tabActionManager->KeyDown(info, focusedComposition)) { return; } if(shortcutKeyManager && shortcutKeyManager->Execute(info)) { return; } auto receiver = focusedComposition; if (!receiver) receiver = controlHost->GetFocusableComposition(); if (!receiver) receiver = controlHost->GetBoundsComposition(); if (receiver && receiver->HasEventReceiver()) { OnKeyInput(info, receiver, &GuiGraphicsEventReceiver::keyDown); } } void GuiGraphicsHost::KeyUp(const NativeWindowKeyInfo& info) { if (altActionManager->KeyUp(info)) { return; } if (!info.ctrl && !info.shift && info.code == VKEY::KEY_MENU && hostRecord.nativeWindow) { hostRecord.nativeWindow->SupressAlt(); } auto receiver = focusedComposition; if (!receiver) receiver = controlHost->GetFocusableComposition(); if (!receiver) receiver = controlHost->GetBoundsComposition(); if(receiver && receiver->HasEventReceiver()) { OnKeyInput(info, receiver, &GuiGraphicsEventReceiver::keyUp); } } void GuiGraphicsHost::Char(const NativeWindowCharInfo& info) { if (altActionManager->Char(info)) { return; } if (tabActionManager->Char(info)) { return; } auto receiver = focusedComposition; if (!receiver) receiver = controlHost->GetFocusableComposition(); if (!receiver) receiver = controlHost->GetBoundsComposition(); if(receiver && receiver->HasEventReceiver()) { OnCharInput(info, receiver, &GuiGraphicsEventReceiver::charInput); } } bool GuiGraphicsHost::NeedRefresh() { return needRender; } void GuiGraphicsHost::ForceRefresh(bool handleFailure, bool& updated, bool& failureByResized, bool& failureByLostDevice) { if (hostRecord.nativeWindow && hostRecord.nativeWindow->IsVisible()) { auto result = Render(true, handleFailure, updated); failureByResized |= result == RenderTargetFailure::ResizeWhileRendering; failureByLostDevice |= result == RenderTargetFailure::LostDevice; } else { updated = false; failureByResized = false; failureByLostDevice = false; } } void GuiGraphicsHost::GlobalTimer() { timerManager.Play(); DateTime now = DateTime::UtcTime(); if (now.osMilliseconds - lastCaretTime >= CaretInterval) { lastCaretTime = now.osMilliseconds; if (focusedComposition && focusedComposition->HasEventReceiver()) { focusedComposition->GetEventReceiver()->caretNotify.Execute(GuiEventArgs(focusedComposition)); } } if (hostRecord.nativeWindow && hostRecord.nativeWindow->IsVisible() && hostRecord.nativeWindow->IsActivelyRefreshing()) { bool updated = false; Render(false, true, updated); } } elements::RenderTargetFailure GuiGraphicsHost::Render(bool forceUpdate, bool handleFailure, bool& updated) { RenderTargetFailure result = RenderTargetFailure::None; if (!renderingTriggeredInLastFrame && needRender) { GuiControlSignalEventArgs arguments(controlHost->boundsComposition); arguments.controlSignal = ControlSignal::UpdateRequested; controlHost->ControlSignalTrigerred.Execute(arguments); } else if (renderingTriggeredInLastFrame && !needRender) { GuiControlSignalEventArgs arguments(controlHost->boundsComposition); arguments.controlSignal = ControlSignal::UpdateFullfilled; controlHost->ControlSignalTrigerred.Execute(arguments); } updated = needRender; renderingTriggeredInLastFrame = needRender; if (!forceUpdate && !needRender) { return result; } needRender = false; if(hostRecord.nativeWindow && hostRecord.nativeWindow->IsVisible()) { supressPaint = true; { hostRecord.renderTarget->StartRendering(); auto nativeOffset = hostRecord.nativeWindow->GetRenderingOffset(); auto localOffset = hostRecord.nativeWindow->Convert(nativeOffset); windowComposition->Render(localOffset); result = hostRecord.renderTarget->StopRendering(); hostRecord.nativeWindow->RedrawContent(); } supressPaint = false; switch (result) { case RenderTargetFailure::ResizeWhileRendering: if (handleFailure) { GetGuiGraphicsResourceManager()->ResizeRenderTarget(hostRecord.nativeWindow); needRender = true; } break; case RenderTargetFailure::LostDevice: if (handleFailure) { ResetRenderTarget(); CreateRenderTarget(); needRender = true; } break; default: { supressPaint = true; auto bounds = windowComposition->GetCachedBounds(); windowComposition->Layout_UpdateMinSize(); auto preferred = windowComposition->GetCachedMinSize(); auto width = bounds.Width() > preferred.x ? bounds.Width() : preferred.x; auto height = bounds.Height() > preferred.y ? bounds.Height() : preferred.y; controlHost->UpdateClientSizeAfterRendering(preferred, Size(width, height)); windowComposition->Layout_UpdateBounds({ width,height }); supressPaint = false; } } } if (!needRender) { { ProcList procs; CopyFrom(procs, afterRenderProcs); afterRenderProcs.Clear(); // TODO: (enumerable) foreach for (vint i = 0; i < procs.Count(); i++) { procs[i](); } } { ProcMap procs; CopyFrom(procs, afterRenderKeyedProcs); afterRenderKeyedProcs.Clear(); // TODO: (enumerable) foreach for (vint i = 0; i < procs.Count(); i++) { procs.Values()[i](); } } } return result; } GuiGraphicsHost::GuiGraphicsHost(controls::GuiControlHost* _controlHost, GuiGraphicsComposition* boundsComposition) :controlHost(_controlHost) { altActionManager = new GuiAltActionManager(controlHost); tabActionManager = new GuiTabActionManager(controlHost); hostRecord.host = this; windowComposition=new GuiWindowComposition; windowComposition->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); windowComposition->AddChild(boundsComposition); RefreshRelatedHostRecord(nullptr); } GuiGraphicsHost::~GuiGraphicsHost() { windowComposition->RemoveChild(windowComposition->Children()[0]); NotifyFinalizeInstance(windowComposition); delete altActionManager; delete tabActionManager; if (shortcutKeyManager) { delete shortcutKeyManager; shortcutKeyManager = nullptr; } delete windowComposition; } INativeWindow* GuiGraphicsHost::GetNativeWindow() { return hostRecord.nativeWindow; } void GuiGraphicsHost::SetNativeWindow(INativeWindow* _nativeWindow) { if (hostRecord.nativeWindow != _nativeWindow) { if (hostRecord.nativeWindow) { GetCurrentController()->CallbackService()->UninstallListener(this); hostRecord.nativeWindow->UninstallListener(this); } if (_nativeWindow) { _nativeWindow->InstallListener(this); GetCurrentController()->CallbackService()->InstallListener(this); previousClientSize = _nativeWindow->GetClientSize(); minSize = windowComposition->GetCachedMinSize(); _nativeWindow->SetCaretPoint(_nativeWindow->Convert(caretPoint)); needRender = true; } RefreshRelatedHostRecord(_nativeWindow); } } GuiGraphicsComposition* GuiGraphicsHost::GetMainComposition() { return windowComposition; } void GuiGraphicsHost::RequestRender() { needRender = true; } void GuiGraphicsHost::RequestUpdateSizeFromNativeWindow() { NativeSize size = hostRecord.nativeWindow->GetClientSize(); if (previousClientSize != size) { previousClientSize = size; windowComposition->Layout_UpdateMinSize(); windowComposition->Layout_UpdateBounds(hostRecord.nativeWindow->Convert(size)); minSize = windowComposition->GetCachedMinSize(); needRender = true; } } void GuiGraphicsHost::InvokeAfterRendering(const Func& proc, ProcKey key) { if (key.key == nullptr) { afterRenderProcs.Add(proc); } else { afterRenderKeyedProcs.Set(key, proc); } } void GuiGraphicsHost::InvalidateTabOrderCache() { tabActionManager->InvalidateTabOrderCache(); } IGuiShortcutKeyManager* GuiGraphicsHost::GetShortcutKeyManager() { return shortcutKeyManager; } void GuiGraphicsHost::SetShortcutKeyManager(IGuiShortcutKeyManager* value) { shortcutKeyManager=value; } bool GuiGraphicsHost::SetFocus(GuiGraphicsComposition* composition) { if(!composition || composition->GetRelatedGraphicsHost()!=this) { return false; } if(focusedComposition == composition) { return true; } SetFocusInternal(composition); return true; } bool GuiGraphicsHost::ClearFocus() { if (!focusedComposition) return false; SetFocusInternal(nullptr); return true; } GuiGraphicsComposition* GuiGraphicsHost::GetFocusedComposition() { return focusedComposition; } Point GuiGraphicsHost::GetCaretPoint() { return caretPoint; } void GuiGraphicsHost::SetCaretPoint(Point value, GuiGraphicsComposition* referenceComposition) { if (referenceComposition) { Rect bounds = referenceComposition->GetGlobalBounds(); value.x += bounds.x1; value.y += bounds.y1; } caretPoint = value; if (hostRecord.nativeWindow) { hostRecord.nativeWindow->SetCaretPoint(hostRecord.nativeWindow->Convert(caretPoint)); } } GuiGraphicsTimerManager* GuiGraphicsHost::GetTimerManager() { return &timerManager; } void GuiGraphicsHost::DisconnectComposition(GuiGraphicsComposition* composition) { DisconnectCompositionInternal(composition); } } } } /*********************************************************************** .\APPLICATION\GRAPHICSHOST\GUIGRAPHICSHOST_ALT.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace compositions { using namespace collections; using namespace controls; using namespace theme; const wchar_t* const IGuiAltAction::Identifier = L"vl::presentation::compositions::IGuiAltAction"; const wchar_t* const IGuiAltActionContainer::Identifier = L"vl::presentation::compositions::IGuiAltActionContainer"; const wchar_t* const IGuiAltActionHost::Identifier = L"vl::presentation::compositions::IGuiAltActionHost"; /*********************************************************************** IGuiAltAction ***********************************************************************/ bool IGuiAltAction::IsLegalAlt(const WString& alt) { for (vint i = 0; i < alt.Length(); i++) { auto c = alt[i]; if (('A' <= c && c <= 'Z') || ('0' <= c && c <= '9')) { continue; } return false; } return true; } /*********************************************************************** IGuiAltActionHost ***********************************************************************/ void IGuiAltActionHost::CollectAltActionsFromControl(controls::GuiControl* control, bool includeThisControl, collections::Group& actions) { List controls; controls.Add(control); vint index = 0; while (index < controls.Count()) { auto current = controls[index++]; if (current != control || includeThisControl) { if (auto container = current->QueryTypedService()) { vint count = container->GetAltActionCount(); for (vint i = 0; i < count; i++) { auto action = container->GetAltAction(i); actions.Add(action->GetAlt(), action); } continue; } else if (auto action = current->QueryTypedService()) { if (action->IsAltAvailable()) { if (action->IsAltEnabled()) { actions.Add(action->GetAlt(), action); continue; } } } } vint count = current->GetChildrenCount(); for (vint i = 0; i < count; i++) { controls.Add(current->GetChild(i)); } } } /*********************************************************************** GuiAltActionHostBase ***********************************************************************/ void GuiAltActionHostBase::SetAltComposition(GuiGraphicsComposition* _composition) { composition = _composition; } void GuiAltActionHostBase::SetAltControl(controls::GuiControl* _control, bool _includeControl) { control = _control; includeControl = _includeControl; } GuiGraphicsComposition* GuiAltActionHostBase::GetAltComposition() { CHECK_ERROR(composition, L"GuiAltActionHostBase::GetAltComposition()#Need to call SetAltComposition."); return composition; } IGuiAltActionHost* GuiAltActionHostBase::GetPreviousAltHost() { return previousHost; } void GuiAltActionHostBase::OnActivatedAltHost(IGuiAltActionHost* _previousHost) { previousHost = _previousHost; } void GuiAltActionHostBase::OnDeactivatedAltHost() { previousHost = nullptr; } void GuiAltActionHostBase::CollectAltActions(collections::Group& actions) { CHECK_ERROR(control, L"GuiAltActionHostBase::CollectAltActions(Group&)#Need to call SetAltControl."); CollectAltActionsFromControl(control, includeControl, actions); } /*********************************************************************** GuiAltActionManager ***********************************************************************/ void GuiAltActionManager::EnterAltHost(IGuiAltActionHost* host) { ClearAltHost(); Group actions; host->CollectAltActions(actions); if (actions.Count() == 0) { CloseAltHost(); return; } host->OnActivatedAltHost(currentAltHost); currentAltHost = host; CreateAltTitles(actions); } void GuiAltActionManager::LeaveAltHost() { if (currentAltHost) { ClearAltHost(); auto previousHost = currentAltHost->GetPreviousAltHost(); currentAltHost->OnDeactivatedAltHost(); currentAltHost = previousHost; if (currentAltHost) { Group actions; currentAltHost->CollectAltActions(actions); CreateAltTitles(actions); } } } bool GuiAltActionManager::EnterAltKey(wchar_t key) { currentAltPrefix += WString::FromChar(key); vint index = currentActiveAltActions.Keys().IndexOf(currentAltPrefix); if (index == -1) { if (FilterTitles() == 0) { currentAltPrefix = currentAltPrefix.Left(currentAltPrefix.Length() - 1); FilterTitles(); } } else { auto action = currentActiveAltActions.Values()[index]; if (action->GetActivatingAltHost()) { EnterAltHost(action->GetActivatingAltHost()); } else { CloseAltHost(); } action->OnActiveAlt(); return true; } return false; } void GuiAltActionManager::LeaveAltKey() { if (currentAltPrefix.Length() >= 1) { currentAltPrefix = currentAltPrefix.Left(currentAltPrefix.Length() - 1); } FilterTitles(); } void GuiAltActionManager::CreateAltTitles(const collections::Group& actions) { if (currentAltHost) { // TODO: (enumerable) foreach on group (key, value[]) vint count = actions.Count(); for (vint i = 0; i < count; i++) { WString key = actions.Keys()[i]; const auto& values = actions.GetByIndex(i); vint numberLength = 0; if (values.Count() == 1 && key.Length() > 0) { numberLength = 0; } else if (values.Count() <= 10) { numberLength = 1; } else if (values.Count() <= 100) { numberLength = 2; } else if (values.Count() <= 1000) { numberLength = 3; } else { continue; } for (auto [action, index] : indexed(values)) { WString key = actions.Keys()[i]; if (numberLength > 0) { WString number = itow(index); while (number.Length() < numberLength) { number = L"0" + number; } key += number; } currentActiveAltActions.Add(key, action); } } // TODO: (enumerable) foreach on dictionary count = currentActiveAltActions.Count(); auto window = dynamic_cast(currentAltHost->GetAltComposition()->GetRelatedControlHost()); for (vint i = 0; i < count; i++) { auto key = currentActiveAltActions.Keys()[i]; auto composition = currentActiveAltActions.Values()[i]->GetAltComposition(); auto label = new GuiLabel(theme::ThemeName::ShortcutKey); if (auto labelStyle = window->TypedControlTemplateObject(true)->GetShortcutKeyTemplate()) { label->SetControlTemplate(labelStyle); } label->SetText(key); composition->AddChild(label->GetBoundsComposition()); currentActiveAltTitles.Add(key, label); } FilterTitles(); } } vint GuiAltActionManager::FilterTitles() { // TODO: (enumerable) foreach on dictionary vint count = currentActiveAltTitles.Count(); vint visibles = 0; for (vint i = 0; i < count; i++) { auto key = currentActiveAltTitles.Keys()[i]; auto value = currentActiveAltTitles.Values()[i]; if (key.Length() >= currentAltPrefix.Length() && key.Left(currentAltPrefix.Length()) == currentAltPrefix) { value->SetVisible(true); if (currentAltPrefix.Length() <= key.Length()) { value->SetText( key .Insert(currentAltPrefix.Length(), L"[") .Insert(currentAltPrefix.Length() + 2, L"]") ); } else { value->SetText(key); } visibles++; } else { value->SetVisible(false); } } return visibles; } void GuiAltActionManager::ClearAltHost() { for (auto title : currentActiveAltTitles.Values()) { SafeDeleteControl(title); } currentActiveAltActions.Clear(); currentActiveAltTitles.Clear(); currentAltPrefix = L""; } void GuiAltActionManager::CloseAltHost() { ClearAltHost(); while (currentAltHost) { currentAltHost->OnDeactivatedAltHost(); currentAltHost = currentAltHost->GetPreviousAltHost(); } } GuiAltActionManager::GuiAltActionManager(controls::GuiControlHost* _controlHost) :controlHost(_controlHost) { } GuiAltActionManager::~GuiAltActionManager() { } bool GuiAltActionManager::KeyDown(const NativeWindowKeyInfo& info) { if (!info.ctrl && !info.shift) { if (currentAltHost) { if (info.code == VKEY::KEY_ESCAPE) { LeaveAltHost(); return true; } else if (info.code == VKEY::KEY_BACK) { LeaveAltKey(); } else if (VKEY::KEY_NUMPAD0 <= info.code && info.code <= VKEY::KEY_NUMPAD9) { if (EnterAltKey((wchar_t)(L'0' + ((vint)info.code - (vint)VKEY::KEY_NUMPAD0)))) { supressAltKey = info.code; return true; } } else if ((VKEY::KEY_0 <= info.code && info.code <= VKEY::KEY_9) || (VKEY::KEY_A <= info.code && info.code <= VKEY::KEY_Z)) { if (EnterAltKey((wchar_t)info.code)) { supressAltKey = info.code; return true; } } } else { if (info.code == VKEY::KEY_MENU) { if (auto altHost = controlHost->QueryTypedService()) { if (!altHost->GetPreviousAltHost()) { EnterAltHost(altHost); } } } } } if (currentAltHost) { return true; } return false; } bool GuiAltActionManager::KeyUp(const NativeWindowKeyInfo& info) { if (!info.ctrl && !info.shift && info.code == supressAltKey) { supressAltKey = VKEY::KEY_UNKNOWN; return true; } return false; } bool GuiAltActionManager::Char(const NativeWindowCharInfo& info) { if (currentAltHost || supressAltKey != VKEY::KEY_UNKNOWN) { return true; } return false; } } } } /*********************************************************************** .\APPLICATION\GRAPHICSHOST\GUIGRAPHICSHOST_SHORTCUTKEY.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace compositions { /*********************************************************************** GuiShortcutKeyItem ***********************************************************************/ GuiShortcutKeyItem::GuiShortcutKeyItem(GuiShortcutKeyManager* _shortcutKeyManager, bool _global, bool _ctrl, bool _shift, bool _alt, VKEY _key) :shortcutKeyManager(_shortcutKeyManager) ,global(_global) ,ctrl(_ctrl) ,shift(_shift) ,alt(_alt) ,key(_key) { } GuiShortcutKeyItem::~GuiShortcutKeyItem() { } IGuiShortcutKeyManager* GuiShortcutKeyItem::GetManager() { return shortcutKeyManager; } WString GuiShortcutKeyItem::GetName() { WString name; if (global) name += L"{"; if (ctrl) name += L"Ctrl+"; if (shift) name += L"Shift+"; if (alt) name += L"Alt+"; name += GetCurrentController()->InputService()->GetKeyName(key); if (global) name += L"}"; return name; } void GuiShortcutKeyItem::ReadKeyConfig(bool& _ctrl, bool& _shift, bool& _alt, VKEY& _key) { _ctrl = ctrl; _shift = shift; _alt = alt; _key = key; } bool GuiShortcutKeyItem::CanActivate(const NativeWindowKeyInfo& info) { return info.ctrl==ctrl && info.shift==shift && info.alt==alt && info.code==key; } bool GuiShortcutKeyItem::CanActivate(bool _ctrl, bool _shift, bool _alt, VKEY _key) { return _ctrl==ctrl && _shift==shift && _alt==alt && _key==key; } void GuiShortcutKeyItem::Execute() { GuiEventArgs arguments; Executed.Execute(arguments); } /*********************************************************************** GuiShortcutKeyManager ***********************************************************************/ bool GuiShortcutKeyManager::IsGlobal() { return false; } bool GuiShortcutKeyManager::OnCreatingShortcut(GuiShortcutKeyItem* item) { return true; } void GuiShortcutKeyManager::OnDestroyingShortcut(GuiShortcutKeyItem* item) { } IGuiShortcutKeyItem* GuiShortcutKeyManager::CreateShortcutInternal(bool ctrl, bool shift, bool alt, VKEY key) { auto item = Ptr(new GuiShortcutKeyItem(this, IsGlobal(), ctrl, shift, alt, key)); if (!OnCreatingShortcut(item.Obj())) return nullptr; shortcutKeyItems.Add(item); return item.Obj(); } GuiShortcutKeyManager::GuiShortcutKeyManager() { } GuiShortcutKeyManager::~GuiShortcutKeyManager() { for (auto item : shortcutKeyItems) { OnDestroyingShortcut(item.Obj()); } } vint GuiShortcutKeyManager::GetItemCount() { return shortcutKeyItems.Count(); } IGuiShortcutKeyItem* GuiShortcutKeyManager::GetItem(vint index) { return shortcutKeyItems[index].Obj(); } bool GuiShortcutKeyManager::Execute(const NativeWindowKeyInfo& info) { bool executed=false; for (auto item : shortcutKeyItems) { if(item->CanActivate(info)) { item->Execute(); executed=true; } } return executed; } IGuiShortcutKeyItem* GuiShortcutKeyManager::TryGetShortcut(bool ctrl, bool shift, bool alt, VKEY key) { for (auto item : shortcutKeyItems) { if (item->CanActivate(ctrl, shift, alt, key)) { return item.Obj(); } } return nullptr; } IGuiShortcutKeyItem* GuiShortcutKeyManager::CreateNewShortcut(bool ctrl, bool shift, bool alt, VKEY key) { CHECK_ERROR( TryGetShortcut(ctrl, shift, alt, key) == nullptr, L"vl::presentation::compositions::GuiShortcutKeyManager::CreateNewShortcut(bool, bool, bool, VKEY)#The shortcut key exists." ); return CreateShortcutInternal(ctrl, shift, alt, key); } IGuiShortcutKeyItem* GuiShortcutKeyManager::CreateShortcutIfNotExist(bool ctrl, bool shift, bool alt, VKEY key) { if (TryGetShortcut(ctrl, shift, alt, key)) { return nullptr; } return CreateShortcutInternal(ctrl, shift, alt, key); } bool GuiShortcutKeyManager::DestroyShortcut(IGuiShortcutKeyItem* item) { if (!item) return false; if (item->GetManager() != this) return false; auto skItem = dynamic_cast(item); if (!skItem) return false; vint index = shortcutKeyItems.IndexOf(skItem); if (index == -1) return false; OnDestroyingShortcut(skItem); return shortcutKeyItems.RemoveAt(index); } } } } /*********************************************************************** .\APPLICATION\GRAPHICSHOST\GUIGRAPHICSHOST_TAB.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace compositions { using namespace collections; using namespace controls; const wchar_t* const IGuiTabAction::Identifier = L"vl::presentation::compositions::IGuiTabAction"; /*********************************************************************** GuiTabActionManager ***********************************************************************/ namespace tab_focus { void CollectControls(GuiControl* current, bool includeCurrent, Group& prioritized) { if (includeCurrent) { auto tabAction = current->QueryTypedService(); if (tabAction && (tabAction->IsTabAvailable() || tabAction->GetTabPriority() != -1)) { vint priority = tabAction->GetTabPriority(); vuint64_t normalized = priority < 0 ? ~(vuint64_t)0 : (vuint64_t)priority; prioritized.Add(normalized, current); return; } } vint count = current->GetChildrenCount(); for (vint i = 0; i < count; i++) { CollectControls(current->GetChild(i), true, prioritized); } } void InsertPrioritized(List& controls, vint index, Group& prioritized) { // TODO: (enumerable) Linq:SelectMany vint count = prioritized.Count(); for (vint i = 0; i < count; i++) { auto& values = prioritized.GetByIndex(i); for (vint j = 0; j < values.Count(); j++) { controls.Insert(index++, values[j]); } } } } using namespace tab_focus; void GuiTabActionManager::BuildControlList() { controlsInOrder.Clear(); { Group prioritized; CollectControls(controlHost, false, prioritized); InsertPrioritized(controlsInOrder, 0, prioritized); } // TODO: (enumerable) foreach for (vint i = 0; i < controlsInOrder.Count(); i++) { Group prioritized; CollectControls(controlsInOrder[i], false, prioritized); InsertPrioritized(controlsInOrder, i + 1, prioritized); } } controls::GuiControl* GuiTabActionManager::GetNextFocusControl(controls::GuiControl* focusedControl, vint offset) { if (!available) { BuildControlList(); available = true; } #define STEP_AND_NORMALIZE(INDEX) (((INDEX) + offset + controlsInOrder.Count()) % controlsInOrder.Count()) if (controlsInOrder.Count() == 0) return nullptr; vint startIndex = controlsInOrder.IndexOf(focusedControl); startIndex = startIndex == -1 ? 0 : STEP_AND_NORMALIZE(startIndex); vint index = startIndex; do { auto control = controlsInOrder[index]; if (auto tabAction = control->QueryTypedService()) { if (tabAction->IsTabAvailable() && tabAction->IsTabEnabled()) { return control; } } index = STEP_AND_NORMALIZE(index); } while (index != startIndex); #undef STEP_AND_NORMALIZE return nullptr; } GuiTabActionManager::GuiTabActionManager(controls::GuiControlHost* _controlHost) :controlHost(_controlHost) { } GuiTabActionManager::~GuiTabActionManager() { } void GuiTabActionManager::InvalidateTabOrderCache() { available = false; controlsInOrder.Clear(); } bool GuiTabActionManager::KeyDown(const NativeWindowKeyInfo& info, GuiGraphicsComposition* focusedComposition) { if (!info.ctrl && !info.alt && info.code == VKEY::KEY_TAB) { GuiControl* focusedControl = nullptr; if (focusedComposition) { focusedControl = focusedComposition->GetRelatedControl(); if (focusedControl && focusedControl->GetAcceptTabInput()) { return false; } } if (auto next = GetNextFocusControl(focusedControl, (info.shift ? -1 : 1))) { next->SetFocused(); supressTabOnce = true; return true; } } return false; } bool GuiTabActionManager::Char(const NativeWindowCharInfo& info) { bool supress = supressTabOnce; supressTabOnce = false; return supress && info.code == L'\t'; } } } } /*********************************************************************** .\CONTROLS\GUIBUTTONCONTROLS.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { using namespace elements; using namespace compositions; using namespace collections; using namespace reflection::description; /*********************************************************************** GuiButton ***********************************************************************/ void GuiButton::BeforeControlTemplateUninstalled_() { } void GuiButton::AfterControlTemplateInstalled_(bool initialize) { TypedControlTemplateObject(true)->SetState(controlState); } void GuiButton::OnParentLineChanged() { GuiControl::OnParentLineChanged(); if (GetRelatedControlHost() == 0) { mousePressingDirect = false; mousePressingIndirect = false; mouseHoving = false; UpdateControlState(); } } void GuiButton::OnActiveAlt() { if (autoFocus) { GuiControl::OnActiveAlt(); } Clicked.Execute(GetNotifyEventArguments()); } bool GuiButton::IsTabAvailable() { return autoFocus && GuiControl::IsTabAvailable(); } void GuiButton::UpdateControlState() { auto newControlState = ButtonState::Normal; if (keyPressing) { newControlState = ButtonState::Pressed; } else if (mousePressingDirect || mousePressingIndirect) { if (mouseHoving) { newControlState = ButtonState::Pressed; } else { newControlState = ButtonState::Active; } } else { if (mouseHoving) { newControlState = ButtonState::Active; } else { newControlState = ButtonState::Normal; } } if (controlState != newControlState) { controlState = newControlState; TypedControlTemplateObject(true)->SetState(controlState); } } void GuiButton::CheckAndClick(bool skipChecking, compositions::GuiEventArgs& arguments) { if (!skipChecking) { auto eventSource = arguments.eventSource->GetAssociatedControl(); while (eventSource && eventSource != this) { if (eventSource->GetFocusableComposition()) { return; } eventSource = eventSource->GetParent(); } } Clicked.Execute(GetNotifyEventArguments()); } void GuiButton::OnLeftButtonDown(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) { if (arguments.eventSource == boundsComposition) { mousePressingDirect = true; } else if (!ignoreChildControlMouseEvents) { mousePressingIndirect = true; } if (mousePressingDirect || mousePressingIndirect) { if (GetVisuallyEnabled() && autoFocus) { SetFocused(); } UpdateControlState(); if (GetVisuallyEnabled() && !clickOnMouseUp) { CheckAndClick(mousePressingIndirect, arguments); } } } void GuiButton::OnLeftButtonUp(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) { if (mousePressingDirect || mousePressingIndirect) { bool skipChecking = mousePressingIndirect; mousePressingDirect = false; mousePressingIndirect = false; UpdateControlState(); if (GetVisuallyEnabled() && mouseHoving && clickOnMouseUp) { CheckAndClick(skipChecking, arguments); } } } void GuiButton::OnMouseEnter(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if (arguments.eventSource == boundsComposition || !ignoreChildControlMouseEvents) { mouseHoving = true; UpdateControlState(); } } void GuiButton::OnMouseLeave(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if (arguments.eventSource == boundsComposition || !ignoreChildControlMouseEvents) { mouseHoving = false; UpdateControlState(); } } void GuiButton::OnKeyDown(compositions::GuiGraphicsComposition* sender, compositions::GuiKeyEventArgs& arguments) { if (arguments.eventSource == focusableComposition && !arguments.ctrl && !arguments.shift && !arguments.alt) { switch (arguments.code) { case VKEY::KEY_RETURN: CheckAndClick(false, arguments); arguments.handled = true; break; case VKEY::KEY_SPACE: if (!arguments.autoRepeatKeyDown) { keyPressing = true; UpdateControlState(); } arguments.handled = true; break; default:; } } } void GuiButton::OnKeyUp(compositions::GuiGraphicsComposition* sender, compositions::GuiKeyEventArgs& arguments) { if (arguments.eventSource == focusableComposition && !arguments.ctrl && !arguments.shift && !arguments.alt) { switch (arguments.code) { case VKEY::KEY_SPACE: if (keyPressing) { keyPressing = false; UpdateControlState(); CheckAndClick(false, arguments); } arguments.handled = true; break; default:; } } } void GuiButton::OnLostFocus(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if (keyPressing) { keyPressing = false; UpdateControlState(); } } GuiButton::GuiButton(theme::ThemeName themeName) :GuiControl(themeName) { Clicked.SetAssociatedComposition(boundsComposition); SetFocusableComposition(boundsComposition); boundsComposition->GetEventReceiver()->leftButtonDown.AttachMethod(this, &GuiButton::OnLeftButtonDown); boundsComposition->GetEventReceiver()->leftButtonUp.AttachMethod(this, &GuiButton::OnLeftButtonUp); boundsComposition->GetEventReceiver()->mouseEnter.AttachMethod(this, &GuiButton::OnMouseEnter); boundsComposition->GetEventReceiver()->mouseLeave.AttachMethod(this, &GuiButton::OnMouseLeave); boundsComposition->GetEventReceiver()->keyDown.AttachMethod(this, &GuiButton::OnKeyDown); boundsComposition->GetEventReceiver()->keyUp.AttachMethod(this, &GuiButton::OnKeyUp); boundsComposition->GetEventReceiver()->lostFocus.AttachMethod(this, &GuiButton::OnLostFocus); } GuiButton::~GuiButton() { } bool GuiButton::GetClickOnMouseUp() { return clickOnMouseUp; } void GuiButton::SetClickOnMouseUp(bool value) { clickOnMouseUp=value; } bool GuiButton::GetAutoFocus() { return autoFocus; } void GuiButton::SetAutoFocus(bool value) { autoFocus = value; } bool GuiButton::GetIgnoreChildControlMouseEvents() { return ignoreChildControlMouseEvents; } void GuiButton::SetIgnoreChildControlMouseEvents(bool value) { ignoreChildControlMouseEvents = value; } /*********************************************************************** GuiSelectableButton::GroupController ***********************************************************************/ GuiSelectableButton::GroupController::GroupController() { } GuiSelectableButton::GroupController::~GroupController() { // TODO: (enumerable) foreach:reversed for(vint i=buttons.Count()-1;i>=0;i--) { buttons[i]->SetGroupController(0); } } void GuiSelectableButton::GroupController::Attach(GuiSelectableButton* button) { if(!buttons.Contains(button)) { buttons.Add(button); } } void GuiSelectableButton::GroupController::Detach(GuiSelectableButton* button) { buttons.Remove(button); } /*********************************************************************** GuiSelectableButton::MutexGroupController ***********************************************************************/ GuiSelectableButton::MutexGroupController::MutexGroupController() :suppress(false) { } GuiSelectableButton::MutexGroupController::~MutexGroupController() { } void GuiSelectableButton::MutexGroupController::OnSelectedChanged(GuiSelectableButton* button) { if(!suppress) { suppress=true; // TODO: (enumerable) foreach for(vint i=0;iSetSelected(buttons[i]==button); } suppress=false; } } /*********************************************************************** GuiSelectableButton ***********************************************************************/ void GuiSelectableButton::BeforeControlTemplateUninstalled_() { } void GuiSelectableButton::AfterControlTemplateInstalled_(bool initialize) { TypedControlTemplateObject(true)->SetSelected(isSelected); } void GuiSelectableButton::OnClicked(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if(autoSelection) { SetSelected(!GetSelected()); } } GuiSelectableButton::GuiSelectableButton(theme::ThemeName themeName) :GuiButton(themeName) { GroupControllerChanged.SetAssociatedComposition(boundsComposition); AutoSelectionChanged.SetAssociatedComposition(boundsComposition); SelectedChanged.SetAssociatedComposition(boundsComposition); Clicked.AttachMethod(this, &GuiSelectableButton::OnClicked); } GuiSelectableButton::~GuiSelectableButton() { if(groupController) { groupController->Detach(this); } } GuiSelectableButton::GroupController* GuiSelectableButton::GetGroupController() { return groupController; } void GuiSelectableButton::SetGroupController(GroupController* value) { if(groupController) { groupController->Detach(this); } groupController=value; if(groupController) { groupController->Attach(this); } GroupControllerChanged.Execute(GetNotifyEventArguments()); } bool GuiSelectableButton::GetAutoSelection() { return autoSelection; } void GuiSelectableButton::SetAutoSelection(bool value) { if(autoSelection!=value) { autoSelection=value; AutoSelectionChanged.Execute(GetNotifyEventArguments()); } } bool GuiSelectableButton::GetSelected() { return isSelected; } void GuiSelectableButton::SetSelected(bool value) { if (isSelected != value) { isSelected = value; TypedControlTemplateObject(true)->SetSelected(isSelected); if (groupController) { groupController->OnSelectedChanged(this); } SelectedChanged.Execute(GetNotifyEventArguments()); } } } } } /*********************************************************************** .\CONTROLS\GUICONTAINERCONTROLS.CPP ***********************************************************************/ namespace vl { namespace presentation { using namespace compositions; namespace controls { using namespace reflection::description; /*********************************************************************** GuiTabPage ***********************************************************************/ bool GuiTabPage::IsAltAvailable() { return false; } GuiTabPage::GuiTabPage(theme::ThemeName themeName) :GuiCustomControl(themeName) { } GuiTabPage::~GuiTabPage() { FinalizeAggregation(); } GuiTab* GuiTabPage::GetOwnerTab() { return tab; } /*********************************************************************** GuiTabPageList ***********************************************************************/ bool GuiTabPageList::QueryInsert(vint index, GuiTabPage* const& value) { return !items.Contains(value) && value->tab == nullptr; } void GuiTabPageList::AfterInsert(vint index, GuiTabPage* const& value) { value->tab = tab; value->SetVisible(false); value->boundsComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); tab->containerComposition->AddChild(value->boundsComposition); if (!tab->selectedPage) { tab->SetSelectedPage(value); } } void GuiTabPageList::BeforeRemove(vint index, GuiTabPage* const& value) { tab->containerComposition->RemoveChild(value->boundsComposition); value->tab = nullptr; if (items.Count() > 1) { if (items.Count() > index + 1) { tab->SetSelectedPage(items[index + 1]); } else if (items.Count() == index + 1) { tab->SetSelectedPage(items[index - 1]); } } } void GuiTabPageList::AfterRemove(vint index, vint count) { if (items.Count() == 0) { tab->SetSelectedPage(nullptr); } } GuiTabPageList::GuiTabPageList(GuiTab* _tab) :tab(_tab) { } GuiTabPageList::~GuiTabPageList() { } /*********************************************************************** GuiTab::CommandExecutor ***********************************************************************/ GuiTab::CommandExecutor::CommandExecutor(GuiTab* _tab) :tab(_tab) { } GuiTab::CommandExecutor::~CommandExecutor() { } void GuiTab::CommandExecutor::ShowTab(vint index, bool setFocus) { tab->SetSelectedPage(tab->GetPages().Get(index)); if (setFocus) { tab->SetFocused(); } } /*********************************************************************** GuiTab ***********************************************************************/ void GuiTab::BeforeControlTemplateUninstalled_() { auto ct = TypedControlTemplateObject(false); if (!ct) return; ct->SetCommands(nullptr); ct->SetTabPages(nullptr); ct->SetSelectedTabPage(nullptr); } void GuiTab::AfterControlTemplateInstalled_(bool initialize) { auto ct = TypedControlTemplateObject(true); ct->SetCommands(commandExecutor.Obj()); ct->SetTabPages(UnboxValue>(BoxParameter(tabPages))); ct->SetSelectedTabPage(selectedPage); } void GuiTab::OnKeyDown(compositions::GuiGraphicsComposition* sender, compositions::GuiKeyEventArgs& arguments) { if (arguments.eventSource == focusableComposition) { if (auto ct = TypedControlTemplateObject(false)) { vint index = tabPages.IndexOf(selectedPage); if (index != -1) { auto hint = ct->GetTabOrder(); vint tabOffset = 0; switch (hint) { case TabPageOrder::LeftToRight: if (arguments.code == VKEY::KEY_LEFT) tabOffset = -1; else if (arguments.code == VKEY::KEY_RIGHT) tabOffset = 1; break; case TabPageOrder::RightToLeft: if (arguments.code == VKEY::KEY_LEFT) tabOffset = 1; else if (arguments.code == VKEY::KEY_RIGHT) tabOffset = -1; break; case TabPageOrder::TopToBottom: if (arguments.code == VKEY::KEY_UP) tabOffset = -1; else if (arguments.code == VKEY::KEY_DOWN) tabOffset = 1; break; case TabPageOrder::BottomToTop: if (arguments.code == VKEY::KEY_UP) tabOffset = 1; else if (arguments.code == VKEY::KEY_DOWN) tabOffset = -1; break; default:; } if (tabOffset != 0) { arguments.handled = true; index += tabOffset; if (index < 0) index = 0; else if (index >= tabPages.Count()) index = tabPages.Count() - 1; SetSelectedPage(tabPages[index]); } } } } } GuiTab::GuiTab(theme::ThemeName themeName) :GuiControl(themeName) , tabPages(this) { commandExecutor = Ptr(new CommandExecutor(this)); SetFocusableComposition(boundsComposition); boundsComposition->GetEventReceiver()->keyDown.AttachMethod(this, &GuiTab::OnKeyDown); } GuiTab::~GuiTab() { } collections::ObservableList& GuiTab::GetPages() { return tabPages; } GuiTabPage* GuiTab::GetSelectedPage() { return selectedPage; } bool GuiTab::SetSelectedPage(GuiTabPage* value) { if (!value) { if (tabPages.Count() == 0) { selectedPage = nullptr; } } else if (value->GetOwnerTab() == this) { if (selectedPage == value) { return true; } selectedPage = value; for (auto tabPage : tabPages) { tabPage->SetVisible(tabPage == selectedPage); } } if (auto ct = TypedControlTemplateObject(false)) { ct->SetSelectedTabPage(selectedPage); } SelectedPageChanged.Execute(GetNotifyEventArguments()); return selectedPage == value; } /*********************************************************************** GuiScrollView ***********************************************************************/ void GuiScrollView::BeforeControlTemplateUninstalled_() { auto ct = TypedControlTemplateObject(false); if (!ct) return; if (auto scroll = ct->GetHorizontalScroll()) { scroll->PositionChanged.Detach(hScrollHandler); } if (auto scroll = ct->GetVerticalScroll()) { scroll->PositionChanged.Detach(vScrollHandler); } ct->GetEventReceiver()->horizontalWheel.Detach(hWheelHandler); ct->GetEventReceiver()->verticalWheel.Detach(vWheelHandler); ct->CachedBoundsChanged.Detach(containerCachedBoundsChangedHandler); hScrollHandler = nullptr; vScrollHandler = nullptr; hWheelHandler = nullptr; vWheelHandler = nullptr; containerCachedBoundsChangedHandler = nullptr; supressScrolling = false; } void GuiScrollView::AfterControlTemplateInstalled_(bool initialize) { auto ct = TypedControlTemplateObject(true); if (auto scroll = ct->GetHorizontalScroll()) { hScrollHandler = scroll->PositionChanged.AttachMethod(this, &GuiScrollView::OnHorizontalScroll); } if (auto scroll = ct->GetVerticalScroll()) { vScrollHandler = scroll->PositionChanged.AttachMethod(this, &GuiScrollView::OnVerticalScroll); } hWheelHandler = ct->GetEventReceiver()->horizontalWheel.AttachMethod(this, &GuiScrollView::OnHorizontalWheel); vWheelHandler = ct->GetEventReceiver()->verticalWheel.AttachMethod(this, &GuiScrollView::OnVerticalWheel); containerCachedBoundsChangedHandler = ct->CachedBoundsChanged.AttachMethod(this, &GuiScrollView::OnContainerCachedBoundsChanged); CalculateView(); } void GuiScrollView::UpdateDisplayFont() { GuiControl::UpdateDisplayFont(); CalculateView(); } void GuiScrollView::OnContainerCachedBoundsChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { CalculateView(); } void GuiScrollView::OnHorizontalScroll(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if(!supressScrolling) { CallUpdateView(); } } void GuiScrollView::OnVerticalScroll(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if(!supressScrolling) { CallUpdateView(); } } void GuiScrollView::OnHorizontalWheel(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) { if(!supressScrolling) { if (auto scroll = TypedControlTemplateObject(true)->GetHorizontalScroll()) { if (scroll->GetEnabled()) { vint position = scroll->GetPosition(); vint move = scroll->GetSmallMove(); position -= move * arguments.wheel / 60; scroll->SetPosition(position); } } } } void GuiScrollView::OnVerticalWheel(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) { if(!supressScrolling && GetVisuallyEnabled()) { if (auto scroll = TypedControlTemplateObject(true)->GetVerticalScroll()) { if (scroll->GetEnabled()) { vint position = scroll->GetPosition(); vint move = scroll->GetSmallMove(); position -= move * arguments.wheel / 60; scroll->SetPosition(position); } } } } void GuiScrollView::CallUpdateView() { Rect viewBounds=GetViewBounds(); UpdateView(viewBounds); } bool GuiScrollView::AdjustView(Size fullSize) { auto ct = TypedControlTemplateObject(true); auto hScroll = ct->GetHorizontalScroll(); auto vScroll = ct->GetVerticalScroll(); Size viewSize = ct->GetContainerComposition()->GetCachedBounds().GetSize(); auto hVisible = hScroll ? hScroll->GetVisible() : false; auto vVisible = vScroll ? vScroll->GetVisible() : false; if (hScroll) { if (fullSize.x <= viewSize.x) { hScroll->SetVisible(horizontalAlwaysVisible); hScroll->SetEnabled(false); hScroll->SetPosition(0); } else { hScroll->SetVisible(true); hScroll->SetEnabled(true); hScroll->SetTotalSize(fullSize.x); hScroll->SetPageSize(viewSize.x); } } if (vScroll) { if (fullSize.y <= viewSize.y) { vScroll->SetVisible(verticalAlwaysVisible); vScroll->SetEnabled(false); vScroll->SetPosition(0); } else { vScroll->SetVisible(true); vScroll->SetEnabled(true); vScroll->SetTotalSize(fullSize.y); vScroll->SetPageSize(viewSize.y); } } auto hVisible2 = hScroll ? hScroll->GetVisible() : false; auto vVisible2 = vScroll ? vScroll->GetVisible() : false; return hVisible != hVisible2 || vVisible != vVisible2; } GuiScrollView::GuiScrollView(theme::ThemeName themeName) :GuiControl(themeName) { containerComposition->CachedBoundsChanged.AttachMethod(this, &GuiScrollView::OnContainerCachedBoundsChanged); } vint GuiScrollView::GetSmallMove() { return GetDisplayFont().size * 2; } Size GuiScrollView::GetBigMove() { return GetViewSize(); } GuiScrollView::~GuiScrollView() { } void GuiScrollView::CalculateView() { TryDelayExecuteIfNotDeleted([=]() { auto ct = TypedControlTemplateObject(true); auto hScroll = ct->GetHorizontalScroll(); auto vScroll = ct->GetVerticalScroll(); if (!supressScrolling) { Size fullSize = QueryFullSize(); while (true) { bool flagA = false; bool flagB = false; flagA = AdjustView(fullSize); bool bothInvisible = (hScroll ? !hScroll->GetVisible() : true) && (vScroll ? !vScroll->GetVisible() : true); if (!bothInvisible) { flagB = AdjustView(fullSize); bothInvisible = (hScroll ? !hScroll->GetVisible() : true) && (vScroll ? !vScroll->GetVisible() : true); } supressScrolling = true; CallUpdateView(); supressScrolling = false; Size newSize = QueryFullSize(); if (fullSize == newSize) { vint smallMove = GetSmallMove(); Size bigMove = GetBigMove(); if (hScroll) { hScroll->SetSmallMove(smallMove); hScroll->SetBigMove(bigMove.x); } if (vScroll) { vScroll->SetSmallMove(smallMove); vScroll->SetBigMove(bigMove.y); } if (bothInvisible || !flagA && !flagB) { break; } } else { fullSize = newSize; } } } }); } Size GuiScrollView::GetViewSize() { Size viewSize = TypedControlTemplateObject(true)->GetContainerComposition()->GetCachedBounds().GetSize(); return viewSize; } Rect GuiScrollView::GetViewBounds() { return Rect(GetViewPosition(), GetViewSize()); } Point GuiScrollView::GetViewPosition() { auto ct = TypedControlTemplateObject(true); auto hScroll = ct->GetHorizontalScroll(); auto vScroll = ct->GetVerticalScroll(); return Point( (hScroll && hScroll->GetEnabled()) ? hScroll->GetPosition() : 0, (vScroll && vScroll->GetEnabled()) ? vScroll->GetPosition() : 0 ); } void GuiScrollView::SetViewPosition(Point value) { if (GetViewPosition() == value) return; auto ct = TypedControlTemplateObject(true); if (auto hScroll = ct->GetHorizontalScroll()) { hScroll->SetPosition(value.x); } if (auto vScroll = ct->GetVerticalScroll()) { vScroll->SetPosition(value.y); } } GuiScroll* GuiScrollView::GetHorizontalScroll() { return TypedControlTemplateObject(true)->GetHorizontalScroll(); } GuiScroll* GuiScrollView::GetVerticalScroll() { return TypedControlTemplateObject(true)->GetVerticalScroll(); } bool GuiScrollView::GetHorizontalAlwaysVisible() { return horizontalAlwaysVisible; } void GuiScrollView::SetHorizontalAlwaysVisible(bool value) { if (horizontalAlwaysVisible != value) { horizontalAlwaysVisible = value; CalculateView(); } } bool GuiScrollView::GetVerticalAlwaysVisible() { return verticalAlwaysVisible; } void GuiScrollView::SetVerticalAlwaysVisible(bool value) { if (verticalAlwaysVisible != value) { verticalAlwaysVisible = value; CalculateView(); } } /*********************************************************************** GuiScrollContainer ***********************************************************************/ Size GuiScrollContainer::QueryFullSize() { return containerComposition->GetCachedBounds().GetSize(); } void GuiScrollContainer::UpdateView(Rect viewBounds) { auto leftTop = Point(-viewBounds.x1, -viewBounds.y1); containerComposition->SetExpectedBounds(Rect(leftTop, Size(0, 0))); } GuiScrollContainer::GuiScrollContainer(theme::ThemeName themeName) :GuiScrollView(themeName) { containerComposition->SetAlignmentToParent(Margin(-1, -1, -1, -1)); UpdateView(Rect(0, 0, 0, 0)); } GuiScrollContainer::~GuiScrollContainer() { } bool GuiScrollContainer::GetExtendToFullWidth() { return extendToFullWidth; } void GuiScrollContainer::SetExtendToFullWidth(bool value) { if (extendToFullWidth != value) { extendToFullWidth = value; auto margin = containerComposition->GetAlignmentToParent(); if (value) { containerComposition->SetAlignmentToParent(Margin(0, margin.top, 0, margin.bottom)); } else { containerComposition->SetAlignmentToParent(Margin(-1, margin.top, -1, margin.bottom)); } } } bool GuiScrollContainer::GetExtendToFullHeight() { return extendToFullHeight; } void GuiScrollContainer::SetExtendToFullHeight(bool value) { if (extendToFullHeight != value) { extendToFullHeight = value; auto margin = containerComposition->GetAlignmentToParent(); if (value) { containerComposition->SetAlignmentToParent(Margin(margin.left, 0, margin.right, 0)); } else { containerComposition->SetAlignmentToParent(Margin(margin.left, -1, margin.right, -1)); } } } } } } /*********************************************************************** .\CONTROLS\GUIDATETIMECONTROLS.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { using namespace collections; using namespace compositions; using namespace elements; /*********************************************************************** GuiDatePicker::CommandExecutor ***********************************************************************/ GuiDatePicker::CommandExecutor::CommandExecutor(GuiDatePicker* _datePicker) :datePicker(_datePicker) { } GuiDatePicker::CommandExecutor::~CommandExecutor() { } void GuiDatePicker::CommandExecutor::NotifyDateChanged() { datePicker->date = datePicker->TypedControlTemplateObject(true)->GetDate(); datePicker->UpdateText(); datePicker->DateChanged.Execute(datePicker->GetNotifyEventArguments()); } void GuiDatePicker::CommandExecutor::NotifyDateNavigated() { datePicker->DateNavigated.Execute(datePicker->GetNotifyEventArguments()); } void GuiDatePicker::CommandExecutor::NotifyDateSelected() { datePicker->DateSelected.Execute(datePicker->GetNotifyEventArguments()); } /*********************************************************************** GuiDatePicker ***********************************************************************/ void GuiDatePicker::BeforeControlTemplateUninstalled_() { auto ct = TypedControlTemplateObject(false); if (!ct) return; ct->SetCommands(nullptr); } void GuiDatePicker::AfterControlTemplateInstalled_(bool initialize) { auto ct = TypedControlTemplateObject(true); ct->SetCommands(commandExecutor.Obj()); ct->SetDate(date); ct->SetDateLocale(dateLocale); UpdateText(); } void GuiDatePicker::UpdateText() { GuiControl::SetText(dateLocale.FormatDate(dateFormat, date)); } bool GuiDatePicker::IsAltAvailable() { if (nestedAlt) { return alt != L""; } else { return GuiControl::IsAltAvailable(); } } compositions::IGuiAltActionHost* GuiDatePicker::GetActivatingAltHost() { if (nestedAlt) { return this; } else { return GuiControl::GetActivatingAltHost(); } } GuiDatePicker::GuiDatePicker(theme::ThemeName themeName, bool _nestedAlt) :GuiControl(themeName) , nestedAlt(_nestedAlt) { commandExecutor = Ptr(new CommandExecutor(this)); SetDate(DateTime::LocalTime()); SetDateLocale(Locale::UserDefault()); SetAltComposition(boundsComposition); SetAltControl(this, false); DateChanged.SetAssociatedComposition(boundsComposition); DateNavigated.SetAssociatedComposition(boundsComposition); DateSelected.SetAssociatedComposition(boundsComposition); DateFormatChanged.SetAssociatedComposition(boundsComposition); DateLocaleChanged.SetAssociatedComposition(boundsComposition); commandExecutor->NotifyDateChanged(); } GuiDatePicker::~GuiDatePicker() { } const DateTime& GuiDatePicker::GetDate() { return date; } void GuiDatePicker::SetDate(const DateTime& value) { if (date != value) { date = value; TypedControlTemplateObject(true)->SetDate(value); } } const WString& GuiDatePicker::GetDateFormat() { return dateFormat; } void GuiDatePicker::SetDateFormat(const WString& value) { dateFormat=value; UpdateText(); DateFormatChanged.Execute(GetNotifyEventArguments()); } const Locale& GuiDatePicker::GetDateLocale() { return dateLocale; } void GuiDatePicker::SetDateLocale(const Locale& value) { dateLocale=value; List formats; dateLocale.GetLongDateFormats(formats); if(formats.Count()>0) { dateFormat=formats[0]; } TypedControlTemplateObject(true)->SetDateLocale(dateLocale); UpdateText(); DateFormatChanged.Execute(GetNotifyEventArguments()); DateLocaleChanged.Execute(GetNotifyEventArguments()); } void GuiDatePicker::SetText(const WString& value) { } /*********************************************************************** GuiDateComboBox ***********************************************************************/ void GuiDateComboBox::BeforeControlTemplateUninstalled_() { } void GuiDateComboBox::AfterControlTemplateInstalled_(bool initialize) { auto ct = TypedControlTemplateObject(true); datePicker->SetControlTemplate(ct->GetDatePickerTemplate()); } void GuiDateComboBox::UpdateText() { SetText(datePicker->GetDateLocale().FormatDate(datePicker->GetDateFormat(), selectedDate)); } void GuiDateComboBox::NotifyUpdateSelectedDate() { UpdateText(); SelectedDateChanged.Execute(GetNotifyEventArguments()); } void GuiDateComboBox::OnSubMenuOpeningChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { datePicker->SetDate(selectedDate); } void GuiDateComboBox::datePicker_DateLocaleChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { UpdateText(); } void GuiDateComboBox::datePicker_DateFormatChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { UpdateText(); } void GuiDateComboBox::datePicker_DateSelected(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { selectedDate=datePicker->GetDate(); GetSubMenu()->Hide(); NotifyUpdateSelectedDate(); } GuiDateComboBox::GuiDateComboBox(theme::ThemeName themeName) :GuiComboBoxBase(themeName) { SelectedDateChanged.SetAssociatedComposition(GetBoundsComposition()); datePicker = new GuiDatePicker(theme::ThemeName::DatePicker, false); datePicker->DateSelected.AttachMethod(this, &GuiDateComboBox::datePicker_DateSelected); datePicker->DateLocaleChanged.AttachMethod(this, &GuiDateComboBox::datePicker_DateLocaleChanged); datePicker->DateFormatChanged.AttachMethod(this, &GuiDateComboBox::datePicker_DateFormatChanged); datePicker->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); GetSubMenu()->GetContainerComposition()->AddChild(datePicker->GetBoundsComposition()); GetSubMenu()->SetHideOnDeactivateAltHost(false); selectedDate=datePicker->GetDate(); SubMenuOpeningChanged.AttachMethod(this, &GuiDateComboBox::OnSubMenuOpeningChanged); SetFont(GetFont()); SetText(datePicker->GetText()); } GuiDateComboBox::~GuiDateComboBox() { } void GuiDateComboBox::SetFont(const Nullable& value) { GuiComboBoxBase::SetFont(value); datePicker->SetFont(value); } const DateTime& GuiDateComboBox::GetSelectedDate() { return selectedDate; } void GuiDateComboBox::SetSelectedDate(const DateTime& value) { selectedDate=value; datePicker->SetDate(selectedDate); NotifyUpdateSelectedDate(); } GuiDatePicker* GuiDateComboBox::GetDatePicker() { return datePicker; } } } } /*********************************************************************** .\CONTROLS\GUIDIALOGS.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { using namespace elements; using namespace compositions; using namespace collections; using namespace reflection::description; /*********************************************************************** GuiDialogBase ***********************************************************************/ GuiWindow* GuiDialogBase::GetHostWindow() { if (rootObject) { if (auto control = dynamic_cast(rootObject)) { if (auto host = control->GetRelatedControlHost()) { return dynamic_cast(host); } } else if (auto composition = dynamic_cast(rootObject)) { if (auto host = composition->GetRelatedControlHost()) { return dynamic_cast(host); } } } return nullptr; } GuiDialogBase::GuiDialogBase() { } GuiDialogBase::~GuiDialogBase() { } void GuiDialogBase::Attach(GuiInstanceRootObject* _rootObject) { rootObject = _rootObject; } void GuiDialogBase::Detach(GuiInstanceRootObject* _rootObject) { rootObject = nullptr; } /*********************************************************************** GuiMessageDialog ***********************************************************************/ GuiMessageDialog::GuiMessageDialog() { } GuiMessageDialog::~GuiMessageDialog() { } INativeDialogService::MessageBoxButtonsInput GuiMessageDialog::GetInput() { return input; } void GuiMessageDialog::SetInput(INativeDialogService::MessageBoxButtonsInput value) { input = value; } INativeDialogService::MessageBoxDefaultButton GuiMessageDialog::GetDefaultButton() { return defaultButton; } void GuiMessageDialog::SetDefaultButton(INativeDialogService::MessageBoxDefaultButton value) { defaultButton = value; } INativeDialogService::MessageBoxIcons GuiMessageDialog::GetIcon() { return icon; } void GuiMessageDialog::SetIcon(INativeDialogService::MessageBoxIcons value) { icon = value; } INativeDialogService::MessageBoxModalOptions GuiMessageDialog::GetModalOption() { return modalOption; } void GuiMessageDialog::SetModalOption(INativeDialogService::MessageBoxModalOptions value) { modalOption = value; } const WString& GuiMessageDialog::GetText() { return text; } void GuiMessageDialog::SetText(const WString& value) { text = value; } const WString& GuiMessageDialog::GetTitle() { return title; } void GuiMessageDialog::SetTitle(const WString& value) { title = value; } INativeDialogService::MessageBoxButtonsOutput GuiMessageDialog::ShowDialog() { auto service = GetCurrentController()->DialogService(); return service->ShowMessageBox(GetHostWindow()->GetNativeWindow(), text, title, input, defaultButton, icon, modalOption); } /*********************************************************************** GuiColorDialog ***********************************************************************/ GuiColorDialog::GuiColorDialog() { for (vint i = 0; i < 16; i++) { customColors.Add(Color()); } } GuiColorDialog::~GuiColorDialog() { } bool GuiColorDialog::GetEnabledCustomColor() { return enabledCustomColor; } void GuiColorDialog::SetEnabledCustomColor(bool value) { enabledCustomColor = value; } bool GuiColorDialog::GetOpenedCustomColor() { return openedCustomColor; } void GuiColorDialog::SetOpenedCustomColor(bool value) { openedCustomColor = value; } Color GuiColorDialog::GetSelectedColor() { return selectedColor; } void GuiColorDialog::SetSelectedColor(Color value) { if (selectedColor != value) { selectedColor = value; SelectedColorChanged.Execute(GuiEventArgs()); } } collections::List& GuiColorDialog::GetCustomColors() { return customColors; } bool GuiColorDialog::ShowDialog() { Array colors; CopyFrom(colors, customColors); colors.Resize(16); INativeDialogService::ColorDialogCustomColorOptions options = !enabledCustomColor ? INativeDialogService::CustomColorDisabled : !openedCustomColor ? INativeDialogService::CustomColorEnabled : INativeDialogService::CustomColorOpened; auto service = GetCurrentController()->DialogService(); if (!service->ShowColorDialog(GetHostWindow()->GetNativeWindow(), selectedColor, showSelection, options, &colors[0])) { return false; } CopyFrom(customColors, colors); SelectedColorChanged.Execute(GuiEventArgs()); return true; } /*********************************************************************** GuiFontDialog ***********************************************************************/ GuiFontDialog::GuiFontDialog() { } GuiFontDialog::~GuiFontDialog() { } const FontProperties& GuiFontDialog::GetSelectedFont() { return selectedFont; } void GuiFontDialog::SetSelectedFont(const FontProperties& value) { if (selectedFont != value) { selectedFont = value; SelectedFontChanged.Execute(GuiEventArgs()); } } Color GuiFontDialog::GetSelectedColor() { return selectedColor; } void GuiFontDialog::SetSelectedColor(Color value) { if (selectedColor != value) { selectedColor = value; SelectedColorChanged.Execute(GuiEventArgs()); } } bool GuiFontDialog::GetShowSelection() { return showSelection; } void GuiFontDialog::SetShowSelection(bool value) { showSelection = value; } bool GuiFontDialog::GetShowEffect() { return showEffect; } void GuiFontDialog::SetShowEffect(bool value) { showEffect = value; } bool GuiFontDialog::GetForceFontExist() { return forceFontExist; } void GuiFontDialog::SetForceFontExist(bool value) { forceFontExist = value; } bool GuiFontDialog::ShowDialog() { auto service = GetCurrentController()->DialogService(); if (!service->ShowFontDialog(GetHostWindow()->GetNativeWindow(), selectedFont, selectedColor, showSelection, showEffect, forceFontExist)) { return false; } SelectedColorChanged.Execute(GuiEventArgs()); SelectedFontChanged.Execute(GuiEventArgs()); return true; } /*********************************************************************** GuiFileDialogBase ***********************************************************************/ GuiFileDialogBase::GuiFileDialogBase() { } GuiFileDialogBase::~GuiFileDialogBase() { } const WString& GuiFileDialogBase::GetFilter() { return filter; } void GuiFileDialogBase::SetFilter(const WString& value) { filter = value; } vint GuiFileDialogBase::GetFilterIndex() { return filterIndex; } void GuiFileDialogBase::SetFilterIndex(vint value) { if (filterIndex != value) { filterIndex = value; FilterIndexChanged.Execute(GuiEventArgs()); } } bool GuiFileDialogBase::GetEnabledPreview() { return enabledPreview; } void GuiFileDialogBase::SetEnabledPreview(bool value) { enabledPreview = value; } WString GuiFileDialogBase::GetTitle() { return title; } void GuiFileDialogBase::SetTitle(const WString& value) { title = value; } WString GuiFileDialogBase::GetFileName() { return fileName; } void GuiFileDialogBase::SetFileName(const WString& value) { if (fileName != value) { FileNameChanged.Execute(GuiEventArgs()); } } WString GuiFileDialogBase::GetDirectory() { return directory; } void GuiFileDialogBase::SetDirectory(const WString& value) { directory = value; } WString GuiFileDialogBase::GetDefaultExtension() { return defaultExtension; } void GuiFileDialogBase::SetDefaultExtension(const WString& value) { defaultExtension = value; } INativeDialogService::FileDialogOptions GuiFileDialogBase::GetOptions() { return options; } void GuiFileDialogBase::SetOptions(INativeDialogService::FileDialogOptions value) { options = value; } /*********************************************************************** GuiOpenFileDialog ***********************************************************************/ GuiOpenFileDialog::GuiOpenFileDialog() { } GuiOpenFileDialog::~GuiOpenFileDialog() { } collections::List& GuiOpenFileDialog::GetFileNames() { return fileNames; } bool GuiOpenFileDialog::ShowDialog() { fileNames.Clear(); auto service = GetCurrentController()->DialogService(); if (!service->ShowFileDialog( GetHostWindow()->GetNativeWindow(), fileNames, filterIndex, (enabledPreview ? INativeDialogService::FileDialogOpenPreview : INativeDialogService::FileDialogOpen), title, fileName, directory, defaultExtension, filter, options)) { return false; } if (fileNames.Count() > 0) { fileName = fileNames[0]; FileNameChanged.Execute(GuiEventArgs()); FilterIndexChanged.Execute(GuiEventArgs()); } return true; } /*********************************************************************** GuiSaveFileDialog ***********************************************************************/ GuiSaveFileDialog::GuiSaveFileDialog() { } GuiSaveFileDialog::~GuiSaveFileDialog() { } bool GuiSaveFileDialog::ShowDialog() { List fileNames; auto service = GetCurrentController()->DialogService(); if (!service->ShowFileDialog( GetHostWindow()->GetNativeWindow(), fileNames, filterIndex, (enabledPreview ? INativeDialogService::FileDialogSavePreview : INativeDialogService::FileDialogSave), title, fileName, directory, defaultExtension, filter, options)) { return false; } if (fileNames.Count() > 0) { fileName = fileNames[0]; FileNameChanged.Execute(GuiEventArgs()); FilterIndexChanged.Execute(GuiEventArgs()); } return true; } } } } /*********************************************************************** .\CONTROLS\GUISCROLLCONTROLS.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { using namespace elements; using namespace compositions; using namespace collections; using namespace reflection::description; /*********************************************************************** GuiScroll::CommandExecutor ***********************************************************************/ GuiScroll::CommandExecutor::CommandExecutor(GuiScroll* _scroll) :scroll(_scroll) { } GuiScroll::CommandExecutor::~CommandExecutor() { } void GuiScroll::CommandExecutor::SmallDecrease() { scroll->SetPosition(scroll->GetPosition()-scroll->GetSmallMove()); } void GuiScroll::CommandExecutor::SmallIncrease() { scroll->SetPosition(scroll->GetPosition()+scroll->GetSmallMove()); } void GuiScroll::CommandExecutor::BigDecrease() { scroll->SetPosition(scroll->GetPosition()-scroll->GetBigMove()); } void GuiScroll::CommandExecutor::BigIncrease() { scroll->SetPosition(scroll->GetPosition()+scroll->GetBigMove()); } void GuiScroll::CommandExecutor::SetTotalSize(vint value) { scroll->SetTotalSize(value); } void GuiScroll::CommandExecutor::SetPageSize(vint value) { scroll->SetPageSize(value); } void GuiScroll::CommandExecutor::SetPosition(vint value) { scroll->SetPosition(value); } /*********************************************************************** GuiScroll ***********************************************************************/ void GuiScroll::OnActiveAlt() { if (autoFocus) { GuiControl::OnActiveAlt(); } } bool GuiScroll::IsTabAvailable() { return autoFocus && GuiControl::IsTabAvailable(); } void GuiScroll::OnKeyDown(compositions::GuiGraphicsComposition* sender, compositions::GuiKeyEventArgs& arguments) { if (arguments.eventSource == focusableComposition) { switch (arguments.code) { case VKEY::KEY_HOME: SetPosition(GetMinPosition()); arguments.handled = true; break; case VKEY::KEY_END: SetPosition(GetMaxPosition()); arguments.handled = true; break; case VKEY::KEY_PRIOR: commandExecutor->BigDecrease(); arguments.handled = true; break; case VKEY::KEY_NEXT: commandExecutor->BigIncrease(); arguments.handled = true; break; case VKEY::KEY_LEFT: case VKEY::KEY_UP: commandExecutor->SmallDecrease(); arguments.handled = true; break; case VKEY::KEY_RIGHT: case VKEY::KEY_DOWN: commandExecutor->SmallIncrease(); arguments.handled = true; break; default:; } } } void GuiScroll::OnMouseDown(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) { if (autoFocus) { SetFocused(); } } void GuiScroll::BeforeControlTemplateUninstalled_() { auto ct = TypedControlTemplateObject(false); if (!ct) return; ct->SetCommands(nullptr); } void GuiScroll::AfterControlTemplateInstalled_(bool initialize) { auto ct = TypedControlTemplateObject(true); ct->SetCommands(commandExecutor.Obj()); ct->SetPageSize(pageSize); ct->SetTotalSize(totalSize); ct->SetPosition(position); } GuiScroll::GuiScroll(theme::ThemeName themeName) :GuiControl(themeName) { if (themeName == theme::ThemeName::ProgressBar) { autoFocus = false; } else { SetFocusableComposition(boundsComposition); } TotalSizeChanged.SetAssociatedComposition(boundsComposition); PageSizeChanged.SetAssociatedComposition(boundsComposition); PositionChanged.SetAssociatedComposition(boundsComposition); SmallMoveChanged.SetAssociatedComposition(boundsComposition); BigMoveChanged.SetAssociatedComposition(boundsComposition); commandExecutor = Ptr(new CommandExecutor(this)); boundsComposition->GetEventReceiver()->keyDown.AttachMethod(this, &GuiScroll::OnKeyDown); boundsComposition->GetEventReceiver()->leftButtonDown.AttachMethod(this, &GuiScroll::OnMouseDown); boundsComposition->GetEventReceiver()->rightButtonDown.AttachMethod(this, &GuiScroll::OnMouseDown); } GuiScroll::~GuiScroll() { } vint GuiScroll::GetTotalSize() { return totalSize; } void GuiScroll::SetTotalSize(vint value) { if(totalSize!=value && 0totalSize) { SetPageSize(totalSize); } if(position>GetMaxPosition()) { SetPosition(GetMaxPosition()); } TypedControlTemplateObject(true)->SetTotalSize(totalSize); TotalSizeChanged.Execute(GetNotifyEventArguments()); } } vint GuiScroll::GetPageSize() { return pageSize; } void GuiScroll::SetPageSize(vint value) { if(pageSize!=value && 0<=value && value<=totalSize) { pageSize=value; if(position>GetMaxPosition()) { SetPosition(GetMaxPosition()); } TypedControlTemplateObject(true)->SetPageSize(pageSize); PageSizeChanged.Execute(GetNotifyEventArguments()); } } vint GuiScroll::GetPosition() { return position; } void GuiScroll::SetPosition(vint value) { vint min=GetMinPosition(); vint max=GetMaxPosition(); vint newPosition= valuemax?max: value; if(position!=newPosition) { position=newPosition; TypedControlTemplateObject(true)->SetPosition(position); PositionChanged.Execute(GetNotifyEventArguments()); } } vint GuiScroll::GetSmallMove() { return smallMove; } void GuiScroll::SetSmallMove(vint value) { if(value>0 && smallMove!=value) { smallMove=value; SmallMoveChanged.Execute(GetNotifyEventArguments()); } } vint GuiScroll::GetBigMove() { return bigMove; } void GuiScroll::SetBigMove(vint value) { if(value>0 && bigMove!=value) { bigMove=value; BigMoveChanged.Execute(GetNotifyEventArguments()); } } vint GuiScroll::GetMinPosition() { return 0; } vint GuiScroll::GetMaxPosition() { return totalSize-pageSize; } bool GuiScroll::GetAutoFocus() { return autoFocus; } void GuiScroll::SetAutoFocus(bool value) { autoFocus = value; } } } } /*********************************************************************** .\CONTROLS\LISTCONTROLPACKAGE\DATASOURCEIMPL_IITEMPROVIDER_ITEMPROVIDERBASE.CPP ***********************************************************************/ namespace vl::presentation::controls::list { /*********************************************************************** ItemProviderBase ***********************************************************************/ void ItemProviderBase::InvokeOnItemModified(vint start, vint count, vint newCount, bool itemReferenceUpdated) { CHECK_ERROR(!callingOnItemModified, L"ItemProviderBase::InvokeOnItemModified(vint, vint, vint)#Canning modify the observable data source during its item modified event, which will cause this event to be executed recursively."); callingOnItemModified = true; // TODO: (enumerable) foreach for (vint i = 0; i < callbacks.Count(); i++) { callbacks[i]->OnItemModified(start, count, newCount, itemReferenceUpdated); } callingOnItemModified = false; } ItemProviderBase::ItemProviderBase() { } ItemProviderBase::~ItemProviderBase() { // TODO: (enumerable) foreach for(vint i=0;iOnAttached(0); } } bool ItemProviderBase::AttachCallback(IItemProviderCallback* value) { if(callbacks.Contains(value)) { return false; } else { callbacks.Add(value); value->OnAttached(this); return true; } } bool ItemProviderBase::DetachCallback(IItemProviderCallback* value) { vint index=callbacks.IndexOf(value); if(index==-1) { return false; } else { value->OnAttached(0); callbacks.Remove(value); return true; } } void ItemProviderBase::PushEditing() { editingCounter++; } bool ItemProviderBase::PopEditing() { if (editingCounter == 0)return false; editingCounter--; return true; } bool ItemProviderBase::IsEditing() { return editingCounter > 0; } } /*********************************************************************** .\CONTROLS\LISTCONTROLPACKAGE\DATASOURCEIMPL_IITEMPROVIDER_NODEITEMPROVIDER.CPP ***********************************************************************/ namespace vl::presentation::controls::tree { using namespace reflection::description; const wchar_t* const INodeItemView::Identifier = L"vl::presentation::controls::tree::INodeItemView"; /*********************************************************************** NodeItemProvider ***********************************************************************/ Ptr NodeItemProvider::GetNodeByOffset(Ptr provider, vint offset) { if (offset == 0) return provider; Ptr result; if (provider->GetExpanding() && offset > 0) { offset -= 1; vint count = provider->GetChildCount(); for (vint i = 0; (!result && i < count); i++) { auto child = provider->GetChild(i); vint visibleCount = child->CalculateTotalVisibleNodes(); if (offset < visibleCount) { result = GetNodeByOffset(child, offset); } else { offset -= visibleCount; } } } return result; } void NodeItemProvider::OnAttached(INodeRootProvider* provider) { } void NodeItemProvider::OnBeforeItemModified(INodeProvider* parentNode, vint start, vint count, vint newCount, bool itemReferenceUpdated) { vint offset = 0; vint base = CalculateNodeVisibilityIndexInternal(parentNode); if (base != -2 && parentNode->GetExpanding()) { for (vint i = 0; i < count; i++) { auto child = parentNode->GetChild(start + i); offset += child->CalculateTotalVisibleNodes(); } } offsetBeforeChildModifieds.Set(parentNode, offset); } void NodeItemProvider::OnAfterItemModified(INodeProvider* parentNode, vint start, vint count, vint newCount, bool itemReferenceUpdated) { vint offsetBeforeChildModified = 0; { vint index = offsetBeforeChildModifieds.Keys().IndexOf(parentNode); if (index != -1) { offsetBeforeChildModified = offsetBeforeChildModifieds.Values().Get(index); offsetBeforeChildModifieds.Remove(parentNode); } } vint base = CalculateNodeVisibilityIndexInternal(parentNode); if (base != -2 && parentNode->GetExpanding()) { vint offset = 0; vint firstChildStart = -1; for (vint i = 0; i < newCount; i++) { auto child = parentNode->GetChild(start + i); if (i == 0) { firstChildStart = CalculateNodeVisibilityIndexInternal(child.Obj()); } offset += child->CalculateTotalVisibleNodes(); } if (firstChildStart == -1) { vint childCount = parentNode->GetChildCount(); if (childCount == 0) { firstChildStart = base + 1; } else if (start < childCount) { auto child = parentNode->GetChild(start); firstChildStart = CalculateNodeVisibilityIndexInternal(child.Obj()); } else { auto child = parentNode->GetChild(start - 1); firstChildStart = CalculateNodeVisibilityIndexInternal(child.Obj()); firstChildStart += child->CalculateTotalVisibleNodes(); } } InvokeOnItemModified(firstChildStart, offsetBeforeChildModified, offset, itemReferenceUpdated); } } void NodeItemProvider::OnItemExpanded(INodeProvider* node) { vint base = CalculateNodeVisibilityIndexInternal(node); if (base != -2) { vint visibility = node->CalculateTotalVisibleNodes(); InvokeOnItemModified(base + 1, 0, visibility - 1, true); } } void NodeItemProvider::OnItemCollapsed(INodeProvider* node) { vint base = CalculateNodeVisibilityIndexInternal(node); if (base != -2) { vint visibility = 0; vint count = node->GetChildCount(); for (vint i = 0; i < count; i++) { auto child = node->GetChild(i); visibility += child->CalculateTotalVisibleNodes(); } InvokeOnItemModified(base + 1, visibility, 0, true); } } vint NodeItemProvider::CalculateNodeVisibilityIndexInternal(INodeProvider* node) { auto parent = node->GetParent(); if (!parent) { return -1; } if (!parent->GetExpanding()) { return -2; } vint index = CalculateNodeVisibilityIndexInternal(parent.Obj()); if (index == -2) { return -2; } vint count = parent->GetChildCount(); for (vint i = 0; i < count; i++) { auto child = parent->GetChild(i); bool findResult = child == node; if (findResult) { index++; } else { index += child->CalculateTotalVisibleNodes(); } if (findResult) { return index; } } return -1; } vint NodeItemProvider::CalculateNodeVisibilityIndex(INodeProvider* node) { vint result = CalculateNodeVisibilityIndexInternal(node); return result < 0 ? -1 : result; } Ptr NodeItemProvider::RequestNode(vint index) { if(root->CanGetNodeByVisibleIndex()) { return root->GetNodeByVisibleIndex(index+1); } else { return GetNodeByOffset(root->GetRootNode(), index+1); } } NodeItemProvider::NodeItemProvider(Ptr _root) :root(_root) { root->AttachCallback(this); } NodeItemProvider::~NodeItemProvider() { root->DetachCallback(this); } Ptr NodeItemProvider::GetRoot() { return root; } vint NodeItemProvider::Count() { return root->GetRootNode()->CalculateTotalVisibleNodes()-1; } WString NodeItemProvider::GetTextValue(vint itemIndex) { if (auto node = RequestNode(itemIndex)) { return root->GetTextValue(node.Obj()); } return L""; } description::Value NodeItemProvider::GetBindingValue(vint itemIndex) { if (auto node = RequestNode(itemIndex)) { return root->GetBindingValue(node.Obj()); } return Value(); } IDescriptable* NodeItemProvider::RequestView(const WString& identifier) { if(identifier==INodeItemView::Identifier) { return (INodeItemView*)this; } else { return root->RequestView(identifier); } } } /*********************************************************************** .\CONTROLS\LISTCONTROLPACKAGE\DATASOURCEIMPL_INODEPROVIDER_MEMORYNODEPROVIDER.CPP ***********************************************************************/ namespace vl::presentation::controls::tree { /*********************************************************************** MemoryNodeProvider::NodeCollection ***********************************************************************/ void MemoryNodeProvider::NodeCollection::OnBeforeChildModified(vint start, vint count, vint newCount) { if (ownerProvider->expanding) { for (vint i = 0; i < count; i++) { offsetBeforeChildModified += items[start + i]->totalVisibleNodeCount; } } INodeProviderCallback* proxy = ownerProvider->GetCallbackProxyInternal(); if (proxy) { proxy->OnBeforeItemModified(ownerProvider, start, count, newCount, true); } } void MemoryNodeProvider::NodeCollection::OnAfterChildModified(vint start, vint count, vint newCount) { ownerProvider->childCount += (newCount - count); if (ownerProvider->expanding) { vint offset = 0; for (vint i = 0; i < newCount; i++) { offset += items[start + i]->totalVisibleNodeCount; } ownerProvider->OnChildTotalVisibleNodesChanged(offset - offsetBeforeChildModified); } offsetBeforeChildModified = 0; INodeProviderCallback* proxy = ownerProvider->GetCallbackProxyInternal(); if (proxy) { proxy->OnAfterItemModified(ownerProvider, start, count, newCount, true); } } bool MemoryNodeProvider::NodeCollection::QueryInsert(vint index, Ptr const& child) { return child->parent == 0; } bool MemoryNodeProvider::NodeCollection::QueryRemove(vint index, Ptr const& child) { return child->parent == ownerProvider; } void MemoryNodeProvider::NodeCollection::BeforeInsert(vint index, Ptr const& child) { OnBeforeChildModified(index, 0, 1); child->parent = ownerProvider; } void MemoryNodeProvider::NodeCollection::BeforeRemove(vint index, Ptr const& child) { OnBeforeChildModified(index, 1, 0); child->parent = 0; } void MemoryNodeProvider::NodeCollection::AfterInsert(vint index, Ptr const& child) { OnAfterChildModified(index, 0, 1); } void MemoryNodeProvider::NodeCollection::AfterRemove(vint index, vint count) { OnAfterChildModified(index, count, 0); } MemoryNodeProvider::NodeCollection::NodeCollection() :ownerProvider(0) { } /*********************************************************************** MemoryNodeProvider ***********************************************************************/ INodeProviderCallback* MemoryNodeProvider::GetCallbackProxyInternal() { if(parent) { return parent->GetCallbackProxyInternal(); } else { return 0; } } void MemoryNodeProvider::OnChildTotalVisibleNodesChanged(vint offset) { totalVisibleNodeCount+=offset; if(parent) { parent->OnChildTotalVisibleNodesChanged(offset); } } MemoryNodeProvider::MemoryNodeProvider(Ptr _data) :data(_data) { children.ownerProvider=this; } MemoryNodeProvider::~MemoryNodeProvider() { } Ptr MemoryNodeProvider::GetData() { return data; } void MemoryNodeProvider::SetData(const Ptr& value) { data=value; NotifyDataModified(); } MemoryNodeProvider::NodeCollection& MemoryNodeProvider::Children() { return children; } bool MemoryNodeProvider::GetExpanding() { return expanding; } void MemoryNodeProvider::SetExpanding(bool value) { if(expanding!=value) { expanding=value; vint offset=0; for(vint i=0;itotalVisibleNodeCount; } OnChildTotalVisibleNodesChanged(expanding?offset:-offset); INodeProviderCallback* proxy=GetCallbackProxyInternal(); if(proxy) { if(expanding) { proxy->OnItemExpanded(this); } else { proxy->OnItemCollapsed(this); } } } } vint MemoryNodeProvider::CalculateTotalVisibleNodes() { return totalVisibleNodeCount; } void MemoryNodeProvider::NotifyDataModified() { if (parent) { vint index = parent->children.IndexOf(this); INodeProviderCallback* proxy = GetCallbackProxyInternal(); if (proxy) { proxy->OnBeforeItemModified(parent, index, 1, 1, false); proxy->OnAfterItemModified(parent, index, 1, 1, false); } } } vint MemoryNodeProvider::GetChildCount() { return childCount; } Ptr MemoryNodeProvider::GetParent() { return Ptr(parent); } Ptr MemoryNodeProvider::GetChild(vint index) { if (0 <= index && index < childCount) { return children[index]; } else { return nullptr; } } /*********************************************************************** NodeRootProviderBase ***********************************************************************/ void NodeRootProviderBase::OnAttached(INodeRootProvider* provider) { } void NodeRootProviderBase::OnBeforeItemModified(INodeProvider* parentNode, vint start, vint count, vint newCount, bool itemReferenceUpdated) { // TODO: (enumerable) foreach for(vint i=0;iOnBeforeItemModified(parentNode, start, count, newCount, itemReferenceUpdated); } } void NodeRootProviderBase::OnAfterItemModified(INodeProvider* parentNode, vint start, vint count, vint newCount, bool itemReferenceUpdated) { // TODO: (enumerable) foreach for(vint i=0;iOnAfterItemModified(parentNode, start, count, newCount, itemReferenceUpdated); } } void NodeRootProviderBase::OnItemExpanded(INodeProvider* node) { // TODO: (enumerable) foreach for(vint i=0;iOnItemExpanded(node); } } void NodeRootProviderBase::OnItemCollapsed(INodeProvider* node) { // TODO: (enumerable) foreach for(vint i=0;iOnItemCollapsed(node); } } NodeRootProviderBase::NodeRootProviderBase() { } NodeRootProviderBase::~NodeRootProviderBase() { } bool NodeRootProviderBase::CanGetNodeByVisibleIndex() { return false; } Ptr NodeRootProviderBase::GetNodeByVisibleIndex(vint index) { return nullptr; } bool NodeRootProviderBase::AttachCallback(INodeProviderCallback* value) { if(callbacks.Contains(value)) { return false; } else { callbacks.Add(value); value->OnAttached(this); return true; } } bool NodeRootProviderBase::DetachCallback(INodeProviderCallback* value) { vint index=callbacks.IndexOf(value); if(index==-1) { return false; } else { value->OnAttached(0); callbacks.Remove(value); return true; } } IDescriptable* NodeRootProviderBase::RequestView(const WString& identifier) { return 0; } /*********************************************************************** MemoryNodeRootProvider ***********************************************************************/ INodeProviderCallback* MemoryNodeRootProvider::GetCallbackProxyInternal() { return this; } MemoryNodeRootProvider::MemoryNodeRootProvider() { SetExpanding(true); } MemoryNodeRootProvider::~MemoryNodeRootProvider() { } Ptr MemoryNodeRootProvider::GetRootNode() { return Ptr(this); } MemoryNodeProvider* MemoryNodeRootProvider::GetMemoryNode(INodeProvider* node) { return dynamic_cast(node); } } /*********************************************************************** .\CONTROLS\LISTCONTROLPACKAGE\GUIBINDABLEDATAGRID.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { using namespace collections; using namespace description; using namespace templates; namespace list { /*********************************************************************** DataFilterBase ***********************************************************************/ void DataFilterBase::InvokeOnProcessorChanged() { if (callback) { callback->OnProcessorChanged(); } } DataFilterBase::DataFilterBase() { } void DataFilterBase::SetCallback(IDataProcessorCallback* value) { callback = value; } /*********************************************************************** DataMultipleFilter ***********************************************************************/ DataMultipleFilter::DataMultipleFilter() { } bool DataMultipleFilter::AddSubFilter(Ptr value) { if (!value) return false; if (filters.Contains(value.Obj())) return false; filters.Add(value); value->SetCallback(callback); InvokeOnProcessorChanged(); return true; } bool DataMultipleFilter::RemoveSubFilter(Ptr value) { if (!value) return false; if (!filters.Contains(value.Obj())) return false; value->SetCallback(nullptr); filters.Remove(value.Obj()); InvokeOnProcessorChanged(); return true; } void DataMultipleFilter::SetCallback(IDataProcessorCallback* value) { DataFilterBase::SetCallback(value); // TODO: (enumerable) foreach for (vint i = 0; i < filters.Count(); i++) { filters[i]->SetCallback(value); } } /*********************************************************************** DataAndFilter ***********************************************************************/ DataAndFilter::DataAndFilter() { } bool DataAndFilter::Filter(const description::Value& row) { return From(filters) .All([row](Ptr filter) { return filter->Filter(row); }); } /*********************************************************************** DataOrFilter ***********************************************************************/ DataOrFilter::DataOrFilter() { } bool DataOrFilter::Filter(const description::Value& row) { return From(filters) .Any([row](Ptr filter) { return filter->Filter(row); }); } /*********************************************************************** DataNotFilter ***********************************************************************/ DataNotFilter::DataNotFilter() { } bool DataNotFilter::SetSubFilter(Ptr value) { if (filter == value) return false; if (filter) filter->SetCallback(nullptr); filter = value; if (filter) filter->SetCallback(callback); InvokeOnProcessorChanged(); return true; } void DataNotFilter::SetCallback(IDataProcessorCallback* value) { DataFilterBase::SetCallback(value); if (filter) filter->SetCallback(value); } bool DataNotFilter::Filter(const description::Value& row) { return filter ? true : !filter->Filter(row); } /*********************************************************************** DataSorterBase ***********************************************************************/ void DataSorterBase::InvokeOnProcessorChanged() { if (callback) { callback->OnProcessorChanged(); } } DataSorterBase::DataSorterBase() { } void DataSorterBase::SetCallback(IDataProcessorCallback* value) { callback = value; } /*********************************************************************** DataMultipleSorter ***********************************************************************/ DataMultipleSorter::DataMultipleSorter() { } bool DataMultipleSorter::SetLeftSorter(Ptr value) { if (leftSorter == value) return false; if (leftSorter) leftSorter->SetCallback(nullptr); leftSorter = value; if (leftSorter) leftSorter->SetCallback(callback); return true; } bool DataMultipleSorter::SetRightSorter(Ptr value) { if (rightSorter == value) return false; if (rightSorter) rightSorter->SetCallback(nullptr); rightSorter = value; if (rightSorter) rightSorter->SetCallback(callback); return true; } void DataMultipleSorter::SetCallback(IDataProcessorCallback* value) { DataSorterBase::SetCallback(value); if (leftSorter) leftSorter->SetCallback(value); if (rightSorter) rightSorter->SetCallback(value); } vint DataMultipleSorter::Compare(const description::Value& row1, const description::Value& row2) { if (leftSorter) { vint result = leftSorter->Compare(row1, row2); if (result != 0) return result; } if (rightSorter) { vint result = rightSorter->Compare(row1, row2); if (result != 0) return result; } return 0; } /*********************************************************************** DataReverseSorter ***********************************************************************/ DataReverseSorter::DataReverseSorter() { } bool DataReverseSorter::SetSubSorter(Ptr value) { if (sorter == value) return false; if (sorter) sorter->SetCallback(nullptr); sorter = value; if (sorter) sorter->SetCallback(callback); return true; } void DataReverseSorter::SetCallback(IDataProcessorCallback* value) { DataSorterBase::SetCallback(value); if (sorter) sorter->SetCallback(value); } vint DataReverseSorter::Compare(const description::Value& row1, const description::Value& row2) { return sorter ? -sorter->Compare(row1, row2) : 0; } /*********************************************************************** DataColumn ***********************************************************************/ void DataColumn::NotifyRebuilt() { if (dataProvider) { vint index = dataProvider->columns.IndexOf(this); if (index != -1) { dataProvider->columns.NotifyColumnRebuilt(index); } } } void DataColumn::NotifyChanged(bool needToRefreshItems) { if (dataProvider) { vint index = dataProvider->columns.IndexOf(this); if (index != -1) { dataProvider->columns.NotifyColumnChanged(index, needToRefreshItems); } } } DataColumn::DataColumn() { } DataColumn::~DataColumn() { if (popup && ownPopup) { SafeDeleteControl(popup); } } WString DataColumn::GetText() { return text; } void DataColumn::SetText(const WString& value) { if (text != value) { text = value; NotifyChanged(false); } } vint DataColumn::GetSize() { return size; } void DataColumn::SetSize(vint value) { if (size != value) { size = value; NotifyChanged(true); } } bool DataColumn::GetOwnPopup() { return ownPopup; } void DataColumn::SetOwnPopup(bool value) { ownPopup = value; } GuiMenu* DataColumn::GetPopup() { return popup; } void DataColumn::SetPopup(GuiMenu* value) { if (popup != value) { popup = value; NotifyChanged(false); } } Ptr DataColumn::GetFilter() { return associatedFilter; } void DataColumn::SetFilter(Ptr value) { if (associatedFilter != value) { if (associatedFilter) associatedFilter->SetCallback(nullptr); associatedFilter = value; if (associatedFilter) associatedFilter->SetCallback(dataProvider); if (dataProvider) { vint index = dataProvider->columns.IndexOf(this); if (index != -1) { dataProvider->OnProcessorChanged(); return; } } NotifyChanged(false); } } Ptr DataColumn::GetSorter() { return associatedSorter; } void DataColumn::SetSorter(Ptr value) { if (associatedSorter != value) { if (associatedSorter) associatedSorter->SetCallback(nullptr); associatedSorter = value; if (associatedSorter) associatedSorter->SetCallback(dataProvider); if (dataProvider) { vint index = dataProvider->columns.IndexOf(this); if (index == dataProvider->GetSortedColumn()) { dataProvider->SortByColumn(index, sortingState == ColumnSortingState::Ascending); return; } } NotifyChanged(false); } } Ptr DataColumn::GetVisualizerFactory() { return visualizerFactory; } void DataColumn::SetVisualizerFactory(Ptr value) { visualizerFactory = value; NotifyRebuilt(); } Ptr DataColumn::GetEditorFactory() { return editorFactory; } void DataColumn::SetEditorFactory(Ptr value) { editorFactory = value; NotifyRebuilt(); } WString DataColumn::GetCellText(vint row) { if (0 <= row && row < dataProvider->Count()) { return ReadProperty(dataProvider->GetBindingValue(row), textProperty); } return L""; } description::Value DataColumn::GetCellValue(vint row) { if (0 <= row && row < dataProvider->Count()) { return ReadProperty(dataProvider->GetBindingValue(row), valueProperty); } return Value(); } void DataColumn::SetCellValue(vint row, description::Value value) { if (0 <= row && row < dataProvider->Count()) { auto rowValue = dataProvider->GetBindingValue(row); WriteProperty(rowValue, valueProperty, value); dataProvider->InvokeOnItemModified(row, 1, 1, false); } } ItemProperty DataColumn::GetTextProperty() { return textProperty; } void DataColumn::SetTextProperty(const ItemProperty& value) { if (textProperty != value) { textProperty = value; NotifyRebuilt(); compositions::GuiEventArgs arguments; TextPropertyChanged.Execute(arguments); } } WritableItemProperty DataColumn::GetValueProperty() { return valueProperty; } void DataColumn::SetValueProperty(const WritableItemProperty& value) { if (valueProperty != value) { valueProperty = value; NotifyRebuilt(); compositions::GuiEventArgs arguments; ValuePropertyChanged.Execute(arguments); } } /*********************************************************************** DataColumns ***********************************************************************/ void DataColumns::NotifyColumnRebuilt(vint column) { NotifyUpdate(column, 1); } void DataColumns::NotifyColumnChanged(vint column, bool needToRefreshItems) { dataProvider->NotifyColumnChanged(); } void DataColumns::NotifyUpdateInternal(vint start, vint count, vint newCount) { dataProvider->NotifyColumnRebuilt(); } bool DataColumns::QueryInsert(vint index, const Ptr& value) { return !items.Contains(value.Obj()); } void DataColumns::AfterInsert(vint index, const Ptr& value) { value->dataProvider = dataProvider; } void DataColumns::BeforeRemove(vint index, const Ptr& value) { value->dataProvider = nullptr; } DataColumns::DataColumns(DataProvider* _dataProvider) :dataProvider(_dataProvider) { } DataColumns::~DataColumns() { } /*********************************************************************** DataProvider ***********************************************************************/ bool DataProvider::NotifyUpdate(vint start, vint count, bool itemReferenceUpdated) { if (!itemSource) return false; if (start<0 || start >= itemSource->GetCount() || count <= 0 || start + count > itemSource->GetCount()) { return false; } else { InvokeOnItemModified(start, count, count, itemReferenceUpdated); return true; } } void DataProvider::RebuildAllItems() { NotifyUpdate(0, Count(), true); } void DataProvider::RefreshAllItems() { NotifyUpdate(0, Count(), false); } void DataProvider::NotifyColumnRebuilt() { for (auto callback : columnItemViewCallbacks) { callback->OnColumnRebuilt(); } RefreshAllItems(); } void DataProvider::NotifyColumnChanged() { for (auto callback : columnItemViewCallbacks) { callback->OnColumnChanged(true); } RefreshAllItems(); } list::IItemProvider* DataProvider::GetItemProvider() { return this; } void DataProvider::OnProcessorChanged() { RebuildFilter(); ReorderRows(true); } void DataProvider::OnItemSourceModified(vint start, vint count, vint newCount) { if (!currentSorter && !currentFilter && count == newCount) { InvokeOnItemModified(start, count, newCount, true); } else { ReorderRows(true); } } ListViewDataColumns& DataProvider::GetDataColumns() { return dataColumns; } DataColumns& DataProvider::GetColumns() { return columns; } Ptr DataProvider::GetItemSource() { return itemSource; } void DataProvider::SetItemSource(Ptr _itemSource) { vint oldCount = 0; if (itemSource) { oldCount = itemSource->GetCount(); } if (itemChangedEventHandler) { auto ol = itemSource.Cast(); ol->ItemChanged.Remove(itemChangedEventHandler); } itemSource = nullptr; itemChangedEventHandler = nullptr; if (_itemSource) { if (auto ol = _itemSource.Cast()) { itemSource = ol; itemChangedEventHandler = ol->ItemChanged.Add([this](vint start, vint oldCount, vint newCount) { OnItemSourceModified(start, oldCount, newCount); }); } else if (auto rl = _itemSource.Cast()) { itemSource = rl; } else { itemSource = IValueList::Create(GetLazyList(_itemSource)); } } OnItemSourceModified(0, oldCount, itemSource ? itemSource->GetCount() : 0); } void DataProvider::RebuildFilter() { if (currentFilter) { currentFilter->SetCallback(nullptr); currentFilter = nullptr; } List> selectedFilters; CopyFrom( selectedFilters, From(columns) .Select([](Ptr column) {return column->GetFilter(); }) .Where([](Ptr filter) {return filter != nullptr; }) ); if (additionalFilter) { selectedFilters.Add(additionalFilter); } if (selectedFilters.Count() > 0) { auto andFilter = Ptr(new DataAndFilter); for (auto filter : selectedFilters) { andFilter->AddSubFilter(filter); } currentFilter = andFilter; } if (currentFilter) { currentFilter->SetCallback(this); } } void DataProvider::ReorderRows(bool invokeCallback) { vint oldRowCount = Count(); vint rowCount = itemSource ? itemSource->GetCount() : 0; virtualRowToSourceRow = nullptr; if (currentFilter) { virtualRowToSourceRow = Ptr(new List); for (vint i = 0; i < rowCount; i++) { if (currentFilter->Filter(itemSource->Get(i))) { virtualRowToSourceRow->Add(i); } } } else if (currentSorter) { virtualRowToSourceRow = Ptr(new List); for (vint i = 0; i < rowCount; i++) { virtualRowToSourceRow->Add(i); } } if (currentSorter && virtualRowToSourceRow->Count() > 0) { IDataSorter* sorter = currentSorter.Obj(); SortLambda( &virtualRowToSourceRow->operator[](0), virtualRowToSourceRow->Count(), [=](vint a, vint b) { auto ordering = sorter->Compare(itemSource->Get(a), itemSource->Get(b)) <=> 0; return ordering == 0 ? a <=> b : ordering; }); } if (invokeCallback) { vint newRowCount = Count(); InvokeOnItemModified(0, oldRowCount, newRowCount, true); } } DataProvider::DataProvider() :dataColumns(this) , columns(this) { RebuildFilter(); ReorderRows(false); } DataProvider::~DataProvider() { if (itemChangedEventHandler) { auto ol = itemSource.Cast(); ol->ItemChanged.Remove(itemChangedEventHandler); } } Ptr DataProvider::GetAdditionalFilter() { return additionalFilter; } void DataProvider::SetAdditionalFilter(Ptr value) { additionalFilter = value; OnProcessorChanged(); } // ===================== GuiListControl::IItemProvider ===================== vint DataProvider::Count() { if (itemSource) { if (virtualRowToSourceRow) { return virtualRowToSourceRow->Count(); } else { return itemSource->GetCount(); } } else { return 0; } } WString DataProvider::GetTextValue(vint itemIndex) { return GetText(itemIndex); } description::Value DataProvider::GetBindingValue(vint itemIndex) { if (itemSource) { if (virtualRowToSourceRow) { return itemSource->Get(virtualRowToSourceRow->Get(itemIndex)); } else { return itemSource->Get(itemIndex); } } else { return Value(); } } IDescriptable* DataProvider::RequestView(const WString& identifier) { if (identifier == IListViewItemView::Identifier) { return (IListViewItemView*)this; } else if (identifier == ListViewColumnItemArranger::IColumnItemView::Identifier) { return (ListViewColumnItemArranger::IColumnItemView*)this; } else if (identifier == IDataGridView::Identifier) { return (IDataGridView*)this; } else { return nullptr; } } // ===================== list::IListViewItemProvider ===================== Ptr DataProvider::GetSmallImage(vint itemIndex) { if (0 <= itemIndex && itemIndex < Count()) { return ReadProperty(GetBindingValue(itemIndex), smallImageProperty); } return nullptr; } Ptr DataProvider::GetLargeImage(vint itemIndex) { if (0 <= itemIndex && itemIndex < Count()) { return ReadProperty(GetBindingValue(itemIndex), largeImageProperty); } return nullptr; } WString DataProvider::GetText(vint itemIndex) { if (columns.Count() == 0)return L""; return columns[0]->GetCellText(itemIndex); } WString DataProvider::GetSubItem(vint itemIndex, vint index) { return columns[index + 1]->GetCellText(itemIndex); } vint DataProvider::GetDataColumnCount() { return dataColumns.Count(); } vint DataProvider::GetDataColumn(vint index) { return dataColumns[index]; } vint DataProvider::GetColumnCount() { return columns.Count(); } WString DataProvider::GetColumnText(vint index) { return columns[index]->GetText(); } // ===================== list::ListViewColumnItemArranger::IColumnItemView ===================== bool DataProvider::AttachCallback(ListViewColumnItemArranger::IColumnItemViewCallback* value) { if (columnItemViewCallbacks.Contains(value)) { return false; } else { columnItemViewCallbacks.Add(value); return true; } } bool DataProvider::DetachCallback(ListViewColumnItemArranger::IColumnItemViewCallback* value) { vint index = columnItemViewCallbacks.IndexOf(value); if (index == -1) { return false; } else { columnItemViewCallbacks.Remove(value); return true; } } vint DataProvider::GetColumnSize(vint index) { return columns[index]->GetSize(); } void DataProvider::SetColumnSize(vint index, vint value) { columns[index]->SetSize(value); } GuiMenu* DataProvider::GetDropdownPopup(vint index) { return columns[index]->GetPopup(); } ColumnSortingState DataProvider::GetSortingState(vint index) { return columns[index]->sortingState; } // ===================== list::IDataGridView ===================== bool DataProvider::IsColumnSortable(vint column) { return columns[column]->GetSorter(); } void DataProvider::SortByColumn(vint column, bool ascending) { if (0 <= column && column < columns.Count()) { auto sorter = columns[column]->GetSorter(); if (!sorter) { currentSorter = nullptr; } else if (ascending) { currentSorter = sorter; } else { auto reverseSorter = Ptr(new DataReverseSorter); reverseSorter->SetSubSorter(sorter); currentSorter = reverseSorter; } } else { currentSorter = nullptr; } // TODO: (enumerable) foreach:indexed for (vint i = 0; i < columns.Count(); i++) { columns[i]->sortingState = i != column ? ColumnSortingState::NotSorted : ascending ? ColumnSortingState::Ascending : ColumnSortingState::Descending ; } NotifyColumnChanged(); ReorderRows(true); } vint DataProvider::GetSortedColumn() { // TODO: (enumerable) foreach:indexed for (vint i = 0; i < columns.Count(); i++) { auto state = columns[i]->sortingState; if (state != ColumnSortingState::NotSorted) { return i; } } return -1; } bool DataProvider::IsSortOrderAscending() { // TODO: (enumerable) foreach for (vint i = 0; i < columns.Count(); i++) { auto state = columns[i]->sortingState; if (state != ColumnSortingState::NotSorted) { return state == ColumnSortingState::Ascending; } } return true; } vint DataProvider::GetCellSpan(vint row, vint column) { return 1; } IDataVisualizerFactory* DataProvider::GetCellDataVisualizerFactory(vint row, vint column) { return columns[column]->GetVisualizerFactory().Obj(); } IDataEditorFactory* DataProvider::GetCellDataEditorFactory(vint row, vint column) { return columns[column]->GetEditorFactory().Obj(); } description::Value DataProvider::GetBindingCellValue(vint row, vint column) { return columns[column]->GetCellValue(row); } void DataProvider::SetBindingCellValue(vint row, vint column, const description::Value& value) { columns[column]->SetCellValue(row, value); } } /*********************************************************************** GuiBindableDataGrid ***********************************************************************/ GuiBindableDataGrid::GuiBindableDataGrid(theme::ThemeName themeName) :GuiVirtualDataGrid(themeName, new list::DataProvider) { dataProvider = dynamic_cast(GetItemProvider()); } GuiBindableDataGrid::~GuiBindableDataGrid() { } list::ListViewDataColumns& GuiBindableDataGrid::GetDataColumns() { return dataProvider->GetDataColumns(); } list::DataColumns& GuiBindableDataGrid::GetColumns() { return dataProvider->GetColumns(); } Ptr GuiBindableDataGrid::GetItemSource() { return dataProvider->GetItemSource(); } void GuiBindableDataGrid::SetItemSource(Ptr _itemSource) { dataProvider->SetItemSource(_itemSource); } Ptr GuiBindableDataGrid::GetAdditionalFilter() { return dataProvider->GetAdditionalFilter(); } void GuiBindableDataGrid::SetAdditionalFilter(Ptr value) { dataProvider->SetAdditionalFilter(value); } ItemProperty> GuiBindableDataGrid::GetLargeImageProperty() { return dataProvider->largeImageProperty; } void GuiBindableDataGrid::SetLargeImageProperty(const ItemProperty>& value) { if (dataProvider->largeImageProperty != value) { dataProvider->largeImageProperty = value; dataProvider->RefreshAllItems(); LargeImagePropertyChanged.Execute(GetNotifyEventArguments()); } } ItemProperty> GuiBindableDataGrid::GetSmallImageProperty() { return dataProvider->smallImageProperty; } void GuiBindableDataGrid::SetSmallImageProperty(const ItemProperty>& value) { if (dataProvider->smallImageProperty != value) { dataProvider->smallImageProperty = value; dataProvider->RefreshAllItems(); SmallImagePropertyChanged.Execute(GetNotifyEventArguments()); } } description::Value GuiBindableDataGrid::GetSelectedRowValue() { auto pos = GetSelectedCell(); if (pos.row == -1 || pos.column == -1) { return Value(); } return dataProvider->GetBindingValue(GetSelectedCell().row); } description::Value GuiBindableDataGrid::GetSelectedCellValue() { auto pos = GetSelectedCell(); if (pos.row == -1 || pos.column == -1) { return Value(); } return dataProvider->GetColumns()[pos.column]->GetCellValue(pos.row); } bool GuiBindableDataGrid::NotifyItemDataModified(vint start, vint count) { StopEdit(); return dataProvider->NotifyUpdate(start, count, false); } } } } /*********************************************************************** .\CONTROLS\LISTCONTROLPACKAGE\GUIBINDABLELISTCONTROLS.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { using namespace collections; using namespace list; using namespace tree; using namespace reflection::description; using namespace templates; /*********************************************************************** GuiBindableTextList::ItemSource ***********************************************************************/ GuiBindableTextList::ItemSource::ItemSource() { } GuiBindableTextList::ItemSource::~ItemSource() { if (itemChangedEventHandler) { auto ol = itemSource.Cast(); ol->ItemChanged.Remove(itemChangedEventHandler); } } Ptr GuiBindableTextList::ItemSource::GetItemSource() { return itemSource; } void GuiBindableTextList::ItemSource::SetItemSource(Ptr _itemSource) { vint oldCount = 0; if (itemSource) { oldCount = itemSource->GetCount(); } if (itemChangedEventHandler) { auto ol = itemSource.Cast(); ol->ItemChanged.Remove(itemChangedEventHandler); } itemSource = nullptr; itemChangedEventHandler = nullptr; if (_itemSource) { if (auto ol = _itemSource.Cast()) { itemSource = ol; itemChangedEventHandler = ol->ItemChanged.Add([this](vint start, vint oldCount, vint newCount) { InvokeOnItemModified(start, oldCount, newCount, true); }); } else if (auto rl = _itemSource.Cast()) { itemSource = rl; } else { itemSource = IValueList::Create(GetLazyList(_itemSource)); } } InvokeOnItemModified(0, oldCount, itemSource ? itemSource->GetCount() : 0, true); } description::Value GuiBindableTextList::ItemSource::Get(vint index) { if (!itemSource) return Value(); return itemSource->Get(index); } void GuiBindableTextList::ItemSource::UpdateBindingProperties() { InvokeOnItemModified(0, Count(), Count(), false); } bool GuiBindableTextList::ItemSource::NotifyUpdate(vint start, vint count, bool itemReferenceUpdated) { if (!itemSource) return false; if (start<0 || start >= itemSource->GetCount() || count <= 0 || start + count > itemSource->GetCount()) { return false; } else { InvokeOnItemModified(start, count, count, itemReferenceUpdated); return true; } } // ===================== GuiListControl::IItemProvider ===================== vint GuiBindableTextList::ItemSource::Count() { if (!itemSource) return 0; return itemSource->GetCount(); } WString GuiBindableTextList::ItemSource::GetTextValue(vint itemIndex) { if (itemSource) { if (0 <= itemIndex && itemIndex < itemSource->GetCount()) { return ReadProperty(itemSource->Get(itemIndex), textProperty); } } return L""; } IDescriptable* GuiBindableTextList::ItemSource::RequestView(const WString& identifier) { if (identifier == ITextItemView::Identifier) { return (ITextItemView*)this; } else { return 0; } } // ===================== GuiListControl::IItemBindingView ===================== description::Value GuiBindableTextList::ItemSource::GetBindingValue(vint itemIndex) { if (itemSource) { if (0 <= itemIndex && itemIndex < itemSource->GetCount()) { return itemSource->Get(itemIndex); } } return Value(); } // ===================== list::TextItemStyleProvider::ITextItemView ===================== bool GuiBindableTextList::ItemSource::GetChecked(vint itemIndex) { if (itemSource) { if (0 <= itemIndex && itemIndex < itemSource->GetCount()) { return ReadProperty(itemSource->Get(itemIndex), checkedProperty); } } return false; } void GuiBindableTextList::ItemSource::SetChecked(vint itemIndex, bool value) { if (itemSource) { if (0 <= itemIndex && itemIndex < itemSource->GetCount()) { auto thisValue = itemSource->Get(itemIndex); WriteProperty(thisValue, checkedProperty, value); InvokeOnItemModified(itemIndex, 1, 1, false); } } } /*********************************************************************** GuiBindableTextList ***********************************************************************/ GuiBindableTextList::GuiBindableTextList(theme::ThemeName themeName) :GuiVirtualTextList(themeName, new ItemSource) { itemSource = dynamic_cast(GetItemProvider()); TextPropertyChanged.SetAssociatedComposition(boundsComposition); TextPropertyChanged.SetAssociatedComposition(boundsComposition); } GuiBindableTextList::~GuiBindableTextList() { } Ptr GuiBindableTextList::GetItemSource() { return itemSource->GetItemSource(); } void GuiBindableTextList::SetItemSource(Ptr _itemSource) { itemSource->SetItemSource(_itemSource); } ItemProperty GuiBindableTextList::GetTextProperty() { return itemSource->textProperty; } void GuiBindableTextList::SetTextProperty(const ItemProperty& value) { if (itemSource->textProperty != value) { itemSource->textProperty = value; itemSource->UpdateBindingProperties(); TextPropertyChanged.Execute(GetNotifyEventArguments()); } } WritableItemProperty GuiBindableTextList::GetCheckedProperty() { return itemSource->checkedProperty; } void GuiBindableTextList::SetCheckedProperty(const WritableItemProperty& value) { if (itemSource->checkedProperty != value) { itemSource->checkedProperty = value; itemSource->UpdateBindingProperties(); CheckedPropertyChanged.Execute(GetNotifyEventArguments()); } } description::Value GuiBindableTextList::GetSelectedItem() { vint index = GetSelectedItemIndex(); if (index == -1) return Value(); return itemSource->Get(index); } bool GuiBindableTextList::NotifyItemDataModified(vint start, vint count) { return itemSource->NotifyUpdate(start, count, false); } /*********************************************************************** GuiBindableListView::ItemSource ***********************************************************************/ GuiBindableListView::ItemSource::ItemSource() :columns(this) , dataColumns(this) { } GuiBindableListView::ItemSource::~ItemSource() { if (itemChangedEventHandler) { auto ol = itemSource.Cast(); ol->ItemChanged.Remove(itemChangedEventHandler); } } Ptr GuiBindableListView::ItemSource::GetItemSource() { return itemSource; } void GuiBindableListView::ItemSource::SetItemSource(Ptr _itemSource) { vint oldCount = 0; if (itemSource) { oldCount = itemSource->GetCount(); } if (itemChangedEventHandler) { auto ol = itemSource.Cast(); ol->ItemChanged.Remove(itemChangedEventHandler); } itemSource = nullptr; itemChangedEventHandler = nullptr; if (_itemSource) { if (auto ol = _itemSource.Cast()) { itemSource = ol; itemChangedEventHandler = ol->ItemChanged.Add([this](vint start, vint oldCount, vint newCount) { InvokeOnItemModified(start, oldCount, newCount, true); }); } else if (auto rl = _itemSource.Cast()) { itemSource = rl; } else { itemSource = IValueList::Create(GetLazyList(_itemSource)); } } InvokeOnItemModified(0, oldCount, itemSource ? itemSource->GetCount() : 0, true); } description::Value GuiBindableListView::ItemSource::Get(vint index) { if (!itemSource) return Value(); return itemSource->Get(index); } void GuiBindableListView::ItemSource::UpdateBindingProperties() { InvokeOnItemModified(0, Count(), Count(), false); } bool GuiBindableListView::ItemSource::NotifyUpdate(vint start, vint count, bool itemReferenceUpdated) { if (!itemSource) return false; if (start<0 || start >= itemSource->GetCount() || count <= 0 || start + count > itemSource->GetCount()) { return false; } else { InvokeOnItemModified(start, count, count, itemReferenceUpdated); return true; } } list::ListViewDataColumns& GuiBindableListView::ItemSource::GetDataColumns() { return dataColumns; } list::ListViewColumns& GuiBindableListView::ItemSource::GetColumns() { return columns; } // ===================== list::IListViewItemProvider ===================== void GuiBindableListView::ItemSource::RebuildAllItems() { InvokeOnItemModified(0, Count(), Count(), true); } void GuiBindableListView::ItemSource::RefreshAllItems() { InvokeOnItemModified(0, Count(), Count(), false); } void GuiBindableListView::ItemSource::NotifyColumnRebuilt() { for (auto callback : columnItemViewCallbacks) { callback->OnColumnRebuilt(); } RebuildAllItems(); } void GuiBindableListView::ItemSource::NotifyColumnChanged() { for (auto callback : columnItemViewCallbacks) { callback->OnColumnChanged(true); } RefreshAllItems(); } // ===================== GuiListControl::IItemProvider ===================== vint GuiBindableListView::ItemSource::Count() { if (!itemSource) return 0; return itemSource->GetCount(); } WString GuiBindableListView::ItemSource::GetTextValue(vint itemIndex) { return GetText(itemIndex); } description::Value GuiBindableListView::ItemSource::GetBindingValue(vint itemIndex) { if (itemSource) { if (0 <= itemIndex && itemIndex < itemSource->GetCount()) { return itemSource->Get(itemIndex); } } return Value(); } IDescriptable* GuiBindableListView::ItemSource::RequestView(const WString& identifier) { if (identifier == IListViewItemView::Identifier) { return (IListViewItemView*)this; } else if (identifier == ListViewColumnItemArranger::IColumnItemView::Identifier) { return (ListViewColumnItemArranger::IColumnItemView*)this; } else { return 0; } } // ===================== list::ListViewItemStyleProvider::IListViewItemView ===================== Ptr GuiBindableListView::ItemSource::GetSmallImage(vint itemIndex) { if (itemSource) { if (0 <= itemIndex && itemIndex < itemSource->GetCount()) { return ReadProperty(itemSource->Get(itemIndex), smallImageProperty); } } return nullptr; } Ptr GuiBindableListView::ItemSource::GetLargeImage(vint itemIndex) { if (itemSource) { if (0 <= itemIndex && itemIndex < itemSource->GetCount()) { return ReadProperty(itemSource->Get(itemIndex), largeImageProperty); } } return nullptr; } WString GuiBindableListView::ItemSource::GetText(vint itemIndex) { if (itemSource) { if (0 <= itemIndex && itemIndex < itemSource->GetCount() && columns.Count()>0) { return ReadProperty(itemSource->Get(itemIndex), columns[0]->GetTextProperty()); } } return L""; } WString GuiBindableListView::ItemSource::GetSubItem(vint itemIndex, vint index) { if (itemSource) { if (0 <= itemIndex && itemIndex < itemSource->GetCount() && 0 <= index && index < columns.Count() - 1) { return ReadProperty(itemSource->Get(itemIndex), columns[index + 1]->GetTextProperty()); } } return L""; } vint GuiBindableListView::ItemSource::GetDataColumnCount() { return dataColumns.Count(); } vint GuiBindableListView::ItemSource::GetDataColumn(vint index) { return dataColumns[index]; } vint GuiBindableListView::ItemSource::GetColumnCount() { return columns.Count(); } WString GuiBindableListView::ItemSource::GetColumnText(vint index) { if (index < 0 || index >= columns.Count()) { return L""; } else { return columns[index]->GetText(); } } // ===================== list::ListViewColumnItemArranger::IColumnItemView ===================== bool GuiBindableListView::ItemSource::AttachCallback(ListViewColumnItemArranger::IColumnItemViewCallback* value) { if(columnItemViewCallbacks.Contains(value)) { return false; } else { columnItemViewCallbacks.Add(value); return true; } } bool GuiBindableListView::ItemSource::DetachCallback(ListViewColumnItemArranger::IColumnItemViewCallback* value) { vint index = columnItemViewCallbacks.IndexOf(value); if (index == -1) { return false; } else { columnItemViewCallbacks.Remove(value); return true; } } vint GuiBindableListView::ItemSource::GetColumnSize(vint index) { if (index < 0 || index >= columns.Count()) { return 0; } else { return columns[index]->GetSize(); } } void GuiBindableListView::ItemSource::SetColumnSize(vint index, vint value) { if (index >= 0 && index < columns.Count()) { columns[index]->SetSize(value); } } GuiMenu* GuiBindableListView::ItemSource::GetDropdownPopup(vint index) { if (index < 0 || index >= columns.Count()) { return 0; } else { return columns[index]->GetDropdownPopup(); } } ColumnSortingState GuiBindableListView::ItemSource::GetSortingState(vint index) { if (index < 0 || index >= columns.Count()) { return ColumnSortingState::NotSorted; } else { return columns[index]->GetSortingState(); } } /*********************************************************************** GuiBindableListView ***********************************************************************/ GuiBindableListView::GuiBindableListView(theme::ThemeName themeName) :GuiVirtualListView(themeName, new ItemSource) { itemSource = dynamic_cast(GetItemProvider()); LargeImagePropertyChanged.SetAssociatedComposition(boundsComposition); SmallImagePropertyChanged.SetAssociatedComposition(boundsComposition); } GuiBindableListView::~GuiBindableListView() { } list::ListViewDataColumns& GuiBindableListView::GetDataColumns() { return itemSource->GetDataColumns(); } list::ListViewColumns& GuiBindableListView::GetColumns() { return itemSource->GetColumns(); } Ptr GuiBindableListView::GetItemSource() { return itemSource->GetItemSource(); } void GuiBindableListView::SetItemSource(Ptr _itemSource) { itemSource->SetItemSource(_itemSource); } ItemProperty> GuiBindableListView::GetLargeImageProperty() { return itemSource->largeImageProperty; } void GuiBindableListView::SetLargeImageProperty(const ItemProperty>& value) { if (itemSource->largeImageProperty != value) { itemSource->largeImageProperty = value; itemSource->UpdateBindingProperties(); LargeImagePropertyChanged.Execute(GetNotifyEventArguments()); } } ItemProperty> GuiBindableListView::GetSmallImageProperty() { return itemSource->smallImageProperty; } void GuiBindableListView::SetSmallImageProperty(const ItemProperty>& value) { if (itemSource->smallImageProperty != value) { itemSource->smallImageProperty = value; itemSource->UpdateBindingProperties(); SmallImagePropertyChanged.Execute(GetNotifyEventArguments()); } } description::Value GuiBindableListView::GetSelectedItem() { vint index = GetSelectedItemIndex(); if (index == -1) return Value(); return itemSource->Get(index); } bool GuiBindableListView::NotifyItemDataModified(vint start, vint count) { return itemSource->NotifyUpdate(start, count, false); } /*********************************************************************** GuiBindableTreeView::ItemSourceNode ***********************************************************************/ Ptr GuiBindableTreeView::ItemSourceNode::PrepareValueList(const description::Value& inputItemSource) { if (auto value = ReadProperty(inputItemSource, rootProvider->childrenProperty)) { if (auto ol = value.Cast()) { return ol; } else if (auto rl = value.Cast()) { return rl; } else { return IValueList::Create(GetLazyList(value)); } } else { return IValueList::Create(); } } void GuiBindableTreeView::ItemSourceNode::PrepareChildren(Ptr newValueList) { if (!childrenVirtualList) { childrenVirtualList = newValueList; if (auto ol = childrenVirtualList.Cast()) { itemChangedEventHandler = ol->ItemChanged.Add([this](vint start, vint oldCount, vint newCount) { callback->OnBeforeItemModified(this, start, oldCount, newCount, true); children.RemoveRange(start, oldCount); for (vint i = 0; i < newCount; i++) { Value value = childrenVirtualList->Get(start + i); auto node = Ptr(new ItemSourceNode(value, this)); children.Insert(start + i, node); } callback->OnAfterItemModified(this, start, oldCount, newCount, true); }); } vint count = childrenVirtualList->GetCount(); for (vint i = 0; i < count; i++) { Value value = childrenVirtualList->Get(i); auto node = Ptr(new ItemSourceNode(value, this)); children.Add(node); } } } void GuiBindableTreeView::ItemSourceNode::UnprepareChildren() { if (itemChangedEventHandler) { auto ol = childrenVirtualList.Cast(); ol->ItemChanged.Remove(itemChangedEventHandler); itemChangedEventHandler = nullptr; } childrenVirtualList = nullptr; for (auto node : children) { node->UnprepareChildren(); } children.Clear(); } void GuiBindableTreeView::ItemSourceNode::PrepareReverseMapping() { #define ERROR_MESSAGE_PREFIX L"vl::presentation::controls::GuiBindableTreeView::ItemSourceNode::PrepareReverseMapping()#" if (rootProvider->reverseMappingProperty && !itemSource.IsNull()) { auto oldValue = ReadProperty(itemSource, rootProvider->reverseMappingProperty); CHECK_ERROR(oldValue.IsNull(), ERROR_MESSAGE_PREFIX L"The reverse mapping property of an item has been unexpectedly changed."); WriteProperty(itemSource, rootProvider->reverseMappingProperty, Value::From(this)); } #undef ERROR_MESSAGE_PREFIX } void GuiBindableTreeView::ItemSourceNode::UnprepareReverseMapping() { #define ERROR_MESSAGE_PREFIX L"vl::presentation::controls::GuiBindableTreeView::ItemSourceNode::PrepareReverseMapping()#" if (rootProvider->reverseMappingProperty && !itemSource.IsNull()) { auto oldValue = ReadProperty(itemSource, rootProvider->reverseMappingProperty); CHECK_ERROR(oldValue.GetRawPtr() == this, ERROR_MESSAGE_PREFIX L"The reverse mapping property of an item has been unexpectedly changed."); WriteProperty(itemSource, rootProvider->reverseMappingProperty, {}); } #undef ERROR_MESSAGE_PREFIX } GuiBindableTreeView::ItemSourceNode::ItemSourceNode(const description::Value& _itemSource, ItemSourceNode* _parent) :itemSource(_itemSource) , rootProvider(_parent->rootProvider) , parent(_parent) , callback(_parent->callback) { PrepareReverseMapping(); } GuiBindableTreeView::ItemSourceNode::ItemSourceNode(ItemSource* _rootProvider) :rootProvider(_rootProvider) , parent(nullptr) , callback(_rootProvider) { } GuiBindableTreeView::ItemSourceNode::~ItemSourceNode() { UnprepareReverseMapping(); if (itemChangedEventHandler) { auto ol = childrenVirtualList.Cast(); ol->ItemChanged.Remove(itemChangedEventHandler); } } description::Value GuiBindableTreeView::ItemSourceNode::GetItemSource() { return itemSource; } void GuiBindableTreeView::ItemSourceNode::SetItemSource(const description::Value& _itemSource) { auto newVirtualList = PrepareValueList(_itemSource); vint oldCount = childrenVirtualList ? childrenVirtualList->GetCount() : 0; vint newCount = newVirtualList->GetCount(); callback->OnBeforeItemModified(this, 0, oldCount, newCount, true); UnprepareChildren(); UnprepareReverseMapping(); itemSource = _itemSource; PrepareReverseMapping(); PrepareChildren(newVirtualList); callback->OnAfterItemModified(this, 0, oldCount, newCount, true); } bool GuiBindableTreeView::ItemSourceNode::GetExpanding() { return this == rootProvider->rootNode.Obj() ? true : expanding; } void GuiBindableTreeView::ItemSourceNode::SetExpanding(bool value) { if (this != rootProvider->rootNode.Obj() && expanding != value) { expanding = value; if (expanding) { callback->OnItemExpanded(this); } else { callback->OnItemCollapsed(this); } } } vint GuiBindableTreeView::ItemSourceNode::CalculateTotalVisibleNodes() { if (!GetExpanding()) { return 1; } if (!childrenVirtualList) { PrepareChildren(PrepareValueList(itemSource)); } vint count = 1; for (auto child : children) { count += child->CalculateTotalVisibleNodes(); } return count; } void GuiBindableTreeView::ItemSourceNode::NotifyDataModified() { if (parent) { vint index = parent->children.IndexOf(this); callback->OnBeforeItemModified(parent, index, 1, 1, false); callback->OnAfterItemModified(parent, index, 1, 1, false); } } vint GuiBindableTreeView::ItemSourceNode::GetChildCount() { if (!childrenVirtualList) { PrepareChildren(PrepareValueList(itemSource)); } return children.Count(); } Ptr GuiBindableTreeView::ItemSourceNode::GetParent() { return Ptr(parent); } Ptr GuiBindableTreeView::ItemSourceNode::GetChild(vint index) { if (!childrenVirtualList) { PrepareChildren(PrepareValueList(itemSource)); } if (0 <= index && index < children.Count()) { return children[index]; } return nullptr; } /*********************************************************************** GuiBindableTreeView::ItemSource ***********************************************************************/ GuiBindableTreeView::ItemSource::ItemSource() { rootNode = Ptr(new ItemSourceNode(this)); } GuiBindableTreeView::ItemSource::~ItemSource() { } description::Value GuiBindableTreeView::ItemSource::GetItemSource() { return rootNode->GetItemSource(); } void GuiBindableTreeView::ItemSource::SetItemSource(const description::Value& _itemSource) { rootNode->SetItemSource(_itemSource); } void GuiBindableTreeView::ItemSource::UpdateBindingProperties(bool updateChildrenProperty) { vint oldCount = rootNode->GetChildCount(); if (updateChildrenProperty) { rootNode->UnprepareChildren(); } vint newCount = rootNode->GetChildCount(); OnBeforeItemModified(rootNode.Obj(), 0, oldCount, newCount, updateChildrenProperty); OnAfterItemModified(rootNode.Obj(), 0, oldCount, newCount, updateChildrenProperty); } // ===================== tree::INodeRootProvider ===================== Ptr GuiBindableTreeView::ItemSource::GetRootNode() { return rootNode; } WString GuiBindableTreeView::ItemSource::GetTextValue(tree::INodeProvider* node) { return ReadProperty(GetBindingValue(node), textProperty); } description::Value GuiBindableTreeView::ItemSource::GetBindingValue(tree::INodeProvider* node) { if (auto itemSourceNode = dynamic_cast(node)) { return itemSourceNode->GetItemSource(); } return Value(); } IDescriptable* GuiBindableTreeView::ItemSource::RequestView(const WString& identifier) { if(identifier==ITreeViewItemView::Identifier) { return (ITreeViewItemView*)this; } else { return 0; } } // ===================== tree::ITreeViewItemView ===================== Ptr GuiBindableTreeView::ItemSource::GetNodeImage(tree::INodeProvider* node) { if (auto itemSourceNode = dynamic_cast(node)) { return ReadProperty(itemSourceNode->GetItemSource(), imageProperty); } return nullptr; } /*********************************************************************** GuiBindableTreeView ***********************************************************************/ GuiBindableTreeView::GuiBindableTreeView(theme::ThemeName themeName, WritableItemProperty reverseMappingProperty) :GuiVirtualTreeView(themeName, Ptr(new ItemSource)) { itemSource = dynamic_cast(GetNodeRootProvider()); itemSource->reverseMappingProperty = reverseMappingProperty; TextPropertyChanged.SetAssociatedComposition(boundsComposition); ImagePropertyChanged.SetAssociatedComposition(boundsComposition); ChildrenPropertyChanged.SetAssociatedComposition(boundsComposition); } GuiBindableTreeView::~GuiBindableTreeView() { } description::Value GuiBindableTreeView::GetItemSource() { return itemSource->GetItemSource(); } void GuiBindableTreeView::SetItemSource(description::Value _itemSource) { itemSource->SetItemSource(_itemSource); } WritableItemProperty GuiBindableTreeView::GetReverseMappingProperty() { return itemSource->reverseMappingProperty; } ItemProperty GuiBindableTreeView::GetTextProperty() { return itemSource->textProperty; } void GuiBindableTreeView::SetTextProperty(const ItemProperty& value) { if (itemSource->textProperty != value) { itemSource->textProperty = value; itemSource->UpdateBindingProperties(false); TextPropertyChanged.Execute(GetNotifyEventArguments()); } } ItemProperty> GuiBindableTreeView::GetImageProperty() { return itemSource->imageProperty; } void GuiBindableTreeView::SetImageProperty(const ItemProperty>& value) { if (itemSource->imageProperty != value) { itemSource->imageProperty = value; itemSource->UpdateBindingProperties(false); ImagePropertyChanged.Execute(GetNotifyEventArguments()); } } ItemProperty> GuiBindableTreeView::GetChildrenProperty() { return itemSource->childrenProperty; } void GuiBindableTreeView::SetChildrenProperty(const ItemProperty>& value) { if (itemSource->childrenProperty != value) { itemSource->childrenProperty = value; itemSource->UpdateBindingProperties(true); ChildrenPropertyChanged.Execute(GetNotifyEventArguments()); } } description::Value GuiBindableTreeView::GetSelectedItem() { vint index = GetSelectedItemIndex(); if (index == -1) return Value(); Value result; if (auto node = nodeItemView->RequestNode(index)) { if (auto itemSourceNode = node.Cast()) { result = itemSourceNode->GetItemSource(); } } return result; } void GuiBindableTreeView::NotifyNodeDataModified(description::Value value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::controls::GuiBindableTreeView::NotifyNodeDataModified(Value)#" CHECK_ERROR(itemSource->reverseMappingProperty, ERROR_MESSAGE_PREFIX L"This function can only be called when the ReverseMappingProperty is in use."); CHECK_ERROR(!value.IsNull(), ERROR_MESSAGE_PREFIX L"The item cannot be null."); auto mapping = ReadProperty(value, itemSource->reverseMappingProperty); auto node = dynamic_cast(mapping.GetRawPtr()); CHECK_ERROR(node, ERROR_MESSAGE_PREFIX L"The item is not binded to a GuiBindableTreeView control or its reverse mapping property has been unexpectedly changed."); auto rootNode = node; while (rootNode->GetParent()) { rootNode = rootNode->GetParent().Obj(); } CHECK_ERROR(rootNode == itemSource->rootNode.Obj(), ERROR_MESSAGE_PREFIX L"The item is not binded to this control."); CHECK_ERROR(node != itemSource->rootNode.Obj(), ERROR_MESSAGE_PREFIX L"The item should not be the root item, which is the item source assigned to this control."); node->NotifyDataModified(); #undef ERROR_MESSAGE_PREFIX } } } } /*********************************************************************** .\CONTROLS\LISTCONTROLPACKAGE\GUICOMBOCONTROLS.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { /*********************************************************************** GuiComboBoxBase ***********************************************************************/ void GuiComboBoxBase::BeforeControlTemplateUninstalled_() { } void GuiComboBoxBase::AfterControlTemplateInstalled_(bool initialize) { } IGuiMenuService::Direction GuiComboBoxBase::GetSubMenuDirection() { return IGuiMenuService::Horizontal; } void GuiComboBoxBase::OnCachedBoundsChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { Size size=GetPreferredMenuClientSize(); size.x=boundsComposition->GetCachedBounds().Width(); SetPreferredMenuClientSize(size); } GuiComboBoxBase::GuiComboBoxBase(theme::ThemeName themeName) :GuiMenuButton(themeName) { CreateSubMenu(); SetCascadeAction(false); boundsComposition->CachedBoundsChanged.AttachMethod(this, &GuiComboBoxBase::OnCachedBoundsChanged); } GuiComboBoxBase::~GuiComboBoxBase() { } /*********************************************************************** GuiComboButton ***********************************************************************/ GuiComboButton::GuiComboButton(theme::ThemeName themeName, GuiControl* _dropdownControl) :GuiComboBoxBase(themeName) , dropdownControl(_dropdownControl) { dropdownControl->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); GetSubMenu()->GetContainerComposition()->AddChild(dropdownControl->GetBoundsComposition()); } GuiComboButton::~GuiComboButton() { } /*********************************************************************** GuiComboBoxListControl ***********************************************************************/ void GuiComboBoxListControl::UpdateDisplayFont() { GuiControl::UpdateDisplayFont(); if (itemStyleController) { itemStyleController->SetFont(GetDisplayFont()); } } void GuiComboBoxListControl::BeforeControlTemplateUninstalled() { GuiComboBoxBase::BeforeControlTemplateUninstalled(); } void GuiComboBoxListControl::AfterControlTemplateInstalled(bool initialize) { GuiComboBoxBase::AfterControlTemplateInstalled(initialize); TypedControlTemplateObject(true)->SetTextVisible(!itemStyleProperty); } void GuiComboBoxListControl::RemoveStyleController() { if (itemStyleController) { SafeDeleteComposition(itemStyleController); itemStyleController = nullptr; } } void GuiComboBoxListControl::InstallStyleController(vint itemIndex) { if (itemStyleProperty) { if (itemIndex != -1) { auto item = containedListControl->GetItemProvider()->GetBindingValue(itemIndex); if (!item.IsNull()) { if (auto style = itemStyleProperty(item)) { itemStyleController = style; itemStyleController->SetText(GetText()); itemStyleController->SetFont(GetDisplayFont()); itemStyleController->SetContext(GetContext()); itemStyleController->SetVisuallyEnabled(GetVisuallyEnabled()); itemStyleController->SetAlignmentToParent(Margin(0, 0, 0, 0)); containerComposition->AddChild(itemStyleController); } } } } } void GuiComboBoxListControl::DisplaySelectedContent(vint itemIndex) { if (itemIndex == -1) { SetText(L""); } else { WString text = containedListControl->GetItemProvider()->GetTextValue(itemIndex); SetText(text); } RemoveStyleController(); InstallStyleController(itemIndex); if (selectedIndex != itemIndex) { selectedIndex = itemIndex; SelectedIndexChanged.Execute(GetNotifyEventArguments()); } } void GuiComboBoxListControl::AdoptSubMenuSize() { if (auto subMenu = GetSubMenu()) { Size expectedSize(0, GetDisplayFont().size * 20); Size adoptedSize = containedListControl->GetAdoptedSize(expectedSize); Size clientSize = GetPreferredMenuClientSize(); vint height = adoptedSize.y + subMenu->GetClientSize().y - containedListControl->GetBoundsComposition()->GetCachedBounds().Height(); if (clientSize.y != height) { clientSize.y = height; SetPreferredMenuClientSize(clientSize); } } } void GuiComboBoxListControl::OnTextChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if (itemStyleController) { itemStyleController->SetText(GetText()); } } void GuiComboBoxListControl::OnContextChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if (itemStyleController) { itemStyleController->SetContext(GetContext()); } } void GuiComboBoxListControl::OnVisuallyEnabledChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if (itemStyleController) { itemStyleController->SetVisuallyEnabled(GetVisuallyEnabled()); } } void GuiComboBoxListControl::OnAfterSubMenuOpening(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { containedListControl->SelectItemsByClick(selectedIndex, false, false, true); containedListControl->EnsureItemVisible(selectedIndex); } void GuiComboBoxListControl::OnListControlAdoptedSizeInvalidated(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { TryDelayExecuteIfNotDeleted([=]() { AdoptSubMenuSize(); }); } void GuiComboBoxListControl::OnListControlItemMouseDown(compositions::GuiGraphicsComposition* sender, compositions::GuiItemMouseEventArgs& arguments) { DisplaySelectedContent(containedListControl->GetSelectedItemIndex()); GetSubMenu()->Hide(); } void GuiComboBoxListControl::OnKeyDown(compositions::GuiGraphicsComposition* sender, compositions::GuiKeyEventArgs& arguments) { if (!arguments.autoRepeatKeyDown) { switch (arguments.code) { case VKEY::KEY_RETURN: DisplaySelectedContent(containedListControl->GetSelectedItemIndex()); arguments.handled = true; case VKEY::KEY_ESCAPE: GetSubMenu()->Hide(); arguments.handled = true; break; default: containedListControl->SelectItemsByKey(arguments.code, arguments.ctrl, arguments.shift); } } } void GuiComboBoxListControl::OnAttached(list::IItemProvider* provider) { } void GuiComboBoxListControl::OnItemModified(vint start, vint count, vint newCount, bool itemReferenceUpdated) { if (count == newCount && start <= selectedIndex && selectedIndex < start + count) { DisplaySelectedContent(selectedIndex); } else { DisplaySelectedContent(-1); } } GuiComboBoxListControl::GuiComboBoxListControl(theme::ThemeName themeName, GuiSelectableListControl* _containedListControl) :GuiComboBoxBase(themeName) , containedListControl(_containedListControl) { TextChanged.AttachMethod(this, &GuiComboBoxListControl::OnTextChanged); ContextChanged.AttachMethod(this, &GuiComboBoxListControl::OnContextChanged); VisuallyEnabledChanged.AttachMethod(this, &GuiComboBoxListControl::OnVisuallyEnabledChanged); AfterSubMenuOpening.AttachMethod(this, &GuiComboBoxListControl::OnAfterSubMenuOpening); containedListControl->GetItemProvider()->AttachCallback(this); containedListControl->SetMultiSelect(false); containedListControl->AdoptedSizeInvalidated.AttachMethod(this, &GuiComboBoxListControl::OnListControlAdoptedSizeInvalidated); containedListControl->ItemLeftButtonDown.AttachMethod(this, &GuiComboBoxListControl::OnListControlItemMouseDown); containedListControl->ItemRightButtonDown.AttachMethod(this, &GuiComboBoxListControl::OnListControlItemMouseDown); boundsComposition->GetEventReceiver()->keyDown.AttachMethod(this, &GuiComboBoxListControl::OnKeyDown); auto itemProvider = containedListControl->GetItemProvider(); SelectedIndexChanged.SetAssociatedComposition(boundsComposition); containedListControl->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); GetSubMenu()->GetContainerComposition()->AddChild(containedListControl->GetBoundsComposition()); SetFont(GetFont()); } GuiComboBoxListControl::~GuiComboBoxListControl() { containedListControl->GetItemProvider()->DetachCallback(this); containedListControl->GetBoundsComposition()->CachedBoundsChanged.Detach(boundsChangedHandler); boundsChangedHandler = nullptr; } GuiSelectableListControl* GuiComboBoxListControl::GetContainedListControl() { return containedListControl; } GuiComboBoxListControl::ItemStyleProperty GuiComboBoxListControl::GetItemTemplate() { return itemStyleProperty; } void GuiComboBoxListControl::SetItemTemplate(ItemStyleProperty value) { RemoveStyleController(); itemStyleProperty = value; TypedControlTemplateObject(true)->SetTextVisible(!itemStyleProperty); InstallStyleController(selectedIndex); ItemTemplateChanged.Execute(GetNotifyEventArguments()); } vint GuiComboBoxListControl::GetSelectedIndex() { return selectedIndex; } void GuiComboBoxListControl::SetSelectedIndex(vint value) { if (selectedIndex != value) { if (0 <= value && value < containedListControl->GetItemProvider()->Count()) { DisplaySelectedContent(value); } } GetSubMenu()->Hide(); } description::Value GuiComboBoxListControl::GetSelectedItem() { if (selectedIndex != -1) { return containedListControl->GetItemProvider()->GetBindingValue(selectedIndex); } return description::Value(); } list::IItemProvider* GuiComboBoxListControl::GetItemProvider() { return containedListControl->GetItemProvider(); } } } } /*********************************************************************** .\CONTROLS\LISTCONTROLPACKAGE\GUIDATAGRIDCONTROLS.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { namespace list { using namespace compositions; using namespace collections; using namespace description; using namespace templates; const wchar_t* const IDataGridView::Identifier = L"vl::presentation::controls::list::IDataGridView"; /*********************************************************************** DefaultDataGridItemTemplate ***********************************************************************/ IDataVisualizerFactory* DefaultDataGridItemTemplate::GetDataVisualizerFactory(vint row, vint column) { if (auto dataGrid = dynamic_cast(listControl)) { if (auto factory = dataGrid->dataGridView->GetCellDataVisualizerFactory(row, column)) { return factory; } if (column == 0) { return dataGrid->defaultMainColumnVisualizerFactory.Obj(); } else { return dataGrid->defaultSubColumnVisualizerFactory.Obj(); } } return nullptr; } IDataEditorFactory* DefaultDataGridItemTemplate::GetDataEditorFactory(vint row, vint column) { if (auto dataGrid = dynamic_cast(listControl)) { return dataGrid->dataGridView->GetCellDataEditorFactory(row, column); } return nullptr; } vint DefaultDataGridItemTemplate::GetCellColumnIndex(compositions::GuiGraphicsComposition* composition) { for (vint i = 0; i < textTable->GetColumns(); i++) { auto cell = textTable->GetSitedCell(0, i); if (composition == cell) { return i; } } return -1; } bool DefaultDataGridItemTemplate::IsInEditor(GuiVirtualDataGrid* dataGrid, compositions::GuiMouseEventArgs& arguments) { if (!dataGrid->currentEditor) return false; auto editorComposition = dataGrid->currentEditor->GetTemplate(); auto currentComposition = arguments.eventSource; while (currentComposition) { if (currentComposition == editorComposition) { return true; } else if (currentComposition == this) { break; } else { currentComposition = currentComposition->GetParent(); } } return false; } void DefaultDataGridItemTemplate::OnCellButtonDown(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) { if (auto dataGrid = dynamic_cast(listControl)) { if (IsInEditor(dataGrid, arguments)) { arguments.handled = true; } } } void DefaultDataGridItemTemplate::OnCellLeftButtonUp(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) { if (auto dataGrid = dynamic_cast(listControl)) { if (IsInEditor(dataGrid, arguments)) { arguments.handled = true; } else if (dataGrid->GetVisuallyEnabled()) { vint index = GetCellColumnIndex(sender); if (index != -1) { vint currentRow = GetIndex(); dataGrid->SelectCell({ currentRow,index }, true); } } } } void DefaultDataGridItemTemplate::OnCellRightButtonUp(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) { if (auto dataGrid = dynamic_cast(listControl)) { if (IsInEditor(dataGrid, arguments)) { arguments.handled = true; } else if (dataGrid->GetVisuallyEnabled()) { vint index = GetCellColumnIndex(sender); if (index != -1) { vint currentRow = GetIndex(); dataGrid->SelectCell({ currentRow,index }, false); } } } } void DefaultDataGridItemTemplate::DeleteAllVisualizers() { for (vint i = 0; i < dataVisualizers.Count(); i++) { DeleteVisualizer(i); } } void DefaultDataGridItemTemplate::DeleteVisualizer(vint column) { auto visualizer = dataVisualizers[column]; auto composition = visualizer->GetTemplate(); visualizer->NotifyDeletedTemplate(); if (composition->GetParent()) { composition->GetParent()->RemoveChild(composition); } SafeDeleteComposition(composition); dataVisualizers[column] = nullptr; } void DefaultDataGridItemTemplate::ResetDataTable(vint columnCount) { vint itemIndex = GetIndex(); if (dataVisualizers.Count() == columnCount) { for (vint i = 0; i < columnCount; i++) { auto factory = GetDataVisualizerFactory(itemIndex, i); if (dataVisualizerFactories[i] != factory) { DeleteVisualizer(i); dataVisualizerFactories[i] = factory; } } } else { DeleteAllVisualizers(); dataVisualizerFactories.Resize(columnCount); dataVisualizers.Resize(columnCount); for (auto cell : dataCells) { SafeDeleteComposition(cell); } dataCells.Resize(columnCount); for (vint i = 0; i < columnCount; i++) { dataVisualizerFactories[i] = GetDataVisualizerFactory(itemIndex, i); } textTable->SetRowsAndColumns(1, columnCount); for (vint i = 0; i < columnCount; i++) { auto cell = new GuiCellComposition; textTable->AddChild(cell); cell->SetSite(0, i, 1, 1); cell->GetEventReceiver()->leftButtonDown.AttachMethod(this, &DefaultDataGridItemTemplate::OnCellButtonDown); cell->GetEventReceiver()->rightButtonDown.AttachMethod(this, &DefaultDataGridItemTemplate::OnCellButtonDown); cell->GetEventReceiver()->leftButtonUp.AttachMethod(this, &DefaultDataGridItemTemplate::OnCellLeftButtonUp); cell->GetEventReceiver()->rightButtonUp.AttachMethod(this, &DefaultDataGridItemTemplate::OnCellRightButtonUp); dataCells[i] = cell; } } } void DefaultDataGridItemTemplate::OnInitialize() { { textTable = new GuiTableComposition; textTable->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); textTable->SetAlignmentToParent(Margin(0, 0, 0, 0)); textTable->SetRowsAndColumns(1, 1); textTable->SetRowOption(0, GuiCellOption::MinSizeOption()); textTable->SetColumnOption(0, GuiCellOption::AbsoluteOption(0)); AddChild(textTable); } SelectedChanged.AttachMethod(this, &DefaultDataGridItemTemplate::OnSelectedChanged); FontChanged.AttachMethod(this, &DefaultDataGridItemTemplate::OnFontChanged); ContextChanged.AttachMethod(this, &DefaultDataGridItemTemplate::OnContextChanged); VisuallyEnabledChanged.AttachMethod(this, &DefaultDataGridItemTemplate::OnVisuallyEnabledChanged); SelectedChanged.Execute(compositions::GuiEventArgs(this)); FontChanged.Execute(compositions::GuiEventArgs(this)); ContextChanged.Execute(compositions::GuiEventArgs(this)); VisuallyEnabledChanged.Execute(compositions::GuiEventArgs(this)); } void DefaultDataGridItemTemplate::OnRefresh() { if (auto dataGrid = dynamic_cast(listControl)) { vint columnCount = dataGrid->listViewItemView->GetColumnCount(); vint itemIndex = GetIndex(); ResetDataTable(columnCount); for (vint i = 0; i < columnCount; i++) { auto& dataVisualizer = dataVisualizers[i]; if (!dataVisualizer) { dataVisualizer = dataVisualizerFactories[i]->CreateVisualizer(dataGrid); dataVisualizer->GetTemplate()->SetFont(GetFont()); dataVisualizers[i] = dataVisualizer; auto cell = dataCells[i]; auto composition = dataVisualizer->GetTemplate(); composition->SetAlignmentToParent(Margin(0, 0, 0, 0)); cell->AddChild(composition); } dataVisualizer->BeforeVisualizeCell(dataGrid->GetItemProvider(), itemIndex, i); } GridPos selectedCell = dataGrid->GetSelectedCell(); if (selectedCell.row == itemIndex) { NotifySelectCell(selectedCell.column); } else { NotifySelectCell(-1); } } UpdateSubItemSize(); } void DefaultDataGridItemTemplate::OnSelectedChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if (!GetSelected()) { NotifySelectCell(-1); } } void DefaultDataGridItemTemplate::OnFontChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { for (auto visualizer : dataVisualizers) { visualizer->GetTemplate()->SetFont(GetFont()); } if (currentEditor) { currentEditor->GetTemplate()->SetFont(GetFont()); } } void DefaultDataGridItemTemplate::OnContextChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { for (auto visualizer : dataVisualizers) { visualizer->GetTemplate()->SetContext(GetContext()); } if (currentEditor) { currentEditor->GetTemplate()->SetContext(GetContext()); } } void DefaultDataGridItemTemplate::OnVisuallyEnabledChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { for (auto visualizer : dataVisualizers) { visualizer->GetTemplate()->SetVisuallyEnabled(GetVisuallyEnabled()); } if (currentEditor) { currentEditor->GetTemplate()->SetVisuallyEnabled(GetVisuallyEnabled()); } } DefaultDataGridItemTemplate::DefaultDataGridItemTemplate() { } DefaultDataGridItemTemplate::~DefaultDataGridItemTemplate() { for (auto visualizer : dataVisualizers) { visualizer->NotifyDeletedTemplate(); } if (currentEditor) { currentEditor->NotifyDeletedTemplate(); } } void DefaultDataGridItemTemplate::UpdateSubItemSize() { if (auto dataGrid = dynamic_cast(listControl)) { vint columnCount = dataGrid->listViewItemView->GetColumnCount(); if (columnCount > textTable->GetColumns()) { columnCount = textTable->GetColumns(); } for (vint i = 0; i < columnCount; i++) { textTable->SetColumnOption(i, GuiCellOption::AbsoluteOption(dataGrid->columnItemView->GetColumnSize(i))); } } } bool DefaultDataGridItemTemplate::IsEditorOpened() { return currentEditor != nullptr; } void DefaultDataGridItemTemplate::NotifyOpenEditor(vint column, IDataEditor* editor) { currentEditor = editor; if (currentEditor) { auto cell = textTable->GetSitedCell(0, column); auto* editorBounds = currentEditor->GetTemplate(); editorBounds->SetFont(GetFont()); editorBounds->SetContext(GetContext()); if (editorBounds->GetParent() && editorBounds->GetParent() != cell) { editorBounds->GetParent()->RemoveChild(editorBounds); } editorBounds->SetAlignmentToParent(Margin(0, 0, 0, 0)); cell->AddChild(editorBounds); if (auto focusControl = currentEditor->GetTemplate()->GetFocusControl()) { focusControl->SetFocused(); } dataVisualizers[column]->GetTemplate()->SetVisible(false); } } void DefaultDataGridItemTemplate::NotifyCloseEditor() { if (currentEditor) { for (vint i = 0; i < dataVisualizers.Count(); i++) { dataVisualizers[i]->GetTemplate()->SetVisible(true); } auto composition = currentEditor->GetTemplate(); if (composition->GetParent()) { composition->GetParent()->RemoveChild(composition); } currentEditor = nullptr; } } void DefaultDataGridItemTemplate::NotifySelectCell(vint column) { for (vint i = 0; i < dataVisualizers.Count(); i++) { dataVisualizers[i]->SetSelected(i == column); } } void DefaultDataGridItemTemplate::NotifyCellEdited() { for (vint i = 0; i < dataVisualizers.Count(); i++) { dataVisualizers[i]->BeforeVisualizeCell(listControl->GetItemProvider(), GetIndex(), i); } } } /*********************************************************************** GuiVirtualDataGrid (Editor) ***********************************************************************/ using namespace list; compositions::IGuiAltActionHost* GuiVirtualDataGrid::GetActivatingAltHost() { if (currentEditor) { if (auto focusControl = currentEditor->GetTemplate()->GetFocusControl()) { if (auto action = focusControl->QueryTypedService()) { if (action->IsAltAvailable() && action->IsAltEnabled()) { SetAltComposition(currentEditor->GetTemplate()); SetAltControl(focusControl, true); return this; } } } } SetAltComposition(nullptr); SetAltControl(nullptr, false); return GuiVirtualListView::GetActivatingAltHost(); } void GuiVirtualDataGrid::NotifySelectionChanged(bool triggeredByItemContentModified) { GuiVirtualListView::NotifySelectionChanged(triggeredByItemContentModified); if (!skipOnSelectionChanged && !triggeredByItemContentModified) { vint row = GetSelectedItemIndex(); if (row == selectedCell.row) { // do nothing } else if (row != -1) { if (selectedCell.row != row && selectedCell.column != -1) { SelectCell({ row,selectedCell.column }, false); } else { SelectCell({ row,0 }, false); } } else { StopEdit(); NotifySelectCell(-1, -1); } } } void GuiVirtualDataGrid::OnItemModified(vint start, vint count, vint newCount, bool itemReferenceUpdated) { GuiVirtualListView::OnItemModified(start, count, newCount, itemReferenceUpdated); if (!GetItemProvider()->IsEditing()) { StopEdit(); } } void GuiVirtualDataGrid::OnStyleInstalled(vint index, ItemStyle* style, bool refreshPropertiesOnly) { GuiVirtualListView::OnStyleInstalled(index, style, refreshPropertiesOnly); if (auto itemStyle = dynamic_cast(style)) { if (selectedCell.row == index && selectedCell.column != -1) { itemStyle->NotifySelectCell(selectedCell.column); } } } void GuiVirtualDataGrid::OnStyleUninstalled(ItemStyle* style) { GuiVirtualListView::OnStyleUninstalled(style); if (auto itemStyle = dynamic_cast(style)) { if (itemStyle->IsEditorOpened()) { itemStyle->NotifyCloseEditor(); currentEditor = nullptr; currentEditorPos = { -1,-1 }; } } } void GuiVirtualDataGrid::NotifyCloseEditor() { if (currentEditorPos.row != -1 && GetArranger()) { auto style = GetArranger()->GetVisibleStyle(currentEditorPos.row); if (auto itemStyle = dynamic_cast(style)) { itemStyle->NotifyCloseEditor(); } } } void GuiVirtualDataGrid::NotifySelectCell(vint row, vint column) { if (selectedCell.row != row || selectedCell.column != column) { selectedCell = { row, column }; SelectedCellChanged.Execute(GetNotifyEventArguments()); auto style = GetArranger()->GetVisibleStyle(row); if (auto itemStyle = dynamic_cast(style)) { itemStyle->NotifySelectCell(column); } } } bool GuiVirtualDataGrid::StartEdit(vint row, vint column) { StopEdit(); NotifySelectCell(row, column); auto style = GetArranger()->GetVisibleStyle(row); if (auto itemStyle = dynamic_cast(style)) { if (auto factory = dataGridView->GetCellDataEditorFactory(row, column)) { currentEditorOpeningEditor = true; currentEditorPos = { row,column }; currentEditor = factory->CreateEditor(this); if (auto focusControl = currentEditor->GetTemplate()->GetFocusControl()) { focusControl->SetAlt(L"E"); } currentEditor->BeforeEditCell(GetItemProvider(), row, column); itemStyle->NotifyOpenEditor(column, currentEditor.Obj()); currentEditorOpeningEditor = false; return true; } } return false; } void GuiVirtualDataGrid::StopEdit() { if (GetItemProvider()->IsEditing()) { NotifyCloseEditor(); } else { if (currentEditorPos != GridPos{-1, -1}) { if (currentEditor) { NotifyCloseEditor(); } } } SetAltComposition(nullptr); SetAltControl(nullptr, false); currentEditor = nullptr; currentEditorPos = { -1,-1 }; } /*********************************************************************** GuiVirtualDataGrid (IDataGridContext) ***********************************************************************/ templates::GuiListViewTemplate* GuiVirtualDataGrid::GetListViewControlTemplate() { return TypedControlTemplateObject(true); } void GuiVirtualDataGrid::RequestSaveData() { #define ERROR_MESSAGE_PREFIX L"vl::presentation::controls::list::DefaultTextListItemTemplate::OnBulletSelectedChanged(GuiGraphicsComposition*, GuiEventArgs&)#" if (currentEditor && !currentEditorOpeningEditor) { GuiControl* focusedControl = nullptr; if (auto controlHost = GetRelatedControlHost()) { if (auto graphicsHost = controlHost->GetGraphicsHost()) { if (auto focusComposition = graphicsHost->GetFocusedComposition()) { focusedControl = focusComposition->GetRelatedControl(); } } } GetItemProvider()->PushEditing(); dataGridView->SetBindingCellValue(currentEditorPos.row, currentEditorPos.column, currentEditor->GetTemplate()->GetCellValue()); CHECK_ERROR(GetItemProvider()->PopEditing(), ERROR_MESSAGE_PREFIX L"BeginEditListItem and EndEditListItem calls are not paired."); auto style = GetArranger()->GetVisibleStyle(currentEditorPos.row); if (auto itemStyle = dynamic_cast(style)) { itemStyle->NotifyCellEdited(); } if (currentEditor && focusedControl) { focusedControl->SetFocused(); } } #undef ERROR_MESSAGE_PREFIX } /*********************************************************************** GuiVirtualDataGrid ***********************************************************************/ void GuiVirtualDataGrid::OnColumnClicked(compositions::GuiGraphicsComposition* sender, compositions::GuiItemEventArgs& arguments) { if(dataGridView->IsColumnSortable(arguments.itemIndex)) { switch(columnItemView->GetSortingState(arguments.itemIndex)) { case ColumnSortingState::NotSorted: dataGridView->SortByColumn(arguments.itemIndex, true); break; case ColumnSortingState::Ascending: dataGridView->SortByColumn(arguments.itemIndex, false); break; case ColumnSortingState::Descending: dataGridView->SortByColumn(-1, false); break; } } } void GuiVirtualDataGrid::OnKeyDown(compositions::GuiGraphicsComposition* sender, compositions::GuiKeyEventArgs& arguments) { if (selectedCell.row != -1) { if (arguments.code == VKEY::KEY_RETURN) { RequestSaveData(); SelectCell(selectedCell, !currentEditor); arguments.handled = true; if (!currentEditor) { SetFocused(); } arguments.handled = true; } else if (arguments.code == VKEY::KEY_ESCAPE) { if (currentEditor) { SelectCell(currentEditorPos, false); SetFocused(); arguments.handled = true; } } else { vint columnOffset = 0; switch (arguments.code) { case VKEY::KEY_LEFT: columnOffset = -1; arguments.handled = true; break; case VKEY::KEY_RIGHT: columnOffset = 1; arguments.handled = true; break; default: return; } vint column = selectedCell.column + columnOffset; if (column < 0) { column = 0; } else if (column >= listViewItemView->GetColumnCount()) { column = listViewItemView->GetColumnCount(); } SelectCell({ selectedCell.row, column }, false); } } } void GuiVirtualDataGrid::OnKeyUp(compositions::GuiGraphicsComposition* sender, compositions::GuiKeyEventArgs& arguments) { } GuiVirtualDataGrid::GuiVirtualDataGrid(theme::ThemeName themeName, list::IItemProvider* _itemProvider) :GuiVirtualListView(themeName, _itemProvider) { listViewItemView = dynamic_cast(_itemProvider->RequestView(WString::Unmanaged(IListViewItemView::Identifier))); columnItemView = dynamic_cast(_itemProvider->RequestView(WString::Unmanaged(ListViewColumnItemArranger::IColumnItemView::Identifier))); dataGridView = dynamic_cast(_itemProvider->RequestView(WString::Unmanaged(IDataGridView::Identifier))); { auto mainProperty = [](const Value&) { return new MainColumnVisualizerTemplate; }; auto subProperty = [](const Value&) { return new SubColumnVisualizerTemplate; }; auto focusRectangleProperty = [](const Value&) { return new FocusRectangleVisualizerTemplate; }; auto cellBorderProperty = [](const Value&) { return new CellBorderVisualizerTemplate; }; defaultMainColumnVisualizerFactory = Ptr(new DataVisualizerFactory(cellBorderProperty, Ptr(new DataVisualizerFactory(focusRectangleProperty, Ptr(new DataVisualizerFactory(mainProperty) ))))); defaultSubColumnVisualizerFactory = Ptr(new DataVisualizerFactory(cellBorderProperty, Ptr(new DataVisualizerFactory(focusRectangleProperty, Ptr(new DataVisualizerFactory(subProperty) ))))); } CHECK_ERROR(listViewItemView != nullptr, L"GuiVirtualDataGrid::GuiVirtualDataGrid(IStyleController*, GuiListControl::IItemProvider*)#Missing IListViewItemView from item provider."); CHECK_ERROR(columnItemView != nullptr, L"GuiVirtualDataGrid::GuiVirtualDataGrid(IStyleController*, GuiListControl::IItemProvider*)#Missing ListViewColumnItemArranger::IColumnItemView from item provider."); CHECK_ERROR(dataGridView != nullptr, L"GuiVirtualDataGrid::GuiVirtualDataGrid(IStyleController*, GuiListControl::IItemProvider*)#Missing IDataGridView from item provider."); SetViewToDefault(); ColumnClicked.AttachMethod(this, &GuiVirtualDataGrid::OnColumnClicked); focusableComposition->GetEventReceiver()->keyDown.AttachMethod(this, &GuiVirtualDataGrid::OnKeyDown); focusableComposition->GetEventReceiver()->keyUp.AttachMethod(this, &GuiVirtualDataGrid::OnKeyUp); SelectedCellChanged.SetAssociatedComposition(boundsComposition); } GuiVirtualDataGrid::~GuiVirtualDataGrid() { } list::IItemProvider* GuiVirtualDataGrid::GetItemProvider() { return GuiVirtualListView::GetItemProvider(); } void GuiVirtualDataGrid::SetViewToDefault() { SetStyleAndArranger( [](const Value&) { return new list::DefaultDataGridItemTemplate; }, Ptr(new list::ListViewColumnItemArranger) ); } GridPos GuiVirtualDataGrid::GetSelectedCell() { return selectedCell; } Ptr GuiVirtualDataGrid::GetOpenedEditor() { return currentEditor; } bool GuiVirtualDataGrid::SelectCell(const GridPos& value, bool openEditor) { bool validPos = 0 <= value.row && value.row < GetItemProvider()->Count() && 0 <= value.column && value.column < listViewItemView->GetColumnCount(); if (validPos && selectedCell == value) { if (currentEditor && !openEditor) { StopEdit(); } else if (!currentEditor && openEditor) { StartEdit(value.row, value.column); } return currentEditor != nullptr; } StopEdit(); if (validPos) { NotifySelectCell(value.row, value.column); if (openEditor) { EnsureItemVisible(value.row); if (GetMultiSelect()) { ClearSelection(); } } skipOnSelectionChanged = true; SetSelected(value.row, true); skipOnSelectionChanged = false; if (openEditor) { return StartEdit(value.row, value.column); } } else { NotifySelectCell(-1, -1); ClearSelection(); } return false; } } } } /*********************************************************************** .\CONTROLS\LISTCONTROLPACKAGE\GUIDATAGRIDEXTENSIONS.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { namespace list { using namespace compositions; using namespace elements; using namespace theme; using namespace templates; /*********************************************************************** DataVisualizerBase ***********************************************************************/ DataVisualizerBase::DataVisualizerBase() { } DataVisualizerBase::~DataVisualizerBase() { if (visualizerTemplate) { SafeDeleteComposition(visualizerTemplate); } } IDataVisualizerFactory* DataVisualizerBase::GetFactory() { return factory; } templates::GuiGridVisualizerTemplate* DataVisualizerBase::GetTemplate() { return visualizerTemplate; } void DataVisualizerBase::NotifyDeletedTemplate() { visualizerTemplate = nullptr; } void DataVisualizerBase::BeforeVisualizeCell(list::IItemProvider* itemProvider, vint row, vint column) { if (auto listViewItemView = dynamic_cast(dataGridContext->GetItemProvider()->RequestView(WString::Unmanaged(IListViewItemView::Identifier)))) { auto style = dataGridContext->GetListViewControlTemplate(); visualizerTemplate->SetPrimaryTextColor(style->GetPrimaryTextColor()); visualizerTemplate->SetSecondaryTextColor(style->GetSecondaryTextColor()); visualizerTemplate->SetItemSeparatorColor(style->GetItemSeparatorColor()); visualizerTemplate->SetLargeImage(listViewItemView->GetLargeImage(row)); visualizerTemplate->SetSmallImage(listViewItemView->GetSmallImage(row)); visualizerTemplate->SetText(column == 0 ? listViewItemView->GetText(row) : listViewItemView->GetSubItem(row, column - 1)); } if (auto dataGridView = dynamic_cast(dataGridContext->GetItemProvider()->RequestView(WString::Unmanaged(IDataGridView::Identifier)))) { visualizerTemplate->SetRowValue(itemProvider->GetBindingValue(row)); visualizerTemplate->SetCellValue(dataGridView->GetBindingCellValue(row, column)); } } void DataVisualizerBase::SetSelected(bool value) { if (visualizerTemplate) { visualizerTemplate->SetSelected(value); } } /*********************************************************************** DataVisualizerFactory ***********************************************************************/ DataVisualizerFactory::ItemTemplate* DataVisualizerFactory::CreateItemTemplate(controls::list::IDataGridContext* dataGridContext) { ItemTemplate* itemTemplate = templateFactory({}); CHECK_ERROR(itemTemplate, L"DataVisualizerFactory::CreateItemTemplate(IDataGridContext*)#An instance of GuiGridEditorTemplate is expected."); if (decoratedFactory) { auto childTemplate = decoratedFactory->CreateItemTemplate(dataGridContext); childTemplate->SetAlignmentToParent(Margin(0, 0, 0, 0)); itemTemplate->GetContainerComposition()->AddChild(childTemplate); #define FORWARD_EVENT(NAME)\ itemTemplate->NAME##Changed.AttachLambda([=](GuiGraphicsComposition* sender, GuiEventArgs& arguments)\ {\ childTemplate->Set##NAME(itemTemplate->Get##NAME());\ });\ #define FORWARD_EVENT_IMPL(CLASS, TYPE, NAME, VALUE) FORWARD_EVENT(NAME) GuiTemplate_PROPERTIES(FORWARD_EVENT_IMPL) GuiControlTemplate_PROPERTIES(FORWARD_EVENT_IMPL) GuiGridCellTemplate_PROPERTIES(FORWARD_EVENT_IMPL) GuiGridVisualizerTemplate_PROPERTIES(FORWARD_EVENT_IMPL) #undef FORWARD_EVENT_IMPL #undef FORWARD_EVENT } return itemTemplate; } DataVisualizerFactory::DataVisualizerFactory(TemplateProperty _templateFactory, Ptr _decoratedFactory) :templateFactory(_templateFactory) , decoratedFactory(_decoratedFactory) { } DataVisualizerFactory::~DataVisualizerFactory() { } Ptr DataVisualizerFactory::CreateVisualizer(controls::list::IDataGridContext* dataGridContext) { auto dataVisualizer = Ptr(new DataVisualizerBase); dataVisualizer->factory = this; dataVisualizer->dataGridContext = dataGridContext; dataVisualizer->visualizerTemplate = CreateItemTemplate(dataGridContext); return dataVisualizer; } /*********************************************************************** DataEditorBase ***********************************************************************/ void DataEditorBase::OnCellValueChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { dataGridContext->RequestSaveData(); } DataEditorBase::DataEditorBase() { } DataEditorBase::~DataEditorBase() { if (editorTemplate) { SafeDeleteComposition(editorTemplate); } } IDataEditorFactory* DataEditorBase::GetFactory() { return factory; } templates::GuiGridEditorTemplate* DataEditorBase::GetTemplate() { return editorTemplate; } void DataEditorBase::NotifyDeletedTemplate() { editorTemplate = nullptr; } void DataEditorBase::BeforeEditCell(list::IItemProvider* itemProvider, vint row, vint column) { if (auto listViewItemView = dynamic_cast(dataGridContext->GetItemProvider()->RequestView(WString::Unmanaged(IListViewItemView::Identifier)))) { auto style = dataGridContext->GetListViewControlTemplate(); editorTemplate->SetPrimaryTextColor(style->GetPrimaryTextColor()); editorTemplate->SetSecondaryTextColor(style->GetSecondaryTextColor()); editorTemplate->SetItemSeparatorColor(style->GetItemSeparatorColor()); editorTemplate->SetLargeImage(listViewItemView->GetLargeImage(row)); editorTemplate->SetSmallImage(listViewItemView->GetSmallImage(row)); editorTemplate->SetText(column == 0 ? listViewItemView->GetText(row) : listViewItemView->GetSubItem(row, column - 1)); } if (auto dataGridView = dynamic_cast(dataGridContext->GetItemProvider()->RequestView(WString::Unmanaged(IDataGridView::Identifier)))) { editorTemplate->SetRowValue(itemProvider->GetBindingValue(row)); editorTemplate->SetCellValue(dataGridView->GetBindingCellValue(row, column)); } editorTemplate->CellValueChanged.AttachMethod(this, &DataEditorBase::OnCellValueChanged); } bool DataEditorBase::GetCellValueSaved() { if (editorTemplate) { return editorTemplate->GetCellValueSaved(); } return true; } /*********************************************************************** DataEditorFactory ***********************************************************************/ DataEditorFactory::DataEditorFactory(TemplateProperty _templateFactory) :templateFactory(_templateFactory) { } DataEditorFactory::~DataEditorFactory() { } Ptr DataEditorFactory::CreateEditor(controls::list::IDataGridContext* dataGridContext) { auto editor = Ptr(new DataEditorBase); editor->factory = this; editor->dataGridContext = dataGridContext; ItemTemplate* itemTemplate = templateFactory({}); CHECK_ERROR(itemTemplate, L"DataEditorFactory::CreateEditor(IDataGridContext*)#An instance of GuiGridEditorTemplate is expected."); editor->editorTemplate = itemTemplate; return editor; } /*********************************************************************** MainColumnVisualizerTemplate ***********************************************************************/ void MainColumnVisualizerTemplate::OnTextChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { text->SetText(GetText()); } void MainColumnVisualizerTemplate::OnFontChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { text->SetFont(GetFont()); } void MainColumnVisualizerTemplate::OnTextColorChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { text->SetColor(GetPrimaryTextColor()); } void MainColumnVisualizerTemplate::OnSmallImageChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { auto imageData = GetSmallImage(); if (imageData) { image->SetImage(imageData->GetImage(), imageData->GetFrameIndex()); } else { image->SetImage(nullptr); } } MainColumnVisualizerTemplate::MainColumnVisualizerTemplate() { GuiTableComposition* table = new GuiTableComposition; table->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); table->SetRowsAndColumns(3, 2); table->SetRowOption(0, GuiCellOption::PercentageOption(0.5)); table->SetRowOption(1, GuiCellOption::MinSizeOption()); table->SetRowOption(2, GuiCellOption::PercentageOption(0.5)); table->SetColumnOption(0, GuiCellOption::MinSizeOption()); table->SetColumnOption(1, GuiCellOption::PercentageOption(1.0)); table->SetCellPadding(2); { GuiCellComposition* cell = new GuiCellComposition; table->AddChild(cell); cell->SetSite(1, 0, 1, 1); cell->SetPreferredMinSize(Size(16, 16)); image = GuiImageFrameElement::Create(); image->SetStretch(true); cell->SetOwnedElement(Ptr(image)); } { GuiCellComposition* cell = new GuiCellComposition; table->AddChild(cell); cell->SetSite(0, 1, 3, 1); auto textBounds = new GuiBoundsComposition; cell->AddChild(textBounds); textBounds->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElement); textBounds->SetAlignmentToParent(Margin(0, 0, 8, 0)); text = GuiSolidLabelElement::Create(); text->SetAlignments(Alignment::Left, Alignment::Center); text->SetEllipse(true); textBounds->SetOwnedElement(Ptr(text)); } table->SetAlignmentToParent(Margin(0, 0, 0, 0)); SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); AddChild(table); TextChanged.AttachMethod(this, &MainColumnVisualizerTemplate::OnTextChanged); FontChanged.AttachMethod(this, &MainColumnVisualizerTemplate::OnFontChanged); PrimaryTextColorChanged.AttachMethod(this, &MainColumnVisualizerTemplate::OnTextColorChanged); SmallImageChanged.AttachMethod(this, &MainColumnVisualizerTemplate::OnSmallImageChanged); TextChanged.Execute(compositions::GuiEventArgs(this)); FontChanged.Execute(compositions::GuiEventArgs(this)); PrimaryTextColorChanged.Execute(compositions::GuiEventArgs(this)); SmallImageChanged.Execute(compositions::GuiEventArgs(this)); } MainColumnVisualizerTemplate::~MainColumnVisualizerTemplate() { } /*********************************************************************** SubColumnVisualizerTemplate ***********************************************************************/ void SubColumnVisualizerTemplate::OnTextChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { text->SetText(GetText()); } void SubColumnVisualizerTemplate::OnFontChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { text->SetFont(GetFont()); } void SubColumnVisualizerTemplate::OnTextColorChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { text->SetColor(GetSecondaryTextColor()); } void SubColumnVisualizerTemplate::Initialize(bool fixTextColor) { SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); auto textBounds = new GuiBoundsComposition; AddChild(textBounds); textBounds->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElement); textBounds->SetAlignmentToParent(Margin(8, 0, 8, 0)); text = GuiSolidLabelElement::Create(); text->SetVerticalAlignment(Alignment::Center); text->SetEllipse(true); textBounds->SetOwnedElement(Ptr(text)); TextChanged.AttachMethod(this, &SubColumnVisualizerTemplate::OnTextChanged); FontChanged.AttachMethod(this, &SubColumnVisualizerTemplate::OnFontChanged); if (!fixTextColor) { SecondaryTextColorChanged.AttachMethod(this, &SubColumnVisualizerTemplate::OnTextColorChanged); } TextChanged.Execute(compositions::GuiEventArgs(this)); FontChanged.Execute(compositions::GuiEventArgs(this)); if (!fixTextColor) { SecondaryTextColorChanged.Execute(compositions::GuiEventArgs(this)); } } SubColumnVisualizerTemplate::SubColumnVisualizerTemplate(bool fixTextColor) { Initialize(fixTextColor); } SubColumnVisualizerTemplate::SubColumnVisualizerTemplate() { Initialize(false); } SubColumnVisualizerTemplate::~SubColumnVisualizerTemplate() { } /*********************************************************************** HyperlinkVisualizerTemplate ***********************************************************************/ void HyperlinkVisualizerTemplate::label_MouseEnter(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { FontProperties font = text->GetFont(); font.underline = true; text->SetFont(font); } void HyperlinkVisualizerTemplate::label_MouseLeave(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { FontProperties font = text->GetFont(); font.underline = false; text->SetFont(font); } HyperlinkVisualizerTemplate::HyperlinkVisualizerTemplate() :SubColumnVisualizerTemplate(true) { text->SetColor(Color(0, 0, 255)); text->SetEllipse(true); GetEventReceiver()->mouseEnter.AttachMethod(this, &HyperlinkVisualizerTemplate::label_MouseEnter); GetEventReceiver()->mouseLeave.AttachMethod(this, &HyperlinkVisualizerTemplate::label_MouseLeave); SetAssociatedCursor(GetCurrentController()->ResourceService()->GetSystemCursor(INativeCursor::Hand)); } HyperlinkVisualizerTemplate::~HyperlinkVisualizerTemplate() { } /*********************************************************************** CellBorderVisualizerTemplate ***********************************************************************/ void FocusRectangleVisualizerTemplate::OnSelectedChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { focusComposition->SetVisible(GetSelected()); } FocusRectangleVisualizerTemplate::FocusRectangleVisualizerTemplate() { SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); focusComposition = new GuiBoundsComposition(); { auto focus = Ptr(GuiFocusRectangleElement::Create()); focusComposition->SetOwnedElement(focus); focusComposition->SetAlignmentToParent(Margin(1, 1, 1, 1)); } auto container = new GuiBoundsComposition(); { container->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); container->SetAlignmentToParent(Margin(2, 2, 2, 2)); } AddChild(focusComposition); AddChild(container); SetContainerComposition(container); SelectedChanged.AttachMethod(this, &FocusRectangleVisualizerTemplate::OnSelectedChanged); SelectedChanged.Execute(compositions::GuiEventArgs(this)); } FocusRectangleVisualizerTemplate::~FocusRectangleVisualizerTemplate() { } /*********************************************************************** CellBorderVisualizerTemplate ***********************************************************************/ void CellBorderVisualizerTemplate::OnItemSeparatorColorChanged(GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { border1->SetColor(GetItemSeparatorColor()); border2->SetColor(GetItemSeparatorColor()); } CellBorderVisualizerTemplate::CellBorderVisualizerTemplate() { SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); auto bounds1 = new GuiBoundsComposition; { border1 = GuiSolidBorderElement::Create(); bounds1->SetOwnedElement(Ptr(border1)); bounds1->SetAlignmentToParent(Margin(-1, 0, 0, 0)); } auto bounds2 = new GuiBoundsComposition; { border2 = GuiSolidBorderElement::Create(); bounds2->SetOwnedElement(Ptr(border2)); bounds2->SetAlignmentToParent(Margin(0, -1, 0, 0)); } auto container = new GuiBoundsComposition(); { container->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); container->SetAlignmentToParent(Margin(0, 0, 1, 1)); } AddChild(bounds1); AddChild(bounds2); AddChild(container); SetContainerComposition(container); ItemSeparatorColorChanged.AttachMethod(this, &CellBorderVisualizerTemplate::OnItemSeparatorColorChanged); ItemSeparatorColorChanged.Execute(compositions::GuiEventArgs(this)); } CellBorderVisualizerTemplate::~CellBorderVisualizerTemplate() { } } } } } /*********************************************************************** .\CONTROLS\LISTCONTROLPACKAGE\GUILISTCONTROLITEMARRANGERS.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { using namespace collections; using namespace elements; using namespace compositions; namespace list { /*********************************************************************** RangedItemArrangerBase (ItemSource) ***********************************************************************/ class ArrangerItemSource : public Object, public virtual description::IValueObservableList { protected: list::IItemProvider* itemProvider = nullptr; public: ArrangerItemSource(list::IItemProvider* _itemProvider) : itemProvider(_itemProvider) { } vint GetCount() override { return itemProvider->Count(); } description::Value Get(vint index) override { return itemProvider->GetBindingValue(index); } Ptr CreateEnumerator() override { CHECK_FAIL(L"ArrangerItemSource::CreateEnumerator should not be called."); } bool Contains(const description::Value& value) override { CHECK_FAIL(L"ArrangerItemSource::Contains should not be called."); } vint IndexOf(const description::Value& value) override { CHECK_FAIL(L"ArrangerItemSource::IndexOf should not be called."); } void Set(vint index, const description::Value& value) override { CHECK_FAIL(L"ArrangerItemSource::Set should not be called."); } vint Add(const description::Value& value) override { CHECK_FAIL(L"ArrangerItemSource::Add should not be called."); } vint Insert(vint index, const description::Value& value) override { CHECK_FAIL(L"ArrangerItemSource::Insert should not be called."); } bool Remove(const description::Value& value) override { CHECK_FAIL(L"ArrangerItemSource::Remove should not be called."); } bool RemoveAt(vint index) override { CHECK_FAIL(L"ArrangerItemSource::RemoveAt should not be called."); } void Clear() override { CHECK_FAIL(L"ArrangerItemSource::Clear should not be called."); } }; /*********************************************************************** RangedItemArrangerBase ***********************************************************************/ void RangedItemArrangerBase::OnViewLocationChanged(compositions::GuiGraphicsComposition* composition, compositions::GuiEventArgs& arguments) { if (callback) { callback->SetViewLocation(repeat->GetViewLocation()); } } void RangedItemArrangerBase::OnTotalSizeChanged(compositions::GuiGraphicsComposition* composition, compositions::GuiEventArgs& arguments) { if (callback) { callback->OnTotalSizeChanged(); } } void RangedItemArrangerBase::OnAdoptedSizeInvalidated(compositions::GuiGraphicsComposition* composition, compositions::GuiEventArgs& arguments) { if (callback) { callback->OnAdoptedSizeChanged(); } } RangedItemArrangerBase::RangedItemArrangerBase(compositions::GuiVirtualRepeatCompositionBase* _repeat) : repeat(_repeat) { repeat->ViewLocationChanged.AttachMethod(this, &RangedItemArrangerBase::OnViewLocationChanged); repeat->TotalSizeChanged.AttachMethod(this, &RangedItemArrangerBase::OnTotalSizeChanged); repeat->AdoptedSizeInvalidated.AttachMethod(this, &RangedItemArrangerBase::OnAdoptedSizeInvalidated); } RangedItemArrangerBase::~RangedItemArrangerBase() { SafeDeleteComposition(repeat); } void RangedItemArrangerBase::OnAttached(list::IItemProvider* provider) { itemProvider = provider; if (provider) { itemSource = Ptr(new ArrangerItemSource(provider)); repeat->SetItemSource(itemSource); } else { repeat->SetItemSource(nullptr); itemSource = nullptr; } } void RangedItemArrangerBase::OnItemModified(vint start, vint count, vint newCount, bool itemReferenceUpdated) { if (itemSource && itemReferenceUpdated) { itemSource->ItemChanged(start, count, newCount); } } void RangedItemArrangerBase::AttachListControl(GuiListControl* value) { listControl = value; repeat->SetAxis(Ptr(listControl->GetAxis())); } void RangedItemArrangerBase::DetachListControl() { repeat->SetAxis(nullptr); listControl = nullptr; } GuiListControl::IItemArrangerCallback* RangedItemArrangerBase::GetCallback() { return callback; } void RangedItemArrangerBase::SetCallback(GuiListControl::IItemArrangerCallback* value) { if (callback != value) { if (callback) { repeat->GetParent()->RemoveChild(repeat); repeat->SetItemTemplate({}); } callback = value; if (callback) { callback->GetContainerComposition()->AddChild(repeat); repeat->SetItemTemplate([](const description::Value&)->templates::GuiTemplate* { CHECK_FAIL(L"This function should not be called, it is used to enable the virtual repeat composition."); }); } } } Size RangedItemArrangerBase::GetTotalSize() { if (callback && repeat) { return repeat->GetTotalSize(); } return Size(0, 0); } GuiListControl::ItemStyle* RangedItemArrangerBase::GetVisibleStyle(vint itemIndex) { auto bounds = repeat->GetVisibleStyle(itemIndex); return bounds ? callback->GetItem(bounds) : nullptr; } vint RangedItemArrangerBase::GetVisibleIndex(GuiListControl::ItemStyle* style) { auto bounds = callback->GetItemBounds(style); return repeat->GetVisibleIndex(bounds); } void RangedItemArrangerBase::ReloadVisibleStyles() { if (repeat) repeat->ResetLayout(true); } void RangedItemArrangerBase::OnViewChanged(Rect bounds) { repeat->SetViewLocation(bounds.LeftTop()); repeat->SetExpectedBounds(Rect({ 0,0 }, bounds.GetSize())); } vint RangedItemArrangerBase::FindItemByVirtualKeyDirection(vint itemIndex, compositions::KeyDirection key) { return repeat->FindItemByVirtualKeyDirection(itemIndex, key); } GuiListControl::EnsureItemVisibleResult RangedItemArrangerBase::EnsureItemVisible(vint itemIndex) { switch (repeat->EnsureItemVisible(itemIndex)) { case VirtualRepeatEnsureItemVisibleResult::Moved: return GuiListControl::EnsureItemVisibleResult::Moved; case VirtualRepeatEnsureItemVisibleResult::NotMoved: return GuiListControl::EnsureItemVisibleResult::NotMoved; default: return GuiListControl::EnsureItemVisibleResult::ItemNotExists; } } Size RangedItemArrangerBase::GetAdoptedSize(Size expectedSize) { return repeat->GetAdoptedSize(expectedSize); } /*********************************************************************** VirtualRepeatRangedItemArrangerBase ***********************************************************************/ FreeHeightItemArranger::FreeHeightItemArranger() { } FreeHeightItemArranger::~FreeHeightItemArranger() { } FixedHeightItemArranger::FixedHeightItemArranger() { } FixedHeightItemArranger::~FixedHeightItemArranger() { } FixedSizeMultiColumnItemArranger::FixedSizeMultiColumnItemArranger() { } FixedSizeMultiColumnItemArranger::~FixedSizeMultiColumnItemArranger() { } FixedHeightMultiColumnItemArranger::FixedHeightMultiColumnItemArranger() { } FixedHeightMultiColumnItemArranger::~FixedHeightMultiColumnItemArranger() { } } } } } /*********************************************************************** .\CONTROLS\LISTCONTROLPACKAGE\GUILISTCONTROLS.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { using namespace collections; using namespace elements; using namespace compositions; /*********************************************************************** GuiListControl::ItemCallback ***********************************************************************/ GuiListControl::ItemStyleRecord GuiListControl::ItemCallback::InstallStyle(ItemStyle* style, vint itemIndex) { templates::GuiTemplate* bounds = style; if (listControl->GetDisplayItemBackground()) { style->SetAlignmentToParent(Margin(0, 0, 0, 0)); auto backgroundButton = new GuiSelectableButton(theme::ThemeName::ListItemBackground); if (auto backgroundStyle = listControl->TypedControlTemplateObject(true)->GetBackgroundTemplate()) { backgroundButton->SetControlTemplate(backgroundStyle); } backgroundButton->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); backgroundButton->SetAutoFocus(false); backgroundButton->SetAutoSelection(false); backgroundButton->SetSelected(style->GetSelected()); backgroundButton->GetContainerComposition()->AddChild(style); bounds = new templates::GuiTemplate; bounds->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); bounds->AddChild(backgroundButton->GetBoundsComposition()); style->SelectedChanged.AttachLambda([=](GuiGraphicsComposition* sender, GuiEventArgs& arguments) { backgroundButton->SetSelected(style->GetSelected()); }); } listControl->OnStyleInstalled(itemIndex, style, false); return { style,bounds }; } GuiListControl::ItemStyleRecord GuiListControl::ItemCallback::UninstallStyle(vint index) { auto style = installedStyles.Keys()[index]; auto bounds = installedStyles.Values()[index]; listControl->OnStyleUninstalled(style); return { style,bounds }; } GuiListControl::ItemCallback::ItemCallback(GuiListControl* _listControl) :listControl(_listControl) { } GuiListControl::ItemCallback::~ItemCallback() { ClearCache(); } void GuiListControl::ItemCallback::ClearCache() { // TODO: (enumerable) foreach:indexed for (vint i = 0; i < installedStyles.Count(); i++) { auto [style, bounds] = UninstallStyle(i); SafeDeleteComposition(bounds); } installedStyles.Clear(); } void GuiListControl::ItemCallback::OnAttached(list::IItemProvider* provider) { itemProvider = provider; } void GuiListControl::ItemCallback::OnItemModified(vint start, vint count, vint newCount, bool itemReferenceUpdated) { listControl->OnItemModified(start, count, newCount, itemReferenceUpdated); } GuiListControl::ItemStyle* GuiListControl::ItemCallback::CreateItem(vint itemIndex) { #define ERROR_MESSAGE_PREFIX L"GuiListControl::ItemCallback::RequestItem(vint)#" CHECK_ERROR(0 <= itemIndex && itemIndex < itemProvider->Count(), ERROR_MESSAGE_PREFIX L"Index out of range."); CHECK_ERROR(listControl->itemStyleProperty, ERROR_MESSAGE_PREFIX L"SetItemTemplate function should be called before adding items to the list control."); auto style = listControl->itemStyleProperty(itemProvider->GetBindingValue(itemIndex)); auto record = InstallStyle(style, itemIndex); installedStyles.Add(record); return style; #undef ERROR_MESSAGE_PREFIX } GuiListControl::ItemStyleBounds* GuiListControl::ItemCallback::GetItemBounds(ItemStyle * style) { #define ERROR_MESSAGE_PREFIX L"GuiListControl::ItemCallback::GetItemBounds(GuiListItemTemplate*)#The style is not created from CreateItem." vint index = installedStyles.Keys().IndexOf(style); CHECK_ERROR(index != -1, ERROR_MESSAGE_PREFIX); return installedStyles.Values()[index]; #undef ERROR_MESSAGE_PREFIX } GuiListControl::ItemStyle* GuiListControl::ItemCallback::GetItem(ItemStyleBounds* bounds) { #define ERROR_MESSAGE_PREFIX L"GuiListControl::ItemCallback::GetItem(GuiTemplate*)#The bounds is not created from CreateItem." auto style = dynamic_cast(bounds); if (style) return style; CHECK_ERROR(bounds->Children().Count() == 1, ERROR_MESSAGE_PREFIX); auto backgroundButton = dynamic_cast(bounds->Children()[0]->GetAssociatedControl()); CHECK_ERROR(backgroundButton != nullptr, ERROR_MESSAGE_PREFIX); CHECK_ERROR(backgroundButton->GetContainerComposition()->Children().Count() == 1, ERROR_MESSAGE_PREFIX); style = dynamic_cast(backgroundButton->GetContainerComposition()->Children()[0]); CHECK_ERROR(style != nullptr, ERROR_MESSAGE_PREFIX); vint index = installedStyles.Keys().IndexOf(style); CHECK_ERROR(index != -1, ERROR_MESSAGE_PREFIX); CHECK_ERROR(installedStyles.Values()[index] == bounds, ERROR_MESSAGE_PREFIX); return style; #undef ERROR_MESSAGE_PREFIX } void GuiListControl::ItemCallback::ReleaseItem(ItemStyle* style) { #define ERROR_MESSAGE_PREFIX L"GuiListControl::ItemCallback::GetItemBounds(GuiListItemTemplate*)#The style is not created from CreateItem." vint index = installedStyles.Keys().IndexOf(style); CHECK_ERROR(index != -1, ERROR_MESSAGE_PREFIX); auto bounds = UninstallStyle(index).value; installedStyles.Remove(style); SafeDeleteComposition(bounds); #undef ERROR_MESSAGE_PREFIX } void GuiListControl::ItemCallback::SetViewLocation(Point value) { listControl->SetViewPosition(value); } compositions::GuiGraphicsComposition* GuiListControl::ItemCallback::GetContainerComposition() { return listControl->GetContainerComposition(); } void GuiListControl::ItemCallback::OnTotalSizeChanged() { listControl->CalculateView(); } void GuiListControl::ItemCallback::OnAdoptedSizeChanged() { listControl->AdoptedSizeInvalidated.Execute(listControl->GetNotifyEventArguments()); } /*********************************************************************** GuiListControl ***********************************************************************/ void GuiListControl::BeforeControlTemplateUninstalled_() { } void GuiListControl::AfterControlTemplateInstalled_(bool initialize) { if (itemArranger) { itemArranger->ReloadVisibleStyles(); CalculateView(); } } void GuiListControl::OnItemModified(vint start, vint count, vint newCount, bool itemReferenceUpdated) { // this function is executed before RangedItemArrangerBase::OnItemModified // but we only handle itemReferenceUpdated==false // so RangedItemArrangerBase::GetVisibleStyle is good here // even it is possible that the style object will be replaced later // OnStyleInstalled will be executed on affected style objects anyway if (!itemReferenceUpdated && itemArranger && count == newCount) { for (vint i = 0; i < newCount; i++) { vint index = start + i; if (auto style = itemArranger->GetVisibleStyle(index)) { OnStyleInstalled(index, style, true); } } } } void GuiListControl::OnStyleInstalled(vint itemIndex, ItemStyle* style, bool refreshPropertiesOnly) { style->SetFont(GetDisplayFont()); style->SetContext(GetContext()); style->SetText(itemProvider->GetTextValue(itemIndex)); style->SetVisuallyEnabled(GetVisuallyEnabled()); style->SetSelected(false); style->SetIndex(itemIndex); style->SetAssociatedListControl(this); if (!refreshPropertiesOnly) { AttachItemEvents(style); } } void GuiListControl::OnStyleUninstalled(ItemStyle* style) { DetachItemEvents(style); } void GuiListControl::OnRenderTargetChanged(elements::IGuiGraphicsRenderTarget* renderTarget) { SetStyleAndArranger(itemStyleProperty, itemArranger); GuiScrollView::OnRenderTargetChanged(renderTarget); } void GuiListControl::OnBeforeReleaseGraphicsHost() { GuiScrollView::OnBeforeReleaseGraphicsHost(); SetStyleAndArranger({}, nullptr); } Size GuiListControl::QueryFullSize() { fullSize = itemArranger ? itemArranger->GetTotalSize() : Size(0, 0); return fullSize; } void GuiListControl::UpdateView(Rect viewBounds) { if (itemArranger) { itemArranger->OnViewChanged(viewBounds); } } void GuiListControl::OnBoundsMouseButtonDown(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) { if(GetVisuallyEnabled()) { SetFocused(); } } void GuiListControl::SetStyleAndArranger(ItemStyleProperty styleProperty, Ptr arranger) { if (itemArranger) { itemProvider->DetachCallback(itemArranger.Obj()); itemArranger->DetachListControl(); itemArranger->SetCallback(nullptr); } callback->ClearCache(); itemStyleProperty = styleProperty; itemArranger = arranger; if (itemArranger) { itemArranger->SetCallback(callback.Obj()); itemArranger->AttachListControl(this); itemProvider->AttachCallback(itemArranger.Obj()); } if (auto scroll = GetVerticalScroll()) { scroll->SetPosition(0); } if (auto scroll = GetHorizontalScroll()) { scroll->SetPosition(0); } CalculateView(); } void GuiListControl::UpdateDisplayFont() { GuiControl::UpdateDisplayFont(); for (auto style : visibleStyles.Keys()) { style->SetFont(GetDisplayFont()); } } void GuiListControl::OnVisuallyEnabledChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { for (auto style : visibleStyles.Keys()) { style->SetVisuallyEnabled(GetVisuallyEnabled()); } } void GuiListControl::OnContextChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { for (auto style : visibleStyles.Keys()) { style->SetContext(GetContext()); } } void GuiListControl::OnItemMouseEvent(compositions::GuiItemMouseEvent& itemEvent, ItemStyle* style, compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) { if (itemArranger && GetVisuallyEnabled()) { vint itemIndex = itemArranger->GetVisibleIndex(style); if (itemIndex != -1) { GuiItemMouseEventArgs redirectArguments; (GuiMouseEventArgs&)redirectArguments = arguments; redirectArguments.compositionSource = boundsComposition; redirectArguments.eventSource = boundsComposition; redirectArguments.itemIndex = itemIndex; itemEvent.Execute(redirectArguments); arguments = redirectArguments; } } } void GuiListControl::OnItemNotifyEvent(compositions::GuiItemNotifyEvent& itemEvent, ItemStyle* style, compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if (itemArranger && GetVisuallyEnabled()) { vint itemIndex = itemArranger->GetVisibleIndex(style); if (itemIndex != -1) { GuiItemEventArgs redirectArguments; (GuiEventArgs&)redirectArguments = arguments; redirectArguments.compositionSource = boundsComposition; redirectArguments.eventSource = boundsComposition; redirectArguments.itemIndex = itemIndex; itemEvent.Execute(redirectArguments); arguments = redirectArguments; } } } #define ATTACH_ITEM_MOUSE_EVENT(EVENTNAME, ITEMEVENTNAME)\ {\ helper->EVENTNAME##Handler = style->GetEventReceiver()->EVENTNAME.AttachFunction(\ [this, style](GuiGraphicsComposition* sender, GuiMouseEventArgs& args){ OnItemMouseEvent(ITEMEVENTNAME, style, sender, args); }\ );\ }\ #define ATTACH_ITEM_NOTIFY_EVENT(EVENTNAME, ITEMEVENTNAME)\ {\ helper->EVENTNAME##Handler = style->GetEventReceiver()->EVENTNAME.AttachFunction(\ [this, style](GuiGraphicsComposition* sender, GuiEventArgs& args){ OnItemNotifyEvent(ITEMEVENTNAME, style, sender, args); }\ );\ }\ void GuiListControl::AttachItemEvents(ItemStyle* style) { vint index=visibleStyles.Keys().IndexOf(style); if(index==-1) { auto helper=Ptr(new VisibleStyleHelper); visibleStyles.Add(style, helper); ATTACH_ITEM_MOUSE_EVENT(leftButtonDown, ItemLeftButtonDown); ATTACH_ITEM_MOUSE_EVENT(leftButtonUp, ItemLeftButtonUp); ATTACH_ITEM_MOUSE_EVENT(leftButtonDoubleClick, ItemLeftButtonDoubleClick); ATTACH_ITEM_MOUSE_EVENT(middleButtonDown, ItemMiddleButtonDown); ATTACH_ITEM_MOUSE_EVENT(middleButtonUp, ItemMiddleButtonUp); ATTACH_ITEM_MOUSE_EVENT(middleButtonDoubleClick, ItemMiddleButtonDoubleClick); ATTACH_ITEM_MOUSE_EVENT(rightButtonDown, ItemRightButtonDown); ATTACH_ITEM_MOUSE_EVENT(rightButtonUp, ItemRightButtonUp); ATTACH_ITEM_MOUSE_EVENT(rightButtonDoubleClick, ItemRightButtonDoubleClick); ATTACH_ITEM_MOUSE_EVENT(mouseMove, ItemMouseMove); ATTACH_ITEM_NOTIFY_EVENT(mouseEnter, ItemMouseEnter); ATTACH_ITEM_NOTIFY_EVENT(mouseLeave, ItemMouseLeave); } } #undef ATTACH_ITEM_MOUSE_EVENT #undef ATTACH_ITEM_NOTIFY_EVENT #define DETACH_ITEM_EVENT(EVENTNAME) style->GetEventReceiver()->EVENTNAME.Detach(helper->EVENTNAME##Handler) void GuiListControl::DetachItemEvents(ItemStyle* style) { vint index=visibleStyles.Keys().IndexOf(style); if(index!=-1) { Ptr helper=visibleStyles.Values().Get(index); visibleStyles.Remove(style); DETACH_ITEM_EVENT(leftButtonDown); DETACH_ITEM_EVENT(leftButtonUp); DETACH_ITEM_EVENT(leftButtonDoubleClick); DETACH_ITEM_EVENT(middleButtonDown); DETACH_ITEM_EVENT(middleButtonUp); DETACH_ITEM_EVENT(middleButtonDoubleClick); DETACH_ITEM_EVENT(rightButtonDown); DETACH_ITEM_EVENT(rightButtonUp); DETACH_ITEM_EVENT(rightButtonDoubleClick); DETACH_ITEM_EVENT(mouseMove); DETACH_ITEM_EVENT(mouseEnter); DETACH_ITEM_EVENT(mouseLeave); } } #undef DETACH_ITEM_EVENT GuiListControl::GuiListControl(theme::ThemeName themeName, list::IItemProvider* _itemProvider, bool acceptFocus) :GuiScrollView(themeName) , itemProvider(_itemProvider) { ContextChanged.AttachMethod(this, &GuiListControl::OnContextChanged); VisuallyEnabledChanged.AttachMethod(this, &GuiListControl::OnVisuallyEnabledChanged); ItemTemplateChanged.SetAssociatedComposition(boundsComposition); ArrangerChanged.SetAssociatedComposition(boundsComposition); AxisChanged.SetAssociatedComposition(boundsComposition); AdoptedSizeInvalidated.SetAssociatedComposition(boundsComposition); ItemLeftButtonDown.SetAssociatedComposition(boundsComposition); ItemLeftButtonUp.SetAssociatedComposition(boundsComposition); ItemLeftButtonDoubleClick.SetAssociatedComposition(boundsComposition); ItemMiddleButtonDown.SetAssociatedComposition(boundsComposition); ItemMiddleButtonUp.SetAssociatedComposition(boundsComposition); ItemMiddleButtonDoubleClick.SetAssociatedComposition(boundsComposition); ItemRightButtonDown.SetAssociatedComposition(boundsComposition); ItemRightButtonUp.SetAssociatedComposition(boundsComposition); ItemRightButtonDoubleClick.SetAssociatedComposition(boundsComposition); ItemMouseMove.SetAssociatedComposition(boundsComposition); ItemMouseEnter.SetAssociatedComposition(boundsComposition); ItemMouseLeave.SetAssociatedComposition(boundsComposition); callback = Ptr(new ItemCallback(this)); itemProvider->AttachCallback(callback.Obj()); axis = Ptr(new GuiDefaultAxis); if (acceptFocus) { boundsComposition->GetEventReceiver()->leftButtonDown.AttachMethod(this, &GuiListControl::OnBoundsMouseButtonDown); boundsComposition->GetEventReceiver()->middleButtonDown.AttachMethod(this, &GuiListControl::OnBoundsMouseButtonDown); boundsComposition->GetEventReceiver()->rightButtonDown.AttachMethod(this, &GuiListControl::OnBoundsMouseButtonDown); SetFocusableComposition(boundsComposition); } } GuiListControl::~GuiListControl() { if(itemArranger) { itemProvider->DetachCallback(itemArranger.Obj()); } callback->ClearCache(); itemStyleProperty = {}; itemArranger = nullptr; } list::IItemProvider* GuiListControl::GetItemProvider() { return itemProvider.Obj(); } GuiListControl::ItemStyleProperty GuiListControl::GetItemTemplate() { return itemStyleProperty; } void GuiListControl::SetItemTemplate(ItemStyleProperty value) { SetStyleAndArranger(value, itemArranger); ItemTemplateChanged.Execute(GetNotifyEventArguments()); } GuiListControl::IItemArranger* GuiListControl::GetArranger() { return itemArranger.Obj(); } void GuiListControl::SetArranger(Ptr value) { SetStyleAndArranger(itemStyleProperty, value); ArrangerChanged.Execute(GetNotifyEventArguments()); } compositions::IGuiAxis* GuiListControl::GetAxis() { return axis.Obj(); } void GuiListControl::SetAxis(Ptr value) { Ptr old = axis; axis = value; SetStyleAndArranger(itemStyleProperty, itemArranger); AxisChanged.Execute(GetNotifyEventArguments()); } bool GuiListControl::EnsureItemVisible(vint itemIndex) { if (itemIndex < 0 || itemIndex >= itemProvider->Count()) { return false; } if (!itemArranger) return false; auto result = itemArranger->EnsureItemVisible(itemIndex); if (result == EnsureItemVisibleResult::Moved) { if (auto host = GetBoundsComposition()->GetRelatedGraphicsHost()) { auto flag = GetDisposedFlag(); host->InvokeAfterRendering([=]() { if (!flag->IsDisposed()) { EnsureItemVisible(itemIndex); } }, { this,0 }); } } return result != EnsureItemVisibleResult::ItemNotExists; } Size GuiListControl::GetAdoptedSize(Size expectedSize) { if (itemArranger) { Size controlSize = boundsComposition->GetCachedBounds().GetSize(); Size viewSize = containerComposition->GetCachedBounds().GetSize(); vint dx = controlSize.x - viewSize.x; vint dy = controlSize.y - viewSize.y; if (dx < 0) dx = 0; if (dy < 0) dy = 0; auto hscroll = GetHorizontalScroll(); auto vscroll = GetVerticalScroll(); if (!vscroll || vscroll->GetBoundsComposition()->GetEventuallyVisible()) { if (adoptedSizeDiffWithScroll.x < dx) adoptedSizeDiffWithScroll.x = dx; } if (!vscroll || !vscroll->GetBoundsComposition()->GetEventuallyVisible()) { if (adoptedSizeDiffWithoutScroll.x < dx) adoptedSizeDiffWithoutScroll.x = dx; } if (!hscroll || hscroll->GetBoundsComposition()->GetEventuallyVisible()) { if (adoptedSizeDiffWithScroll.y < dy) adoptedSizeDiffWithScroll.y = dy; } if (!hscroll || !hscroll->GetBoundsComposition()->GetEventuallyVisible()) { if (adoptedSizeDiffWithoutScroll.y < dy) adoptedSizeDiffWithoutScroll.y = dy; } vint x = adoptedSizeDiffWithoutScroll.x != -1 ? adoptedSizeDiffWithoutScroll.x : adoptedSizeDiffWithScroll.x; vint y = adoptedSizeDiffWithoutScroll.y != -1 ? adoptedSizeDiffWithoutScroll.y : adoptedSizeDiffWithScroll.y; Size expectedViewSize(expectedSize.x - x, expectedSize.y - y); Size adoptedViewSize = itemArranger->GetAdoptedSize(expectedViewSize); Size adoptedSize = Size(adoptedViewSize.x + x, adoptedViewSize.y + y); return adoptedSize; } return expectedSize; } bool GuiListControl::GetDisplayItemBackground() { return displayItemBackground; } void GuiListControl::SetDisplayItemBackground(bool value) { if (displayItemBackground != value) { displayItemBackground = value; SetStyleAndArranger(itemStyleProperty, itemArranger); } } /*********************************************************************** GuiSelectableListControl ***********************************************************************/ void GuiSelectableListControl::NotifySelectionChanged(bool triggeredByItemContentModified) { SelectionChanged.Execute(GetNotifyEventArguments()); } void GuiSelectableListControl::OnItemModified(vint start, vint count, vint newCount, bool itemReferenceUpdated) { GuiListControl::OnItemModified(start, count, newCount, itemReferenceUpdated); if (count != newCount) { ClearSelection(); } else if (itemReferenceUpdated) { if (selectedItems.Count() > 0) { vint cmin = start; vint cmax = start + count - 1; vint smin = selectedItems[0]; vint smax = selectedItems[selectedItems.Count() - 1]; if (cmin <= smax && smin <= cmax) { ClearSelection(); } } } else { if (GetSelectedItemIndex() == start) { NotifySelectionChanged(true); } } } void GuiSelectableListControl::OnStyleInstalled(vint itemIndex, ItemStyle* style, bool refreshPropertiesOnly) { GuiListControl::OnStyleInstalled(itemIndex, style, refreshPropertiesOnly); style->SetSelected(selectedItems.Contains(itemIndex)); } void GuiSelectableListControl::OnItemSelectionChanged(vint itemIndex, bool value) { if(auto style = itemArranger->GetVisibleStyle(itemIndex)) { style->SetSelected(value); } } void GuiSelectableListControl::OnItemSelectionCleared() { for (auto style : visibleStyles.Keys()) { style->SetSelected(false); } } void GuiSelectableListControl::OnItemLeftButtonDown(compositions::GuiGraphicsComposition* sender, compositions::GuiItemMouseEventArgs& arguments) { if(GetVisuallyEnabled()) { SelectItemsByClick(arguments.itemIndex, arguments.ctrl, arguments.shift, true); } } void GuiSelectableListControl::OnItemRightButtonDown(compositions::GuiGraphicsComposition* sender, compositions::GuiItemMouseEventArgs& arguments) { if(GetVisuallyEnabled()) { SelectItemsByClick(arguments.itemIndex, arguments.ctrl, arguments.shift, false); } } void GuiSelectableListControl::NormalizeSelectedItemIndexStartEnd() { if (selectedItemIndexStart < 0 || selectedItemIndexStart >= itemProvider->Count()) { selectedItemIndexStart = 0; } if (selectedItemIndexEnd < 0 || selectedItemIndexEnd >= itemProvider->Count()) { selectedItemIndexEnd = 0; } } void GuiSelectableListControl::SetMultipleItemsSelectedSilently(vint start, vint end, bool selected) { if(start>end) { vint temp=start; start=end; end=temp; } vint count=itemProvider->Count(); if(start<0) start=0; if(end>=count) end=count-1; for(vint i=start;i<=end;i++) { if(selected) { if(!selectedItems.Contains(i)) { selectedItems.Add(i); } } else { selectedItems.Remove(i); } OnItemSelectionChanged(i, selected); } } void GuiSelectableListControl::OnKeyDown(compositions::GuiGraphicsComposition* sender, compositions::GuiKeyEventArgs& arguments) { if(GetVisuallyEnabled()) { if(SelectItemsByKey(arguments.code, arguments.ctrl, arguments.shift)) { arguments.handled=true; } } } vint GuiSelectableListControl::FindItemByVirtualKeyDirection(vint index, compositions::KeyDirection keyDirection) { return GetArranger()->FindItemByVirtualKeyDirection(selectedItemIndexEnd, keyDirection); } GuiSelectableListControl::GuiSelectableListControl(theme::ThemeName themeName, list::IItemProvider* _itemProvider) :GuiListControl(themeName, _itemProvider, true) , multiSelect(false) , selectedItemIndexStart(-1) , selectedItemIndexEnd(-1) { SelectionChanged.SetAssociatedComposition(boundsComposition); ItemLeftButtonDown.AttachMethod(this, &GuiSelectableListControl::OnItemLeftButtonDown); ItemRightButtonDown.AttachMethod(this, &GuiSelectableListControl::OnItemRightButtonDown); if (focusableComposition) { focusableComposition->GetEventReceiver()->keyDown.AttachMethod(this, &GuiSelectableListControl::OnKeyDown); } } GuiSelectableListControl::~GuiSelectableListControl() { } bool GuiSelectableListControl::GetMultiSelect() { return multiSelect; } void GuiSelectableListControl::SetMultiSelect(bool value) { if (multiSelect != value) { multiSelect = value; ClearSelection(); } } const collections::SortedList& GuiSelectableListControl::GetSelectedItems() { return selectedItems; } vint GuiSelectableListControl::GetSelectedItemIndex() { return selectedItems.Count() == 1 ? selectedItems[0] : -1; } WString GuiSelectableListControl::GetSelectedItemText() { vint index = GetSelectedItemIndex(); if (index != -1) { return itemProvider->GetTextValue(index); } return L""; } bool GuiSelectableListControl::GetSelected(vint itemIndex) { return selectedItems.Contains(itemIndex); } void GuiSelectableListControl::SetSelected(vint itemIndex, bool value) { if (0 <= itemIndex && itemIndex < itemProvider->Count()) { if (value) { if (!selectedItems.Contains(itemIndex)) { if (!multiSelect) { selectedItems.Clear(); OnItemSelectionCleared(); } selectedItems.Add(itemIndex); OnItemSelectionChanged(itemIndex, value); NotifySelectionChanged(false); } } else { if (selectedItems.Remove(itemIndex)) { OnItemSelectionChanged(itemIndex, value); NotifySelectionChanged(false); } } selectedItemIndexStart = itemIndex; selectedItemIndexEnd = itemIndex; } } bool GuiSelectableListControl::SelectItemsByClick(vint itemIndex, bool ctrl, bool shift, bool leftButton) { NormalizeSelectedItemIndexStartEnd(); if (0 <= itemIndex && itemIndex < itemProvider->Count()) { if (!leftButton) { if (selectedItems.Contains(itemIndex)) { return true; } } if (!multiSelect) { shift = false; ctrl = false; } if (shift) { if (!ctrl) { selectedItems.Clear(); OnItemSelectionCleared(); } selectedItemIndexEnd = itemIndex; SetMultipleItemsSelectedSilently(selectedItemIndexStart, selectedItemIndexEnd, true); NotifySelectionChanged(false); } else { if (ctrl) { vint index = selectedItems.IndexOf(itemIndex); if (index == -1) { selectedItems.Add(itemIndex); } else { selectedItems.RemoveAt(index); } OnItemSelectionChanged(itemIndex, index == -1); NotifySelectionChanged(false); } else { selectedItems.Clear(); OnItemSelectionCleared(); selectedItems.Add(itemIndex); OnItemSelectionChanged(itemIndex, true); NotifySelectionChanged(false); } selectedItemIndexStart = itemIndex; selectedItemIndexEnd = itemIndex; } return true; } return false; } bool GuiSelectableListControl::SelectItemsByKey(VKEY code, bool ctrl, bool shift) { if (!GetArranger()) return false; NormalizeSelectedItemIndexStartEnd(); KeyDirection keyDirection = KeyDirection::Up; switch (code) { case VKEY::KEY_UP: keyDirection = KeyDirection::Up; break; case VKEY::KEY_DOWN: keyDirection = KeyDirection::Down; break; case VKEY::KEY_LEFT: keyDirection = KeyDirection::Left; break; case VKEY::KEY_RIGHT: keyDirection = KeyDirection::Right; break; case VKEY::KEY_HOME: keyDirection = KeyDirection::Home; break; case VKEY::KEY_END: keyDirection = KeyDirection::End; break; case VKEY::KEY_PRIOR: keyDirection = KeyDirection::PageUp; break; case VKEY::KEY_NEXT: keyDirection = KeyDirection::PageDown; break; default: return false; } if (GetAxis()) { keyDirection = GetAxis()->RealKeyDirectionToVirtualKeyDirection(keyDirection); } vint itemIndex = FindItemByVirtualKeyDirection(selectedItemIndexEnd, keyDirection); if (SelectItemsByClick(itemIndex, ctrl, shift, true)) { return EnsureItemVisible(itemIndex); } else { return false; } } void GuiSelectableListControl::ClearSelection() { if(selectedItems.Count()>0) { selectedItems.Clear(); selectedItemIndexStart = -1; selectedItemIndexEnd = -1; OnItemSelectionCleared(); NotifySelectionChanged(false); } } } } } /*********************************************************************** .\CONTROLS\LISTCONTROLPACKAGE\GUILISTVIEWCONTROLS.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { using namespace elements; using namespace compositions; using namespace collections; using namespace reflection::description; /*********************************************************************** GuiListViewColumnHeader ***********************************************************************/ void GuiListViewColumnHeader::BeforeControlTemplateUninstalled_() { } void GuiListViewColumnHeader::AfterControlTemplateInstalled_(bool initialize) { TypedControlTemplateObject(true)->SetSortingState(columnSortingState); } GuiListViewColumnHeader::GuiListViewColumnHeader(theme::ThemeName themeName) :GuiMenuButton(themeName) { } GuiListViewColumnHeader::~GuiListViewColumnHeader() { } bool GuiListViewColumnHeader::IsAltAvailable() { return false; } ColumnSortingState GuiListViewColumnHeader::GetColumnSortingState() { return columnSortingState; } void GuiListViewColumnHeader::SetColumnSortingState(ColumnSortingState value) { if (columnSortingState != value) { columnSortingState = value; TypedControlTemplateObject(true)->SetSortingState(columnSortingState); } } /*********************************************************************** GuiListViewBase ***********************************************************************/ void GuiListViewBase::BeforeControlTemplateUninstalled_() { } void GuiListViewBase::AfterControlTemplateInstalled_(bool initialize) { } GuiListViewBase::GuiListViewBase(theme::ThemeName themeName, list::IItemProvider* _itemProvider) :GuiSelectableListControl(themeName, _itemProvider) { ColumnClicked.SetAssociatedComposition(boundsComposition); } GuiListViewBase::~GuiListViewBase() { } namespace list { const wchar_t* const IListViewItemView::Identifier = L"vl::presentation::controls::list::IListViewItemView"; /*********************************************************************** ListViewColumnItemArranger::ColumnItemViewCallback ***********************************************************************/ ListViewColumnItemArranger::ColumnItemViewCallback::ColumnItemViewCallback(ListViewColumnItemArranger* _arranger) :arranger(_arranger) { } ListViewColumnItemArranger::ColumnItemViewCallback::~ColumnItemViewCallback() { } void ListViewColumnItemArranger::ColumnItemViewCallback::OnColumnRebuilt() { arranger->RebuildColumns(); } void ListViewColumnItemArranger::ColumnItemViewCallback::OnColumnChanged(bool needToRefreshItems) { arranger->RefreshColumns(); } /*********************************************************************** ListViewColumnItemArranger::ColumnItemArrangerRepeatComposition ***********************************************************************/ void ListViewColumnItemArranger::ColumnItemArrangerRepeatComposition::Layout_EndLayout(bool totalSizeUpdated) { TBase::ArrangerRepeatComposition::Layout_EndLayout(totalSizeUpdated); arranger->FixColumnsAfterLayout(); } void ListViewColumnItemArranger::ColumnItemArrangerRepeatComposition::Layout_CalculateTotalSize(Size& full, Size& minimum) { TBase::ArrangerRepeatComposition::Layout_CalculateTotalSize(full, minimum); full.x += arranger->SplitterWidth; minimum.x += arranger->SplitterWidth; } ListViewColumnItemArranger::ColumnItemArrangerRepeatComposition::ColumnItemArrangerRepeatComposition(ListViewColumnItemArranger* _arranger) : TBase::ArrangerRepeatComposition(_arranger) , arranger(_arranger) { } /*********************************************************************** ListViewColumnItemArranger ***********************************************************************/ const wchar_t* const ListViewColumnItemArranger::IColumnItemView::Identifier = L"vl::presentation::controls::list::ListViewColumnItemArranger::IColumnItemView"; void ListViewColumnItemArranger::OnViewLocationChanged(compositions::GuiGraphicsComposition* composition, compositions::GuiEventArgs& arguments) { FixColumnsAfterViewLocationChanged(); } void ListViewColumnItemArranger::ColumnClicked(vint index, compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { GuiItemEventArgs args(listView->ColumnClicked.GetAssociatedComposition()); args.itemIndex=index; listView->ColumnClicked.Execute(args); } void ListViewColumnItemArranger::ColumnCachedBoundsChanged(vint index, compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { GuiBoundsComposition* buttonBounds=columnHeaderButtons[index]->GetBoundsComposition(); vint size=buttonBounds->GetCachedBounds().Width(); if(size>columnItemView->GetColumnSize(index)) { columnItemView->SetColumnSize(index, size); } } void ListViewColumnItemArranger::ColumnHeaderSplitterLeftButtonDown(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) { if(listView->GetVisuallyEnabled()) { arguments.handled=true; splitterDragging=true; splitterLatestX=arguments.x; } } void ListViewColumnItemArranger::ColumnHeaderSplitterLeftButtonUp(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) { if(listView->GetVisuallyEnabled()) { arguments.handled=true; splitterDragging=false; splitterLatestX=0; } } void ListViewColumnItemArranger::ColumnHeaderSplitterMouseMove(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) { if(splitterDragging) { vint offset=arguments.x-splitterLatestX; vint index=columnHeaderSplitters.IndexOf(dynamic_cast(sender)); if(index!=-1) { GuiBoundsComposition* buttonBounds=columnHeaderButtons[index]->GetBoundsComposition(); Rect bounds=buttonBounds->GetCachedBounds(); Rect newBounds(bounds.LeftTop(), Size(bounds.Width()+offset, bounds.Height())); buttonBounds->SetExpectedBounds(newBounds); vint finalSize=buttonBounds->GetCachedBounds().Width(); columnItemView->SetColumnSize(index, finalSize); } } } void ListViewColumnItemArranger::ColumnHeadersCachedBoundsChanged(compositions::GuiGraphicsComposition* composition, compositions::GuiEventArgs& arguments) { UpdateRepeatConfig(); } void ListViewColumnItemArranger::FixColumnsAfterViewLocationChanged() { vint x = GetRepeatComposition()->GetViewLocation().x; columnHeaders->SetExpectedBounds(Rect(Point(-x, 0), Size(0, 0))); } void ListViewColumnItemArranger::FixColumnsAfterLayout() { vint count = columnHeaders->GetParent()->Children().Count(); columnHeaders->GetParent()->MoveChild(columnHeaders, count - 1); FixColumnsAfterViewLocationChanged(); } vint ListViewColumnItemArranger::GetColumnsWidth() { vint width=columnHeaders->GetCachedBounds().Width()-SplitterWidth; if(widthGetCachedBounds().Height(); } void ListViewColumnItemArranger::DeleteColumnButtons() { // TODO: (enumerable) foreach:reversed for(vint i=columnHeaders->GetStackItems().Count()-1;i>=0;i--) { GuiStackItemComposition* item=columnHeaders->GetStackItems().Get(i); columnHeaders->RemoveChild(item); GuiControl* button=item->Children().Get(0)->GetAssociatedControl(); if(button) { item->RemoveChild(button->GetBoundsComposition()); delete button; } delete item; } columnHeaderButtons.Clear(); columnHeaderSplitters.Clear(); } void ListViewColumnItemArranger::RebuildColumns() { if (columnItemView && columnHeaderButtons.Count() == listViewItemView->GetColumnCount()) { for (vint i = 0; i < listViewItemView->GetColumnCount(); i++) { GuiListViewColumnHeader* button = columnHeaderButtons[i]; button->SetText(listViewItemView->GetColumnText(i)); button->SetSubMenu(columnItemView->GetDropdownPopup(i), false); button->SetColumnSortingState(columnItemView->GetSortingState(i)); button->GetBoundsComposition()->SetExpectedBounds(Rect(Point(0, 0), Size(columnItemView->GetColumnSize(i), 0))); } } else { DeleteColumnButtons(); if (columnItemView && listViewItemView) { for (vint i = 0; i < listViewItemView->GetColumnCount(); i++) { GuiBoundsComposition* splitterComposition = new GuiBoundsComposition; splitterComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); splitterComposition->SetAssociatedCursor(GetCurrentController()->ResourceService()->GetSystemCursor(INativeCursor::SizeWE)); splitterComposition->SetAlignmentToParent(Margin(0, 0, -1, 0)); splitterComposition->SetPreferredMinSize(Size(SplitterWidth, 0)); columnHeaderSplitters.Add(splitterComposition); splitterComposition->GetEventReceiver()->leftButtonDown.AttachMethod(this, &ListViewColumnItemArranger::ColumnHeaderSplitterLeftButtonDown); splitterComposition->GetEventReceiver()->leftButtonUp.AttachMethod(this, &ListViewColumnItemArranger::ColumnHeaderSplitterLeftButtonUp); splitterComposition->GetEventReceiver()->mouseMove.AttachMethod(this, &ListViewColumnItemArranger::ColumnHeaderSplitterMouseMove); } for (vint i = 0; i < listViewItemView->GetColumnCount(); i++) { GuiListViewColumnHeader* button = new GuiListViewColumnHeader(theme::ThemeName::Unknown); button->SetAutoFocus(false); button->SetControlTemplate(listView->TypedControlTemplateObject(true)->GetColumnHeaderTemplate()); button->Clicked.AttachLambda([this, i](GuiGraphicsComposition* sender, GuiEventArgs& args) { ColumnClicked(i, sender, args); }); button->GetBoundsComposition()->CachedBoundsChanged.AttachLambda([this, i](GuiGraphicsComposition* sender, GuiEventArgs& args) { ColumnCachedBoundsChanged(i, sender, args); }); columnHeaderButtons.Add(button); if (i > 0) { button->GetContainerComposition()->AddChild(columnHeaderSplitters[i - 1]); } GuiStackItemComposition* item = new GuiStackItemComposition; item->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); item->AddChild(button->GetBoundsComposition()); columnHeaders->AddChild(item); } if (listViewItemView->GetColumnCount() > 0) { GuiBoundsComposition* splitterComposition = columnHeaderSplitters[listViewItemView->GetColumnCount() - 1]; GuiStackItemComposition* item = new GuiStackItemComposition; item->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); item->AddChild(splitterComposition); columnHeaders->AddChild(item); } } } RefreshColumns(); callback->OnTotalSizeChanged(); } void ListViewColumnItemArranger::UpdateRepeatConfig() { GetRepeatComposition()->SetItemWidth(GetColumnsWidth()); GetRepeatComposition()->SetItemYOffset(GetColumnsYOffset()); } void ListViewColumnItemArranger::RefreshColumns() { if (columnItemView && listViewItemView) { for (vint i = 0; i < listViewItemView->GetColumnCount(); i++) { auto button = columnHeaderButtons[i]; button->SetText(listViewItemView->GetColumnText(i)); button->SetSubMenu(columnItemView->GetDropdownPopup(i), false); button->SetColumnSortingState(columnItemView->GetSortingState(i)); button->GetBoundsComposition()->SetExpectedBounds(Rect(Point(0, 0), Size(columnItemView->GetColumnSize(i), 0))); } columnHeaders->ForceCalculateSizeImmediately(); UpdateRepeatConfig(); } } ListViewColumnItemArranger::ListViewColumnItemArranger() : TBase(new TBase::ArrangerRepeatComposition(this)) { columnHeaders = new GuiStackComposition; columnHeaders->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); columnHeaders->CachedBoundsChanged.AttachMethod(this, &ListViewColumnItemArranger::ColumnHeadersCachedBoundsChanged); columnItemViewCallback = Ptr(new ColumnItemViewCallback(this)); GetRepeatComposition()->ViewLocationChanged.AttachMethod(this, &ListViewColumnItemArranger::OnViewLocationChanged); } ListViewColumnItemArranger::~ListViewColumnItemArranger() { if(!columnHeaders->GetParent()) { DeleteColumnButtons(); delete columnHeaders; } } Size ListViewColumnItemArranger::GetTotalSize() { Size size = TBase::GetTotalSize(); size.x += SplitterWidth; return size; } void ListViewColumnItemArranger::AttachListControl(GuiListControl* value) { TBase::AttachListControl(value); listView = dynamic_cast(value); if (listView) { listViewItemView = dynamic_cast(listView->GetItemProvider()->RequestView(WString::Unmanaged(IListViewItemView::Identifier))); columnItemView = dynamic_cast(listView->GetItemProvider()->RequestView(WString::Unmanaged(IColumnItemView::Identifier))); listView->GetContainerComposition()->AddChild(columnHeaders); if (columnItemView) { columnItemView->AttachCallback(columnItemViewCallback.Obj()); RebuildColumns(); } } } void ListViewColumnItemArranger::DetachListControl() { if (listView) { if (columnItemView) { columnItemView->DetachCallback(columnItemViewCallback.Obj()); columnItemView = nullptr; } listViewItemView = nullptr; listView->GetContainerComposition()->RemoveChild(columnHeaders); listView = nullptr; } TBase::DetachListControl(); } const ListViewColumnItemArranger::ColumnHeaderButtonList& ListViewColumnItemArranger::GetColumnButtons() { return columnHeaderButtons; } const ListViewColumnItemArranger::ColumnHeaderSplitterList& ListViewColumnItemArranger::GetColumnSplitters() { return columnHeaderSplitters; } /*********************************************************************** ListViewSubItems ***********************************************************************/ void ListViewSubItems::NotifyUpdateInternal(vint start, vint count, vint newCount) { owner->NotifyUpdate(); } /*********************************************************************** ListViewItem ***********************************************************************/ void ListViewItem::NotifyUpdate() { if (owner) { vint index = owner->IndexOf(this); owner->InvokeOnItemModified(index, 1, 1, false); } } ListViewItem::ListViewItem() :owner(0) { subItems.owner = this; } ListViewSubItems& ListViewItem::GetSubItems() { return subItems; } Ptr ListViewItem::GetSmallImage() { return smallImage; } void ListViewItem::SetSmallImage(Ptr value) { smallImage = value; NotifyUpdate(); } Ptr ListViewItem::GetLargeImage() { return largeImage; } void ListViewItem::SetLargeImage(Ptr value) { largeImage = value; NotifyUpdate(); } const WString& ListViewItem::GetText() { return text; } void ListViewItem::SetText(const WString& value) { text = value; NotifyUpdate(); } description::Value ListViewItem::GetTag() { return tag; } void ListViewItem::SetTag(const description::Value& value) { tag = value; NotifyUpdate(); } /*********************************************************************** ListViewColumn ***********************************************************************/ void ListViewColumn::NotifyRebuilt() { if (owner) { vint index = owner->IndexOf(this); if (index != -1) { owner->NotifyColumnRebuilt(index); } } } void ListViewColumn::NotifyChanged(bool needToRefreshItems) { if (owner) { vint index = owner->IndexOf(this); if (index != -1) { owner->NotifyColumnChanged(index, needToRefreshItems); } } } ListViewColumn::ListViewColumn(const WString& _text, vint _size) :text(_text) ,size(_size) { } ListViewColumn::~ListViewColumn() { if (dropdownPopup && ownPopup) { SafeDeleteControl(dropdownPopup); } } const WString& ListViewColumn::GetText() { return text; } void ListViewColumn::SetText(const WString& value) { if (text != value) { text = value; NotifyChanged(false); } } ItemProperty ListViewColumn::GetTextProperty() { return textProperty; } void ListViewColumn::SetTextProperty(const ItemProperty& value) { textProperty = value; NotifyChanged(true); } vint ListViewColumn::GetSize() { return size; } void ListViewColumn::SetSize(vint value) { if (size != value) { size = value; NotifyChanged(true); } } bool ListViewColumn::GetOwnPopup() { return ownPopup; } void ListViewColumn::SetOwnPopup(bool value) { ownPopup = value; } GuiMenu* ListViewColumn::GetDropdownPopup() { return dropdownPopup; } void ListViewColumn::SetDropdownPopup(GuiMenu* value) { if (dropdownPopup != value) { dropdownPopup = value; NotifyChanged(false); } } ColumnSortingState ListViewColumn::GetSortingState() { return sortingState; } void ListViewColumn::SetSortingState(ColumnSortingState value) { if (sortingState != value) { sortingState = value; NotifyChanged(false); } } /*********************************************************************** ListViewDataColumns ***********************************************************************/ void ListViewDataColumns::NotifyUpdateInternal(vint start, vint count, vint newCount) { itemProvider->RefreshAllItems(); } ListViewDataColumns::ListViewDataColumns(IListViewItemProvider* _itemProvider) :itemProvider(_itemProvider) { } ListViewDataColumns::~ListViewDataColumns() { } /*********************************************************************** ListViewColumns ***********************************************************************/ void ListViewColumns::NotifyColumnRebuilt(vint column) { NotifyUpdate(column, 1); } void ListViewColumns::NotifyColumnChanged(vint column, bool needToRefreshItems) { itemProvider->NotifyColumnChanged(); } void ListViewColumns::AfterInsert(vint index, const Ptr& value) { collections::ObservableListBase>::AfterInsert(index, value); value->owner = this; } void ListViewColumns::BeforeRemove(vint index, const Ptr& value) { value->owner = 0; collections::ObservableListBase>::BeforeRemove(index, value); } void ListViewColumns::NotifyUpdateInternal(vint start, vint count, vint newCount) { itemProvider->NotifyColumnRebuilt(); } ListViewColumns::ListViewColumns(IListViewItemProvider* _itemProvider) :itemProvider(_itemProvider) { } ListViewColumns::~ListViewColumns() { } /*********************************************************************** ListViewItemProvider ***********************************************************************/ void ListViewItemProvider::AfterInsert(vint index, const Ptr& value) { ListProvider>::AfterInsert(index, value); value->owner = this; } void ListViewItemProvider::BeforeRemove(vint index, const Ptr& value) { value->owner = 0; ListProvider>::AfterInsert(index, value); } void ListViewItemProvider::RebuildAllItems() { InvokeOnItemModified(0, Count(), Count(), true); } void ListViewItemProvider::RefreshAllItems() { InvokeOnItemModified(0, Count(), Count(), false); } void ListViewItemProvider::NotifyColumnRebuilt() { for (auto callback : columnItemViewCallbacks) { callback->OnColumnRebuilt(); } RefreshAllItems(); } void ListViewItemProvider::NotifyColumnChanged() { for (auto callback : columnItemViewCallbacks) { callback->OnColumnChanged(true); } RefreshAllItems(); } ListViewItemProvider::ListViewItemProvider() :columns(this) , dataColumns(this) { } ListViewItemProvider::~ListViewItemProvider() { } WString ListViewItemProvider::GetTextValue(vint itemIndex) { return GetText(itemIndex); } description::Value ListViewItemProvider::GetBindingValue(vint itemIndex) { return Value::From(Get(itemIndex)); } IDescriptable* ListViewItemProvider::RequestView(const WString& identifier) { if (identifier == IListViewItemView::Identifier) { return (IListViewItemView*)this; } else if (identifier == ListViewColumnItemArranger::IColumnItemView::Identifier) { return (ListViewColumnItemArranger::IColumnItemView*)this; } else { return 0; } } Ptr ListViewItemProvider::GetSmallImage(vint itemIndex) { return Get(itemIndex)->smallImage; } Ptr ListViewItemProvider::GetLargeImage(vint itemIndex) { return Get(itemIndex)->largeImage; } WString ListViewItemProvider::GetText(vint itemIndex) { return Get(itemIndex)->text; } WString ListViewItemProvider::GetSubItem(vint itemIndex, vint index) { Ptr item=Get(itemIndex); if(index<0 || index>=item->GetSubItems().Count()) { return L""; } else { return item->GetSubItems()[index]; } } vint ListViewItemProvider::GetDataColumnCount() { return dataColumns.Count(); } vint ListViewItemProvider::GetDataColumn(vint index) { return dataColumns[index]; } vint ListViewItemProvider::GetColumnCount() { return columns.Count(); } WString ListViewItemProvider::GetColumnText(vint index) { if (index<0 || index >= columns.Count()) { return L""; } else { return columns[index]->GetText(); } } bool ListViewItemProvider::AttachCallback(ListViewColumnItemArranger::IColumnItemViewCallback* value) { if(columnItemViewCallbacks.Contains(value)) { return false; } else { columnItemViewCallbacks.Add(value); return true; } } bool ListViewItemProvider::DetachCallback(ListViewColumnItemArranger::IColumnItemViewCallback* value) { vint index=columnItemViewCallbacks.IndexOf(value); if(index==-1) { return false; } else { columnItemViewCallbacks.Remove(value); return true; } } vint ListViewItemProvider::GetColumnSize(vint index) { if(index<0 || index>=columns.Count()) { return 0; } else { return columns[index]->GetSize(); } } void ListViewItemProvider::SetColumnSize(vint index, vint value) { if(index>=0 && indexSetSize(value); } } GuiMenu* ListViewItemProvider::GetDropdownPopup(vint index) { if(index<0 || index>=columns.Count()) { return 0; } else { return columns[index]->GetDropdownPopup(); } } ColumnSortingState ListViewItemProvider::GetSortingState(vint index) { if (index < 0 || index >= columns.Count()) { return ColumnSortingState::NotSorted; } else { return columns[index]->GetSortingState(); } } ListViewDataColumns& ListViewItemProvider::GetDataColumns() { return dataColumns; } ListViewColumns& ListViewItemProvider::GetColumns() { return columns; } } /*********************************************************************** GuiListView ***********************************************************************/ void GuiVirtualListView::OnStyleInstalled(vint itemIndex, ItemStyle* style, bool refreshPropertiesOnly) { GuiListViewBase::OnStyleInstalled(itemIndex, style, refreshPropertiesOnly); if (auto textItemStyle = dynamic_cast(style)) { textItemStyle->SetTextColor(TypedControlTemplateObject(true)->GetPrimaryTextColor()); } if (refreshPropertiesOnly) { if (auto predefinedItemStyle = dynamic_cast(style)) { predefinedItemStyle->RefreshItem(); } } } void GuiVirtualListView::OnItemTemplateChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { view = ListViewView::Unknown; } GuiVirtualListView::GuiVirtualListView(theme::ThemeName themeName, list::IItemProvider* _itemProvider) :GuiListViewBase(themeName, _itemProvider) { SetView(ListViewView::Detail); } GuiVirtualListView::~GuiVirtualListView() { } ListViewView GuiVirtualListView::GetView() { return view; } void GuiVirtualListView::SetView(ListViewView _view) { switch (_view) { case ListViewView::BigIcon: SetStyleAndArranger( [](const Value&) { return new list::BigIconListViewItemTemplate; }, Ptr(new list::FixedSizeMultiColumnItemArranger) ); break; case ListViewView::SmallIcon: SetStyleAndArranger( [](const Value&) { return new list::SmallIconListViewItemTemplate; }, Ptr(new list::FixedSizeMultiColumnItemArranger) ); break; case ListViewView::List: SetStyleAndArranger( [](const Value&) { return new list::ListListViewItemTemplate; }, Ptr(new list::FixedHeightMultiColumnItemArranger) ); break; case ListViewView::Tile: SetStyleAndArranger( [](const Value&) { return new list::TileListViewItemTemplate; }, Ptr(new list::FixedSizeMultiColumnItemArranger) ); break; case ListViewView::Information: SetStyleAndArranger( [](const Value&) { return new list::InformationListViewItemTemplate; }, Ptr(new list::FixedHeightItemArranger) ); break; case ListViewView::Detail: SetStyleAndArranger( [](const Value&) { return new list::DetailListViewItemTemplate; }, Ptr(new list::ListViewColumnItemArranger) ); break; default:; } view = _view; } /*********************************************************************** GuiListView ***********************************************************************/ GuiListView::GuiListView(theme::ThemeName themeName) :GuiVirtualListView(themeName, new list::ListViewItemProvider) { items=dynamic_cast(itemProvider.Obj()); } GuiListView::~GuiListView() { } list::ListViewItemProvider& GuiListView::GetItems() { return *items; } list::ListViewDataColumns& GuiListView::GetDataColumns() { return items->GetDataColumns(); } list::ListViewColumns& GuiListView::GetColumns() { return items->GetColumns(); } Ptr GuiListView::GetSelectedItem() { vint index = GetSelectedItemIndex(); if (index == -1) return 0; return items->Get(index); } } } } /*********************************************************************** .\CONTROLS\LISTCONTROLPACKAGE\GUILISTVIEWITEMTEMPLATES.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { using namespace elements; using namespace compositions; using namespace collections; using namespace reflection::description; namespace list { /*********************************************************************** DefaultListViewItemTemplate ***********************************************************************/ DefaultListViewItemTemplate::DefaultListViewItemTemplate() { SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); } DefaultListViewItemTemplate::~DefaultListViewItemTemplate() { } /*********************************************************************** BigIconListViewItemTemplate ***********************************************************************/ void BigIconListViewItemTemplate::OnInitialize() { { auto table = new GuiTableComposition; AddChild(table); table->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); table->SetRowsAndColumns(2, 3); table->SetRowOption(0, GuiCellOption::MinSizeOption()); table->SetRowOption(1, GuiCellOption::MinSizeOption()); table->SetColumnOption(0, GuiCellOption::PercentageOption(0.5)); table->SetColumnOption(1, GuiCellOption::MinSizeOption()); table->SetColumnOption(2, GuiCellOption::PercentageOption(0.5)); table->SetAlignmentToParent(Margin(0, 0, 0, 0)); table->SetCellPadding(5); { auto cell = new GuiCellComposition; table->AddChild(cell); cell->SetSite(0, 1, 1, 1); cell->SetPreferredMinSize(Size(32, 32)); image = GuiImageFrameElement::Create(); image->SetStretch(true); cell->SetOwnedElement(Ptr(image)); } { auto cell = new GuiCellComposition; table->AddChild(cell); cell->SetMinSizeLimitation(GuiGraphicsComposition::NoLimit); cell->SetSite(1, 0, 1, 3); cell->SetPreferredMinSize(Size(64, 40)); text = GuiSolidLabelElement::Create(); text->SetAlignments(Alignment::Center, Alignment::Top); text->SetWrapLine(true); text->SetEllipse(true); cell->SetOwnedElement(Ptr(text)); } } FontChanged.AttachMethod(this, &BigIconListViewItemTemplate::OnFontChanged); FontChanged.Execute(compositions::GuiEventArgs(this)); } void BigIconListViewItemTemplate::OnRefresh() { if (auto listView = dynamic_cast(listControl)) { auto itemIndex = GetIndex(); if (auto view = dynamic_cast(listView->GetItemProvider()->RequestView(WString::Unmanaged(IListViewItemView::Identifier)))) { auto imageData = view->GetLargeImage(itemIndex); if (imageData) { image->SetImage(imageData->GetImage(), imageData->GetFrameIndex()); } else { image->SetImage(nullptr); } text->SetText(view->GetText(itemIndex)); text->SetColor(listView->TypedControlTemplateObject(true)->GetPrimaryTextColor()); } } } void BigIconListViewItemTemplate::OnFontChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { text->SetFont(GetFont()); } BigIconListViewItemTemplate::BigIconListViewItemTemplate() { } BigIconListViewItemTemplate::~BigIconListViewItemTemplate() { } /*********************************************************************** SmallIconListViewItemTemplate ***********************************************************************/ void SmallIconListViewItemTemplate::OnInitialize() { { auto table = new GuiTableComposition; AddChild(table); table->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); table->SetRowsAndColumns(3, 2); table->SetRowOption(0, GuiCellOption::PercentageOption(0.5)); table->SetRowOption(1, GuiCellOption::MinSizeOption()); table->SetRowOption(2, GuiCellOption::PercentageOption(0.5)); table->SetColumnOption(0, GuiCellOption::MinSizeOption()); table->SetColumnOption(1, GuiCellOption::MinSizeOption()); table->SetAlignmentToParent(Margin(0, 0, 0, 0)); table->SetCellPadding(2); { GuiCellComposition* cell = new GuiCellComposition; table->AddChild(cell); cell->SetSite(1, 0, 1, 1); cell->SetPreferredMinSize(Size(16, 16)); image = GuiImageFrameElement::Create(); image->SetStretch(true); cell->SetOwnedElement(Ptr(image)); } { GuiCellComposition* cell = new GuiCellComposition; table->AddChild(cell); cell->SetSite(0, 1, 3, 1); cell->SetPreferredMinSize(Size(192, 0)); text = GuiSolidLabelElement::Create(); text->SetAlignments(Alignment::Left, Alignment::Center); text->SetEllipse(true); cell->SetOwnedElement(Ptr(text)); } } FontChanged.AttachMethod(this, &SmallIconListViewItemTemplate::OnFontChanged); FontChanged.Execute(compositions::GuiEventArgs(this)); } void SmallIconListViewItemTemplate::OnRefresh() { if (auto listView = dynamic_cast(listControl)) { auto itemIndex = GetIndex(); if (auto view = dynamic_cast(listView->GetItemProvider()->RequestView(WString::Unmanaged(IListViewItemView::Identifier)))) { auto imageData = view->GetSmallImage(itemIndex); if (imageData) { image->SetImage(imageData->GetImage(), imageData->GetFrameIndex()); } else { image->SetImage(nullptr); } text->SetText(view->GetText(itemIndex)); text->SetColor(listView->TypedControlTemplateObject(true)->GetPrimaryTextColor()); } } } void SmallIconListViewItemTemplate::OnFontChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { text->SetFont(GetFont()); } SmallIconListViewItemTemplate::SmallIconListViewItemTemplate() { } SmallIconListViewItemTemplate::~SmallIconListViewItemTemplate() { } /*********************************************************************** ListListViewItemTemplate ***********************************************************************/ void ListListViewItemTemplate::OnInitialize() { { auto table = new GuiTableComposition; AddChild(table); table->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); table->SetRowsAndColumns(3, 2); table->SetRowOption(0, GuiCellOption::PercentageOption(0.5)); table->SetRowOption(1, GuiCellOption::MinSizeOption()); table->SetRowOption(2, GuiCellOption::PercentageOption(0.5)); table->SetColumnOption(0, GuiCellOption::MinSizeOption()); table->SetColumnOption(1, GuiCellOption::MinSizeOption()); table->SetAlignmentToParent(Margin(0, 0, 0, 0)); table->SetCellPadding(2); { auto cell = new GuiCellComposition; table->AddChild(cell); cell->SetSite(1, 0, 1, 1); cell->SetPreferredMinSize(Size(16, 16)); image = GuiImageFrameElement::Create(); image->SetStretch(true); cell->SetOwnedElement(Ptr(image)); } { auto cell = new GuiCellComposition; table->AddChild(cell); cell->SetSite(0, 1, 3, 1); auto textBounds = new GuiBoundsComposition; cell->AddChild(textBounds); textBounds->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElement); textBounds->SetAlignmentToParent(Margin(0, 0, 16, 0)); text = GuiSolidLabelElement::Create(); text->SetAlignments(Alignment::Left, Alignment::Center); textBounds->SetOwnedElement(Ptr(text)); } } FontChanged.AttachMethod(this, &ListListViewItemTemplate::OnFontChanged); FontChanged.Execute(compositions::GuiEventArgs(this)); } void ListListViewItemTemplate::OnRefresh() { if (auto listView = dynamic_cast(listControl)) { auto itemIndex = GetIndex(); if (auto view = dynamic_cast(listView->GetItemProvider()->RequestView(WString::Unmanaged(IListViewItemView::Identifier)))) { auto imageData = view->GetSmallImage(itemIndex); if (imageData) { image->SetImage(imageData->GetImage(), imageData->GetFrameIndex()); } else { image->SetImage(nullptr); } text->SetText(view->GetText(itemIndex)); text->SetColor(listView->TypedControlTemplateObject(true)->GetPrimaryTextColor()); } } } void ListListViewItemTemplate::OnFontChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { text->SetFont(GetFont()); } ListListViewItemTemplate::ListListViewItemTemplate() { } ListListViewItemTemplate::~ListListViewItemTemplate() { } /*********************************************************************** TileListViewItemTemplate ***********************************************************************/ elements::GuiSolidLabelElement* TileListViewItemTemplate::CreateTextElement(vint textRow) { auto cell = new GuiCellComposition; textTable->AddChild(cell); cell->SetSite(textRow + 1, 0, 1, 1); auto textElement = Ptr(GuiSolidLabelElement::Create()); textElement->SetAlignments(Alignment::Left, Alignment::Center); textElement->SetEllipse(true); cell->SetOwnedElement(textElement); return textElement.Obj(); } void TileListViewItemTemplate::ResetTextTable(vint dataColumnCount) { if (text && dataTexts.Count() == dataColumnCount) return; for (vint i = textTable->Children().Count() - 1; i >= 0; i--) { if (auto cell = dynamic_cast(textTable->Children()[i])) { SafeDeleteComposition(cell); } } { vint textRows = dataColumnCount + 1; textTable->SetRowsAndColumns(textRows + 2, 1); textTable->SetRowOption(0, GuiCellOption::PercentageOption(0.5)); for (vint i = 0; i < textRows; i++) { textTable->SetRowOption(i + 1, GuiCellOption::MinSizeOption()); } textTable->SetRowOption(textRows + 1, GuiCellOption::PercentageOption(0.5)); textTable->SetColumnOption(0, GuiCellOption::PercentageOption(1.0)); } text = CreateTextElement(0); text->SetFont(GetFont()); { dataTexts.Resize(dataColumnCount); for (vint i = 0; i < dataColumnCount; i++) { dataTexts[i] = CreateTextElement(i + 1); dataTexts[i]->SetFont(GetFont()); } } } void TileListViewItemTemplate::OnInitialize() { { auto table = new GuiTableComposition; AddChild(table); table->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); table->SetRowsAndColumns(3, 2); table->SetRowOption(0, GuiCellOption::PercentageOption(0.5)); table->SetRowOption(1, GuiCellOption::MinSizeOption()); table->SetRowOption(2, GuiCellOption::PercentageOption(0.5)); table->SetColumnOption(0, GuiCellOption::MinSizeOption()); table->SetColumnOption(1, GuiCellOption::MinSizeOption()); table->SetAlignmentToParent(Margin(0, 0, 0, 0)); table->SetCellPadding(4); { auto cell = new GuiCellComposition; table->AddChild(cell); cell->SetSite(1, 0, 1, 1); cell->SetPreferredMinSize(Size(32, 32)); image = GuiImageFrameElement::Create(); image->SetStretch(true); cell->SetOwnedElement(Ptr(image)); } { auto cell = new GuiCellComposition; table->AddChild(cell); cell->SetSite(0, 1, 3, 1); cell->SetPreferredMinSize(Size(224, 0)); textTable = new GuiTableComposition; textTable->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); textTable->SetCellPadding(1); textTable->SetAlignmentToParent(Margin(0, 0, 0, 0)); cell->AddChild(textTable); } } ResetTextTable(0); FontChanged.AttachMethod(this, &TileListViewItemTemplate::OnFontChanged); FontChanged.Execute(compositions::GuiEventArgs(this)); } void TileListViewItemTemplate::OnRefresh() { if (auto listView = dynamic_cast(listControl)) { auto itemIndex = GetIndex(); if (auto view = dynamic_cast(listView->GetItemProvider()->RequestView(WString::Unmanaged(IListViewItemView::Identifier)))) { auto imageData = view->GetLargeImage(itemIndex); if (imageData) { image->SetImage(imageData->GetImage(), imageData->GetFrameIndex()); } else { image->SetImage(nullptr); } vint subColumnCount = view->GetColumnCount() - 1; vint dataColumnCount = view->GetDataColumnCount(); if (dataColumnCount > subColumnCount) dataColumnCount = subColumnCount; if (dataColumnCount < 0) dataColumnCount = 0; ResetTextTable(dataColumnCount); text->SetText(view->GetText(itemIndex)); text->SetColor(listView->TypedControlTemplateObject(true)->GetPrimaryTextColor()); for (vint i = 0; i < dataColumnCount; i++) { dataTexts[i]->SetText(view->GetSubItem(itemIndex, view->GetDataColumn(i))); dataTexts[i]->SetColor(listView->TypedControlTemplateObject(true)->GetSecondaryTextColor()); } } } } void TileListViewItemTemplate::OnFontChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if (text) text->SetFont(GetFont()); for (auto dataText : dataTexts) { dataText->SetFont(GetFont()); } } TileListViewItemTemplate::TileListViewItemTemplate() { } TileListViewItemTemplate::~TileListViewItemTemplate() { } /*********************************************************************** InformationListViewItemTemplate ***********************************************************************/ void InformationListViewItemTemplate::ResetTextTable(vint dataColumnCount) { if (dataTexts.Count() == dataColumnCount) return; for (vint i = textTable->Children().Count() - 1; i >= 0; i--) { if (auto cell = dynamic_cast(textTable->Children()[i])) { SafeDeleteComposition(cell); } } { textTable->SetRowsAndColumns(dataColumnCount + 2, 1); textTable->SetRowOption(0, GuiCellOption::PercentageOption(0.5)); for (vint i = 0; i < dataColumnCount; i++) { textTable->SetRowOption(i + 1, GuiCellOption::MinSizeOption()); } textTable->SetRowOption(dataColumnCount + 1, GuiCellOption::PercentageOption(0.5)); textTable->SetColumnOption(0, GuiCellOption::PercentageOption(1.0)); } columnTexts.Resize(dataColumnCount); dataTexts.Resize(dataColumnCount); for (vint i = 0; i < dataColumnCount; i++) { auto cell = new GuiCellComposition; textTable->AddChild(cell); cell->SetSite(i + 1, 0, 1, 1); auto dataTable = new GuiTableComposition; dataTable->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); dataTable->SetRowsAndColumns(1, 2); dataTable->SetRowOption(0, GuiCellOption::MinSizeOption()); dataTable->SetColumnOption(0, GuiCellOption::MinSizeOption()); dataTable->SetColumnOption(1, GuiCellOption::PercentageOption(1.0)); dataTable->SetAlignmentToParent(Margin(0, 0, 0, 0)); cell->AddChild(dataTable); { auto cell = new GuiCellComposition; dataTable->AddChild(cell); cell->SetSite(0, 0, 1, 1); columnTexts[i] = GuiSolidLabelElement::Create(); columnTexts[i]->SetFont(GetFont()); cell->SetOwnedElement(Ptr(columnTexts[i])); } { auto cell = new GuiCellComposition; dataTable->AddChild(cell); cell->SetSite(0, 1, 1, 1); dataTexts[i] = GuiSolidLabelElement::Create(); dataTexts[i]->SetFont(GetFont()); dataTexts[i]->SetEllipse(true); cell->SetOwnedElement(Ptr(dataTexts[i])); } } } void InformationListViewItemTemplate::OnInitialize() { { bottomLine = GuiSolidBackgroundElement::Create(); bottomLineComposition = new GuiBoundsComposition; bottomLineComposition->SetOwnedElement(Ptr(bottomLine)); bottomLineComposition->SetAlignmentToParent(Margin(8, -1, 8, 0)); bottomLineComposition->SetPreferredMinSize(Size(0, 1)); AddChild(bottomLineComposition); auto table = new GuiTableComposition; AddChild(table); table->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); table->SetRowsAndColumns(3, 3); table->SetRowOption(0, GuiCellOption::PercentageOption(0.5)); table->SetRowOption(1, GuiCellOption::MinSizeOption()); table->SetRowOption(2, GuiCellOption::PercentageOption(0.5)); table->SetColumnOption(0, GuiCellOption::MinSizeOption()); table->SetColumnOption(1, GuiCellOption::PercentageOption(1.0)); table->SetColumnOption(2, GuiCellOption::MinSizeOption()); table->SetAlignmentToParent(Margin(0, 0, 0, 0)); table->SetCellPadding(4); { auto cell = new GuiCellComposition; table->AddChild(cell); cell->SetSite(1, 0, 1, 1); cell->SetPreferredMinSize(Size(32, 32)); image = GuiImageFrameElement::Create(); image->SetStretch(true); cell->SetOwnedElement(Ptr(image)); } { auto cell = new GuiCellComposition; table->AddChild(cell); cell->SetSite(0, 1, 3, 1); text = GuiSolidLabelElement::Create(); text->SetEllipse(true); cell->SetOwnedElement(Ptr(text)); } { auto cell = new GuiCellComposition; table->AddChild(cell); cell->SetSite(0, 2, 3, 1); cell->SetPreferredMinSize(Size(224, 0)); textTable = new GuiTableComposition; textTable->SetCellPadding(4); textTable->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); textTable->SetAlignmentToParent(Margin(0, 0, 0, 0)); cell->AddChild(textTable); } } FontChanged.AttachMethod(this, &InformationListViewItemTemplate::OnFontChanged); FontChanged.Execute(compositions::GuiEventArgs(this)); } void InformationListViewItemTemplate::OnRefresh() { if (auto listView = dynamic_cast(listControl)) { auto itemIndex = GetIndex(); if (auto view = dynamic_cast(listView->GetItemProvider()->RequestView(WString::Unmanaged(IListViewItemView::Identifier)))) { auto imageData = view->GetLargeImage(itemIndex); if (imageData) { image->SetImage(imageData->GetImage(), imageData->GetFrameIndex()); } else { image->SetImage(nullptr); } text->SetText(view->GetText(itemIndex)); text->SetColor(listView->TypedControlTemplateObject(true)->GetPrimaryTextColor()); bottomLine->SetColor(listView->TypedControlTemplateObject(true)->GetItemSeparatorColor()); vint subColumnCount = view->GetColumnCount() - 1; vint dataColumnCount = view->GetDataColumnCount(); if (dataColumnCount > subColumnCount) dataColumnCount = subColumnCount; if (dataColumnCount < 0) dataColumnCount = 0; ResetTextTable(dataColumnCount); for (vint i = 0; i < dataColumnCount; i++) { { columnTexts[i]->SetText(view->GetColumnText(view->GetDataColumn(i) + 1) + L": "); columnTexts[i]->SetColor(listView->TypedControlTemplateObject(true)->GetSecondaryTextColor()); } { dataTexts[i]->SetText(view->GetSubItem(itemIndex, view->GetDataColumn(i))); dataTexts[i]->SetColor(listView->TypedControlTemplateObject(true)->GetPrimaryTextColor()); } } } } } void InformationListViewItemTemplate::OnFontChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { { auto font = GetFont(); font.size = (vint)(font.size * 1.2); text->SetFont(font); } for (auto columnText : columnTexts) { columnText->SetFont(GetFont()); } for (auto dataText : dataTexts) { dataText->SetFont(GetFont()); } } InformationListViewItemTemplate::InformationListViewItemTemplate() { } InformationListViewItemTemplate::~InformationListViewItemTemplate() { } /*********************************************************************** DetailListViewItemTemplate ***********************************************************************/ void DetailListViewItemTemplate::UpdateSubItemSize() { if (auto view = dynamic_cast(listControl->GetItemProvider()->RequestView(WString::Unmanaged(IListViewItemView::Identifier)))) { if (columnItemView) { vint columnCount = view->GetColumnCount(); if (columnCount > textTable->GetColumns()) { columnCount = textTable->GetColumns(); } for (vint i = 0; i < columnCount; i++) { textTable->SetColumnOption(i, GuiCellOption::AbsoluteOption(columnItemView->GetColumnSize(i))); } } } } void DetailListViewItemTemplate::ResetTextTable(vint subColumnCount) { if (subItemCells.Count() == subColumnCount) return; for (auto cell : subItemCells) { SafeDeleteComposition(cell); } subItemCells.Resize(subColumnCount); subItemTexts.Resize(subColumnCount); textTable->SetRowsAndColumns(1, subColumnCount + 1); for (vint i = 0; i < subColumnCount; i++) { auto cell = new GuiCellComposition; textTable->AddChild(cell); cell->SetSite(0, i + 1, 1, 1); auto textBounds = new GuiBoundsComposition; cell->AddChild(textBounds); textBounds->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElement); textBounds->SetAlignmentToParent(Margin(8, 0, 8, 0)); auto subText = GuiSolidLabelElement::Create(); subText->SetAlignments(Alignment::Left, Alignment::Center); subText->SetFont(GetFont()); subText->SetEllipse(true); textBounds->SetOwnedElement(Ptr(subText)); subItemCells[i] = cell; subItemTexts[i] = subText; } } void DetailListViewItemTemplate::OnInitialize() { columnItemView = dynamic_cast(listControl->GetItemProvider()->RequestView(WString::Unmanaged(ListViewColumnItemArranger::IColumnItemView::Identifier))); { textTable = new GuiTableComposition; textTable->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); textTable->SetAlignmentToParent(Margin(0, 0, 0, 0)); textTable->SetRowsAndColumns(1, 1); textTable->SetRowOption(0, GuiCellOption::MinSizeOption()); textTable->SetColumnOption(0, GuiCellOption::AbsoluteOption(0)); AddChild(textTable); { auto cell = new GuiCellComposition; textTable->AddChild(cell); cell->SetSite(0, 0, 1, 1); auto table = new GuiTableComposition; cell->AddChild(table); table->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); table->SetRowsAndColumns(3, 2); table->SetRowOption(0, GuiCellOption::PercentageOption(0.5)); table->SetRowOption(1, GuiCellOption::MinSizeOption()); table->SetRowOption(2, GuiCellOption::PercentageOption(0.5)); table->SetColumnOption(0, GuiCellOption::MinSizeOption()); table->SetColumnOption(1, GuiCellOption::PercentageOption(1.0)); table->SetAlignmentToParent(Margin(0, 0, 0, 0)); table->SetCellPadding(2); { auto cell = new GuiCellComposition; table->AddChild(cell); cell->SetSite(1, 0, 1, 1); cell->SetPreferredMinSize(Size(16, 16)); image = GuiImageFrameElement::Create(); image->SetStretch(true); cell->SetOwnedElement(Ptr(image)); } { auto cell = new GuiCellComposition; table->AddChild(cell); cell->SetSite(0, 1, 3, 1); auto textBounds = new GuiBoundsComposition; cell->AddChild(textBounds); textBounds->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElement); textBounds->SetAlignmentToParent(Margin(0, 0, 8, 0)); text = GuiSolidLabelElement::Create(); text->SetAlignments(Alignment::Left, Alignment::Center); text->SetFont(GetFont()); text->SetEllipse(true); textBounds->SetOwnedElement(Ptr(text)); } } } FontChanged.AttachMethod(this, &DetailListViewItemTemplate::OnFontChanged); FontChanged.Execute(compositions::GuiEventArgs(this)); } void DetailListViewItemTemplate::OnRefresh() { if (auto listView = dynamic_cast(listControl)) { auto itemIndex = GetIndex(); if (auto view = dynamic_cast(listView->GetItemProvider()->RequestView(WString::Unmanaged(IListViewItemView::Identifier)))) { vint subColumnCount = view->GetColumnCount() - 1; if (subColumnCount < 0) subColumnCount = 0; ResetTextTable(subColumnCount); auto imageData = view->GetSmallImage(itemIndex); if (imageData) { image->SetImage(imageData->GetImage(), imageData->GetFrameIndex()); } else { image->SetImage(0); } text->SetText(view->GetText(itemIndex)); text->SetColor(listView->TypedControlTemplateObject(true)->GetPrimaryTextColor()); for (vint i = 0; i < subColumnCount; i++) { subItemTexts[i]->SetText(view->GetSubItem(itemIndex, i)); subItemTexts[i]->SetColor(listView->TypedControlTemplateObject(true)->GetSecondaryTextColor()); } } } UpdateSubItemSize(); } void DetailListViewItemTemplate::OnFontChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { text->SetFont(GetFont()); for (auto subText : subItemTexts) { subText->SetFont(GetFont()); } } DetailListViewItemTemplate::DetailListViewItemTemplate() { } DetailListViewItemTemplate::~DetailListViewItemTemplate() { } } } } } /*********************************************************************** .\CONTROLS\LISTCONTROLPACKAGE\GUITEXTLISTCONTROLS.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { using namespace collections; using namespace elements; using namespace compositions; using namespace reflection::description; namespace list { const wchar_t* const ITextItemView::Identifier = L"vl::presentation::controls::list::ITextItemView"; /*********************************************************************** DefaultTextListItemTemplate ***********************************************************************/ TemplateProperty DefaultTextListItemTemplate::CreateBulletStyle() { return {}; } void DefaultTextListItemTemplate::OnInitialize() { SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); textElement = GuiSolidLabelElement::Create(); textElement->SetAlignments(Alignment::Left, Alignment::Center); GuiBoundsComposition* textComposition = new GuiBoundsComposition; textComposition->SetOwnedElement(Ptr(textElement)); textComposition->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElement); if (auto bulletStyleController = CreateBulletStyle()) { bulletButton = new GuiSelectableButton(theme::ThemeName::Unknown); bulletButton->SetAutoFocus(false); bulletButton->SetControlTemplate(bulletStyleController); bulletButton->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); bulletButton->SelectedChanged.AttachMethod(this, &DefaultTextListItemTemplate::OnBulletSelectedChanged); GuiTableComposition* table = new GuiTableComposition; AddChild(table); table->SetAlignmentToParent(Margin(0, 0, 0, 0)); table->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); table->SetRowsAndColumns(1, 2); table->SetRowOption(0, GuiCellOption::PercentageOption(1.0)); table->SetColumnOption(0, GuiCellOption::MinSizeOption()); table->SetColumnOption(1, GuiCellOption::PercentageOption(1.0)); { GuiCellComposition* cell = new GuiCellComposition; table->AddChild(cell); cell->SetSite(0, 0, 1, 1); cell->AddChild(bulletButton->GetBoundsComposition()); } { GuiCellComposition* cell = new GuiCellComposition; table->AddChild(cell); cell->SetSite(0, 1, 1, 1); cell->AddChild(textComposition); textComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); } } else { AddChild(textComposition); textComposition->SetAlignmentToParent(Margin(5, 2, 0, 2)); } FontChanged.AttachMethod(this, &DefaultTextListItemTemplate::OnFontChanged); TextChanged.AttachMethod(this, &DefaultTextListItemTemplate::OnTextChanged); TextColorChanged.AttachMethod(this, &DefaultTextListItemTemplate::OnTextColorChanged); CheckedChanged.AttachMethod(this, &DefaultTextListItemTemplate::OnCheckedChanged); FontChanged.Execute(compositions::GuiEventArgs(this)); TextChanged.Execute(compositions::GuiEventArgs(this)); TextColorChanged.Execute(compositions::GuiEventArgs(this)); CheckedChanged.Execute(compositions::GuiEventArgs(this)); } void DefaultTextListItemTemplate::OnRefresh() { } void DefaultTextListItemTemplate::OnFontChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { textElement->SetFont(GetFont()); } void DefaultTextListItemTemplate::OnTextChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { textElement->SetText(GetText()); } void DefaultTextListItemTemplate::OnTextColorChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { textElement->SetColor(GetTextColor()); } void DefaultTextListItemTemplate::OnCheckedChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if (bulletButton) { supressEdit = true; bulletButton->SetSelected(GetChecked()); supressEdit = false; } } void DefaultTextListItemTemplate::OnBulletSelectedChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::controls::list::DefaultTextListItemTemplate::OnBulletSelectedChanged(GuiGraphicsComposition*, GuiEventArgs&)#" if (!supressEdit) { if (auto textItemView = dynamic_cast(listControl->GetItemProvider()->RequestView(WString::Unmanaged(ITextItemView::Identifier)))) { listControl->GetItemProvider()->PushEditing(); textItemView->SetChecked(GetIndex(), bulletButton->GetSelected()); CHECK_ERROR(listControl->GetItemProvider()->PopEditing(), ERROR_MESSAGE_PREFIX L"BeginEditListItem and EndEditListItem calls are not paired."); } } #undef ERROR_MESSAGE_PREFIX } DefaultTextListItemTemplate::DefaultTextListItemTemplate() { } DefaultTextListItemTemplate::~DefaultTextListItemTemplate() { } /*********************************************************************** DefaultCheckTextListItemTemplate ***********************************************************************/ TemplateProperty DefaultCheckTextListItemTemplate::CreateBulletStyle() { if (auto textList = dynamic_cast(listControl)) { auto style = textList->TypedControlTemplateObject(true)->GetCheckBulletTemplate(); if (style) return style; } return theme::GetCurrentTheme()->CreateStyle(theme::ThemeName::CheckTextListItem); } /*********************************************************************** DefaultRadioTextListItemTemplate ***********************************************************************/ TemplateProperty DefaultRadioTextListItemTemplate::CreateBulletStyle() { if (auto textList = dynamic_cast(listControl)) { auto style = textList->TypedControlTemplateObject(true)->GetRadioBulletTemplate(); if (style) return style; } return theme::GetCurrentTheme()->CreateStyle(theme::ThemeName::RadioTextListItem); } /*********************************************************************** TextItem ***********************************************************************/ void TextItem::NotifyUpdate(bool raiseCheckEvent) { if (owner) { vint index = owner->IndexOf(this); owner->InvokeOnItemModified(index, 1, 1, false); if (raiseCheckEvent) { GuiItemEventArgs arguments; arguments.itemIndex = index; owner->listControl->ItemChecked.Execute(arguments); } } } TextItem::TextItem() :owner(0) , checked(false) { } TextItem::TextItem(const WString& _text, bool _checked) :owner(0) , text(_text) , checked(_checked) { } TextItem::~TextItem() { } const WString& TextItem::GetText() { return text; } void TextItem::SetText(const WString& value) { if (text != value) { text = value; NotifyUpdate(false); } } bool TextItem::GetChecked() { return checked; } void TextItem::SetChecked(bool value) { if (checked != value) { checked = value; NotifyUpdate(true); } } /*********************************************************************** TextItemProvider ***********************************************************************/ void TextItemProvider::AfterInsert(vint item, const Ptr& value) { ListProvider>::AfterInsert(item, value); value->owner = this; } void TextItemProvider::BeforeRemove(vint item, const Ptr& value) { value->owner = 0; ListProvider>::BeforeRemove(item, value); } WString TextItemProvider::GetTextValue(vint itemIndex) { return Get(itemIndex)->GetText(); } description::Value TextItemProvider::GetBindingValue(vint itemIndex) { return Value::From(Get(itemIndex)); } bool TextItemProvider::GetChecked(vint itemIndex) { return Get(itemIndex)->GetChecked(); } void TextItemProvider::SetChecked(vint itemIndex, bool value) { return Get(itemIndex)->SetChecked(value); } TextItemProvider::TextItemProvider() :listControl(0) { } TextItemProvider::~TextItemProvider() { } IDescriptable* TextItemProvider::RequestView(const WString& identifier) { if (identifier == ITextItemView::Identifier) { return (ITextItemView*)this; } else { return nullptr; } } } /*********************************************************************** GuiTextList ***********************************************************************/ void GuiVirtualTextList::BeforeControlTemplateUninstalled_() { } void GuiVirtualTextList::AfterControlTemplateInstalled_(bool initialize) { } void GuiVirtualTextList::OnStyleInstalled(vint itemIndex, ItemStyle* style, bool refreshPropertiesOnly) { GuiSelectableListControl::OnStyleInstalled(itemIndex, style, refreshPropertiesOnly); if (auto textItemStyle = dynamic_cast(style)) { textItemStyle->SetTextColor(TypedControlTemplateObject(true)->GetTextColor()); if (auto textItemView = dynamic_cast(itemProvider->RequestView(WString::Unmanaged(list::ITextItemView::Identifier)))) { textItemStyle->SetChecked(textItemView->GetChecked(itemIndex)); } } if (refreshPropertiesOnly) { if (auto predefinedItemStyle = dynamic_cast(style)) { predefinedItemStyle->RefreshItem(); } } } void GuiVirtualTextList::OnItemTemplateChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { view = TextListView::Unknown; } GuiVirtualTextList::GuiVirtualTextList(theme::ThemeName themeName, list::IItemProvider* _itemProvider) :GuiSelectableListControl(themeName, _itemProvider) { ItemTemplateChanged.AttachMethod(this, &GuiVirtualTextList::OnItemTemplateChanged); ItemChecked.SetAssociatedComposition(boundsComposition); SetView(TextListView::Text); } GuiVirtualTextList::~GuiVirtualTextList() { } TextListView GuiVirtualTextList::GetView() { return view; } void GuiVirtualTextList::SetView(TextListView _view) { switch (_view) { case TextListView::Text: SetStyleAndArranger( [](const Value&) { return new list::DefaultTextListItemTemplate; }, Ptr(new list::FixedHeightItemArranger) ); break; case TextListView::Check: SetStyleAndArranger( [](const Value&) { return new list::DefaultCheckTextListItemTemplate; }, Ptr(new list::FixedHeightItemArranger) ); break; case TextListView::Radio: SetStyleAndArranger( [](const Value&) { return new list::DefaultRadioTextListItemTemplate; }, Ptr(new list::FixedHeightItemArranger) ); break; default:; } view = _view; } /*********************************************************************** GuiTextList ***********************************************************************/ GuiTextList::GuiTextList(theme::ThemeName themeName) :GuiVirtualTextList(themeName, new list::TextItemProvider) { items=dynamic_cast(itemProvider.Obj()); items->listControl=this; } GuiTextList::~GuiTextList() { } list::TextItemProvider& GuiTextList::GetItems() { return *items; } Ptr GuiTextList::GetSelectedItem() { vint index = GetSelectedItemIndex(); if (index == -1) return 0; return items->Get(index); } } } } /*********************************************************************** .\CONTROLS\LISTCONTROLPACKAGE\GUITREEVIEWCONTROLS.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { using namespace elements; using namespace compositions; using namespace reflection::description; /*********************************************************************** GuiVirtualTreeListControl ***********************************************************************/ void GuiVirtualTreeListControl::BeforeControlTemplateUninstalled_() { } void GuiVirtualTreeListControl::AfterControlTemplateInstalled_(bool initialize) { } void GuiVirtualTreeListControl::OnAttached(tree::INodeRootProvider* provider) { } void GuiVirtualTreeListControl::OnBeforeItemModified(tree::INodeProvider* parentNode, vint start, vint count, vint newCount, bool itemReferenceUpdated) { } void GuiVirtualTreeListControl::OnAfterItemModified(tree::INodeProvider* parentNode, vint start, vint count, vint newCount, bool itemReferenceUpdated) { } void GuiVirtualTreeListControl::OnItemExpanded(tree::INodeProvider* node) { GuiNodeEventArgs arguments; (GuiEventArgs&)arguments=GetNotifyEventArguments(); arguments.node=node; NodeExpanded.Execute(arguments); } void GuiVirtualTreeListControl::OnItemCollapsed(tree::INodeProvider* node) { GuiNodeEventArgs arguments; (GuiEventArgs&)arguments=GetNotifyEventArguments(); arguments.node=node; NodeCollapsed.Execute(arguments); } vint GuiVirtualTreeListControl::FindItemByVirtualKeyDirection(vint index, compositions::KeyDirection keyDirection) { vint newIndex = GuiSelectableListControl::FindItemByVirtualKeyDirection(index, keyDirection); if (newIndex != -1) return newIndex; auto selectedNode = nodeItemView->RequestNode(index); if (selectedNode) { bool hasChildren = selectedNode->GetChildCount() > 0; bool expanding = selectedNode->GetExpanding(); switch (keyDirection) { case KeyDirection::Right: if (hasChildren) { if (expanding) { selectedNode = selectedNode->GetChild(0); } else { selectedNode->SetExpanding(true); } } break; case KeyDirection::Left: { selectedNode->SetExpanding(false); if (!expanding || !hasChildren) { selectedNode = selectedNode->GetParent(); } } break; default:; } } return selectedNode ? nodeItemView->CalculateNodeVisibilityIndex(selectedNode.Obj()) : -1; } void GuiVirtualTreeListControl::OnItemMouseEvent(compositions::GuiNodeMouseEvent& nodeEvent, compositions::GuiGraphicsComposition* sender, compositions::GuiItemMouseEventArgs& arguments) { auto node = GetNodeItemView()->RequestNode(arguments.itemIndex); if (node) { GuiNodeMouseEventArgs redirectArguments; (GuiMouseEventArgs&)redirectArguments = arguments; redirectArguments.node = node.Obj(); nodeEvent.Execute(redirectArguments); (GuiMouseEventArgs&)arguments = redirectArguments; } } void GuiVirtualTreeListControl::OnItemNotifyEvent(compositions::GuiNodeNotifyEvent& nodeEvent, compositions::GuiGraphicsComposition* sender, compositions::GuiItemEventArgs& arguments) { if (auto node = GetNodeItemView()->RequestNode(arguments.itemIndex)) { GuiNodeEventArgs redirectArguments; (GuiEventArgs&)redirectArguments = arguments; redirectArguments.node = node.Obj(); nodeEvent.Execute(redirectArguments); (GuiEventArgs&)arguments = redirectArguments; } } #define ATTACH_ITEM_MOUSE_EVENT(NODEEVENTNAME, ITEMEVENTNAME)\ {\ ITEMEVENTNAME.AttachFunction([this](GuiGraphicsComposition* sender, GuiItemMouseEventArgs& args){ OnItemMouseEvent(NODEEVENTNAME, sender, args); });\ }\ #define ATTACH_ITEM_NOTIFY_EVENT(NODEEVENTNAME, ITEMEVENTNAME)\ {\ ITEMEVENTNAME.AttachFunction([this](GuiGraphicsComposition* sender, GuiItemEventArgs& args){ OnItemNotifyEvent(NODEEVENTNAME, sender, args); });\ }\ void GuiVirtualTreeListControl::OnNodeLeftButtonDoubleClick(compositions::GuiGraphicsComposition* sender, compositions::GuiNodeMouseEventArgs& arguments) { if (arguments.node->GetChildCount() > 0) { arguments.node->SetExpanding(!arguments.node->GetExpanding()); } } GuiVirtualTreeListControl::GuiVirtualTreeListControl(theme::ThemeName themeName, Ptr _nodeRootProvider) :GuiSelectableListControl(themeName, new tree::NodeItemProvider(_nodeRootProvider)) { nodeItemProvider = dynamic_cast(GetItemProvider()); nodeItemView = dynamic_cast(GetItemProvider()->RequestView(WString::Unmanaged(tree::INodeItemView::Identifier))); NodeLeftButtonDown.SetAssociatedComposition(boundsComposition); NodeLeftButtonUp.SetAssociatedComposition(boundsComposition); NodeLeftButtonDoubleClick.SetAssociatedComposition(boundsComposition); NodeMiddleButtonDown.SetAssociatedComposition(boundsComposition); NodeMiddleButtonUp.SetAssociatedComposition(boundsComposition); NodeMiddleButtonDoubleClick.SetAssociatedComposition(boundsComposition); NodeRightButtonDown.SetAssociatedComposition(boundsComposition); NodeRightButtonUp.SetAssociatedComposition(boundsComposition); NodeRightButtonDoubleClick.SetAssociatedComposition(boundsComposition); NodeMouseMove.SetAssociatedComposition(boundsComposition); NodeMouseEnter.SetAssociatedComposition(boundsComposition); NodeMouseLeave.SetAssociatedComposition(boundsComposition); NodeExpanded.SetAssociatedComposition(boundsComposition); NodeCollapsed.SetAssociatedComposition(boundsComposition); ATTACH_ITEM_MOUSE_EVENT(NodeLeftButtonDown, ItemLeftButtonDown); ATTACH_ITEM_MOUSE_EVENT(NodeLeftButtonUp, ItemLeftButtonUp); ATTACH_ITEM_MOUSE_EVENT(NodeLeftButtonDoubleClick, ItemLeftButtonDoubleClick); ATTACH_ITEM_MOUSE_EVENT(NodeMiddleButtonDown, ItemMiddleButtonDown); ATTACH_ITEM_MOUSE_EVENT(NodeMiddleButtonUp, ItemMiddleButtonUp); ATTACH_ITEM_MOUSE_EVENT(NodeMiddleButtonDoubleClick, ItemMiddleButtonDoubleClick); ATTACH_ITEM_MOUSE_EVENT(NodeRightButtonDown, ItemRightButtonDown); ATTACH_ITEM_MOUSE_EVENT(NodeRightButtonUp, ItemRightButtonUp); ATTACH_ITEM_MOUSE_EVENT(NodeRightButtonDoubleClick, ItemRightButtonDoubleClick); ATTACH_ITEM_MOUSE_EVENT(NodeMouseMove, ItemMouseMove); ATTACH_ITEM_NOTIFY_EVENT(NodeMouseEnter, ItemMouseEnter); ATTACH_ITEM_NOTIFY_EVENT(NodeMouseLeave, ItemMouseLeave); nodeItemProvider->GetRoot()->AttachCallback(this); NodeLeftButtonDoubleClick.AttachMethod(this, &GuiVirtualTreeListControl::OnNodeLeftButtonDoubleClick); } #undef ATTACH_ITEM_MOUSE_EVENT #undef ATTACH_ITEM_NOTIFY_EVENT GuiVirtualTreeListControl::~GuiVirtualTreeListControl() { } tree::INodeItemView* GuiVirtualTreeListControl::GetNodeItemView() { return nodeItemView; } tree::INodeRootProvider* GuiVirtualTreeListControl::GetNodeRootProvider() { return nodeItemProvider->GetRoot().Obj(); } namespace tree { /*********************************************************************** TreeViewItem ***********************************************************************/ const wchar_t* const ITreeViewItemView::Identifier = L"vl::presentation::controls::tree::ITreeViewItemView"; TreeViewItem::TreeViewItem() { } TreeViewItem::TreeViewItem(const Ptr& _image, const WString& _text) :image(_image) ,text(_text) { } /*********************************************************************** TreeViewItemRootProvider ***********************************************************************/ Ptr TreeViewItemRootProvider::GetNodeImage(INodeProvider* node) { MemoryNodeProvider* memoryNode=dynamic_cast(node); if(memoryNode) { Ptr data=memoryNode->GetData().Cast(); if(data) { return data->image; } } return 0; } WString TreeViewItemRootProvider::GetTextValue(INodeProvider* node) { MemoryNodeProvider* memoryNode = dynamic_cast(node); if (memoryNode) { Ptr data = memoryNode->GetData().Cast(); if (data) { return data->text; } } return L""; } description::Value TreeViewItemRootProvider::GetBindingValue(INodeProvider* node) { return Value::From(GetTreeViewData(node)); } TreeViewItemRootProvider::TreeViewItemRootProvider() { } TreeViewItemRootProvider::~TreeViewItemRootProvider() { } IDescriptable* TreeViewItemRootProvider::RequestView(const WString& identifier) { if(identifier==ITreeViewItemView::Identifier) { return (ITreeViewItemView*)this; } else { return MemoryNodeRootProvider::RequestView(identifier); } } Ptr TreeViewItemRootProvider::GetTreeViewData(INodeProvider* node) { MemoryNodeProvider* memoryNode=GetMemoryNode(node); if(memoryNode) { return memoryNode->GetData().Cast(); } else { return 0; } } void TreeViewItemRootProvider::SetTreeViewData(INodeProvider* node, Ptr value) { MemoryNodeProvider* memoryNode=GetMemoryNode(node); if(memoryNode) { memoryNode->SetData(value); } } void TreeViewItemRootProvider::UpdateTreeViewData(INodeProvider* node) { MemoryNodeProvider* memoryNode=GetMemoryNode(node); if(memoryNode) { memoryNode->NotifyDataModified(); } } } /*********************************************************************** GuiVirtualTreeView ***********************************************************************/ templates::GuiTreeItemTemplate* GuiVirtualTreeView::GetStyleFromNode(tree::INodeProvider* node) { if (itemArranger) { vint index = nodeItemView->CalculateNodeVisibilityIndex(node); if (index != -1) { auto style = itemArranger->GetVisibleStyle(index); return dynamic_cast(style); } } return nullptr; } void GuiVirtualTreeView::SetStyleExpanding(tree::INodeProvider* node, bool expanding) { if (auto treeItemStyle = GetStyleFromNode(node)) { treeItemStyle->SetExpanding(expanding); } } void GuiVirtualTreeView::SetStyleExpandable(tree::INodeProvider* node, bool expandable) { if (auto treeItemStyle = GetStyleFromNode(node)) { treeItemStyle->SetExpandable(expandable); } } void GuiVirtualTreeView::OnAfterItemModified(tree::INodeProvider* parentNode, vint start, vint count, vint newCount, bool itemReferenceUpdated) { GuiVirtualTreeListControl::OnAfterItemModified(parentNode, start, count, newCount, itemReferenceUpdated); SetStyleExpandable(parentNode, parentNode->GetChildCount() > 0); } void GuiVirtualTreeView::OnItemExpanded(tree::INodeProvider* node) { GuiVirtualTreeListControl::OnItemExpanded(node); SetStyleExpanding(node, true); } void GuiVirtualTreeView::OnItemCollapsed(tree::INodeProvider* node) { GuiVirtualTreeListControl::OnItemCollapsed(node); SetStyleExpanding(node, false); } void GuiVirtualTreeView::OnStyleInstalled(vint itemIndex, ItemStyle* style, bool refreshPropertiesOnly) { GuiVirtualTreeListControl::OnStyleInstalled(itemIndex, style, refreshPropertiesOnly); if (auto textItemStyle = dynamic_cast(style)) { textItemStyle->SetTextColor(TypedControlTemplateObject(true)->GetTextColor()); } if (auto treeItemStyle = dynamic_cast(style)) { treeItemStyle->SetTextColor(TypedControlTemplateObject(true)->GetTextColor()); if (treeViewItemView) { if (auto node = nodeItemView->RequestNode(itemIndex)) { treeItemStyle->SetImage(treeViewItemView->GetNodeImage(node.Obj())); treeItemStyle->SetExpanding(node->GetExpanding()); treeItemStyle->SetExpandable(node->GetChildCount() > 0); { vint level = -1; auto current = node; while (current->GetParent()) { level++; current = current->GetParent(); } treeItemStyle->SetLevel(level); } } } } if (refreshPropertiesOnly) { if (auto predefinedItemStyle = dynamic_cast(style)) { predefinedItemStyle->RefreshItem(); } } } GuiVirtualTreeView::GuiVirtualTreeView(theme::ThemeName themeName, Ptr _nodeRootProvider) :GuiVirtualTreeListControl(themeName, _nodeRootProvider) { treeViewItemView = dynamic_cast(GetNodeRootProvider()->RequestView(WString::Unmanaged(tree::ITreeViewItemView::Identifier))); SetStyleAndArranger( [](const Value&) { return new tree::DefaultTreeItemTemplate; }, Ptr(new list::FixedHeightItemArranger) ); } GuiVirtualTreeView::~GuiVirtualTreeView() { } /*********************************************************************** GuiTreeView ***********************************************************************/ GuiTreeView::GuiTreeView(theme::ThemeName themeName) :GuiVirtualTreeView(themeName, Ptr(new tree::TreeViewItemRootProvider)) { nodes = nodeItemProvider->GetRoot().Cast(); } GuiTreeView::~GuiTreeView() { } Ptr GuiTreeView::Nodes() { return nodes; } Ptr GuiTreeView::GetSelectedItem() { Ptr result; vint index = GetSelectedItemIndex(); if (index != -1) { if (auto node = nodeItemView->RequestNode(index)) { if (auto memoryNode = node.Cast()) { result = memoryNode->GetData().Cast(); } } } return result; } namespace tree { /*********************************************************************** DefaultTreeItemTemplate ***********************************************************************/ void DefaultTreeItemTemplate::OnInitialize() { SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); table = new GuiTableComposition; AddChild(table); table->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); table->SetRowsAndColumns(3, 4); table->SetRowOption(0, GuiCellOption::PercentageOption(0.5)); table->SetRowOption(1, GuiCellOption::MinSizeOption()); table->SetRowOption(2, GuiCellOption::PercentageOption(0.5)); table->SetColumnOption(0, GuiCellOption::AbsoluteOption(0)); table->SetColumnOption(1, GuiCellOption::MinSizeOption()); table->SetColumnOption(2, GuiCellOption::MinSizeOption()); table->SetColumnOption(3, GuiCellOption::MinSizeOption()); table->SetAlignmentToParent(Margin(0, 0, 0, 0)); table->SetCellPadding(2); { GuiCellComposition* cell = new GuiCellComposition; table->AddChild(cell); cell->SetSite(0, 1, 3, 1); cell->SetPreferredMinSize(Size(16, 16)); expandingButton = new GuiSelectableButton(theme::ThemeName::TreeItemExpander); if (auto treeView = dynamic_cast(listControl)) { if (auto expanderStyle = treeView->TypedControlTemplateObject(true)->GetExpandingDecoratorTemplate()) { expandingButton->SetControlTemplate(expanderStyle); } } expandingButton->SetAutoFocus(false); expandingButton->SetAutoSelection(false); expandingButton->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); expandingButton->GetBoundsComposition()->GetEventReceiver()->leftButtonDoubleClick.AttachMethod(this, &DefaultTreeItemTemplate::OnExpandingButtonDoubleClick); expandingButton->Clicked.AttachMethod(this, &DefaultTreeItemTemplate::OnExpandingButtonClicked); cell->AddChild(expandingButton->GetBoundsComposition()); } { GuiCellComposition* cell = new GuiCellComposition; table->AddChild(cell); cell->SetSite(1, 2, 1, 1); cell->SetPreferredMinSize(Size(16, 16)); imageElement = GuiImageFrameElement::Create(); imageElement->SetStretch(true); cell->SetOwnedElement(Ptr(imageElement)); } { GuiCellComposition* cell = new GuiCellComposition; table->AddChild(cell); cell->SetSite(0, 3, 3, 1); cell->SetPreferredMinSize(Size(192, 0)); textElement = GuiSolidLabelElement::Create(); textElement->SetAlignments(Alignment::Left, Alignment::Center); textElement->SetEllipse(true); cell->SetOwnedElement(Ptr(textElement)); } FontChanged.AttachMethod(this, &DefaultTreeItemTemplate::OnFontChanged); TextChanged.AttachMethod(this, &DefaultTreeItemTemplate::OnTextChanged); TextColorChanged.AttachMethod(this, &DefaultTreeItemTemplate::OnTextColorChanged); ExpandingChanged.AttachMethod(this, &DefaultTreeItemTemplate::OnExpandingChanged); ExpandableChanged.AttachMethod(this, &DefaultTreeItemTemplate::OnExpandableChanged); LevelChanged.AttachMethod(this, &DefaultTreeItemTemplate::OnLevelChanged); ImageChanged.AttachMethod(this, &DefaultTreeItemTemplate::OnImageChanged); FontChanged.Execute(compositions::GuiEventArgs(this)); TextChanged.Execute(compositions::GuiEventArgs(this)); TextColorChanged.Execute(compositions::GuiEventArgs(this)); ExpandingChanged.Execute(compositions::GuiEventArgs(this)); ExpandableChanged.Execute(compositions::GuiEventArgs(this)); LevelChanged.Execute(compositions::GuiEventArgs(this)); ImageChanged.Execute(compositions::GuiEventArgs(this)); } void DefaultTreeItemTemplate::OnRefresh() { } void DefaultTreeItemTemplate::OnFontChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { textElement->SetFont(GetFont()); } void DefaultTreeItemTemplate::OnTextChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { textElement->SetText(GetText()); } void DefaultTreeItemTemplate::OnTextColorChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { textElement->SetColor(GetTextColor()); } void DefaultTreeItemTemplate::OnExpandingChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { expandingButton->SetSelected(GetExpanding()); } void DefaultTreeItemTemplate::OnExpandableChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { expandingButton->SetVisible(GetExpandable()); } void DefaultTreeItemTemplate::OnLevelChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { table->SetColumnOption(0, GuiCellOption::AbsoluteOption(GetLevel() * 12)); } void DefaultTreeItemTemplate::OnImageChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if (auto imageData = GetImage()) { imageElement->SetImage(imageData->GetImage(), imageData->GetFrameIndex()); } else { imageElement->SetImage(nullptr); } } void DefaultTreeItemTemplate::OnExpandingButtonDoubleClick(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) { arguments.handled = true; } void DefaultTreeItemTemplate::OnExpandingButtonClicked(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if (expandingButton->GetVisuallyEnabled()) { if (auto treeControl = dynamic_cast(listControl)) { if (auto view = treeControl->GetNodeItemView()) { vint index = treeControl->GetArranger()->GetVisibleIndex(this); if (index != -1) { if (auto node = view->RequestNode(index)) { bool expanding = node->GetExpanding(); node->SetExpanding(!expanding); } } } } } } DefaultTreeItemTemplate::DefaultTreeItemTemplate() { } DefaultTreeItemTemplate::~DefaultTreeItemTemplate() { } } } } } /*********************************************************************** .\CONTROLS\TEMPLATES\GUIANIMATION.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { using namespace collections; using namespace reflection::description; /*********************************************************************** GuiTimedAnimation ***********************************************************************/ class GuiTimedAnimation : public Object, public virtual IGuiAnimation { protected: DateTime startUtcTime; vuint64_t time; bool running = false; public: GuiTimedAnimation() { } ~GuiTimedAnimation() { } void Start()override { startUtcTime = DateTime::UtcTime(); time = 0; running = true; } void Pause()override { time = GetTime(); running = false; } void Resume()override { startUtcTime = DateTime::UtcTime(); running = true; } vuint64_t GetTime() { if (running) { return time + (DateTime::UtcTime().osMilliseconds - startUtcTime.osMilliseconds); } else { return time; } } }; /*********************************************************************** GuiFiniteAnimation ***********************************************************************/ class GuiFiniteAnimation : public GuiTimedAnimation { protected: vuint64_t length = 0; vuint64_t currentTime = 0; Func run; public: GuiFiniteAnimation(const Func& _run, vuint64_t _length) :run(_run) , length(_length) { } ~GuiFiniteAnimation() { } void Run()override { currentTime = GetTime(); if (currentTime < length && run) { run(currentTime); } } bool GetStopped()override { return currentTime >= length; } }; /*********************************************************************** GuiInfiniteAnimation ***********************************************************************/ class GuiInfiniteAnimation : public GuiTimedAnimation { protected: Func run; public: GuiInfiniteAnimation(const Func& _run) :run(_run) { } ~GuiInfiniteAnimation() { } void Run()override { if (run) { run(GetTime()); } } bool GetStopped()override { return false; } }; /*********************************************************************** IGuiAnimation ***********************************************************************/ Ptr IGuiAnimation::CreateAnimation(const Func& run, vuint64_t milliseconds) { return Ptr(new GuiFiniteAnimation(run, milliseconds)); } Ptr IGuiAnimation::CreateAnimation(const Func& run) { return Ptr(new GuiInfiniteAnimation(run)); } /*********************************************************************** IGuiAnimationCoroutine ***********************************************************************/ class GuiCoroutineAnimation : public Object, public virtual IGuiAnimationCoroutine::IImpl { protected: IGuiAnimationCoroutine::Creator creator; Ptr coroutine; Ptr waitingAnimation; vint waitingGroup = -1; Group> groupAnimations; public: GuiCoroutineAnimation(const IGuiAnimationCoroutine::Creator& _creator) :creator(_creator) { } ~GuiCoroutineAnimation() { } void OnPlayAndWait(Ptr animation)override { CHECK_ERROR(!waitingAnimation && waitingGroup == -1, L"GuiCoroutineAnimation::OnPlayAndWait(Ptr)#Cannot be called when an animation or a group has already been waiting for."); waitingAnimation = animation; waitingAnimation->Start(); } void OnPlayInGroup(Ptr animation, vint groupId)override { groupAnimations.Add(groupId, animation); animation->Start(); } void OnWaitForGroup(vint groupId)override { CHECK_ERROR(!waitingAnimation && waitingGroup == -1, L"GuiCoroutineAnimation::OnWaitForGroup(vint)#Cannot be called when an animation or a group has already been waiting for."); if (groupAnimations.Keys().Contains(groupId)) { waitingGroup = groupId; } } void Start()override { CHECK_ERROR(!coroutine, L"GuiCoroutineAnimation::Start()#Cannot be called more than once."); coroutine = creator(this); } void Pause()override { if (waitingAnimation) { waitingAnimation->Pause(); } // TODO: (enumerable) foreach on group for (vint i = 0; i < groupAnimations.Count(); i++) { for (auto animation : groupAnimations.GetByIndex(i)) { animation->Pause(); } } } void Resume()override { if (waitingAnimation) { waitingAnimation->Resume(); } // TODO: (enumerable) foreach on group for (vint i = 0; i < groupAnimations.Count(); i++) { for (auto animation : groupAnimations.GetByIndex(i)) { animation->Resume(); } } } void Run()override { CHECK_ERROR(coroutine, L"GuiCoroutineAnimation::Run()#Cannot be called before calling Start."); if (waitingAnimation) { waitingAnimation->Run(); if (waitingAnimation->GetStopped()) { waitingAnimation = nullptr; } } // TODO: (enumerable) foreach:reversed on group for (vint i = groupAnimations.Count() - 1; i >= 0; i--) { auto& animations = groupAnimations.GetByIndex(i); for (vint j = animations.Count() - 1; j >= 0; j--) { auto animation = animations[j]; animation->Run(); if (animation->GetStopped()) { groupAnimations.Remove(i, animation.Obj()); } } } if (waitingGroup != -1 && !groupAnimations.Keys().Contains(waitingGroup)) { waitingGroup = -1; } if (coroutine->GetStatus() == CoroutineStatus::Waiting) { if (waitingAnimation || waitingGroup != -1) { return; } coroutine->Resume(true, nullptr); } } bool GetStopped()override { if (!coroutine) return false; if (coroutine->GetStatus() != CoroutineStatus::Stopped) return false; if (waitingAnimation || groupAnimations.Count() > 0) return false; return true; } }; void IGuiAnimationCoroutine::WaitAndPause(IImpl* impl, vuint64_t milliseconds) { return PlayAndWaitAndPause(impl, IGuiAnimation::CreateAnimation({}, milliseconds)); } void IGuiAnimationCoroutine::PlayAndWaitAndPause(IImpl* impl, Ptr animation) { impl->OnPlayAndWait(animation); } void IGuiAnimationCoroutine::PlayInGroupAndPause(IImpl* impl, Ptr animation, vint groupId) { impl->OnPlayInGroup(animation, groupId); } void IGuiAnimationCoroutine::WaitForGroupAndPause(IImpl* impl, vint groupId) { impl->OnWaitForGroup(groupId); } void IGuiAnimationCoroutine::ReturnAndExit(IImpl* impl) { } Ptr IGuiAnimationCoroutine::Create(const Creator& creator) { return Ptr(new GuiCoroutineAnimation(creator)); } } } } /*********************************************************************** .\CONTROLS\TEMPLATES\GUICOMMONTEMPLATES.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace templates { using namespace elements; using namespace compositions; using namespace templates; using namespace controls; using namespace theme; /*********************************************************************** GuiCommonDatePickerLook ***********************************************************************/ vint GetDayCountForMonth(vint year, vint month) { bool isLeapYear = (year % 100 == 0) ? (year % 400 == 0) : (year % 4 == 0); switch (month) { case 1:case 3:case 5:case 7:case 8:case 10:case 12: return 31; case 4:case 6:case 9:case 11: return 30; default: return isLeapYear ? 29 : 28; } } void StepPreviousMonth(vint& year, vint& month) { if (month == 1) { year--; month = 12; } else { month--; } } void StepNextMonth(vint& year, vint& month) { if (month == 12) { year++; month = 1; } else { month++; } } void GuiCommonDatePickerLook::SetDay(const DateTime& day, vint& index, vint monthOffset) { dateDays[index] = day; GuiSolidLabelElement* label = labelDays[index]; label->SetText(itow(day.day)); label->SetColor(monthOffset == 0 ? primaryTextColor : secondaryTextColor); wchar_t alt[] = L"D00"; if (monthOffset == -1) alt[0] = L'C'; else if (monthOffset == 1) alt[0] = L'E'; alt[1] = (wchar_t)(L'0' + day.day / 10); alt[2] = (wchar_t)(L'0' + day.day % 10); buttonDays[index]->SetAlt(alt); index++; } void GuiCommonDatePickerLook::comboYearMonth_SelectedIndexChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if (!preventComboEvent) { if (comboYear->GetSelectedIndex() != -1 && comboMonth->GetSelectedIndex() != -1) { vint year = comboYear->GetSelectedIndex() + YearFirst; vint month = comboMonth->GetSelectedIndex() + 1; SetDate(DateTime::FromDateTime(year, month, 1)); GuiEventArgs arguments(this); DateChanged.Execute(arguments); commands->NotifyDateChanged(); commands->NotifyDateNavigated(); } } } void GuiCommonDatePickerLook::buttonDay_SelectedChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if (!preventButtonEvent) { GuiSelectableButton* button = dynamic_cast(sender->GetRelatedControl()); if (button->GetSelected()) { vint index = buttonDays.IndexOf(button); if (index != -1) { DateTime day = dateDays[index]; if (day.year != currentDate.year || day.month != currentDate.month) { SetDate(day); } else { currentDate = day; } GuiEventArgs arguments(this); DateChanged.Execute(arguments); commands->NotifyDateChanged(); commands->NotifyDateSelected(); } } } } void GuiCommonDatePickerLook::DisplayMonth(vint year, vint month) { if (YearFirst <= year && year <= YearLast && 1 <= month && month <= 12) { preventComboEvent = true; comboYear->SetSelectedIndex(year - YearFirst); comboMonth->SetSelectedIndex(month - 1); preventComboEvent = false; } vint yearPrev = year, yearNext = year, monthPrev = month, monthNext = month; StepPreviousMonth(yearPrev, monthPrev); StepNextMonth(yearNext, monthNext); vint countPrev = GetDayCountForMonth(yearPrev, monthPrev); vint count = GetDayCountForMonth(year, month); vint countNext = GetDayCountForMonth(yearNext, monthNext); DateTime firstDay = DateTime::FromDateTime(year, month, 1); vint showPrev = firstDay.dayOfWeek; if (showPrev == 0) showPrev = DaysOfWeek; vint show = count; vint showNext = DaysOfWeek*DayRows - showPrev - show; vint index = 0; for (vint i = 0; i < showPrev; i++) { DateTime day = DateTime::FromDateTime(yearPrev, monthPrev, countPrev - (showPrev - i - 1)); SetDay(day, index, -1); } for (vint i = 0; i < show; i++) { DateTime day = DateTime::FromDateTime(year, month, i + 1); SetDay(day, index, 0); } for (vint i = 0; i < showNext; i++) { DateTime day = DateTime::FromDateTime(yearNext, monthNext, i + 1); SetDay(day, index, 1); } } void GuiCommonDatePickerLook::SelectDay(vint day) { // TODO: (enumerable) foreach:indexed for (vint i = 0; i < dateDays.Count(); i++) { const DateTime& dt = dateDays[i]; if (dt.year == currentDate.year && dt.month == currentDate.month && dt.day == day) { preventButtonEvent = true; buttonDays[i]->SetSelected(true); preventButtonEvent = false; break; } } } GuiCommonDatePickerLook::GuiCommonDatePickerLook(Color _backgroundColor, Color _primaryTextColor, Color _secondaryTextColor) :backgroundColor(_backgroundColor) , primaryTextColor(_primaryTextColor) , secondaryTextColor(_secondaryTextColor) { DateChanged.SetAssociatedComposition(this); SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); GuiTableComposition* monthTable = 0; GuiTableComposition* dayTable = 0; { listYears = new GuiTextList(theme::ThemeName::TextList); listYears->SetHorizontalAlwaysVisible(false); for (vint i = YearFirst; i <= YearLast; i++) { listYears->GetItems().Add(Ptr(new list::TextItem(itow(i)))); } comboYear = new GuiComboBoxListControl(theme::ThemeName::ComboBox, listYears); comboYear->SetAlt(L"Y"); comboYear->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 2, 0)); comboYear->SelectedIndexChanged.AttachMethod(this, &GuiCommonDatePickerLook::comboYearMonth_SelectedIndexChanged); } { listMonths = new GuiTextList(theme::ThemeName::TextList); listMonths->SetHorizontalAlwaysVisible(false); comboMonth = new GuiComboBoxListControl(theme::ThemeName::ComboBox, listMonths); comboMonth->SetAlt(L"M"); comboMonth->GetBoundsComposition()->SetAlignmentToParent(Margin(2, 0, 0, 0)); comboMonth->SelectedIndexChanged.AttachMethod(this, &GuiCommonDatePickerLook::comboYearMonth_SelectedIndexChanged); } { monthTable = new GuiTableComposition; monthTable->SetAlignmentToParent(Margin(0, 0, 0, 0)); monthTable->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); monthTable->SetRowsAndColumns(1, 2); monthTable->SetRowOption(0, GuiCellOption::MinSizeOption()); monthTable->SetColumnOption(0, GuiCellOption::PercentageOption(0.5)); monthTable->SetColumnOption(1, GuiCellOption::PercentageOption(0.5)); { GuiCellComposition* cell = new GuiCellComposition; monthTable->AddChild(cell); cell->SetSite(0, 0, 1, 1); cell->AddChild(comboYear->GetBoundsComposition()); } { GuiCellComposition* cell = new GuiCellComposition; monthTable->AddChild(cell); cell->SetSite(0, 1, 1, 1); cell->AddChild(comboMonth->GetBoundsComposition()); } } { dayTable = new GuiTableComposition; dayTable->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); dayTable->SetCellPadding(4); dayTable->SetRowsAndColumns(DayRows + DayRowStart, DaysOfWeek); for (vint i = 0; i < DayRowStart; i++) { dayTable->SetRowOption(i, GuiCellOption::MinSizeOption()); } for (vint i = 0; i < DayRows; i++) { dayTable->SetRowOption(i + DayRowStart, GuiCellOption::PercentageOption(1.0)); } for (vint i = 0; i < DaysOfWeek; i++) { dayTable->SetColumnOption(i, GuiCellOption::PercentageOption(1.0)); } { GuiCellComposition* cell = new GuiCellComposition; dayTable->AddChild(cell); cell->SetSite(0, 0, 1, DaysOfWeek); cell->AddChild(monthTable); } labelDaysOfWeek.Resize(7); for (vint i = 0; i < DaysOfWeek; i++) { GuiCellComposition* cell = new GuiCellComposition; dayTable->AddChild(cell); cell->SetSite(1, i, 1, 1); auto element = Ptr(GuiSolidLabelElement::Create()); element->SetAlignments(Alignment::Center, Alignment::Center); element->SetColor(primaryTextColor); labelDaysOfWeek[i] = element.Obj(); cell->SetOwnedElement(element); } buttonDays.Resize(DaysOfWeek*DayRows); labelDays.Resize(DaysOfWeek*DayRows); dateDays.Resize(DaysOfWeek*DayRows); auto dayMutexController = new GuiSelectableButton::MutexGroupController; AddComponent(dayMutexController); for (vint i = 0; i < DaysOfWeek; i++) { for (vint j = 0; j < DayRows; j++) { GuiCellComposition* cell = new GuiCellComposition; dayTable->AddChild(cell); cell->SetSite(j + DayRowStart, i, 1, 1); GuiSelectableButton* button = new GuiSelectableButton(theme::ThemeName::CheckBox); button->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); button->SetGroupController(dayMutexController); button->SelectedChanged.AttachMethod(this, &GuiCommonDatePickerLook::buttonDay_SelectedChanged); cell->AddChild(button->GetBoundsComposition()); buttonDays[j*DaysOfWeek + i] = button; auto element = Ptr(GuiSolidLabelElement::Create()); element->SetAlignments(Alignment::Center, Alignment::Center); element->SetText(L"0"); labelDays[j*DaysOfWeek + i] = element.Obj(); GuiBoundsComposition* elementBounds = new GuiBoundsComposition; elementBounds->SetOwnedElement(element); elementBounds->SetAlignmentToParent(Margin(0, 0, 0, 0)); elementBounds->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElement); button->GetContainerComposition()->AddChild(elementBounds); } } } { auto element = Ptr(GuiSolidBackgroundElement::Create()); element->SetColor(backgroundColor); dayTable->SetOwnedElement(element); } dayTable->SetAlignmentToParent(Margin(0, 0, 0, 0)); AddChild(dayTable); SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); SetFont(font); } GuiCommonDatePickerLook::~GuiCommonDatePickerLook() { FinalizeInstanceRecursively(this); } controls::IDatePickerCommandExecutor* GuiCommonDatePickerLook::GetCommands() { return commands; } void GuiCommonDatePickerLook::SetCommands(controls::IDatePickerCommandExecutor* value) { commands = value; } TemplateProperty GuiCommonDatePickerLook::GetDateButtonTemplate() { return dateButtonTemplate; } void GuiCommonDatePickerLook::SetDateButtonTemplate(const TemplateProperty& value) { dateButtonTemplate = value; for (auto button : buttonDays) { button->SetControlTemplate(value); } } TemplateProperty GuiCommonDatePickerLook::GetDateTextListTemplate() { return dateTextListTemplate; } void GuiCommonDatePickerLook::SetDateTextListTemplate(const TemplateProperty& value) { dateTextListTemplate = value; listYears->SetControlTemplate(value); listMonths->SetControlTemplate(value); } TemplateProperty GuiCommonDatePickerLook::GetDateComboBoxTemplate() { return dateComboBoxTemplate; } void GuiCommonDatePickerLook::SetDateComboBoxTemplate(const TemplateProperty& value) { dateComboBoxTemplate = value; comboYear->SetControlTemplate(value); comboMonth->SetControlTemplate(value); } const Locale& GuiCommonDatePickerLook::GetDateLocale() { return dateLocale; } void GuiCommonDatePickerLook::SetDateLocale(const Locale& value) { if (dateLocale != value) { dateLocale = value; for (vint i = 0; i < DaysOfWeek; i++) { labelDaysOfWeek[i]->SetText(dateLocale.GetShortDayOfWeekName(i)); } listMonths->GetItems().Clear(); for (vint i = 1; i <= 12; i++) { listMonths->GetItems().Add(Ptr(new list::TextItem(dateLocale.GetLongMonthName(i)))); } SetDate(currentDate); } } const DateTime& GuiCommonDatePickerLook::GetDate() { return currentDate; } void GuiCommonDatePickerLook::SetDate(const DateTime& value) { currentDate = value; DisplayMonth(value.year, value.month); SelectDay(value.day); } const FontProperties& GuiCommonDatePickerLook::GetFont() { return font; } void GuiCommonDatePickerLook::SetFont(const FontProperties& value) { if (font != value) { font = value; comboYear->SetFont(value); listYears->SetFont(value); comboMonth->SetFont(value); listMonths->SetFont(value); for (auto label : From(labelDaysOfWeek).Concat(labelDays)) { label->SetFont(value); } } } controls::GuiComboBoxListControl* GuiCommonDatePickerLook::GetYearCombo() { return comboYear; } controls::GuiComboBoxListControl* GuiCommonDatePickerLook::GetMonthCombo() { return comboMonth; } vint GuiCommonDatePickerLook::GetDayRows() { return DaysOfWeek; } vint GuiCommonDatePickerLook::GetDayColumns() { return DayRows; } controls::GuiSelectableButton* GuiCommonDatePickerLook::GetDayButton(vint row, vint column) { return buttonDays[row * DaysOfWeek + column]; } DateTime GuiCommonDatePickerLook::GetDateOfDayButton(vint row, vint column) { return dateDays[row * DaysOfWeek + column]; } /*********************************************************************** GuiCommonScrollViewLook ***********************************************************************/ void GuiCommonScrollViewLook::UpdateTable() { if (horizontalScroll->GetVisible()) { tableComposition->SetRowOption(1, GuiCellOption::AbsoluteOption(defaultScrollSize)); } else { tableComposition->SetRowOption(1, GuiCellOption::AbsoluteOption(0)); } if (verticalScroll->GetVisible()) { tableComposition->SetColumnOption(1, GuiCellOption::AbsoluteOption(defaultScrollSize)); } else { tableComposition->SetColumnOption(1, GuiCellOption::AbsoluteOption(0)); } } void GuiCommonScrollViewLook::hScroll_OnVisibleChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { UpdateTable(); } void GuiCommonScrollViewLook::vScroll_OnVisibleChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { UpdateTable(); } GuiCommonScrollViewLook::GuiCommonScrollViewLook(vint _defaultScrollSize) :defaultScrollSize(_defaultScrollSize) { SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); horizontalScroll = new GuiScroll(theme::ThemeName::HScroll); horizontalScroll->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); horizontalScroll->SetEnabled(false); horizontalScroll->SetAutoFocus(false); verticalScroll = new GuiScroll(theme::ThemeName::VScroll); verticalScroll->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); verticalScroll->SetEnabled(false); verticalScroll->SetAutoFocus(false); tableComposition = new GuiTableComposition; AddChild(tableComposition); tableComposition->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); tableComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); tableComposition->SetRowsAndColumns(2, 2); tableComposition->SetRowOption(0, GuiCellOption::PercentageOption(1.0)); tableComposition->SetRowOption(1, GuiCellOption::MinSizeOption()); tableComposition->SetColumnOption(0, GuiCellOption::PercentageOption(1.0)); tableComposition->SetColumnOption(1, GuiCellOption::MinSizeOption()); UpdateTable(); { GuiCellComposition* cell = new GuiCellComposition; tableComposition->AddChild(cell); cell->SetSite(1, 0, 1, 1); cell->AddChild(horizontalScroll->GetBoundsComposition()); } { GuiCellComposition* cell = new GuiCellComposition; tableComposition->AddChild(cell); cell->SetSite(0, 1, 1, 1); cell->AddChild(verticalScroll->GetBoundsComposition()); } containerCellComposition = new GuiCellComposition; tableComposition->AddChild(containerCellComposition); containerCellComposition->SetSite(0, 0, 1, 1); containerComposition = new GuiBoundsComposition; containerComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); containerCellComposition->AddChild(containerComposition); horizontalScroll->VisibleChanged.AttachMethod(this, &GuiCommonScrollViewLook::hScroll_OnVisibleChanged); verticalScroll->VisibleChanged.AttachMethod(this, &GuiCommonScrollViewLook::vScroll_OnVisibleChanged); UpdateTable(); } GuiCommonScrollViewLook::~GuiCommonScrollViewLook() { } controls::GuiScroll* GuiCommonScrollViewLook::GetHScroll() { return horizontalScroll; } controls::GuiScroll* GuiCommonScrollViewLook::GetVScroll() { return verticalScroll; } compositions::GuiGraphicsComposition* GuiCommonScrollViewLook::GetContainerComposition() { return containerComposition; } TemplateProperty GuiCommonScrollViewLook::GetHScrollTemplate() { return hScrollTemplate; } void GuiCommonScrollViewLook::SetHScrollTemplate(const TemplateProperty& value) { hScrollTemplate = value; horizontalScroll->SetControlTemplate(value); } TemplateProperty GuiCommonScrollViewLook::GetVScrollTemplate() { return vScrollTemplate; } void GuiCommonScrollViewLook::SetVScrollTemplate(const TemplateProperty& value) { vScrollTemplate = value; verticalScroll->SetControlTemplate(value); } /*********************************************************************** GuiCommonScrollBehavior ***********************************************************************/ void GuiCommonScrollBehavior::SetScroll(vint totalPixels, vint newOffset) { vint totalSize = scrollTemplate->GetTotalSize(); double ratio = (double)newOffset / totalPixels; vint newPosition = (vint)round(ratio * totalSize); vint offset1 = (vint)round(((double)newPosition / totalSize) * totalPixels); vint offset2 = (vint)round(((double)(newPosition + 1)) / totalSize * totalPixels); vint delta1 = offset1 - newOffset; vint delta2 = offset2 - newOffset; if (delta1 < 0) { delta1 = -delta1; } if (delta2 < 0) { delta2 = -delta2; } if (delta1 < delta2) { scrollTemplate->GetCommands()->SetPosition(newPosition); } else { scrollTemplate->GetCommands()->SetPosition(newPosition + 1); } } void GuiCommonScrollBehavior::AttachHandle(compositions::GuiGraphicsComposition* handle) { handle->GetEventReceiver()->leftButtonDown.AttachLambda([=](GuiGraphicsComposition*, GuiMouseEventArgs& arguments) { if (scrollTemplate->GetVisuallyEnabled()) { dragging = true; location.x = arguments.x; location.y = arguments.y; } }); handle->GetEventReceiver()->leftButtonUp.AttachLambda([=](GuiGraphicsComposition*, GuiMouseEventArgs&) { if (scrollTemplate->GetVisuallyEnabled()) { dragging = false; } }); } GuiCommonScrollBehavior::GuiCommonScrollBehavior() { } GuiCommonScrollBehavior::~GuiCommonScrollBehavior() { } void GuiCommonScrollBehavior::AttachScrollTemplate(GuiScrollTemplate* value) { scrollTemplate = value; } void GuiCommonScrollBehavior::AttachDecreaseButton(controls::GuiButton* button) { button->Clicked.AttachLambda([=](GuiGraphicsComposition*, GuiEventArgs&) { scrollTemplate->GetCommands()->SmallDecrease(); }); } void GuiCommonScrollBehavior::AttachIncreaseButton(controls::GuiButton* button) { button->Clicked.AttachLambda([=](GuiGraphicsComposition*, GuiEventArgs&) { scrollTemplate->GetCommands()->SmallIncrease(); }); } void GuiCommonScrollBehavior::AttachHorizontalScrollHandle(compositions::GuiPartialViewComposition* partialView) { partialView->GetParent()->GetEventReceiver()->leftButtonDown.AttachLambda([=](GuiGraphicsComposition*, GuiMouseEventArgs& arguments) { if (scrollTemplate->GetVisuallyEnabled()) { if (arguments.x < partialView->GetCachedBounds().x1) { scrollTemplate->GetCommands()->BigDecrease(); } else if (arguments.x >= partialView->GetCachedBounds().x2) { scrollTemplate->GetCommands()->BigIncrease(); } } }); AttachHorizontalTrackerHandle(partialView); } void GuiCommonScrollBehavior::AttachVerticalScrollHandle(compositions::GuiPartialViewComposition* partialView) { partialView->GetParent()->GetEventReceiver()->leftButtonDown.AttachLambda([=](GuiGraphicsComposition*, GuiMouseEventArgs& arguments) { if (scrollTemplate->GetVisuallyEnabled()) { if (arguments.y < partialView->GetCachedBounds().y1) { scrollTemplate->GetCommands()->BigDecrease(); } else if (arguments.y >= partialView->GetCachedBounds().y2) { scrollTemplate->GetCommands()->BigIncrease(); } } }); AttachVerticalTrackerHandle(partialView); } void GuiCommonScrollBehavior::AttachHorizontalTrackerHandle(compositions::GuiPartialViewComposition* partialView) { partialView->GetEventReceiver()->mouseMove.AttachLambda([=](GuiGraphicsComposition*, GuiMouseEventArgs& arguments) { if (dragging) { auto bounds = partialView->GetParent()->GetCachedBounds(); vint totalPixels = bounds.x2 - bounds.x1; vint currentOffset = partialView->GetCachedBounds().x1; vint newOffset = currentOffset + (arguments.x - location.x); SetScroll(totalPixels, newOffset); } }); AttachHandle(partialView); } void GuiCommonScrollBehavior::AttachVerticalTrackerHandle(compositions::GuiPartialViewComposition* partialView) { partialView->GetEventReceiver()->mouseMove.AttachLambda([=](GuiGraphicsComposition*, GuiMouseEventArgs& arguments) { if (dragging) { auto bounds = partialView->GetParent()->GetCachedBounds(); vint totalPixels = bounds.y2 - bounds.y1; vint currentOffset = partialView->GetCachedBounds().y1; vint newOffset = currentOffset + (arguments.y - location.y); SetScroll(totalPixels, newOffset); } }); AttachHandle(partialView); } vint GuiCommonScrollBehavior::GetHorizontalTrackerHandlerPosition(compositions::GuiBoundsComposition* handle, vint totalSize, vint pageSize, vint position) { vint width = handle->GetParent()->GetCachedBounds().Width() - handle->GetCachedBounds().Width(); vint max = totalSize - pageSize; return max == 0 ? 0 : width * position / max; } vint GuiCommonScrollBehavior::GetVerticalTrackerHandlerPosition(compositions::GuiBoundsComposition* handle, vint totalSize, vint pageSize, vint position) { vint height = handle->GetParent()->GetCachedBounds().Height() - handle->GetCachedBounds().Height(); vint max = totalSize - pageSize; return max == 0 ? 0 : height * position / max; } } } } /*********************************************************************** .\CONTROLS\TEMPLATES\GUICONTROLTEMPLATES.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace templates { using namespace collections; using namespace controls; using namespace compositions; using namespace elements; /*********************************************************************** Template Declarations ***********************************************************************/ GUI_CONTROL_TEMPLATE_DECL(GUI_TEMPLATE_CLASS_IMPL) GUI_ITEM_TEMPLATE_DECL(GUI_TEMPLATE_CLASS_IMPL) } } } /*********************************************************************** .\CONTROLS\TEMPLATES\GUITHEMESTYLEFACTORY.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace theme { using namespace collections; using namespace controls; using namespace templates; class Theme : public Object, public virtual theme::ITheme { public: Dictionary> templates; ThemeTemplates* first = nullptr; ThemeTemplates* last = nullptr; bool RegisterTheme(const WString& name, Ptr theme) { CHECK_ERROR(theme->previous == nullptr, L"vl::presentation::theme::RegisterTheme(const WString&, Ptr)#Theme object has been registered"); CHECK_ERROR(theme->next == nullptr, L"vl::presentation::theme::RegisterTheme(const WString&, Ptr)#Theme object has been registered"); if (templates.Keys().Contains(name)) { return false; } templates.Add(name, theme); if (!first) { first = theme.Obj(); } if (last) { last->next = theme.Obj(); } theme->previous = last; last = theme.Obj(); return true; } Ptr UnregisterTheme(const WString& name) { vint index = templates.Keys().IndexOf(name); if (index == -1) { return nullptr; } auto themeTemplates = templates.Values().Get(index); if (themeTemplates->previous) { themeTemplates->previous->next = themeTemplates->next; } else { first = themeTemplates->next; } if (themeTemplates->next) { themeTemplates->next->previous = themeTemplates->previous; } else { last = themeTemplates->previous; } templates.Remove(name); return themeTemplates; } TemplateProperty CreateStyle(ThemeName themeName)override { #define ERROR_MESSAGE_PREFIX L"vl::presentation::theme::ITheme::CreateStyle(ThemeName)#" if (themeName == ThemeName::Window) { bool preferCustomFrameWindow = true; auto current = last; while (current) { if (current->PreferCustomFrameWindow) { preferCustomFrameWindow = current->PreferCustomFrameWindow.Value(); break; } current = current->previous; } CHECK_ERROR(current, ERROR_MESSAGE_PREFIX L"At least one ThemeTemplates::PreferCustomFrameWindow should be defined."); if (preferCustomFrameWindow) { themeName = ThemeName::CustomFrameWindow; } else { themeName = ThemeName::SystemFrameWindow; } } switch (themeName) { #define GUI_DEFINE_ITEM_PROPERTY(TEMPLATE, CONTROL) \ case ThemeName::CONTROL:\ {\ auto current = last;\ while (current) \ {\ if (current->CONTROL)\ {\ return current->CONTROL; \ }\ current = current->previous;\ }\ throw Exception(L"Control template for \"" L ## #CONTROL L"\" is not defined.");\ }\ GUI_CONTROL_TEMPLATE_TYPES(GUI_DEFINE_ITEM_PROPERTY) #undef GUI_DEFINE_ITEM_PROPERTY default: CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unknown theme name."); } #undef ERROR_MESSAGE_PREFIX } }; controls::GuiControlHost* ThemeTemplates::GetControlHostForInstance() { return nullptr; } ThemeTemplates::~ThemeTemplates() { FinalizeAggregation(); } Theme* currentTheme = nullptr; ITheme* GetCurrentTheme() { return currentTheme; } void InitializeTheme() { CHECK_ERROR(currentTheme == nullptr, L"vl::presentation::theme::InitializeTheme()#Theme has already been initialized"); currentTheme = new Theme; } void FinalizeTheme() { CHECK_ERROR(currentTheme != nullptr, L"vl::presentation::theme::FinalizeTheme()#Theme has not been initialized"); delete currentTheme; currentTheme = nullptr; } bool RegisterTheme(Ptr theme) { CHECK_ERROR(currentTheme != nullptr, L"vl::presentation::theme::RegisterTheme(const WString&, Ptr)#Theme has already been initialized"); return currentTheme->RegisterTheme(theme->Name, theme); } Ptr UnregisterTheme(const WString& name) { CHECK_ERROR(currentTheme != nullptr, L"vl::presentation::theme::UnregisterTheme(const WString&)#Theme has already been initialized"); return currentTheme->UnregisterTheme(name); } } } } /*********************************************************************** .\CONTROLS\TEXTEDITORPACKAGE\GUIDOCUMENTVIEWER.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { using namespace collections; using namespace elements; using namespace compositions; /*********************************************************************** GuiDocumentItem ***********************************************************************/ GuiDocumentItem::GuiDocumentItem(const WString& _name) :name(_name) { container = new GuiBoundsComposition; container->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); container->SetAssociatedCursor(GetCurrentController()->ResourceService()->GetSystemCursor(INativeCursor::Arrow)); } GuiDocumentItem::~GuiDocumentItem() { if (!owned) { SafeDeleteComposition(container); } } compositions::GuiGraphicsComposition* GuiDocumentItem::GetContainer() { return container; } WString GuiDocumentItem::GetName() { return name; } /*********************************************************************** GuiDocumentCommonInterface ***********************************************************************/ void GuiDocumentCommonInterface::InvokeUndoRedoChanged() { UndoRedoChanged.Execute(documentControl->GetNotifyEventArguments()); } void GuiDocumentCommonInterface::InvokeModifiedChanged() { ModifiedChanged.Execute(documentControl->GetNotifyEventArguments()); } void GuiDocumentCommonInterface::UpdateCaretPoint() { GuiGraphicsHost* host=documentComposition->GetRelatedGraphicsHost(); if(host) { Rect caret=documentElement->GetCaretBounds(documentElement->GetCaretEnd(), documentElement->IsCaretEndPreferFrontSide()); Point view=GetDocumentViewPosition(); vint x=caret.x1-view.x; vint y=caret.y2-view.y; host->SetCaretPoint(Point(x, y), documentComposition); } } void GuiDocumentCommonInterface::EnsureDocumentRectVisible(Rect bounds) { if (bounds != Rect()) { bounds.x1 -= 15; bounds.y1 -= 15; bounds.x2 += 15; bounds.y2 += 15; EnsureRectVisible(bounds); } } void GuiDocumentCommonInterface::Move(TextPos caret, bool shift, bool frontSide) { TextPos begin=documentElement->GetCaretBegin(); TextPos end=documentElement->GetCaretEnd(); TextPos newBegin=shift?begin:caret; TextPos newEnd=caret; documentElement->SetCaret(newBegin, newEnd, frontSide); documentElement->SetCaretVisible(true); EnsureDocumentRectVisible(documentElement->GetCaretBounds(newEnd, frontSide)); UpdateCaretPoint(); SelectionChanged.Execute(documentControl->GetNotifyEventArguments()); } bool GuiDocumentCommonInterface::ProcessKey(VKEY code, bool shift, bool ctrl) { if(IGuiShortcutKeyItem* item=internalShortcutKeyManager->TryGetShortcut(ctrl, shift, false, code)) { GuiEventArgs arguments(documentControl->GetBoundsComposition()); item->Executed.Execute(arguments); return true; } TextPos currentCaret=documentElement->GetCaretEnd(); bool frontSide=documentElement->IsCaretEndPreferFrontSide(); TextPos begin=documentElement->GetCaretBegin(); TextPos end=documentElement->GetCaretEnd(); switch(code) { case VKEY::KEY_UP: { TextPos newCaret=documentElement->CalculateCaret(currentCaret, IGuiGraphicsParagraph::CaretMoveUp, frontSide); Move(newCaret, shift, frontSide); } break; case VKEY::KEY_DOWN: { TextPos newCaret=documentElement->CalculateCaret(currentCaret, IGuiGraphicsParagraph::CaretMoveDown, frontSide); Move(newCaret, shift, frontSide); } break; case VKEY::KEY_LEFT: { TextPos newCaret=documentElement->CalculateCaret(currentCaret, IGuiGraphicsParagraph::CaretMoveLeft, frontSide); Move(newCaret, shift, frontSide); } break; case VKEY::KEY_RIGHT: { TextPos newCaret=documentElement->CalculateCaret(currentCaret, IGuiGraphicsParagraph::CaretMoveRight, frontSide); Move(newCaret, shift, frontSide); } break; case VKEY::KEY_HOME: { TextPos newCaret=documentElement->CalculateCaret(currentCaret, IGuiGraphicsParagraph::CaretLineFirst, frontSide); if(newCaret==currentCaret) { newCaret=documentElement->CalculateCaret(currentCaret, IGuiGraphicsParagraph::CaretFirst, frontSide); } Move(newCaret, shift, frontSide); } break; case VKEY::KEY_END: { TextPos newCaret=documentElement->CalculateCaret(currentCaret, IGuiGraphicsParagraph::CaretLineLast, frontSide); if(newCaret==currentCaret) { newCaret=documentElement->CalculateCaret(currentCaret, IGuiGraphicsParagraph::CaretLast, frontSide); } Move(newCaret, shift, frontSide); } break; case VKEY::KEY_PRIOR: { } break; case VKEY::KEY_NEXT: { } break; case VKEY::KEY_BACK: if(editMode==Editable) { if(begin==end) { ProcessKey(VKEY::KEY_LEFT, true, false); } Array text; EditText(documentElement->GetCaretBegin(), documentElement->GetCaretEnd(), documentElement->IsCaretEndPreferFrontSide(), text); return true; } break; case VKEY::KEY_DELETE: if(editMode==Editable) { if(begin==end) { ProcessKey(VKEY::KEY_RIGHT, true, false); } Array text; EditText(documentElement->GetCaretBegin(), documentElement->GetCaretEnd(), documentElement->IsCaretEndPreferFrontSide(), text); return true; } break; case VKEY::KEY_RETURN: if(editMode==Editable) { if(ctrl) { Array text(1); text[0]=L"\r\n"; EditText(documentElement->GetCaretBegin(), documentElement->GetCaretEnd(), documentElement->IsCaretEndPreferFrontSide(), text); } else { Array text(2); EditText(documentElement->GetCaretBegin(), documentElement->GetCaretEnd(), documentElement->IsCaretEndPreferFrontSide(), text); } return true; } break; default:; } return false; } void GuiDocumentCommonInterface::InstallDocumentViewer( GuiControl* _sender, compositions::GuiGraphicsComposition* _mouseArea, compositions::GuiGraphicsComposition* _container, compositions::GuiGraphicsComposition* eventComposition, compositions::GuiGraphicsComposition* focusableComposition ) { documentControl = _sender; documentElement = GuiDocumentElement::Create(); documentElement->SetCallback(this); documentComposition = new GuiBoundsComposition; documentComposition->SetOwnedElement(Ptr(documentElement)); documentComposition->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElement); documentComposition->SetAlignmentToParent(Margin(5, 5, 5, 5)); _container->AddChild(documentComposition); ReplaceMouseArea(_mouseArea); focusableComposition->GetEventReceiver()->caretNotify.AttachMethod(this, &GuiDocumentCommonInterface::OnCaretNotify); focusableComposition->GetEventReceiver()->gotFocus.AttachMethod(this, &GuiDocumentCommonInterface::OnGotFocus); focusableComposition->GetEventReceiver()->lostFocus.AttachMethod(this, &GuiDocumentCommonInterface::OnLostFocus); focusableComposition->GetEventReceiver()->keyDown.AttachMethod(this, &GuiDocumentCommonInterface::OnKeyDown); focusableComposition->GetEventReceiver()->charInput.AttachMethod(this, &GuiDocumentCommonInterface::OnCharInput); undoRedoProcessor->Setup(documentElement, documentComposition); ActiveHyperlinkChanged.SetAssociatedComposition(eventComposition); ActiveHyperlinkExecuted.SetAssociatedComposition(eventComposition); SelectionChanged.SetAssociatedComposition(eventComposition); UndoRedoChanged.SetAssociatedComposition(eventComposition); ModifiedChanged.SetAssociatedComposition(eventComposition); undoRedoProcessor->UndoRedoChanged.Add(this, &GuiDocumentCommonInterface::InvokeUndoRedoChanged); undoRedoProcessor->ModifiedChanged.Add(this, &GuiDocumentCommonInterface::InvokeModifiedChanged); SetDocument(Ptr(new DocumentModel)); } void GuiDocumentCommonInterface::ReplaceMouseArea(compositions::GuiGraphicsComposition* _mouseArea) { if (documentMouseArea) { documentMouseArea->GetEventReceiver()->mouseMove.Detach(onMouseMoveHandler); documentMouseArea->GetEventReceiver()->leftButtonDown.Detach(onMouseDownHandler); documentMouseArea->GetEventReceiver()->leftButtonUp.Detach(onMouseUpHandler); documentMouseArea->GetEventReceiver()->mouseLeave.Detach(onMouseLeaveHandler); onMouseMoveHandler = nullptr; onMouseDownHandler = nullptr; onMouseUpHandler = nullptr; onMouseLeaveHandler = nullptr; } documentMouseArea = _mouseArea; if (documentMouseArea) { onMouseMoveHandler = documentMouseArea->GetEventReceiver()->mouseMove.AttachMethod(this, &GuiDocumentCommonInterface::OnMouseMove); onMouseDownHandler = documentMouseArea->GetEventReceiver()->leftButtonDown.AttachMethod(this, &GuiDocumentCommonInterface::OnMouseDown); onMouseUpHandler = documentMouseArea->GetEventReceiver()->leftButtonUp.AttachMethod(this, &GuiDocumentCommonInterface::OnMouseUp); onMouseLeaveHandler = documentMouseArea->GetEventReceiver()->mouseLeave.AttachMethod(this, &GuiDocumentCommonInterface::OnMouseLeave); } } void GuiDocumentCommonInterface::SetActiveHyperlink(Ptr package) { ActivateActiveHyperlink(false); activeHyperlinks = !package ? nullptr : package->hyperlinks.Count() == 0 ? nullptr : package; ActivateActiveHyperlink(true); ActiveHyperlinkChanged.Execute(documentControl->GetNotifyEventArguments()); } void GuiDocumentCommonInterface::ActivateActiveHyperlink(bool activate) { if (activeHyperlinks) { for (auto run : activeHyperlinks->hyperlinks) { run->styleName = activate ? run->activeStyleName : run->normalStyleName; } documentElement->NotifyParagraphUpdated(activeHyperlinks->row, 1, 1, false); } } void GuiDocumentCommonInterface::AddShortcutCommand(VKEY key, const Func& eventHandler) { IGuiShortcutKeyItem* item=internalShortcutKeyManager->CreateNewShortcut(true, false, false, key); item->Executed.AttachLambda([=](GuiGraphicsComposition* sender, GuiEventArgs& arguments) { eventHandler(); }); } void GuiDocumentCommonInterface::EditTextInternal(TextPos begin, TextPos end, const Func& editor) { // save run before editing if(begin>end) { TextPos temp=begin; begin=end; end=temp; } Ptr originalModel=documentElement->GetDocument()->CopyDocument(begin, end, true); if(originalModel) { // edit vint paragraphCount=0; vint lastParagraphLength=0; editor(begin, end, paragraphCount, lastParagraphLength); // calculate new caret TextPos caret; if(paragraphCount==0) { caret=begin; } else if(paragraphCount==1) { caret=TextPos(begin.row, begin.column+lastParagraphLength); } else { caret=TextPos(begin.row+paragraphCount-1, lastParagraphLength); } documentElement->SetCaret(caret, caret, true); EnsureDocumentRectVisible(documentElement->GetCaretBounds(caret, true)); documentControl->TextChanged.Execute(documentControl->GetNotifyEventArguments()); UpdateCaretPoint(); SelectionChanged.Execute(documentControl->GetNotifyEventArguments()); // save run after editing Ptr inputModel=documentElement->GetDocument()->CopyDocument(begin, caret, true); // submit redo-undo GuiDocumentUndoRedoProcessor::ReplaceModelStruct arguments; arguments.originalStart=begin; arguments.originalEnd=end; arguments.originalModel=originalModel; arguments.inputStart=begin; arguments.inputEnd=caret; arguments.inputModel=inputModel; undoRedoProcessor->OnReplaceModel(arguments); } } void GuiDocumentCommonInterface::EditStyleInternal(TextPos begin, TextPos end, const Func& editor) { // save run before editing if(begin>end) { TextPos temp=begin; begin=end; end=temp; } Ptr originalModel=documentElement->GetDocument()->CopyDocument(begin, end, true); if(originalModel) { // edit editor(begin, end); // save run after editing Ptr inputModel=documentElement->GetDocument()->CopyDocument(begin, end, true); // submit redo-undo GuiDocumentUndoRedoProcessor::ReplaceModelStruct arguments; arguments.originalStart=begin; arguments.originalEnd=end; arguments.originalModel=originalModel; arguments.inputStart=begin; arguments.inputEnd=end; arguments.inputModel=inputModel; undoRedoProcessor->OnReplaceModel(arguments); } } void GuiDocumentCommonInterface::MergeBaselineAndDefaultFont(Ptr document) { document->MergeDefaultFont(documentControl->GetDisplayFont()); if (baselineDocument) { document->MergeBaselineStyles(baselineDocument); } } void GuiDocumentCommonInterface::OnFontChanged() { auto document = documentElement->GetDocument(); MergeBaselineAndDefaultFont(document); documentElement->SetDocument(document); } void GuiDocumentCommonInterface::OnCaretNotify(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if(documentControl->GetVisuallyEnabled()) { if(editMode!=ViewOnly) { documentElement->SetCaretVisible(!documentElement->GetCaretVisible()); } } } void GuiDocumentCommonInterface::OnGotFocus(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if(documentControl->GetVisuallyEnabled()) { if(editMode!=ViewOnly) { documentElement->SetCaretVisible(true); UpdateCaretPoint(); } } } void GuiDocumentCommonInterface::OnLostFocus(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if(documentControl->GetVisuallyEnabled()) { documentElement->SetCaretVisible(false); } } void GuiDocumentCommonInterface::OnKeyDown(compositions::GuiGraphicsComposition* sender, compositions::GuiKeyEventArgs& arguments) { if(documentControl->GetVisuallyEnabled()) { if(editMode!=ViewOnly) { if(ProcessKey(arguments.code, arguments.shift, arguments.ctrl)) { arguments.handled=true; } } } } void GuiDocumentCommonInterface::OnCharInput(compositions::GuiGraphicsComposition* sender, compositions::GuiCharEventArgs& arguments) { if (documentControl->GetVisuallyEnabled()) { if (editMode == Editable && arguments.code != (wchar_t)VKEY::KEY_ESCAPE && arguments.code != (wchar_t)VKEY::KEY_BACK && arguments.code != (wchar_t)VKEY::KEY_RETURN && (arguments.code != (wchar_t)VKEY::KEY_TAB || documentControl->GetAcceptTabInput()) && !arguments.ctrl) { Array text(1); text[0] = WString::FromChar(arguments.code); EditText(documentElement->GetCaretBegin(), documentElement->GetCaretEnd(), documentElement->IsCaretEndPreferFrontSide(), text); } } } void GuiDocumentCommonInterface::UpdateCursor(INativeCursor* cursor) { if (documentMouseArea) { documentMouseArea->SetAssociatedCursor(cursor); } } Point GuiDocumentCommonInterface::GetMouseOffset() { if (documentMouseArea) { auto documentBounds = documentComposition->GetGlobalBounds(); auto mouseAreaBounds = documentMouseArea->GetGlobalBounds(); return Point( documentBounds.x1 - mouseAreaBounds.x1, documentBounds.y1 - mouseAreaBounds.y1 ); } else { return Point(0, 0); } } void GuiDocumentCommonInterface::OnMouseMove(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) { auto offset = GetMouseOffset(); auto x = arguments.x - offset.x; auto y = arguments.y - offset.y; if(documentControl->GetVisuallyEnabled()) { switch(editMode) { case ViewOnly: { auto package = documentElement->GetHyperlinkFromPoint({ x, y }); bool handCursor = false; if(dragging) { if(activeHyperlinks) { if (package && CompareEnumerable(activeHyperlinks->hyperlinks, package->hyperlinks) == 0) { ActivateActiveHyperlink(true); handCursor = true; } else { ActivateActiveHyperlink(false); } } } else { SetActiveHyperlink(package); handCursor = activeHyperlinks; } if(handCursor) { auto cursor = GetCurrentController()->ResourceService()->GetSystemCursor(INativeCursor::Hand); UpdateCursor(cursor); } else { UpdateCursor(nullptr); } } break; case Selectable: case Editable: if(dragging) { TextPos caret=documentElement->CalculateCaretFromPoint(Point(x, y)); TextPos oldCaret=documentElement->GetCaretBegin(); Move(caret, true, (oldCaret==caret?documentElement->IsCaretEndPreferFrontSide():caretGetVisuallyEnabled()) { switch(editMode) { case ViewOnly: SetActiveHyperlink(documentElement->GetHyperlinkFromPoint({ x, y })); break; case Selectable: case Editable: { documentControl->SetFocused(); TextPos caret=documentElement->CalculateCaretFromPoint(Point(x, y)); TextPos oldCaret=documentElement->GetCaretEnd(); if(caret!=oldCaret) { Move(caret, arguments.shift, caretGetVisuallyEnabled()) { dragging=false; switch(editMode) { case ViewOnly: { auto package = documentElement->GetHyperlinkFromPoint({ x, y }); if(activeHyperlinks) { if (package && CompareEnumerable(activeHyperlinks->hyperlinks, package->hyperlinks) == 0) { ActiveHyperlinkExecuted.Execute(documentControl->GetNotifyEventArguments()); } else { SetActiveHyperlink(nullptr); } } } break; default:; } } } void GuiDocumentCommonInterface::OnMouseLeave(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { SetActiveHyperlink(nullptr); } Point GuiDocumentCommonInterface::GetDocumentViewPosition() { return Point(0, 0); } void GuiDocumentCommonInterface::EnsureRectVisible(Rect bounds) { } //================ callback void GuiDocumentCommonInterface::OnStartRender() { for (auto item : documentItems.Values()) { item->visible = false; } } void GuiDocumentCommonInterface::OnFinishRender() { for (auto item : documentItems.Values()) { if (item->container->GetVisible() != item->visible) { item->container->SetVisible(item->visible); } } } Size GuiDocumentCommonInterface::OnRenderEmbeddedObject(const WString& name, const Rect& location) { vint index = documentItems.Keys().IndexOf(name); if (index != -1) { auto item = documentItems.Values()[index]; auto size = item->container->GetCachedBounds().GetSize(); item->container->SetExpectedBounds(Rect(location.LeftTop(), Size(0, 0))); item->visible = true; return size; } return Size(); } //================ basic GuiDocumentCommonInterface::GuiDocumentCommonInterface() { undoRedoProcessor = Ptr(new GuiDocumentUndoRedoProcessor); internalShortcutKeyManager = Ptr(new GuiShortcutKeyManager); AddShortcutCommand(VKEY::KEY_Z, Func(this, &GuiDocumentCommonInterface::Undo)); AddShortcutCommand(VKEY::KEY_Y, Func(this, &GuiDocumentCommonInterface::Redo)); AddShortcutCommand(VKEY::KEY_A, Func(this, &GuiDocumentCommonInterface::SelectAll)); AddShortcutCommand(VKEY::KEY_X, Func(this, &GuiDocumentCommonInterface::Cut)); AddShortcutCommand(VKEY::KEY_C, Func(this, &GuiDocumentCommonInterface::Copy)); AddShortcutCommand(VKEY::KEY_V, Func(this, &GuiDocumentCommonInterface::Paste)); } GuiDocumentCommonInterface::~GuiDocumentCommonInterface() { } Ptr GuiDocumentCommonInterface::GetDocument() { return documentElement->GetDocument(); } void GuiDocumentCommonInterface::SetDocument(Ptr value) { SetActiveHyperlink(0); ClearUndoRedo(); NotifyModificationSaved(); if (value) { if (value->paragraphs.Count() == 0) { value->paragraphs.Add(Ptr(new DocumentParagraphRun)); } MergeBaselineAndDefaultFont(value); } documentElement->SetDocument(value); } //================ document items bool GuiDocumentCommonInterface::AddDocumentItem(Ptr value) { if (documentItems.Keys().Contains(value->GetName())) { return false; } documentItems.Add(value->GetName(), value); documentComposition->AddChild(value->container); value->visible = false; value->owned = true; value->container->SetVisible(false); return true; } bool GuiDocumentCommonInterface::RemoveDocumentItem(Ptr value) { vint index = documentItems.Keys().IndexOf(value->GetName()); if (index == -1) { return false; } if (documentItems.Values()[index] != value) { return false; } value->owned = false; documentComposition->RemoveChild(value->container); documentItems.Remove(value->GetName()); return true; } const GuiDocumentCommonInterface::DocumentItemMap& GuiDocumentCommonInterface::GetDocumentItems() { return documentItems; } //================ caret operations TextPos GuiDocumentCommonInterface::GetCaretBegin() { return documentElement->GetCaretBegin(); } TextPos GuiDocumentCommonInterface::GetCaretEnd() { return documentElement->GetCaretEnd(); } void GuiDocumentCommonInterface::SetCaret(TextPos begin, TextPos end) { documentElement->SetCaret(begin, end, end>=begin); UpdateCaretPoint(); SelectionChanged.Execute(documentControl->GetNotifyEventArguments()); } TextPos GuiDocumentCommonInterface::CalculateCaretFromPoint(Point point) { return documentElement->CalculateCaretFromPoint(point); } Rect GuiDocumentCommonInterface::GetCaretBounds(TextPos caret, bool frontSide) { return documentElement->GetCaretBounds(caret, frontSide); } //================ editing operations void GuiDocumentCommonInterface::NotifyParagraphUpdated(vint index, vint oldCount, vint newCount, bool updatedText) { documentElement->NotifyParagraphUpdated(index, oldCount, newCount, updatedText); } void GuiDocumentCommonInterface::EditRun(TextPos begin, TextPos end, Ptr model, bool copy) { EditTextInternal(begin, end, [=](TextPos begin, TextPos end, vint& paragraphCount, vint& lastParagraphLength) { documentElement->EditRun(begin, end, model, copy); paragraphCount=model->paragraphs.Count(); lastParagraphLength=paragraphCount==0?0:model->paragraphs[paragraphCount-1]->GetText(false).Length(); }); } void GuiDocumentCommonInterface::EditText(TextPos begin, TextPos end, bool frontSide, const collections::Array& text) { EditTextInternal(begin, end, [=, &text](TextPos begin, TextPos end, vint& paragraphCount, vint& lastParagraphLength) { documentElement->EditText(begin, end, frontSide, text); paragraphCount=text.Count(); lastParagraphLength=paragraphCount==0?0:text[paragraphCount-1].Length(); }); } void GuiDocumentCommonInterface::EditStyle(TextPos begin, TextPos end, Ptr style) { EditStyleInternal(begin, end, [=](TextPos begin, TextPos end) { documentElement->EditStyle(begin, end, style); }); } void GuiDocumentCommonInterface::EditImage(TextPos begin, TextPos end, Ptr image) { EditTextInternal(begin, end, [=](TextPos begin, TextPos end, vint& paragraphCount, vint& lastParagraphLength) { documentElement->EditImage(begin, end, image); paragraphCount=1; lastParagraphLength=wcslen(DocumentImageRun::RepresentationText); }); } void GuiDocumentCommonInterface::EditHyperlink(vint paragraphIndex, vint begin, vint end, const WString& reference, const WString& normalStyleName, const WString& activeStyleName) { EditStyleInternal(TextPos(paragraphIndex, begin), TextPos(paragraphIndex, end), [=](TextPos begin, TextPos end) { documentElement->EditHyperlink(begin.row, begin.column, end.column, reference, normalStyleName, activeStyleName); }); } void GuiDocumentCommonInterface::RemoveHyperlink(vint paragraphIndex, vint begin, vint end) { EditStyleInternal(TextPos(paragraphIndex, begin), TextPos(paragraphIndex, end), [=](TextPos begin, TextPos end) { documentElement->RemoveHyperlink(begin.row, begin.column, end.column); }); } void GuiDocumentCommonInterface::EditStyleName(TextPos begin, TextPos end, const WString& styleName) { EditStyleInternal(begin, end, [=](TextPos begin, TextPos end) { documentElement->EditStyleName(begin, end, styleName); }); } void GuiDocumentCommonInterface::RemoveStyleName(TextPos begin, TextPos end) { EditStyleInternal(begin, end, [=](TextPos begin, TextPos end) { documentElement->RemoveStyleName(begin, end); }); } void GuiDocumentCommonInterface::RenameStyle(const WString& oldStyleName, const WString& newStyleName) { documentElement->RenameStyle(oldStyleName, newStyleName); // submit redo-undo GuiDocumentUndoRedoProcessor::RenameStyleStruct arguments; arguments.oldStyleName=oldStyleName; arguments.newStyleName=newStyleName; undoRedoProcessor->OnRenameStyle(arguments); } void GuiDocumentCommonInterface::ClearStyle(TextPos begin, TextPos end) { EditStyleInternal(begin, end, [=](TextPos begin, TextPos end) { documentElement->ClearStyle(begin, end); }); } Ptr GuiDocumentCommonInterface::SummarizeStyle(TextPos begin, TextPos end) { if (begin>end) { TextPos temp = begin; begin = end; end = temp; } return documentElement->SummarizeStyle(begin, end); } Nullable GuiDocumentCommonInterface::SummarizeStyleName(TextPos begin, TextPos end) { if (begin>end) { TextPos temp = begin; begin = end; end = temp; } return documentElement->SummarizeStyleName(begin, end); } void GuiDocumentCommonInterface::SetParagraphAlignments(TextPos begin, TextPos end, const collections::Array>& alignments) { vint first = begin.row; vint last = end.row; if (first > last) { vint temp = first; first = last; last = temp; } Ptr document = documentElement->GetDocument(); if (0 <= first && first < document->paragraphs.Count() && 0 <= last && last < document->paragraphs.Count() && last - first + 1 == alignments.Count()) { auto arguments = Ptr(new GuiDocumentUndoRedoProcessor::SetAlignmentStruct); arguments->start = first; arguments->end = last; arguments->originalAlignments.Resize(alignments.Count()); arguments->inputAlignments.Resize(alignments.Count()); for (vint i = first; i <= last; i++) { arguments->originalAlignments[i - first] = document->paragraphs[i]->alignment; arguments->inputAlignments[i - first] = alignments[i - first]; } documentElement->SetParagraphAlignment(begin, end, alignments); undoRedoProcessor->OnSetAlignment(arguments); } } void GuiDocumentCommonInterface::SetParagraphAlignment(TextPos begin, TextPos end, Nullable alignment) { #if defined VCZH_GCC && defined VCZH_64 #define abs labs #endif Array> alignments(abs(begin.row - end.row) + 1); #if defined VCZH_GCC && defined VCZH_64 #undef abs #endif for (vint i = 0; i < alignments.Count(); i++) { alignments[i] = alignment; } SetParagraphAlignments(begin, end, alignments); } Nullable GuiDocumentCommonInterface::SummarizeParagraphAlignment(TextPos begin, TextPos end) { if (begin>end) { TextPos temp = begin; begin = end; end = temp; } return documentElement->SummarizeParagraphAlignment(begin, end); } //================ editing control WString GuiDocumentCommonInterface::GetActiveHyperlinkReference() { return activeHyperlinks ? activeHyperlinks->hyperlinks[0]->reference : L""; } GuiDocumentCommonInterface::EditMode GuiDocumentCommonInterface::GetEditMode() { return editMode; } void GuiDocumentCommonInterface::SetEditMode(EditMode value) { if(activeHyperlinks) { SetActiveHyperlink(nullptr); } editMode=value; if(editMode==ViewOnly) { UpdateCursor(nullptr); } else { INativeCursor* cursor=GetCurrentController()->ResourceService()->GetSystemCursor(INativeCursor::IBeam); UpdateCursor(cursor); } } //================ selection operations void GuiDocumentCommonInterface::SelectAll() { vint lastIndex=documentElement->GetDocument()->paragraphs.Count()-1; Ptr lastParagraph=documentElement->GetDocument()->paragraphs[lastIndex]; TextPos begin(0, 0); TextPos end(lastIndex, lastParagraph->GetText(false).Length()); SetCaret(begin, end); } WString GuiDocumentCommonInterface::GetSelectionText() { TextPos begin=documentElement->GetCaretBegin(); TextPos end=documentElement->GetCaretEnd(); if(begin>end) { TextPos temp=begin; begin=end; end=temp; } Ptr model=documentElement->GetDocument()->CopyDocument(begin, end, false); return model->GetText(true); } void GuiDocumentCommonInterface::SetSelectionText(const WString& value) { List paragraphs; { stream::StringReader reader(value); WString paragraph; bool empty=true; while(!reader.IsEnd()) { WString line=reader.ReadLine(); if(empty) { paragraph+=line; empty=false; } else if(line!=L"") { paragraph+=L"\r\n"+line; } else { paragraphs.Add(paragraph); paragraph=L""; empty=true; } } if(!empty) { paragraphs.Add(paragraph); } } TextPos begin=documentElement->GetCaretBegin(); TextPos end=documentElement->GetCaretEnd(); if(begin>end) { TextPos temp=begin; begin=end; end=temp; } Array text; CopyFrom(text, paragraphs); EditText(begin, end, documentElement->IsCaretEndPreferFrontSide(), text); } Ptr GuiDocumentCommonInterface::GetSelectionModel() { TextPos begin=documentElement->GetCaretBegin(); TextPos end=documentElement->GetCaretEnd(); if(begin>end) { TextPos temp=begin; begin=end; end=temp; } Ptr model=documentElement->GetDocument()->CopyDocument(begin, end, true); return model; } void GuiDocumentCommonInterface::SetSelectionModel(Ptr value) { TextPos begin=documentElement->GetCaretBegin(); TextPos end=documentElement->GetCaretEnd(); if(begin>end) { TextPos temp=begin; begin=end; end=temp; } EditRun(begin, end, value, true); } //================ clipboard operations bool GuiDocumentCommonInterface::CanCut() { return editMode==Editable && documentElement->GetCaretBegin()!=documentElement->GetCaretEnd(); } bool GuiDocumentCommonInterface::CanCopy() { return documentElement->GetCaretBegin()!=documentElement->GetCaretEnd(); } bool GuiDocumentCommonInterface::CanPaste() { if (editMode == Editable) { auto reader = GetCurrentController()->ClipboardService()->ReadClipboard(); return reader->ContainsText() || reader->ContainsDocument() || reader->ContainsImage(); } return false; } bool GuiDocumentCommonInterface::Cut() { if (!CanCut())return false; auto writer = GetCurrentController()->ClipboardService()->WriteClipboard(); auto model = GetSelectionModel(); writer->SetDocument(model); writer->Submit(); SetSelectionText(L""); return true; } bool GuiDocumentCommonInterface::Copy() { if (!CanCopy()) return false; auto writer = GetCurrentController()->ClipboardService()->WriteClipboard(); auto model = GetSelectionModel(); writer->SetDocument(model); writer->Submit(); return true; } bool GuiDocumentCommonInterface::Paste() { if (!CanPaste()) return false; auto reader = GetCurrentController()->ClipboardService()->ReadClipboard(); if (reader->ContainsDocument()) { if (auto document = reader->GetDocument()) { SetSelectionModel(document); return true; } } if (reader->ContainsText()) { SetSelectionText(reader->GetText()); return true; } if (reader->ContainsImage()) { if (auto image = reader->GetImage()) { auto imageData = Ptr(new GuiImageData(image, 0)); EditImage(GetCaretBegin(), GetCaretEnd(), imageData); return true; } } return false; } //================ undo redo control bool GuiDocumentCommonInterface::CanUndo() { return editMode==Editable && undoRedoProcessor->CanUndo(); } bool GuiDocumentCommonInterface::CanRedo() { return editMode==Editable && undoRedoProcessor->CanRedo(); } void GuiDocumentCommonInterface::ClearUndoRedo() { undoRedoProcessor->ClearUndoRedo(); } bool GuiDocumentCommonInterface::GetModified() { return undoRedoProcessor->GetModified(); } void GuiDocumentCommonInterface::NotifyModificationSaved() { undoRedoProcessor->NotifyModificationSaved(); } bool GuiDocumentCommonInterface::Undo() { if(CanUndo()) { return undoRedoProcessor->Undo(); } else { return false; } } bool GuiDocumentCommonInterface::Redo() { if(CanRedo()) { return undoRedoProcessor->Redo(); } else { return false; } } /*********************************************************************** GuiDocumentViewer ***********************************************************************/ void GuiDocumentViewer::BeforeControlTemplateUninstalled_() { ReplaceMouseArea(nullptr); } void GuiDocumentViewer::AfterControlTemplateInstalled_(bool initialize) { auto ct = TypedControlTemplateObject(true); baselineDocument = ct->GetBaselineDocument(); if (documentElement) { documentElement->SetCaretColor(ct->GetCaretColor()); SetDocument(GetDocument()); } ReplaceMouseArea(containerComposition->GetParent()); } void GuiDocumentViewer::UpdateDisplayFont() { GuiScrollContainer::UpdateDisplayFont(); OnFontChanged(); } Point GuiDocumentViewer::GetDocumentViewPosition() { return GetViewBounds().LeftTop(); } void GuiDocumentViewer::EnsureRectVisible(Rect bounds) { Rect viewBounds=GetViewBounds(); vint offset=0; if(bounds.y1viewBounds.y2) { offset=bounds.y2-viewBounds.y2; } if (auto scroll = GetVerticalScroll()) { scroll->SetPosition(viewBounds.y1 + offset); } } GuiDocumentViewer::GuiDocumentViewer(theme::ThemeName themeName) :GuiScrollContainer(themeName) { SetAcceptTabInput(true); SetFocusableComposition(boundsComposition); InstallDocumentViewer(this, containerComposition->GetParent(), containerComposition, boundsComposition, focusableComposition); SetExtendToFullWidth(true); SetHorizontalAlwaysVisible(false); } GuiDocumentViewer::~GuiDocumentViewer() { } const WString& GuiDocumentViewer::GetText() { text=documentElement->GetDocument()->GetText(true); return text; } void GuiDocumentViewer::SetText(const WString& value) { SelectAll(); SetSelectionText(value); } /*********************************************************************** GuiDocumentLabel ***********************************************************************/ void GuiDocumentLabel::BeforeControlTemplateUninstalled_() { } void GuiDocumentLabel::AfterControlTemplateInstalled_(bool initialize) { auto ct = TypedControlTemplateObject(true); baselineDocument = ct->GetBaselineDocument(); if (documentElement) { documentElement->SetCaretColor(ct->GetCaretColor()); SetDocument(GetDocument()); } } void GuiDocumentLabel::UpdateDisplayFont() { GuiControl::UpdateDisplayFont(); OnFontChanged(); } GuiDocumentLabel::GuiDocumentLabel(theme::ThemeName themeName) :GuiControl(themeName) { SetAcceptTabInput(true); SetFocusableComposition(boundsComposition); InstallDocumentViewer(this, containerComposition, containerComposition, boundsComposition, focusableComposition); } GuiDocumentLabel::~GuiDocumentLabel() { } const WString& GuiDocumentLabel::GetText() { text=documentElement->GetDocument()->GetText(true); return text; } void GuiDocumentLabel::SetText(const WString& value) { SelectAll(); SetSelectionText(value); } } } } /*********************************************************************** .\CONTROLS\TEXTEDITORPACKAGE\GUITEXTCOMMONINTERFACE.CPP ***********************************************************************/ #include namespace vl { namespace presentation { namespace controls { using namespace elements; using namespace elements::text; using namespace compositions; /*********************************************************************** GuiTextBoxCommonInterface::DefaultCallback ***********************************************************************/ GuiTextBoxCommonInterface::DefaultCallback::DefaultCallback(elements::GuiColorizedTextElement* _textElement, compositions::GuiGraphicsComposition* _textComposition) :textElement(_textElement) ,textComposition(_textComposition) { } GuiTextBoxCommonInterface::DefaultCallback::~DefaultCallback() { } TextPos GuiTextBoxCommonInterface::DefaultCallback::GetLeftWord(TextPos pos) { return pos; } TextPos GuiTextBoxCommonInterface::DefaultCallback::GetRightWord(TextPos pos) { return pos; } void GuiTextBoxCommonInterface::DefaultCallback::GetWord(TextPos pos, TextPos& begin, TextPos& end) { begin=pos; end=pos; } vint GuiTextBoxCommonInterface::DefaultCallback::GetPageRows() { return textComposition->GetCachedBounds().Height()/textElement->GetLines().GetRowHeight(); } bool GuiTextBoxCommonInterface::DefaultCallback::BeforeModify(TextPos start, TextPos end, const WString& originalText, WString& inputText) { return true; } /*********************************************************************** GuiTextBoxCommonInterface ***********************************************************************/ void GuiTextBoxCommonInterface::InvokeUndoRedoChanged() { UndoRedoChanged.Execute(textControl->GetNotifyEventArguments()); } void GuiTextBoxCommonInterface::InvokeModifiedChanged() { ModifiedChanged.Execute(textControl->GetNotifyEventArguments()); } void GuiTextBoxCommonInterface::UpdateCaretPoint() { GuiGraphicsHost* host=textComposition->GetRelatedGraphicsHost(); if(host) { Rect caret=textElement->GetLines().GetRectFromTextPos(textElement->GetCaretEnd()); Point view=textElement->GetViewPosition(); vint x=caret.x1-view.x; vint y=caret.y2-view.y; host->SetCaretPoint(Point(x, y), textComposition); } } void GuiTextBoxCommonInterface::Move(TextPos pos, bool shift) { TextPos oldBegin = textElement->GetCaretBegin(); TextPos oldEnd = textElement->GetCaretEnd(); #if defined VCZH_MSVC if (0 <= pos.row && pos.row < textElement->GetLines().GetCount()) { TextLine& line = textElement->GetLines().GetLine(pos.row); if (pos.column > 0 && UTF16SPFirst(line.text[pos.column - 1]) && UTF16SPSecond(line.text[pos.column])) { if (pos < oldBegin) { pos.column--; } else if (pos > oldBegin) { pos.column++; } } } #endif pos = textElement->GetLines().Normalize(pos); if (!shift) { textElement->SetCaretBegin(pos); } textElement->SetCaretEnd(pos); if (textControl) { GuiGraphicsHost* host = textComposition->GetRelatedGraphicsHost(); if (host) { if (host->GetFocusedComposition() == textControl->GetFocusableComposition()) { textElement->SetCaretVisible(true); } } } Rect bounds = textElement->GetLines().GetRectFromTextPos(pos); Rect view = Rect(textElement->GetViewPosition(), textComposition->GetCachedBounds().GetSize()); Point viewPoint = view.LeftTop(); if (view.x2 > view.x1 && view.y2 > view.y1) { if (bounds.x1 < view.x1) { viewPoint.x = bounds.x1; } else if (bounds.x2 > view.x2) { viewPoint.x = bounds.x2 - view.Width(); } if (bounds.y1 < view.y1) { viewPoint.y = bounds.y1; } else if (bounds.y2 > view.y2) { viewPoint.y = bounds.y2 - view.Height(); } } callback->ScrollToView(viewPoint); UpdateCaretPoint(); TextPos newBegin = textElement->GetCaretBegin(); TextPos newEnd = textElement->GetCaretEnd(); if (oldBegin != newBegin || oldEnd != newEnd) { ICommonTextEditCallback::TextCaretChangedStruct arguments; arguments.oldBegin = oldBegin; arguments.oldEnd = oldEnd; arguments.newBegin = newBegin; arguments.newEnd = newEnd; arguments.editVersion = editVersion; // TODO: (enumerable) foreach for (vint i = 0; i < textEditCallbacks.Count(); i++) { textEditCallbacks[i]->TextCaretChanged(arguments); } SelectionChanged.Execute(textControl->GetNotifyEventArguments()); } } void GuiTextBoxCommonInterface::Modify(TextPos start, TextPos end, const WString& input, bool asKeyInput) { if(start>end) { TextPos temp=start; start=end; end=temp; } TextPos originalStart=start; TextPos originalEnd=end; WString originalText=textElement->GetLines().GetText(start, end); WString inputText=input; if(callback->BeforeModify(start, end, originalText, inputText)) { { ICommonTextEditCallback::TextEditPreviewStruct arguments; arguments.originalStart=originalStart; arguments.originalEnd=originalEnd; arguments.originalText=originalText; arguments.inputText=inputText; arguments.editVersion=editVersion; arguments.keyInput=asKeyInput; // TODO: (enumerable) foreach for(vint i=0;iTextEditPreview(arguments); } inputText=arguments.inputText; if(originalStart!=arguments.originalStart || originalEnd!=arguments.originalEnd) { originalStart=arguments.originalStart; originalEnd=arguments.originalEnd; originalText=textElement->GetLines().GetText(originalStart, originalEnd); start=originalStart; end=originalEnd; } } SPIN_LOCK(elementModifyLock) { end=textElement->GetLines().Modify(start, end, inputText); } callback->AfterModify(originalStart, originalEnd, originalText, start, end, inputText); editVersion++; { ICommonTextEditCallback::TextEditNotifyStruct arguments; arguments.originalStart=originalStart; arguments.originalEnd=originalEnd; arguments.originalText=originalText; arguments.inputStart=start; arguments.inputEnd=end; arguments.inputText=inputText; arguments.editVersion=editVersion; arguments.keyInput=asKeyInput; // TODO: (enumerable) foreach for(vint i=0;iTextEditNotify(arguments); } } Move(end, false); // TODO: (enumerable) foreach for(vint i=0;iTextEditFinished(editVersion); } textControl->TextChanged.Execute(textControl->GetNotifyEventArguments()); } } bool GuiTextBoxCommonInterface::ProcessKey(VKEY code, bool shift, bool ctrl) { if(IGuiShortcutKeyItem* item=internalShortcutKeyManager->TryGetShortcut(ctrl, shift, false, code)) { GuiEventArgs arguments(textControl->GetBoundsComposition()); item->Executed.Execute(arguments); return true; } TextPos begin=textElement->GetCaretBegin(); TextPos end=textElement->GetCaretEnd(); switch(code) { case VKEY::KEY_ESCAPE: if(autoComplete && autoComplete->IsListOpening() && !shift && !ctrl) { autoComplete->CloseList(); return true; } break; case VKEY::KEY_RETURN: if(autoComplete && autoComplete->IsListOpening() && !shift && !ctrl) { if(autoComplete->ApplySelectedListItem()) { preventEnterDueToAutoComplete=true; return true; } } break; case VKEY::KEY_UP: if(autoComplete && autoComplete->IsListOpening() && !shift && !ctrl) { autoComplete->SelectPreviousListItem(); } else { end.row--; Move(end, shift); } return true; case VKEY::KEY_DOWN: if(autoComplete && autoComplete->IsListOpening() && !shift && !ctrl) { autoComplete->SelectNextListItem(); } else { end.row++; Move(end, shift); } return true; case VKEY::KEY_LEFT: { if(ctrl) { Move(callback->GetLeftWord(end), shift); } else { if(end.column==0) { if(end.row>0) { end.row--; end=textElement->GetLines().Normalize(end); end.column=textElement->GetLines().GetLine(end.row).dataLength; } } else { end.column--; } Move(end, shift); } } return true; case VKEY::KEY_RIGHT: { if(ctrl) { Move(callback->GetRightWord(end), shift); } else { if(end.column==textElement->GetLines().GetLine(end.row).dataLength) { if(end.rowGetLines().GetCount()-1) { end.row++; end.column=0; } } else { end.column++; } Move(end, shift); } } return true; case VKEY::KEY_HOME: { if(ctrl) { Move(TextPos(0, 0), shift); } else { end.column=0; Move(end, shift); } } return true; case VKEY::KEY_END: { if(ctrl) { end.row=textElement->GetLines().GetCount()-1; } end.column=textElement->GetLines().GetLine(end.row).dataLength; Move(end, shift); } return true; case VKEY::KEY_PRIOR: { end.row-=callback->GetPageRows(); Move(end, shift); } return true; case VKEY::KEY_NEXT: { end.row+=callback->GetPageRows(); Move(end, shift); } return true; case VKEY::KEY_BACK: if(!readonly) { if(ctrl && !shift) { ProcessKey(VKEY::KEY_LEFT, true, true); ProcessKey(VKEY::KEY_BACK, false, false); } else if(!ctrl && shift) { ProcessKey(VKEY::KEY_UP, true, false); ProcessKey(VKEY::KEY_BACK, false, false); } else { if(begin==end) { ProcessKey(VKEY::KEY_LEFT, true, false); } SetSelectionTextAsKeyInput(L""); } return true; } break; case VKEY::KEY_DELETE: if(!readonly) { if(ctrl && !shift) { ProcessKey(VKEY::KEY_RIGHT, true, true); ProcessKey(VKEY::KEY_DELETE, false, false); } else if(!ctrl && shift) { ProcessKey(VKEY::KEY_DOWN, true, false); ProcessKey(VKEY::KEY_DELETE, false, false); } else { if(begin==end) { ProcessKey(VKEY::KEY_RIGHT, true, false); } SetSelectionTextAsKeyInput(L""); } return true; } break; default:; } return false; } void GuiTextBoxCommonInterface::OnGotFocus(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { textElement->SetFocused(true); textElement->SetCaretVisible(true); UpdateCaretPoint(); } void GuiTextBoxCommonInterface::OnLostFocus(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { textElement->SetFocused(false); textElement->SetCaretVisible(false); } void GuiTextBoxCommonInterface::OnCaretNotify(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { textElement->SetCaretVisible(!textElement->GetCaretVisible()); } void GuiTextBoxCommonInterface::OnLeftButtonDown(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) { if(textControl->GetVisuallyEnabled() && arguments.compositionSource==arguments.eventSource) { dragging=true; TextPos pos=GetNearestTextPos(Point(arguments.x, arguments.y)); Move(pos, arguments.shift); } } void GuiTextBoxCommonInterface::OnLeftButtonUp(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) { if(textControl->GetVisuallyEnabled() && arguments.compositionSource==arguments.eventSource) { dragging=false; } } void GuiTextBoxCommonInterface::OnMouseMove(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) { if(textControl->GetVisuallyEnabled() && arguments.compositionSource==arguments.eventSource) { if(dragging) { TextPos pos=GetNearestTextPos(Point(arguments.x, arguments.y)); Move(pos, true); } } } void GuiTextBoxCommonInterface::OnKeyDown(compositions::GuiGraphicsComposition* sender, compositions::GuiKeyEventArgs& arguments) { if(textControl->GetVisuallyEnabled() && arguments.compositionSource==arguments.eventSource) { if(ProcessKey(arguments.code, arguments.shift, arguments.ctrl)) { arguments.handled=true; } } } void GuiTextBoxCommonInterface::OnCharInput(compositions::GuiGraphicsComposition* sender, compositions::GuiCharEventArgs& arguments) { if (preventEnterDueToAutoComplete) { preventEnterDueToAutoComplete = false; if (arguments.code == (wchar_t)VKEY::KEY_RETURN) { return; } } if (textControl->GetVisuallyEnabled() && arguments.compositionSource == arguments.eventSource) { if (!readonly && arguments.code != (wchar_t)VKEY::KEY_ESCAPE && arguments.code != (wchar_t)VKEY::KEY_BACK && (arguments.code != (wchar_t)VKEY::KEY_TAB || textControl->GetAcceptTabInput()) && !arguments.ctrl) { SetSelectionTextAsKeyInput(WString::FromChar(arguments.code)); } } } void GuiTextBoxCommonInterface::Install( elements::GuiColorizedTextElement* _textElement, compositions::GuiGraphicsComposition* _textComposition, GuiControl* _textControl, compositions::GuiGraphicsComposition* eventComposition, compositions::GuiGraphicsComposition* focusableComposition ) { textElement=_textElement; textComposition=_textComposition; textControl=_textControl; textComposition->SetAssociatedCursor(GetCurrentController()->ResourceService()->GetSystemCursor(INativeCursor::IBeam)); SelectionChanged.SetAssociatedComposition(eventComposition); UndoRedoChanged.SetAssociatedComposition(eventComposition); ModifiedChanged.SetAssociatedComposition(eventComposition); undoRedoProcessor->UndoRedoChanged.Add(this, &GuiTextBoxCommonInterface::InvokeUndoRedoChanged); undoRedoProcessor->ModifiedChanged.Add(this, &GuiTextBoxCommonInterface::InvokeModifiedChanged); focusableComposition->GetEventReceiver()->gotFocus.AttachMethod(this, &GuiTextBoxCommonInterface::OnGotFocus); focusableComposition->GetEventReceiver()->lostFocus.AttachMethod(this, &GuiTextBoxCommonInterface::OnLostFocus); focusableComposition->GetEventReceiver()->caretNotify.AttachMethod(this, &GuiTextBoxCommonInterface::OnCaretNotify); textComposition->GetEventReceiver()->leftButtonDown.AttachMethod(this, &GuiTextBoxCommonInterface::OnLeftButtonDown); textComposition->GetEventReceiver()->leftButtonUp.AttachMethod(this, &GuiTextBoxCommonInterface::OnLeftButtonUp); textComposition->GetEventReceiver()->mouseMove.AttachMethod(this, &GuiTextBoxCommonInterface::OnMouseMove); focusableComposition->GetEventReceiver()->keyDown.AttachMethod(this, &GuiTextBoxCommonInterface::OnKeyDown); focusableComposition->GetEventReceiver()->charInput.AttachMethod(this, &GuiTextBoxCommonInterface::OnCharInput); // TODO: (enumerable) foreach for(vint i=0;iAttach(textElement, elementModifyLock, textComposition ,editVersion); } } GuiTextBoxCommonInterface::ICallback* GuiTextBoxCommonInterface::GetCallback() { return callback; } void GuiTextBoxCommonInterface::SetCallback(ICallback* value) { callback=value; } bool GuiTextBoxCommonInterface::AttachTextEditCallback(Ptr value) { if(textEditCallbacks.Contains(value.Obj())) { return false; } else { textEditCallbacks.Add(value); if(textElement) { value->Attach(textElement, elementModifyLock, textComposition, editVersion); } return true; } } bool GuiTextBoxCommonInterface::DetachTextEditCallback(Ptr value) { if(textEditCallbacks.Remove(value.Obj())) { value->Detach(); return true; } else { return false; } } void GuiTextBoxCommonInterface::AddShortcutCommand(VKEY key, const Func& eventHandler) { IGuiShortcutKeyItem* item=internalShortcutKeyManager->CreateNewShortcut(true, false, false, key); item->Executed.AttachLambda([=](GuiGraphicsComposition* sender, GuiEventArgs& arguments) { eventHandler(); }); } elements::GuiColorizedTextElement* GuiTextBoxCommonInterface::GetTextElement() { return textElement; } void GuiTextBoxCommonInterface::UnsafeSetText(const WString& value) { if(textElement) { TextPos end; if(textElement->GetLines().GetCount()>0) { end.row=textElement->GetLines().GetCount()-1; end.column=textElement->GetLines().GetLine(end.row).dataLength; } Modify(TextPos(), end, value, false); } } GuiTextBoxCommonInterface::GuiTextBoxCommonInterface() :textElement(0) ,textComposition(0) ,editVersion(0) ,textControl(0) ,callback(0) ,dragging(false) ,readonly(false) ,preventEnterDueToAutoComplete(false) { undoRedoProcessor=Ptr(new GuiTextBoxUndoRedoProcessor); AttachTextEditCallback(undoRedoProcessor); internalShortcutKeyManager=Ptr(new GuiShortcutKeyManager); AddShortcutCommand(VKEY::KEY_Z, Func(this, &GuiTextBoxCommonInterface::Undo)); AddShortcutCommand(VKEY::KEY_Y, Func(this, &GuiTextBoxCommonInterface::Redo)); AddShortcutCommand(VKEY::KEY_A, Func(this, &GuiTextBoxCommonInterface::SelectAll)); AddShortcutCommand(VKEY::KEY_X, Func(this, &GuiTextBoxCommonInterface::Cut)); AddShortcutCommand(VKEY::KEY_C, Func(this, &GuiTextBoxCommonInterface::Copy)); AddShortcutCommand(VKEY::KEY_V, Func(this, &GuiTextBoxCommonInterface::Paste)); } GuiTextBoxCommonInterface::~GuiTextBoxCommonInterface() { if(colorizer) { DetachTextEditCallback(colorizer); colorizer=0; } if(undoRedoProcessor) { DetachTextEditCallback(undoRedoProcessor); undoRedoProcessor=0; } // TODO: (enumerable) foreach for(vint i=0;iDetach(); } textEditCallbacks.Clear(); } //================ clipboard operations bool GuiTextBoxCommonInterface::CanCut() { return !readonly && textElement->GetCaretBegin()!=textElement->GetCaretEnd() && textElement->GetPasswordChar()==L'\0'; } bool GuiTextBoxCommonInterface::CanCopy() { return textElement->GetCaretBegin()!=textElement->GetCaretEnd() && textElement->GetPasswordChar()==L'\0'; } bool GuiTextBoxCommonInterface::CanPaste() { if (!readonly && textElement->GetPasswordChar() == L'\0') { auto reader = GetCurrentController()->ClipboardService()->ReadClipboard(); return reader->ContainsText(); } return false; } bool GuiTextBoxCommonInterface::Cut() { if (!CanCut()) return false; auto writer = GetCurrentController()->ClipboardService()->WriteClipboard(); writer->SetText(GetSelectionText()); writer->Submit(); SetSelectionText(L""); return true; } bool GuiTextBoxCommonInterface::Copy() { if (!CanCopy()) return false; auto writer = GetCurrentController()->ClipboardService()->WriteClipboard(); writer->SetText(GetSelectionText()); writer->Submit(); return true; } bool GuiTextBoxCommonInterface::Paste() { if (!CanPaste()) return false; auto reader = GetCurrentController()->ClipboardService()->ReadClipboard(); SetSelectionText(reader->GetText()); return true; } //================ editing control bool GuiTextBoxCommonInterface::GetReadonly() { return readonly; } void GuiTextBoxCommonInterface::SetReadonly(bool value) { readonly=value; } //================ text operations void GuiTextBoxCommonInterface::Select(TextPos begin, TextPos end) { Move(begin, false); Move(end, true); } void GuiTextBoxCommonInterface::SelectAll() { vint row=textElement->GetLines().GetCount()-1; Move(TextPos(0, 0), false); Move(TextPos(row, textElement->GetLines().GetLine(row).dataLength), true); } WString GuiTextBoxCommonInterface::GetSelectionText() { TextPos selectionBegin=textElement->GetCaretBegin()GetCaretEnd()?textElement->GetCaretBegin():textElement->GetCaretEnd(); TextPos selectionEnd=textElement->GetCaretBegin()>textElement->GetCaretEnd()?textElement->GetCaretBegin():textElement->GetCaretEnd(); return textElement->GetLines().GetText(selectionBegin, selectionEnd); } void GuiTextBoxCommonInterface::SetSelectionText(const WString& value) { Modify(textElement->GetCaretBegin(), textElement->GetCaretEnd(), value, false); } void GuiTextBoxCommonInterface::SetSelectionTextAsKeyInput(const WString& value) { Modify(textElement->GetCaretBegin(), textElement->GetCaretEnd(), value, true); } WString GuiTextBoxCommonInterface::GetRowText(vint row) { TextPos start=textElement->GetLines().Normalize(TextPos(row, 0)); TextPos end=TextPos(start.row, textElement->GetLines().GetLine(start.row).dataLength); return GetFragmentText(start, end); } vint GuiTextBoxCommonInterface::GetRowCount() { return textElement->GetLines().GetCount(); } WString GuiTextBoxCommonInterface::GetFragmentText(TextPos start, TextPos end) { start=textElement->GetLines().Normalize(start); end=textElement->GetLines().Normalize(end); return textElement->GetLines().GetText(start, end); } TextPos GuiTextBoxCommonInterface::GetCaretBegin() { return textElement->GetCaretBegin(); } TextPos GuiTextBoxCommonInterface::GetCaretEnd() { return textElement->GetCaretEnd(); } TextPos GuiTextBoxCommonInterface::GetCaretSmall() { TextPos c1=GetCaretBegin(); TextPos c2=GetCaretEnd(); return c1c2?c1:c2; } //================ position query vint GuiTextBoxCommonInterface::GetRowWidth(vint row) { return textElement->GetLines().GetRowWidth(row); } vint GuiTextBoxCommonInterface::GetRowHeight() { return textElement->GetLines().GetRowHeight(); } vint GuiTextBoxCommonInterface::GetMaxWidth() { return textElement->GetLines().GetMaxWidth(); } vint GuiTextBoxCommonInterface::GetMaxHeight() { return textElement->GetLines().GetMaxHeight(); } TextPos GuiTextBoxCommonInterface::GetTextPosFromPoint(Point point) { Point view=textElement->GetViewPosition(); return textElement->GetLines().GetTextPosFromPoint(Point(point.x+view.x, point.y+view.y)); } Point GuiTextBoxCommonInterface::GetPointFromTextPos(TextPos pos) { Point view=textElement->GetViewPosition(); Point result=textElement->GetLines().GetPointFromTextPos(pos); return Point(result.x-view.x, result.y-view.y); } Rect GuiTextBoxCommonInterface::GetRectFromTextPos(TextPos pos) { Point view=textElement->GetViewPosition(); Rect result=textElement->GetLines().GetRectFromTextPos(pos); return Rect(Point(result.x1-view.x, result.y1-view.y), result.GetSize()); } TextPos GuiTextBoxCommonInterface::GetNearestTextPos(Point point) { Point viewPosition=textElement->GetViewPosition(); Point mousePosition=Point(point.x+viewPosition.x, point.y+viewPosition.y); TextPos pos=textElement->GetLines().GetTextPosFromPoint(mousePosition); if(pos.columnGetLines().GetLine(pos.row).dataLength) { Rect rect=textElement->GetLines().GetRectFromTextPos(pos); if(abs((int)(rect.x1-mousePosition.x))>=abs((int)(rect.x2-1-mousePosition.x))) { pos.column++; } } return pos; } //================ colorizing Ptr GuiTextBoxCommonInterface::GetColorizer() { return colorizer; } void GuiTextBoxCommonInterface::SetColorizer(Ptr value) { if (!filledDefaultColors) { filledDefaultColors = true; CopyFrom(defaultColors, GetTextElement()->GetColors()); } if(colorizer) { DetachTextEditCallback(colorizer); } colorizer=value; if(colorizer) { AttachTextEditCallback(colorizer); GetTextElement()->SetColors(colorizer->GetColors()); } else { GetTextElement()->SetColors(defaultColors); GetTextElement()->ResetTextColorIndex(0); } } //================ auto complete Ptr GuiTextBoxCommonInterface::GetAutoComplete() { return autoComplete; } void GuiTextBoxCommonInterface::SetAutoComplete(Ptr value) { if(autoComplete) { DetachTextEditCallback(autoComplete); } autoComplete=value; if(autoComplete) { AttachTextEditCallback(autoComplete); } } //================ undo redo control vuint GuiTextBoxCommonInterface::GetEditVersion() { return editVersion; } bool GuiTextBoxCommonInterface::CanUndo() { return !readonly && undoRedoProcessor->CanUndo(); } bool GuiTextBoxCommonInterface::CanRedo() { return !readonly && undoRedoProcessor->CanRedo(); } void GuiTextBoxCommonInterface::ClearUndoRedo() { undoRedoProcessor->ClearUndoRedo(); } bool GuiTextBoxCommonInterface::GetModified() { return undoRedoProcessor->GetModified(); } void GuiTextBoxCommonInterface::NotifyModificationSaved() { undoRedoProcessor->NotifyModificationSaved(); } bool GuiTextBoxCommonInterface::Undo() { if(CanUndo()) { return undoRedoProcessor->Undo(); } else { return false; } } bool GuiTextBoxCommonInterface::Redo() { if(CanRedo()) { return undoRedoProcessor->Redo(); } else { return false; } } } } } /*********************************************************************** .\CONTROLS\TEXTEDITORPACKAGE\GUITEXTCONTROLS.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { using namespace elements; using namespace elements::text; using namespace compositions; using namespace collections; /*********************************************************************** GuiMultilineTextBox::DefaultTextElementOperatorCallback ***********************************************************************/ GuiMultilineTextBox::TextElementOperatorCallback::TextElementOperatorCallback(GuiMultilineTextBox* _textControl) :GuiTextBoxCommonInterface::DefaultCallback( _textControl->textElement, _textControl->textComposition ) ,textControl(_textControl) { } void GuiMultilineTextBox::TextElementOperatorCallback::AfterModify(TextPos originalStart, TextPos originalEnd, const WString& originalText, TextPos inputStart, TextPos inputEnd, const WString& inputText) { textControl->CalculateView(); } void GuiMultilineTextBox::TextElementOperatorCallback::ScrollToView(Point point) { point.x+=TextMargin; point.y+=TextMargin; Point oldPoint = textControl->GetViewPosition(); vint marginX=0; vint marginY=0; if(oldPoint.xpoint.x) { marginX=-TextMargin; } if(oldPoint.ypoint.y) { marginY=-TextMargin; } textControl->SetViewPosition(Point(point.x + marginX, point.y + marginY)); } vint GuiMultilineTextBox::TextElementOperatorCallback::GetTextMargin() { return TextMargin; } /*********************************************************************** GuiMultilineTextBox::CommandExecutor ***********************************************************************/ GuiMultilineTextBox::CommandExecutor::CommandExecutor(GuiMultilineTextBox* _textBox) :textBox(_textBox) { } GuiMultilineTextBox::CommandExecutor::~CommandExecutor() { } void GuiMultilineTextBox::CommandExecutor::UnsafeSetText(const WString& value) { textBox->UnsafeSetText(value); } /*********************************************************************** GuiMultilineTextBox ***********************************************************************/ void GuiMultilineTextBox::BeforeControlTemplateUninstalled_() { auto ct = TypedControlTemplateObject(false); if (!ct) return; ct->SetCommands(nullptr); } void GuiMultilineTextBox::AfterControlTemplateInstalled_(bool initialize) { auto ct = TypedControlTemplateObject(true); Array colors(1); colors[0] = ct->GetTextColor(); textElement->SetColors(colors); textElement->SetCaretColor(ct->GetCaretColor()); ct->SetCommands(commandExecutor.Obj()); } void GuiMultilineTextBox::UpdateVisuallyEnabled() { GuiControl::UpdateVisuallyEnabled(); textElement->SetVisuallyEnabled(GetVisuallyEnabled()); } void GuiMultilineTextBox::UpdateDisplayFont() { GuiControl::UpdateDisplayFont(); textElement->SetFont(GetDisplayFont()); CalculateViewAndSetScroll(); } void GuiMultilineTextBox::OnRenderTargetChanged(elements::IGuiGraphicsRenderTarget* renderTarget) { CalculateViewAndSetScroll(); GuiScrollView::OnRenderTargetChanged(renderTarget); } Size GuiMultilineTextBox::QueryFullSize() { TextLines& lines = textElement->GetLines(); return Size(lines.GetMaxWidth() + TextMargin * 2, lines.GetMaxHeight() + TextMargin * 2); } void GuiMultilineTextBox::UpdateView(Rect viewBounds) { textElement->SetViewPosition(viewBounds.LeftTop() - Size(TextMargin, TextMargin)); } void GuiMultilineTextBox::CalculateViewAndSetScroll() { auto ct = TypedControlTemplateObject(true); CalculateView(); vint smallMove = textElement->GetLines().GetRowHeight(); vint bigMove = smallMove * 5; if (auto scroll = ct->GetHorizontalScroll()) { scroll->SetSmallMove(smallMove); scroll->SetBigMove(bigMove); } if (auto scroll = ct->GetVerticalScroll()) { scroll->SetSmallMove(smallMove); scroll->SetBigMove(bigMove); } } void GuiMultilineTextBox::OnBoundsMouseButtonDown(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) { if(GetVisuallyEnabled()) { SetFocused(); } } GuiMultilineTextBox::GuiMultilineTextBox(theme::ThemeName themeName) :GuiScrollView(themeName) { textElement = GuiColorizedTextElement::Create(); textElement->SetFont(GetDisplayFont()); textComposition = new GuiBoundsComposition; textComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); textComposition->SetOwnedElement(Ptr(textElement)); containerComposition->AddChild(textComposition); callback = Ptr(new TextElementOperatorCallback(this)); commandExecutor = Ptr(new CommandExecutor(this)); SetAcceptTabInput(true); SetFocusableComposition(boundsComposition); Install(textElement, textComposition, this, boundsComposition, focusableComposition); SetCallback(callback.Obj()); boundsComposition->GetEventReceiver()->leftButtonDown.AttachMethod(this, &GuiMultilineTextBox::OnBoundsMouseButtonDown); boundsComposition->GetEventReceiver()->middleButtonDown.AttachMethod(this, &GuiMultilineTextBox::OnBoundsMouseButtonDown); boundsComposition->GetEventReceiver()->rightButtonDown.AttachMethod(this, &GuiMultilineTextBox::OnBoundsMouseButtonDown); } GuiMultilineTextBox::~GuiMultilineTextBox() { } const WString& GuiMultilineTextBox::GetText() { text = textElement->GetLines().GetText(); return text; } void GuiMultilineTextBox::SetText(const WString& value) { UnsafeSetText(value); textElement->SetCaretBegin(TextPos(0, 0)); textElement->SetCaretEnd(TextPos(0, 0)); CalculateView(); } /*********************************************************************** GuiSinglelineTextBox::DefaultTextElementOperatorCallback ***********************************************************************/ GuiSinglelineTextBox::TextElementOperatorCallback::TextElementOperatorCallback(GuiSinglelineTextBox* _textControl) :GuiTextBoxCommonInterface::DefaultCallback( _textControl->textElement, _textControl->textComposition ) { } bool GuiSinglelineTextBox::TextElementOperatorCallback::BeforeModify(TextPos start, TextPos end, const WString& originalText, WString& inputText) { vint length = inputText.Length(); const wchar_t* input = inputText.Buffer(); for (vint i = 0; i < length; i++) { if (*input == 0 || *input == L'\r' || *input == L'\n') { length = i; break; } } if (length != inputText.Length()) { if (length == 0) { // if the first line is empty after adjustment // the input should just be canceled // to prevent from making noise in undo return false; } inputText = inputText.Left(length); } return true; } void GuiSinglelineTextBox::TextElementOperatorCallback::AfterModify(TextPos originalStart, TextPos originalEnd, const WString& originalText, TextPos inputStart, TextPos inputEnd, const WString& inputText) { } void GuiSinglelineTextBox::TextElementOperatorCallback::ScrollToView(Point point) { vint newX=point.x; vint oldX=textElement->GetViewPosition().x; vint marginX=0; if(oldXnewX) { marginX=-TextMargin; } newX+=marginX; vint minX=-TextMargin; vint maxX=textElement->GetLines().GetMaxWidth()+TextMargin-textComposition->GetCachedBounds().Width(); if(newX>=maxX) { newX=maxX-1; } if(newXSetViewPosition(Point(newX, -TextMargin)); } vint GuiSinglelineTextBox::TextElementOperatorCallback::GetTextMargin() { return TextMargin; } /*********************************************************************** GuiSinglelineTextBox ***********************************************************************/ void GuiSinglelineTextBox::BeforeControlTemplateUninstalled_() { } void GuiSinglelineTextBox::AfterControlTemplateInstalled_(bool initialize) { auto ct = TypedControlTemplateObject(true); Array colors(1); colors[0] = ct->GetTextColor(); textElement->SetColors(colors); textElement->SetCaretColor(ct->GetCaretColor()); } void GuiSinglelineTextBox::UpdateVisuallyEnabled() { GuiControl::UpdateVisuallyEnabled(); textElement->SetVisuallyEnabled(GetVisuallyEnabled()); } void GuiSinglelineTextBox::UpdateDisplayFont() { GuiControl::UpdateDisplayFont(); textElement->SetFont(GetDisplayFont()); RearrangeTextElement(); } void GuiSinglelineTextBox::RearrangeTextElement() { textCompositionTable->SetRowOption( 1, GuiCellOption::AbsoluteOption( textElement->GetLines().GetRowHeight() + 2 * TextMargin) ); } void GuiSinglelineTextBox::OnRenderTargetChanged(elements::IGuiGraphicsRenderTarget* renderTarget) { GuiControl::OnRenderTargetChanged(renderTarget); RearrangeTextElement(); } void GuiSinglelineTextBox::OnBoundsMouseButtonDown(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) { if(GetVisuallyEnabled()) { SetFocused(); } } GuiSinglelineTextBox::GuiSinglelineTextBox(theme::ThemeName themeName) :GuiControl(themeName) { textElement = GuiColorizedTextElement::Create(); textElement->SetFont(GetDisplayFont()); textElement->SetViewPosition(Point(-GuiSinglelineTextBox::TextMargin, -GuiSinglelineTextBox::TextMargin)); textCompositionTable = new GuiTableComposition; textCompositionTable->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); textCompositionTable->SetAlignmentToParent(Margin(0, 0, 0, 0)); textCompositionTable->SetRowsAndColumns(3, 1); textCompositionTable->SetRowOption(0, GuiCellOption::PercentageOption(0.5)); textCompositionTable->SetRowOption(1, GuiCellOption::AbsoluteOption(0)); textCompositionTable->SetRowOption(2, GuiCellOption::PercentageOption(0.5)); textCompositionTable->SetColumnOption(0, GuiCellOption::PercentageOption(1.0)); containerComposition->AddChild(textCompositionTable); textComposition = new GuiCellComposition; textComposition->SetOwnedElement(Ptr(textElement)); textCompositionTable->AddChild(textComposition); textComposition->SetSite(1, 0, 1, 1); callback = Ptr(new TextElementOperatorCallback(this)); SetAcceptTabInput(true); SetFocusableComposition(boundsComposition); Install(textElement, textComposition, this, boundsComposition, focusableComposition); SetCallback(callback.Obj()); boundsComposition->GetEventReceiver()->leftButtonDown.AttachMethod(this, &GuiSinglelineTextBox::OnBoundsMouseButtonDown); boundsComposition->GetEventReceiver()->middleButtonDown.AttachMethod(this, &GuiSinglelineTextBox::OnBoundsMouseButtonDown); boundsComposition->GetEventReceiver()->rightButtonDown.AttachMethod(this, &GuiSinglelineTextBox::OnBoundsMouseButtonDown); } GuiSinglelineTextBox::~GuiSinglelineTextBox() { } const WString& GuiSinglelineTextBox::GetText() { text = textElement->GetLines().GetText(); return text; } void GuiSinglelineTextBox::SetText(const WString& value) { UnsafeSetText(value); textElement->SetCaretBegin(TextPos(0, 0)); textElement->SetCaretEnd(TextPos(0, 0)); } wchar_t GuiSinglelineTextBox::GetPasswordChar() { return textElement->GetPasswordChar(); } void GuiSinglelineTextBox::SetPasswordChar(wchar_t value) { textElement->SetPasswordChar(value); } } } } /*********************************************************************** .\CONTROLS\TEXTEDITORPACKAGE\EDITORCALLBACK\GUITEXTAUTOCOMPLETE.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { using namespace collections; /*********************************************************************** GuiTextBoxAutoCompleteBase::TextListControlProvider ***********************************************************************/ GuiTextBoxAutoCompleteBase::TextListControlProvider::TextListControlProvider(TemplateProperty controlTemplate) { autoCompleteList = new GuiTextList(theme::ThemeName::TextList); if (controlTemplate) { autoCompleteList->SetControlTemplate(controlTemplate); } autoCompleteList->SetHorizontalAlwaysVisible(false); autoCompleteList->SetVerticalAlwaysVisible(false); } GuiTextBoxAutoCompleteBase::TextListControlProvider::~TextListControlProvider() { } GuiControl* GuiTextBoxAutoCompleteBase::TextListControlProvider::GetAutoCompleteControl() { return autoCompleteList; } GuiSelectableListControl* GuiTextBoxAutoCompleteBase::TextListControlProvider::GetListControl() { return autoCompleteList; } void GuiTextBoxAutoCompleteBase::TextListControlProvider::SetSortedContent(const collections::List& items) { autoCompleteList->GetItems().Clear(); for (auto item : items) { autoCompleteList->GetItems().Add(Ptr(new list::TextItem(item.text))); } } vint GuiTextBoxAutoCompleteBase::TextListControlProvider::GetItemCount() { return autoCompleteList->GetItems().Count(); } WString GuiTextBoxAutoCompleteBase::TextListControlProvider::GetItemText(vint index) { return autoCompleteList->GetItems()[index]->GetText(); } /*********************************************************************** GuiTextBoxAutoCompleteBase ***********************************************************************/ bool GuiTextBoxAutoCompleteBase::IsPrefix(const WString& prefix, const WString& candidate) { if(candidate.Length()>=prefix.Length()) { if(INVLOC.Compare(prefix, candidate.Sub(0, prefix.Length()), Locale::IgnoreCase)==0) { return true; } } return false; } GuiTextBoxAutoCompleteBase::GuiTextBoxAutoCompleteBase(Ptr _autoCompleteControlProvider) :element(0) , elementModifyLock(0) , ownerComposition(0) , autoCompleteControlProvider(_autoCompleteControlProvider) { if (!autoCompleteControlProvider) { autoCompleteControlProvider = Ptr(new TextListControlProvider); } autoCompleteControlProvider->GetAutoCompleteControl()->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); autoCompletePopup = new GuiPopup(theme::ThemeName::Menu); autoCompletePopup->AddChild(autoCompleteControlProvider->GetAutoCompleteControl()); } GuiTextBoxAutoCompleteBase::~GuiTextBoxAutoCompleteBase() { delete autoCompletePopup; } void GuiTextBoxAutoCompleteBase::Attach(elements::GuiColorizedTextElement* _element, SpinLock& _elementModifyLock, compositions::GuiGraphicsComposition* _ownerComposition, vuint editVersion) { if(_element) { SPIN_LOCK(_elementModifyLock) { element=_element; elementModifyLock=&_elementModifyLock; ownerComposition=_ownerComposition; } } } void GuiTextBoxAutoCompleteBase::Detach() { if(element && elementModifyLock) { SPIN_LOCK(*elementModifyLock) { element=0; elementModifyLock=0; } } } void GuiTextBoxAutoCompleteBase::TextEditPreview(TextEditPreviewStruct& arguments) { } void GuiTextBoxAutoCompleteBase::TextEditNotify(const TextEditNotifyStruct& arguments) { if(element && elementModifyLock) { if(IsListOpening()) { TextPos begin=GetListStartPosition(); TextPos end=arguments.inputEnd; WString editingText=element->GetLines().GetText(begin, end); HighlightList(editingText); } } } void GuiTextBoxAutoCompleteBase::TextCaretChanged(const TextCaretChangedStruct& arguments) { } void GuiTextBoxAutoCompleteBase::TextEditFinished(vuint editVersion) { } bool GuiTextBoxAutoCompleteBase::IsListOpening() { return autoCompletePopup->GetOpening(); } void GuiTextBoxAutoCompleteBase::OpenList(TextPos startPosition) { if(element && elementModifyLock) { autoCompleteStartPosition=startPosition; Rect bounds=element->GetLines().GetRectFromTextPos(startPosition); Point viewPosition=element->GetViewPosition(); GuiControl* ownerControl=ownerComposition->GetRelatedControl(); Rect compositionBounds=ownerComposition->GetGlobalBounds(); Rect controlBounds=ownerControl->GetBoundsComposition()->GetGlobalBounds(); vint px=compositionBounds.x1-controlBounds.x1-viewPosition.x; vint py=compositionBounds.y1-controlBounds.y1-viewPosition.y; bounds.x1+=px; bounds.x2+=px; bounds.y1+=py+5; bounds.y2+=py+5; autoCompletePopup->ShowPopup(ownerControl, bounds, true); } } void GuiTextBoxAutoCompleteBase::CloseList() { autoCompletePopup->Close(); } void GuiTextBoxAutoCompleteBase::SetListContent(const collections::List& items) { if(items.Count()==0) { CloseList(); } List sortedItems; CopyFrom( sortedItems, From(items) .OrderBy([](const AutoCompleteItem& a, const AutoCompleteItem& b) { return INVLOC.Compare(a.text, b.text, Locale::IgnoreCase) <=> 0; }) ); autoCompleteControlProvider->SetSortedContent(sortedItems); autoCompleteControlProvider->GetAutoCompleteControl()->GetBoundsComposition()->SetPreferredMinSize(Size(200, 200)); } TextPos GuiTextBoxAutoCompleteBase::GetListStartPosition() { return autoCompleteStartPosition; } bool GuiTextBoxAutoCompleteBase::SelectPreviousListItem() { if(!IsListOpening()) return false; if(autoCompleteControlProvider->GetListControl()->GetSelectedItems().Count()==0) { autoCompleteControlProvider->GetListControl()->SetSelected(0, true); } else { vint index=autoCompleteControlProvider->GetListControl()->GetSelectedItems()[0]; if (index > 0) index--; autoCompleteControlProvider->GetListControl()->SetSelected(index, true); autoCompleteControlProvider->GetListControl()->EnsureItemVisible(index); } return true; } bool GuiTextBoxAutoCompleteBase::SelectNextListItem() { if(!IsListOpening()) return false; if (autoCompleteControlProvider->GetListControl()->GetSelectedItems().Count() == 0) { autoCompleteControlProvider->GetListControl()->SetSelected(0, true); } else { vint index = autoCompleteControlProvider->GetListControl()->GetSelectedItems()[0]; if (index < autoCompleteControlProvider->GetItemCount() - 1) index++; autoCompleteControlProvider->GetListControl()->SetSelected(index, true); autoCompleteControlProvider->GetListControl()->EnsureItemVisible(index); } return true; } bool GuiTextBoxAutoCompleteBase::ApplySelectedListItem() { if(!IsListOpening()) return false; if(!ownerComposition) return false; const auto& selectedItems = autoCompleteControlProvider->GetListControl()->GetSelectedItems(); if (selectedItems.Count() == 0) return false; GuiTextBoxCommonInterface* ci=dynamic_cast(ownerComposition->GetRelatedControl()); if(!ci) return false; vint index = selectedItems[0]; WString selectedItem = autoCompleteControlProvider->GetItemText(index); TextPos begin = autoCompleteStartPosition; TextPos end = ci->GetCaretEnd(); ci->Select(begin, end); ci->SetSelectionText(selectedItem); CloseList(); return true; } WString GuiTextBoxAutoCompleteBase::GetSelectedListItem() { if(!IsListOpening()) return L""; const auto& selectedItems = autoCompleteControlProvider->GetListControl()->GetSelectedItems(); if (selectedItems.Count() == 0) return L""; vint index = selectedItems[0]; return autoCompleteControlProvider->GetItemText(index); } void GuiTextBoxAutoCompleteBase::HighlightList(const WString& editingText) { if(IsListOpening()) { vint first=0; vint last = autoCompleteControlProvider->GetItemCount() - 1; vint selected=-1; while (first <= last) { vint middle = (first + last) / 2; WString text = autoCompleteControlProvider->GetItemText(middle); if (IsPrefix(editingText, text)) { selected = middle; break; } vint result = INVLOC.Compare(editingText, text, Locale::IgnoreCase); if (result <= 0) { last = middle - 1; } else { first = middle + 1; } } while(selected>0) { WString text = autoCompleteControlProvider->GetItemText(selected - 1); if (IsPrefix(editingText, text)) { selected--; } else { break; } } if(selected!=-1) { autoCompleteControlProvider->GetListControl()->SetSelected(selected, true); autoCompleteControlProvider->GetListControl()->EnsureItemVisible(selected); } } } } } } /*********************************************************************** .\CONTROLS\TEXTEDITORPACKAGE\EDITORCALLBACK\GUITEXTCOLORIZER.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { using namespace elements; using namespace elements::text; /*********************************************************************** GuiTextBoxColorizerBase ***********************************************************************/ void GuiTextBoxColorizerBase::ColorizerThreadProc(void* argument) { GuiTextBoxColorizerBase* colorizer=(GuiTextBoxColorizerBase*)argument; while(!colorizer->isFinalizing) { vint lineIndex=-1; wchar_t* text=0; vuint32_t* colors=0; vint length=0; vint lexerState=-1; vint contextState=-1; SPIN_LOCK(*colorizer->elementModifyLock) { if(colorizer->colorizedLineCount>=colorizer->element->GetLines().GetCount()) { colorizer->isColorizerRunning=false; goto CANCEL_COLORIZING; } lineIndex=colorizer->colorizedLineCount++; TextLine& line=colorizer->element->GetLines().GetLine(lineIndex); length=line.dataLength; text=new wchar_t[length+2]; colors=new vuint32_t[length+2]; memcpy(text, line.text, sizeof(wchar_t)*length); text[length]=L'\r'; text[length+1]=L'\n'; lexerState=lineIndex==0?colorizer->GetLexerStartState():colorizer->element->GetLines().GetLine(lineIndex-1).lexerFinalState; contextState=lineIndex==0?colorizer->GetContextStartState():colorizer->element->GetLines().GetLine(lineIndex-1).contextFinalState; } colorizer->ColorizeLineWithCRLF(lineIndex, text, colors, length+2, lexerState, contextState); SPIN_LOCK(*colorizer->elementModifyLock) { if(lineIndexcolorizedLineCount && lineIndexelement->GetLines().GetCount()) { TextLine& line=colorizer->element->GetLines().GetLine(lineIndex); line.lexerFinalState=lexerState; line.contextFinalState=contextState; for(vint i=0;icolorizerRunningEvent.Leave(); } void GuiTextBoxColorizerBase::StartColorizer() { if(!isColorizerRunning) { isColorizerRunning=true; colorizerRunningEvent.Enter(); ThreadPoolLite::Queue(&GuiTextBoxColorizerBase::ColorizerThreadProc, this); } } void GuiTextBoxColorizerBase::StopColorizer(bool forever) { isFinalizing=true; colorizerRunningEvent.Enter(); colorizerRunningEvent.Leave(); colorizedLineCount=0; if(!forever) { isFinalizing=false; } } void GuiTextBoxColorizerBase::StopColorizerForever() { StopColorizer(true); } GuiTextBoxColorizerBase::GuiTextBoxColorizerBase() :element(0) ,elementModifyLock(0) ,colorizedLineCount(0) ,isColorizerRunning(false) ,isFinalizing(false) { } GuiTextBoxColorizerBase::~GuiTextBoxColorizerBase() { StopColorizerForever(); } void GuiTextBoxColorizerBase::Attach(elements::GuiColorizedTextElement* _element, SpinLock& _elementModifyLock, compositions::GuiGraphicsComposition* _ownerComposition, vuint editVersion) { if(_element) { SPIN_LOCK(_elementModifyLock) { element=_element; elementModifyLock=&_elementModifyLock; StartColorizer(); } } } void GuiTextBoxColorizerBase::Detach() { if(element && elementModifyLock) { StopColorizer(false); SPIN_LOCK(*elementModifyLock) { element=0; elementModifyLock=0; } } } void GuiTextBoxColorizerBase::TextEditPreview(TextEditPreviewStruct& arguments) { } void GuiTextBoxColorizerBase::TextEditNotify(const TextEditNotifyStruct& arguments) { if(element && elementModifyLock) { SPIN_LOCK(*elementModifyLock) { vint line =arguments.originalStart.rowline) { colorizedLineCount=line; } StartColorizer(); } } } void GuiTextBoxColorizerBase::TextCaretChanged(const TextCaretChangedStruct& arguments) { } void GuiTextBoxColorizerBase::TextEditFinished(vuint editVersion) { } void GuiTextBoxColorizerBase::RestartColorizer() { if(element && elementModifyLock) { SPIN_LOCK(*elementModifyLock) { colorizedLineCount=0; StartColorizer(); } } } /*********************************************************************** GuiTextBoxRegexColorizer ***********************************************************************/ struct GuiTextBoxRegexColorizerProcData { GuiTextBoxRegexColorizer* colorizer; vint lineIndex; const wchar_t* text; vuint32_t* colors; vint contextState; }; void GuiTextBoxRegexColorizer::ColorizerProc(void* argument, vint start, vint length, vint token) { GuiTextBoxRegexColorizerProcData& data=**(GuiTextBoxRegexColorizerProcData**)argument; data.colorizer->ColorizeTokenContextSensitive(data.lineIndex, data.text, start, length, token, data.contextState); for(vint i=0;i& GuiTextBoxRegexColorizer::GetTokenRegexes() { return tokenRegexes; } collections::List& GuiTextBoxRegexColorizer::GetTokenColors() { return tokenColors; } collections::List& GuiTextBoxRegexColorizer::GetExtraTokenColors() { return extraTokenColors; } vint GuiTextBoxRegexColorizer::GetExtraTokenIndexStart() { if(lexer) { return tokenColors.Count(); } else { return -1; } } bool GuiTextBoxRegexColorizer::SetDefaultColor(elements::text::ColorEntry value) { if(lexer) { return false; } else { defaultColor=value; return true; } } vint GuiTextBoxRegexColorizer::AddToken(const WString& regex, elements::text::ColorEntry color) { if(lexer) { return -1; } else { tokenRegexes.Add(regex); tokenColors.Add(color); return tokenColors.Count()-1; } } vint GuiTextBoxRegexColorizer::AddExtraToken(elements::text::ColorEntry color) { if(lexer) { return -1; } else { extraTokenColors.Add(color); return extraTokenColors.Count()-1; } } void GuiTextBoxRegexColorizer::ClearTokens() { tokenRegexes.Clear(); tokenColors.Clear(); extraTokenColors.Clear(); lexer=0; } void GuiTextBoxRegexColorizer::Setup() { if (lexer || tokenRegexes.Count() == 0) { colors.Resize(1); colors[0] = defaultColor; } else { lexer = Ptr(new regex::RegexLexer(tokenRegexes)); colors.Resize(1 + tokenRegexes.Count() + extraTokenColors.Count()); colors[0] = defaultColor; for (vint i = 0; i < tokenColors.Count(); i++) { colors[i + 1] = tokenColors[i]; } for (vint i = 0; i < extraTokenColors.Count(); i++) { colors[i + 1 + tokenColors.Count()] = extraTokenColors[i]; } { regex::RegexProc proc; proc.colorizeProc = &GuiTextBoxRegexColorizer::ColorizerProc; proc.argument = colorizerArgument; colorizer = Ptr(new regex::RegexLexerColorizer(lexer->Colorize(proc))); } } } void GuiTextBoxRegexColorizer::ColorizeTokenContextSensitive(vint lineIndex, const wchar_t* text, vint start, vint length, vint& token, vint& contextState) { } vint GuiTextBoxRegexColorizer::GetLexerStartState() { return lexer?colorizer->GetStartState():-1; } vint GuiTextBoxRegexColorizer::GetContextStartState() { return 0; } void GuiTextBoxRegexColorizer::ColorizeLineWithCRLF(vint lineIndex, const wchar_t* text, vuint32_t* colors, vint length, vint& lexerState, vint& contextState) { memset(colors, 0, sizeof(*colors)*length); if (lexer) { GuiTextBoxRegexColorizerProcData data; data.colorizer = this; data.lineIndex = lineIndex; data.text = text; data.colors = colors; data.contextState = contextState; regex::RegexLexerColorizer::InternalState internalState; internalState.currentState = lexerState; colorizer->SetInternalState(internalState); colorizerArgument[0] = &data; colorizer->Colorize(text, length); lexerState = colorizer->GetInternalState().currentState; contextState = data.contextState; } else { lexerState = -1; contextState = -1; } } const GuiTextBoxRegexColorizer::ColorArray& GuiTextBoxRegexColorizer::GetColors() { return colors; } } } } /*********************************************************************** .\CONTROLS\TEXTEDITORPACKAGE\EDITORCALLBACK\GUITEXTUNDOREDO.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { using namespace elements; using namespace elements::text; using namespace compositions; /*********************************************************************** GuiGeneralUndoRedoProcessor ***********************************************************************/ GuiGeneralUndoRedoProcessor::GuiGeneralUndoRedoProcessor() :firstFutureStep(0) ,savedStep(0) ,performingUndoRedo(false) { } GuiGeneralUndoRedoProcessor::~GuiGeneralUndoRedoProcessor() { } void GuiGeneralUndoRedoProcessor::PushStep(Ptr step) { if(!performingUndoRedo) { if(firstFutureStep0) { steps.RemoveRange(firstFutureStep, count); } steps.Add(step); firstFutureStep=steps.Count(); UndoRedoChanged(); ModifiedChanged(); } } bool GuiGeneralUndoRedoProcessor::CanUndo() { return firstFutureStep>0; } bool GuiGeneralUndoRedoProcessor::CanRedo() { return steps.Count()>firstFutureStep; } void GuiGeneralUndoRedoProcessor::ClearUndoRedo() { if(!performingUndoRedo) { steps.Clear(); firstFutureStep=0; savedStep=0; } } bool GuiGeneralUndoRedoProcessor::GetModified() { return firstFutureStep!=savedStep; } void GuiGeneralUndoRedoProcessor::NotifyModificationSaved() { if(!performingUndoRedo) { savedStep=firstFutureStep; ModifiedChanged(); } } bool GuiGeneralUndoRedoProcessor::Undo() { if(!CanUndo()) return false; performingUndoRedo=true; firstFutureStep--; steps[firstFutureStep]->Undo(); performingUndoRedo=false; UndoRedoChanged(); ModifiedChanged(); return true; } bool GuiGeneralUndoRedoProcessor::Redo() { if(!CanRedo()) return false; performingUndoRedo=true; firstFutureStep++; steps[firstFutureStep-1]->Redo(); performingUndoRedo=false; UndoRedoChanged(); ModifiedChanged(); return true; } /*********************************************************************** GuiTextBoxUndoRedoProcessor::EditStep ***********************************************************************/ void GuiTextBoxUndoRedoProcessor::EditStep::Undo() { GuiTextBoxCommonInterface* ci=dynamic_cast(processor->ownerComposition->GetRelatedControl()); if(ci) { ci->Select(arguments.inputStart, arguments.inputEnd); ci->SetSelectionText(arguments.originalText); ci->Select(arguments.originalStart, arguments.originalEnd); } } void GuiTextBoxUndoRedoProcessor::EditStep::Redo() { GuiTextBoxCommonInterface* ci=dynamic_cast(processor->ownerComposition->GetRelatedControl()); if(ci) { ci->Select(arguments.originalStart, arguments.originalEnd); ci->SetSelectionText(arguments.inputText); ci->Select(arguments.inputStart, arguments.inputEnd); } } /*********************************************************************** GuiTextBoxUndoRedoProcessor ***********************************************************************/ GuiTextBoxUndoRedoProcessor::GuiTextBoxUndoRedoProcessor() :ownerComposition(0) { } GuiTextBoxUndoRedoProcessor::~GuiTextBoxUndoRedoProcessor() { } void GuiTextBoxUndoRedoProcessor::Attach(elements::GuiColorizedTextElement* element, SpinLock& elementModifyLock, compositions::GuiGraphicsComposition* _ownerComposition, vuint editVersion) { ownerComposition=_ownerComposition; } void GuiTextBoxUndoRedoProcessor::Detach() { ClearUndoRedo(); } void GuiTextBoxUndoRedoProcessor::TextEditPreview(TextEditPreviewStruct& arguments) { } void GuiTextBoxUndoRedoProcessor::TextEditNotify(const TextEditNotifyStruct& arguments) { auto step=Ptr(new EditStep); step->processor=this; step->arguments=arguments; PushStep(step); } void GuiTextBoxUndoRedoProcessor::TextCaretChanged(const TextCaretChangedStruct& arguments) { } void GuiTextBoxUndoRedoProcessor::TextEditFinished(vuint editVersion) { } /*********************************************************************** GuiDocumentUndoRedoProcessor::ReplaceModelStep ***********************************************************************/ void GuiDocumentUndoRedoProcessor::ReplaceModelStep::Undo() { GuiDocumentCommonInterface* ci=dynamic_cast(processor->ownerComposition->GetRelatedControl()); if(ci) { ci->EditRun(arguments.inputStart, arguments.inputEnd, arguments.originalModel, true); ci->SetCaret(arguments.originalStart, arguments.originalEnd); } } void GuiDocumentUndoRedoProcessor::ReplaceModelStep::Redo() { GuiDocumentCommonInterface* ci=dynamic_cast(processor->ownerComposition->GetRelatedControl()); if(ci) { ci->EditRun(arguments.originalStart, arguments.originalEnd, arguments.inputModel, true); ci->SetCaret(arguments.inputStart, arguments.inputEnd); } } /*********************************************************************** GuiDocumentUndoRedoProcessor::RenameStyleStep ***********************************************************************/ void GuiDocumentUndoRedoProcessor::RenameStyleStep::Undo() { GuiDocumentCommonInterface* ci=dynamic_cast(processor->ownerComposition->GetRelatedControl()); if(ci) { ci->RenameStyle(arguments.newStyleName, arguments.oldStyleName); } } void GuiDocumentUndoRedoProcessor::RenameStyleStep::Redo() { GuiDocumentCommonInterface* ci=dynamic_cast(processor->ownerComposition->GetRelatedControl()); if(ci) { ci->RenameStyle(arguments.oldStyleName, arguments.newStyleName); } } /*********************************************************************** GuiDocumentUndoRedoProcessor::SetAlignmentStep ***********************************************************************/ void GuiDocumentUndoRedoProcessor::SetAlignmentStep::Undo() { GuiDocumentCommonInterface* ci=dynamic_cast(processor->ownerComposition->GetRelatedControl()); if(ci) { ci->SetParagraphAlignments(TextPos(arguments->start, 0), TextPos(arguments->end, 0), arguments->originalAlignments); } } void GuiDocumentUndoRedoProcessor::SetAlignmentStep::Redo() { GuiDocumentCommonInterface* ci=dynamic_cast(processor->ownerComposition->GetRelatedControl()); if(ci) { ci->SetParagraphAlignments(TextPos(arguments->start, 0), TextPos(arguments->end, 0), arguments->inputAlignments); } } /*********************************************************************** GuiDocumentUndoRedoProcessor ***********************************************************************/ GuiDocumentUndoRedoProcessor::GuiDocumentUndoRedoProcessor() :element(0) ,ownerComposition(0) { } GuiDocumentUndoRedoProcessor::~GuiDocumentUndoRedoProcessor() { } void GuiDocumentUndoRedoProcessor::Setup(elements::GuiDocumentElement* _element, compositions::GuiGraphicsComposition* _ownerComposition) { element=_element; ownerComposition=_ownerComposition; } void GuiDocumentUndoRedoProcessor::OnReplaceModel(const ReplaceModelStruct& arguments) { auto step=Ptr(new ReplaceModelStep); step->processor=this; step->arguments=arguments; PushStep(step); } void GuiDocumentUndoRedoProcessor::OnRenameStyle(const RenameStyleStruct& arguments) { auto step=Ptr(new RenameStyleStep); step->processor=this; step->arguments=arguments; PushStep(step); } void GuiDocumentUndoRedoProcessor::OnSetAlignment(Ptr arguments) { auto step=Ptr(new SetAlignmentStep); step->processor=this; step->arguments=arguments; PushStep(step); } } } } /*********************************************************************** .\CONTROLS\TEXTEDITORPACKAGE\LANGUAGESERVICE\GUILANGUAGEAUTOCOMPLETE.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { using namespace elements; using namespace regex; using namespace parsing; using namespace parsing::tabling; using namespace collections; /*********************************************************************** GuiGrammarAutoComplete ***********************************************************************/ void GuiGrammarAutoComplete::Attach(elements::GuiColorizedTextElement* _element, SpinLock& _elementModifyLock, compositions::GuiGraphicsComposition* _ownerComposition, vuint editVersion) { GuiTextBoxAutoCompleteBase::Attach(_element, _elementModifyLock, _ownerComposition, editVersion); RepeatingParsingExecutor::CallbackBase::Attach(_element, _elementModifyLock, _ownerComposition, editVersion); } void GuiGrammarAutoComplete::Detach() { GuiTextBoxAutoCompleteBase::Detach(); RepeatingParsingExecutor::CallbackBase::Detach(); if(element && elementModifyLock) { EnsureAutoCompleteFinished(); } } void GuiGrammarAutoComplete::TextEditPreview(TextEditPreviewStruct& arguments) { GuiTextBoxAutoCompleteBase::TextEditPreview(arguments); RepeatingParsingExecutor::CallbackBase::TextEditPreview(arguments); if(element && elementModifyLock) { if(IsListOpening() && arguments.keyInput && arguments.originalText==L"" && arguments.inputText!=L"") { WString selectedItem=GetSelectedListItem(); if(selectedItem!=L"") { TextPos begin=GetListStartPosition(); TextPos end=arguments.originalStart; WString editingText=element->GetLines().GetText(begin, end); editingText+=arguments.inputText; if(grammarParser->GetTable()->GetLexer().Walk().IsClosedToken(editingText)) { arguments.originalStart=begin; arguments.inputText=selectedItem+arguments.inputText; CloseList(); } } } } } void GuiGrammarAutoComplete::TextEditNotify(const TextEditNotifyStruct& arguments) { GuiTextBoxAutoCompleteBase::TextEditNotify(arguments); RepeatingParsingExecutor::CallbackBase::TextEditNotify(arguments); if(element && elementModifyLock) { editing=true; SPIN_LOCK(editTraceLock) { editTrace.Add(arguments); } } } void GuiGrammarAutoComplete::TextCaretChanged(const TextCaretChangedStruct& arguments) { GuiTextBoxAutoCompleteBase::TextCaretChanged(arguments); RepeatingParsingExecutor::CallbackBase::TextCaretChanged(arguments); if(element && elementModifyLock) { SPIN_LOCK(editTraceLock) { // queue a fake TextEditNotifyStruct // a fake struct can be detected by (trace.originalText==L"" && trace.inputText==L"") TextEditNotifyStruct trace; trace.editVersion=arguments.editVersion; trace.originalStart=arguments.oldBegin; trace.originalEnd=arguments.oldEnd; trace.inputStart=arguments.newBegin; trace.inputEnd=arguments.newEnd; // ensure trace.originalStart<=trace.originalEnd if(trace.originalStart>trace.originalEnd) { TextPos temp=trace.originalStart; trace.originalStart=trace.originalEnd; trace.originalEnd=temp; } // ensure trace.inputStart<=trace.inputEnd if(trace.inputStart>trace.inputEnd) { TextPos temp=trace.inputStart; trace.inputStart=trace.inputEnd; trace.inputEnd=temp; } editTrace.Add(trace); } SPIN_LOCK(contextLock) { if(context.input.node) { if(editing) { // if the current caret changing is caused by editing // submit a task with valid editVersion and invalid node and code RepeatingParsingOutput input; input.editVersion=context.input.editVersion; SubmitTask(input); } else if(context.input.editVersion == arguments.editVersion) { // if the current caret changing is not caused by editing // submit a task with the previous input SubmitTask(context.input); } } } } } void GuiGrammarAutoComplete::TextEditFinished(vuint editVersion) { GuiTextBoxAutoCompleteBase::TextEditFinished(editVersion); RepeatingParsingExecutor::CallbackBase::TextEditFinished(editVersion); if(element && elementModifyLock) { editing=false; } } void GuiGrammarAutoComplete::OnParsingFinishedAsync(const RepeatingParsingOutput& arguments) { if(element && elementModifyLock) { GetApplication()->InvokeInMainThread(ownerComposition->GetRelatedControlHost(), [=]() { // submit a task if the RepeatingParsingExecutor notices a new parsing result SubmitTask(arguments); }); } } void GuiGrammarAutoComplete::CollectLeftRecursiveRules() { leftRecursiveRules.Clear(); Ptr parser=parsingExecutor->GetParser(); Ptr table=parser->GetTable(); vint stateCount=table->GetStateCount(); vint tokenCount=table->GetTokenCount(); for(vint i=0;i bag=table->GetTransitionBag(i, j); if(bag) { for (auto item : bag->transitionItems) { for (auto ins : item->instructions) { if(ins.instructionType==ParsingTable::Instruction::LeftRecursiveReduce) { if(!leftRecursiveRules.Contains(ins.creatorRule)) { leftRecursiveRules.Add(ins.creatorRule); } } } } } } } } vint GuiGrammarAutoComplete::UnsafeGetEditTraceIndex(vuint editVersion) { // get the index of the latest TextEditNotifyStruct of a specified edit version // this function should be called inside SPIN_LOCK(editTraceLock) // perform a binary search vint start = 0; vint end = editTrace.Count() - 1; while (start <= end) { vint middle = (start + end) / 2; TextEditNotifyStruct& trace = editTrace[middle]; if (editVersiontrace.editVersion) { start = middle + 1; } else { // if multiple TextEditNotifyStruct is found, choose the latest one while (middle < editTrace.Count() - 1) { if (editTrace[middle + 1].editVersion == editTrace[middle].editVersion) { middle++; } else { break; } } return middle; } } return -1; } TextPos GuiGrammarAutoComplete::ChooseCorrectTextPos(TextPos pos, const regex::RegexTokens& tokens) { Ptr table=grammarParser->GetTable(); RegexToken lastToken; lastToken.reading=0; for (auto token : tokens) { // we treat "class| Name" as editing the first token if(TextPos(token.rowEnd, token.columnEnd+1)>=pos) { if(table->GetTableTokenIndex(token.token)!=-1 && lastToken.reading) { pos=TextPos(lastToken.rowStart, lastToken.columnStart); } break; } lastToken=token; } return pos; } void GuiGrammarAutoComplete::ExecuteRefresh(AutoCompleteContext& newContext) { // process the input of a task is submitted not by text editing // find the text selection by the edit version of the input TextPos startPos, endPos; { SPIN_LOCK(editTraceLock) { vint traceIndex = UnsafeGetEditTraceIndex(newContext.input.editVersion); if (traceIndex == -1) return; TextEditNotifyStruct& trace = editTrace[traceIndex]; startPos = trace.inputStart; endPos = trace.inputEnd; } const RegexLexer& lexer = grammarParser->GetTable()->GetLexer(); RegexTokens tokens = lexer.Parse(newContext.input.code); startPos = ChooseCorrectTextPos(startPos, tokens); } // locate the deepest node using the text selection ParsingTextPos start(startPos.row, startPos.column); ParsingTextPos end(endPos.row, endPos.column); ParsingTextRange range(start, end); ParsingTreeNode* found = newContext.input.node->FindDeepestNode(range); ParsingTreeObject* selectedNode = 0; // if the location failed, choose the root node if (!found || startPos == TextPos(0, 0)) { found = newContext.input.node.Obj(); } if (!selectedNode) { // from the deepest node, traverse towards the root node // find the deepest node whose created rule is a left recursive rule and whose parent is not ParsingTreeObject* lrec = 0; ParsingTreeNode* current = found; while (current) { ParsingTreeObject* obj = dynamic_cast(current); if (obj) { for (auto rule : obj->GetCreatorRules()) { if (leftRecursiveRules.Contains(rule)) { lrec = obj; break; } } if (obj && lrec && lrec != obj) { selectedNode = lrec; break; } } current = current->GetParent(); } } if (!selectedNode) { // if there is no left recursive rule that creates the deepest node and all indirect parents // choose the deepest ParsingTreeObject ParsingTreeNode* current = found; while (current) { ParsingTreeObject* obj = dynamic_cast(current); if (obj) { selectedNode = obj; break; } current = current->GetParent(); } } if (selectedNode) { // get the code range of the selected node start = selectedNode->GetCodeRange().start; end = selectedNode->GetCodeRange().end; // get all properties from the selected node newContext.rule = selectedNode->GetCreatorRules()[selectedNode->GetCreatorRules().Count() - 1]; newContext.originalRange = selectedNode->GetCodeRange(); newContext.originalNode = Ptr(selectedNode); newContext.modifiedNode = newContext.originalNode; newContext.modifiedEditVersion = newContext.input.editVersion; // get the corresponding code of the selected node if (start.index >= 0 && end.index >= 0) { newContext.modifiedCode = newContext.input.code.Sub(start.index, end.index - start.index + 1).Buffer(); } } } bool GuiGrammarAutoComplete::NormalizeTextPos(AutoCompleteContext& newContext, elements::text::TextLines& lines, TextPos& pos) { // get the start position TextPos start(newContext.originalRange.start.row, newContext.originalRange.start.column); // get the end position of the end of lines TextPos end = lines.GetCount() <= 1 ? TextPos(start.row, start.column + lines.GetLine(0).dataLength) : TextPos(start.row + lines.GetCount() - 1, lines.GetLine(lines.GetCount() - 1).dataLength) ; if (start <= pos && pos <= end) { // if the pos is inside the range // normalize the pos to a new coordinate that the beginning position of lines is (row=0, column=0) pos.row -= start.row; if (pos.row == 0) { pos.column -= start.column; } return true; } else { return false; } } void GuiGrammarAutoComplete::ExecuteEdit(AutoCompleteContext& newContext) { // process the input of a task that is submitted by text editing // this function make an approximiation to the context if the RepeatingParsingExecutor is not fast enough // copy all TextEditNotifyStruct that is caused by a text editing before (and including) the edit version of the input List usedTrace; { SPIN_LOCK(editTraceLock) { CopyFrom( usedTrace, From(editTrace) .Where([&newContext](const TextEditNotifyStruct& value) { return (value.originalText != L"" || value.inputText != L"") && value.editVersion > newContext.modifiedEditVersion; }) ); } } // apply all modification to get the new modifiedCode bool failed = false; if (usedTrace.Count() > 0) { if (usedTrace[0].editVersion != newContext.modifiedEditVersion + 1) { // failed if any TextEditNotifyStruct is missing failed = true; } else { // initialize a TextLines with the latest modifiedCode text::TextLines lines(nullptr); lines.SetText(newContext.modifiedCode); for (auto trace : usedTrace) { // apply a modification to lines TextPos start = trace.originalStart; TextPos end = trace.originalEnd; // only if the modification is meaningful if (NormalizeTextPos(newContext, lines, start) && NormalizeTextPos(newContext, lines, end)) { lines.Modify(start, end, trace.inputText); } else { // otherwise, failed failed = true; break; } } if (!failed) { newContext.modifiedCode = lines.GetText(); } } } if (failed) { // clear originalNode to notify that the current context goes wrong newContext.originalNode = 0; } if (usedTrace.Count() > 0) { // update the edit version newContext.modifiedEditVersion = usedTrace[usedTrace.Count() - 1].editVersion; } } void GuiGrammarAutoComplete::DeleteFutures(collections::List& futures) { // delete all futures and clear the list for (auto future : futures) { delete future; } futures.Clear(); } regex::RegexToken* GuiGrammarAutoComplete::TraverseTransitions( parsing::tabling::ParsingState& state, parsing::tabling::ParsingTransitionCollector& transitionCollector, TextPos stopPosition, collections::List& nonRecoveryFutures, collections::List& recoveryFutures ) { const List& transitions = transitionCollector.GetTransitions(); for (vint index = 0; index < transitions.Count(); index++) { const ParsingState::TransitionResult& transition = transitions[index]; switch (transition.transitionType) { case ParsingState::TransitionResult::AmbiguityBegin: break; case ParsingState::TransitionResult::AmbiguityBranch: // ambiguity branches are not nested // tokens in different braches are the same // so we only need to run one branch, and skip the others index = transitionCollector.GetAmbiguityEndFromBegin(transitionCollector.GetAmbiguityBeginFromBranch(index)); break; case ParsingState::TransitionResult::AmbiguityEnd: break; case ParsingState::TransitionResult::ExecuteInstructions: { // test does the token reach the stop position if (transition.token) { // we treat "A|B" as editing A if token A is endless, otherwise treated as editing B TextPos tokenEnd(transition.token->rowEnd, transition.token->columnEnd + 1); // if the caret is not at the end of the token if (tokenEnd > stopPosition) { // stop the traversing and return the editing token return transition.token; } else if (tokenEnd == stopPosition) { // if the caret is at the end of the token, and it is a closed token // e.g. identifier is not a closed token, string is a closed token if (!grammarParser->GetTable()->GetLexer().Walk().IsClosedToken(transition.token->reading, transition.token->length)) { // stop the traversing and return the editing token return transition.token; } } } // traverse the PDA using the token specified in the current transition vint tableTokenIndex = transition.tableTokenIndex; List possibilities; if (recoveryFutures.Count() > 0) { for (auto future : recoveryFutures) { state.Explore(tableTokenIndex, future, possibilities); } } else { for (auto future : nonRecoveryFutures) { state.Explore(tableTokenIndex, future, possibilities); } } // delete duplicated futures List selectedPossibilities; for (vint i = 0; i < possibilities.Count(); i++) { ParsingState::Future* candidateFuture = possibilities[i]; bool duplicated = false; for (auto future : selectedPossibilities) { if ( candidateFuture->currentState == future->currentState && candidateFuture->reduceStateCount == future->reduceStateCount && candidateFuture->shiftStates.Count() == future->shiftStates.Count() ) { bool same = true; for (vint j = 0; j < future->shiftStates.Count(); j++) { if (candidateFuture->shiftStates[i] != future->shiftStates[i]) { same = false; break; } } if ((duplicated = same)) { break; } } } if (duplicated) { delete candidateFuture; } else { selectedPossibilities.Add(candidateFuture); } } // step forward if (transition.token || transition.tableTokenIndex == ParsingTable::TokenBegin) { DeleteFutures(nonRecoveryFutures); DeleteFutures(recoveryFutures); CopyFrom(nonRecoveryFutures, selectedPossibilities); } else { DeleteFutures(recoveryFutures); CopyFrom(recoveryFutures, selectedPossibilities); } } break; default:; } } return 0; } regex::RegexToken* GuiGrammarAutoComplete::SearchValidInputToken( parsing::tabling::ParsingState& state, parsing::tabling::ParsingTransitionCollector& transitionCollector, TextPos stopPosition, AutoCompleteContext& newContext, collections::SortedList& tableTokenIndices ) { // initialize the PDA state state.Reset(newContext.rule); List nonRecoveryFutures, recoveryFutures; nonRecoveryFutures.Add(state.ExploreCreateRootFuture()); // traverse the PDA until it reach the stop position // nonRecoveryFutures store the state when the last token (existing) is reached // recoveryFutures store the state when the last token (inserted by error recovery) is reached RegexToken* token = TraverseTransitions(state, transitionCollector, stopPosition, nonRecoveryFutures, recoveryFutures); // explore all possibilities from the last token before the stop position List possibilities; for (vint i = 0; i < nonRecoveryFutures.Count(); i++) { state.Explore(ParsingTable::NormalReduce, nonRecoveryFutures[i], nonRecoveryFutures); state.Explore(ParsingTable::LeftRecursiveReduce, nonRecoveryFutures[i], nonRecoveryFutures); } for (auto future : nonRecoveryFutures) { vint count = state.GetTable()->GetTokenCount(); for (vint i = ParsingTable::UserTokenStart; i < count; i++) { state.Explore(i, future, possibilities); } } // get all possible tokens that marked using @AutoCompleteCandidate for (auto future : possibilities) { if (!tableTokenIndices.Contains(future->selectedToken)) { tableTokenIndices.Add(future->selectedToken); } } // release all data DeleteFutures(possibilities); DeleteFutures(nonRecoveryFutures); DeleteFutures(recoveryFutures); // return the editing token return token; } TextPos GuiGrammarAutoComplete::GlobalTextPosToModifiedTextPos(AutoCompleteContext& newContext, TextPos pos) { pos.row-=newContext.originalRange.start.row; if(pos.row==0) { pos.column-=newContext.originalRange.start.column; } return pos; } TextPos GuiGrammarAutoComplete::ModifiedTextPosToGlobalTextPos(AutoCompleteContext& newContext, TextPos pos) { if(pos.row==0) { pos.column+=newContext.originalRange.start.column; } pos.row+=newContext.originalRange.start.row; return pos; } void GuiGrammarAutoComplete::ExecuteCalculateList(AutoCompleteContext& newContext) { // calcuate the content of the auto complete list // it is sad that, because the parser's algorithm is too complex // we need to reparse and track the internal state of the PDA(push-down automaton) here. // initialize the PDA ParsingState state(newContext.modifiedCode, grammarParser->GetTable()); state.Reset(newContext.rule); // prepare to get all transitions ParsingTransitionCollector collector; List> errors; // reparse and get all transitions during parsing if (grammarParser->Parse(state, collector, errors)) { // if modifiedNode is not prepared (the task is submitted because of text editing) // use the transition to build the syntax tree if (!newContext.modifiedNode) { ParsingTreeBuilder builder; builder.Reset(); bool succeeded = true; for (auto transition : collector.GetTransitions()) { if (!(succeeded = builder.Run(transition))) { break; } } if (succeeded) { Ptr parsedNode = builder.GetNode(); newContext.modifiedNode = parsedNode.Cast(); newContext.modifiedNode->InitializeQueryCache(); } } if (newContext.modifiedNode) { // get the latest text editing trace TextEditNotifyStruct trace; SPIN_LOCK(editTraceLock) { vint index = UnsafeGetEditTraceIndex(newContext.modifiedEditVersion); if (index == -1) { return; } else { trace = editTrace[index]; } } // calculate the stop position for PDA traversing TextPos stopPosition = GlobalTextPosToModifiedTextPos(newContext, trace.inputStart); // find all possible token before the current caret using the PDA auto autoComplete = Ptr(new AutoCompleteData); SortedList tableTokenIndices; RegexToken* editingToken = SearchValidInputToken(state, collector, stopPosition, newContext, tableTokenIndices); // collect all auto complete types { // collect all keywords that can be put into the auto complete list for (auto token : tableTokenIndices) { vint regexToken = token - ParsingTable::UserTokenStart; if (regexToken >= 0) { autoComplete->candidates.Add(regexToken); if (parsingExecutor->GetTokenMetaData(regexToken).isCandidate) { autoComplete->shownCandidates.Add(regexToken); } } } // calculate the arranged stopPosition if (editingToken) { TextPos tokenPos(editingToken->rowStart, editingToken->columnStart); if (tokenPos < stopPosition) { stopPosition = tokenPos; } } // calculate the start/end position for PDA traversing TextPos startPos, endPos; { startPos = ModifiedTextPosToGlobalTextPos(newContext, stopPosition); autoComplete->startPosition = startPos; endPos = trace.inputEnd; if (newContext.modifiedNode != newContext.originalNode) { startPos = GlobalTextPosToModifiedTextPos(newContext, startPos); endPos = GlobalTextPosToModifiedTextPos(newContext, endPos); } if (startPos0) { endPos.column--; } } // calculate the auto complete type if (editingToken && parsingExecutor->GetTokenMetaData(editingToken->token).hasAutoComplete) { ParsingTextRange range(ParsingTextPos(startPos.row, startPos.column), ParsingTextPos(endPos.row, endPos.column)); AutoCompleteData::RetriveContext(*autoComplete.Obj(), range, newContext.modifiedNode.Obj(), parsingExecutor.Obj()); } } newContext.autoComplete = autoComplete; } } } void GuiGrammarAutoComplete::Execute(const RepeatingParsingOutput& input) { SPIN_LOCK(contextLock) { if(input.editVersionInvokeInMainThread(ownerComposition->GetRelatedControlHost(), [=]() { PostList(newContext, byGlobalCorrection); }); } } void GuiGrammarAutoComplete::PostList(const AutoCompleteContext& newContext, bool byGlobalCorrection) { bool openList = true; // true: make the list visible bool keepListState = false; // true: don't change the list visibility Ptr autoComplete = newContext.autoComplete; // if failed to get the auto complete list, close if (!autoComplete) { openList = false; } if (openList) { if (autoComplete->shownCandidates.Count() + autoComplete->candidateItems.Count() == 0) { openList = false; } } TextPos startPosition, endPosition; WString editingText; if (openList) { SPIN_LOCK(editTraceLock) { // if the edit version is invalid, cancel vint traceIndex = UnsafeGetEditTraceIndex(newContext.modifiedEditVersion); if (traceIndex == -1) { return; } // an edit version has two trace at most, for text change and caret change, here we peak the text change if (traceIndex > 0 && editTrace[traceIndex - 1].editVersion == context.modifiedEditVersion) { traceIndex--; } // if the edit version is not created by keyboard input, close if (traceIndex >= 0) { TextEditNotifyStruct& trace = editTrace[traceIndex]; if (!trace.keyInput) { openList = false; } } // scan all traces from the calculation's edit version until now if (openList) { keepListState = true; startPosition = autoComplete->startPosition; endPosition = editTrace[editTrace.Count() - 1].inputEnd; for (vint i = traceIndex; i < editTrace.Count(); i++) { TextEditNotifyStruct& trace = editTrace[i]; // if there are no text change trace until now, don't change the list if (trace.originalText != L"" || trace.inputText != L"") { keepListState = false; } // if the edit position goes before the start position of the auto complete, refresh if (trace.inputEnd <= startPosition) { openList = false; break; } } } if (traceIndex > 0) { editTrace.RemoveRange(0, traceIndex); } } } // if there is a global correction send to the UI thread but the list is not opening, cancel if (byGlobalCorrection && !IsListOpening()) { return; } // if the input text from the start position to the current position crosses a token, close if (openList && element) { editingText = element->GetLines().GetText(startPosition, endPosition); if (grammarParser->GetTable()->GetLexer().Walk().IsClosedToken(editingText)) { openList = false; } } // calculate the content of the list if (autoComplete && ((!keepListState && openList) || IsListOpening())) { SortedList itemKeys; List itemValues; // copy all candidate keywords for (auto token : autoComplete->shownCandidates) { WString literal = parsingExecutor->GetTokenMetaData(token).unescapedRegexText; if (literal != L"" && !itemKeys.Contains(literal)) { ParsingCandidateItem item; item.name = literal; item.semanticId = -1; itemValues.Insert(itemKeys.Add(literal), item); } } // copy all candidate symbols if (autoComplete->acceptableSemanticIds) { for (auto item : autoComplete->candidateItems) { if (autoComplete->acceptableSemanticIds->Contains(item.semanticId)) { // add all acceptable display of a symbol // because a symbol can has multiple representation in different places if (item.name != L"" && !itemKeys.Contains(item.name)) { itemValues.Insert(itemKeys.Add(item.name), item); } } } } // fill the list List candidateItems; for (vint i = 0; i < itemValues.Count(); i++) { auto& item = itemValues[i]; if (item.tag.IsNull()) { if (auto analyzer = parsingExecutor->GetAnalyzer()) { item.tag = analyzer->CreateTagForCandidateItem(item); } } GuiTextBoxAutoCompleteBase::AutoCompleteItem candidateItem; candidateItem.text = item.name; candidateItem.tag = item.tag; candidateItems.Add(candidateItem); } SetListContent(candidateItems); } // set the list state if (!keepListState) { if (openList) { OpenList(startPosition); } else { CloseList(); } } if (IsListOpening()) { HighlightList(editingText); } } void GuiGrammarAutoComplete::Initialize() { grammarParser=CreateAutoRecoverParser(parsingExecutor->GetParser()->GetTable()); CollectLeftRecursiveRules(); parsingExecutor->AttachCallback(this); } void GuiGrammarAutoComplete::OnContextFinishedAsync(AutoCompleteContext& context) { if (auto analyzer = parsingExecutor->GetAnalyzer()) { if (context.autoComplete && context.autoComplete->acceptableSemanticIds) { analyzer->GetCandidateItemsAsync(*context.autoComplete.Obj(), context, context.autoComplete->candidateItems); } } } void GuiGrammarAutoComplete::EnsureAutoCompleteFinished() { parsingExecutor->EnsureTaskFinished(); SPIN_LOCK(contextLock) { context = AutoCompleteContext(); } } GuiGrammarAutoComplete::GuiGrammarAutoComplete(Ptr _parsingExecutor) :RepeatingParsingExecutor::CallbackBase(_parsingExecutor) ,editing(false) { Initialize(); } GuiGrammarAutoComplete::GuiGrammarAutoComplete(Ptr _grammarParser, const WString& _grammarRule) :RepeatingParsingExecutor::CallbackBase(Ptr(new RepeatingParsingExecutor(_grammarParser, _grammarRule))) ,editing(false) { Initialize(); } GuiGrammarAutoComplete::~GuiGrammarAutoComplete() { EnsureAutoCompleteFinished(); parsingExecutor->DetachCallback(this); } Ptr GuiGrammarAutoComplete::GetParsingExecutor() { return parsingExecutor; } } } } /*********************************************************************** .\CONTROLS\TEXTEDITORPACKAGE\LANGUAGESERVICE\GUILANGUAGECOLORIZER.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { using namespace elements; using namespace parsing; using namespace parsing::tabling; using namespace collections; using namespace theme; /*********************************************************************** GuiGrammarColorizer ***********************************************************************/ void GuiGrammarColorizer::OnParsingFinishedAsync(const RepeatingParsingOutput& output) { SPIN_LOCK(contextLock) { context=output; OnContextFinishedAsync(context); } RestartColorizer(); } void GuiGrammarColorizer::OnContextFinishedAsync(const RepeatingParsingOutput& context) { } void GuiGrammarColorizer::Attach(elements::GuiColorizedTextElement* _element, SpinLock& _elementModifyLock, compositions::GuiGraphicsComposition* _ownerComposition, vuint editVersion) { GuiTextBoxRegexColorizer::Attach(_element, _elementModifyLock, _ownerComposition, editVersion); RepeatingParsingExecutor::CallbackBase::Attach(_element, _elementModifyLock, _ownerComposition, editVersion); } void GuiGrammarColorizer::Detach() { GuiTextBoxRegexColorizer::Detach(); RepeatingParsingExecutor::CallbackBase::Detach(); if(element && elementModifyLock) { parsingExecutor->EnsureTaskFinished(); StopColorizer(false); } } void GuiGrammarColorizer::TextEditPreview(TextEditPreviewStruct& arguments) { GuiTextBoxRegexColorizer::TextEditPreview(arguments); RepeatingParsingExecutor::CallbackBase::TextEditPreview(arguments); } void GuiGrammarColorizer::TextEditNotify(const TextEditNotifyStruct& arguments) { GuiTextBoxRegexColorizer::TextEditNotify(arguments); RepeatingParsingExecutor::CallbackBase::TextEditNotify(arguments); } void GuiGrammarColorizer::TextCaretChanged(const TextCaretChangedStruct& arguments) { GuiTextBoxRegexColorizer::TextCaretChanged(arguments); RepeatingParsingExecutor::CallbackBase::TextCaretChanged(arguments); } void GuiGrammarColorizer::TextEditFinished(vuint editVersion) { GuiTextBoxRegexColorizer::TextEditFinished(editVersion); RepeatingParsingExecutor::CallbackBase::TextEditFinished(editVersion); } void GuiGrammarColorizer::OnSemanticColorize(SemanticColorizeContext& context, const RepeatingParsingOutput& input) { if (auto analyzer = parsingExecutor->GetAnalyzer()) { auto semanticId = analyzer->GetSemanticIdForTokenAsync(context, input); if(semanticId!=-1) { context.semanticId=semanticId; } } } void GuiGrammarColorizer::EnsureColorizerFinished() { parsingExecutor->EnsureTaskFinished(); StopColorizerForever(); SPIN_LOCK(contextLock) { context=RepeatingParsingOutput(); } } GuiGrammarColorizer::GuiGrammarColorizer(Ptr _parsingExecutor) :RepeatingParsingExecutor::CallbackBase(_parsingExecutor) { parsingExecutor->AttachCallback(this); BeginSetColors(); } GuiGrammarColorizer::GuiGrammarColorizer(Ptr _grammarParser, const WString& _grammarRule) :RepeatingParsingExecutor::CallbackBase(Ptr(new RepeatingParsingExecutor(_grammarParser, _grammarRule))) { parsingExecutor->AttachCallback(this); BeginSetColors(); } GuiGrammarColorizer::~GuiGrammarColorizer() { EnsureColorizerFinished(); parsingExecutor->DetachCallback(this); } void GuiGrammarColorizer::BeginSetColors() { ClearTokens(); colorSettings.Clear(); text::ColorEntry entry; { entry.normal.text = Color(0, 0, 0); entry.normal.background = Color(0, 0, 0, 0); entry.selectedFocused.text = Color(255, 255, 255); entry.selectedFocused.background = Color(51, 153, 255); entry.selectedUnfocused.text = Color(255, 255, 255); entry.selectedUnfocused.background = Color(51, 153, 255); } SetDefaultColor(entry); colorSettings.Add(L"Default", entry); } const collections::SortedList& GuiGrammarColorizer::GetColorNames() { return colorSettings.Keys(); } GuiGrammarColorizer::ColorEntry GuiGrammarColorizer::GetColor(const WString& name) { vint index=colorSettings.Keys().IndexOf(name); return index==-1?GetDefaultColor():colorSettings.Values().Get(index); } void GuiGrammarColorizer::SetColor(const WString& name, const ColorEntry& entry) { colorSettings.Set(name, entry); } void GuiGrammarColorizer::SetColor(const WString& name, const Color& color) { text::ColorEntry entry=GetDefaultColor(); entry.normal.text=color; SetColor(name, entry); } void GuiGrammarColorizer::EndSetColors() { SortedList tokenColors; Ptr table=parsingExecutor->GetParser()->GetTable(); semanticColorMap.Clear(); vint tokenCount=table->GetTokenCount(); for(vint token=ParsingTable::UserTokenStart;tokenGetTokenInfo(token); const RepeatingParsingExecutor::TokenMetaData& md=parsingExecutor->GetTokenMetaData(token-ParsingTable::UserTokenStart); if(md.defaultColorIndex==-1) { AddToken(tokenInfo.regex, GetDefaultColor()); } else { WString name=parsingExecutor->GetSemanticName(md.defaultColorIndex); vint color=AddToken(tokenInfo.regex, GetColor(name)); semanticColorMap.Set(md.defaultColorIndex, color); tokenColors.Add(name); } } for (auto [color, index] : indexed(colorSettings.Keys())) { if(!tokenColors.Contains(color)) { vint semanticId=parsingExecutor->GetSemanticId(color); if(semanticId!=-1) { vint tokenId=AddExtraToken(colorSettings.Values().Get(index)); vint color=tokenId+tokenCount-ParsingTable::UserTokenStart; semanticColorMap.Set(semanticId, color); } } } Setup(); } void GuiGrammarColorizer::ColorizeTokenContextSensitive(vint lineIndex, const wchar_t* text, vint start, vint length, vint& token, vint& contextState) { SPIN_LOCK(contextLock) { ParsingTreeObject* node=context.node.Obj(); if(node && token!=-1 && parsingExecutor->GetTokenMetaData(token).hasContextColor) { ParsingTextPos pos(lineIndex, start); SemanticColorizeContext scContext; if(SemanticColorizeContext::RetriveContext(scContext, pos, node, parsingExecutor.Obj())) { const RepeatingParsingExecutor::FieldMetaData& md=parsingExecutor->GetFieldMetaData(scContext.type, scContext.field); vint semantic=md.colorIndex; scContext.semanticId=-1; if(scContext.acceptableSemanticIds) { OnSemanticColorize(scContext, context); if(md.semantics->Contains(scContext.semanticId)) { semantic=scContext.semanticId; } } if(semantic!=-1) { vint index=semanticColorMap.Keys().IndexOf(semantic); if(index!=-1) { token=semanticColorMap.Values()[index]; } } } } } } Ptr GuiGrammarColorizer::GetParsingExecutor() { return parsingExecutor; } } } } /*********************************************************************** .\CONTROLS\TEXTEDITORPACKAGE\LANGUAGESERVICE\GUILANGUAGEOPERATIONS.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { using namespace collections; using namespace parsing; using namespace parsing::tabling; using namespace regex_internal; /*********************************************************************** ParsingContext ***********************************************************************/ bool ParsingTokenContext::RetriveContext(ParsingTokenContext& output, parsing::ParsingTreeNode* foundNode, RepeatingParsingExecutor* executor) { ParsingTreeToken* foundToken=dynamic_cast(foundNode); if(!foundToken) return false; ParsingTreeObject* tokenParent=dynamic_cast(foundNode->GetParent()); if(!tokenParent) return false; vint index=tokenParent->GetMembers().Values().IndexOf(foundNode); if(index==-1) return false; WString type=tokenParent->GetType(); WString field=tokenParent->GetMembers().Keys().Get(index); const RepeatingParsingExecutor::FieldMetaData& md=executor->GetFieldMetaData(type, field); output.foundToken=foundToken; output.tokenParent=tokenParent; output.type=type; output.field=field; output.acceptableSemanticIds=md.semantics; return true; } bool ParsingTokenContext::RetriveContext(ParsingTokenContext& output, parsing::ParsingTextPos pos, parsing::ParsingTreeObject* rootNode, RepeatingParsingExecutor* executor) { ParsingTreeNode* foundNode=rootNode->FindDeepestNode(pos); if(!foundNode) return false; return RetriveContext(output, foundNode, executor); } bool ParsingTokenContext::RetriveContext(ParsingTokenContext& output, parsing::ParsingTextRange range, ParsingTreeObject* rootNode, RepeatingParsingExecutor* executor) { ParsingTreeNode* foundNode=rootNode->FindDeepestNode(range); if(!foundNode) return false; return RetriveContext(output, foundNode, executor); } /*********************************************************************** RepeatingParsingExecutor::IParsingAnalyzer ***********************************************************************/ parsing::ParsingTreeNode* RepeatingParsingExecutor::IParsingAnalyzer::ToParent(parsing::ParsingTreeNode* node, const RepeatingPartialParsingOutput* output) { if (!output || !output->modifiedNode) return node; return node == output->modifiedNode.Obj() ? output->originalNode.Obj() : node; } parsing::ParsingTreeObject* RepeatingParsingExecutor::IParsingAnalyzer::ToChild(parsing::ParsingTreeObject* node, const RepeatingPartialParsingOutput* output) { if (!output || !output->modifiedNode) return node; return node == output->originalNode.Obj() ? output->modifiedNode.Obj() : node; } Ptr RepeatingParsingExecutor::IParsingAnalyzer::ToChild(Ptr node, const RepeatingPartialParsingOutput* output) { if (!output) return node; return node == output->originalNode ? output->modifiedNode.Cast() : node; } parsing::ParsingTreeNode* RepeatingParsingExecutor::IParsingAnalyzer::GetParent(parsing::ParsingTreeNode* node, const RepeatingPartialParsingOutput* output) { return ToParent(node, output)->GetParent(); } Ptr RepeatingParsingExecutor::IParsingAnalyzer::GetMember(parsing::ParsingTreeObject* node, const WString& name, const RepeatingPartialParsingOutput* output) { return ToChild(ToChild(node, output)->GetMember(name), output); } Ptr RepeatingParsingExecutor::IParsingAnalyzer::GetItem(parsing::ParsingTreeArray* node, vint index, const RepeatingPartialParsingOutput* output) { return ToChild(node->GetItem(index), output); } /*********************************************************************** RepeatingParsingExecutor::CallbackBase ***********************************************************************/ RepeatingParsingExecutor::CallbackBase::CallbackBase(Ptr _parsingExecutor) :parsingExecutor(_parsingExecutor) ,callbackAutoPushing(false) ,callbackElement(0) ,callbackElementModifyLock(0) { } RepeatingParsingExecutor::CallbackBase::~CallbackBase() { } void RepeatingParsingExecutor::CallbackBase::RequireAutoSubmitTask(bool enabled) { callbackAutoPushing=enabled; } void RepeatingParsingExecutor::CallbackBase::Attach(elements::GuiColorizedTextElement* _element, SpinLock& _elementModifyLock, compositions::GuiGraphicsComposition* _ownerComposition, vuint editVersion) { if(_element) { SPIN_LOCK(_elementModifyLock) { callbackElement=_element; callbackElementModifyLock=&_elementModifyLock; } } parsingExecutor->ActivateCallback(this); if(callbackElement && callbackElementModifyLock && callbackAutoPushing) { SPIN_LOCK(*callbackElementModifyLock) { RepeatingParsingInput input; input.editVersion=editVersion; input.code=callbackElement->GetLines().GetText(); parsingExecutor->SubmitTask(input); } } } void RepeatingParsingExecutor::CallbackBase::Detach() { if(callbackElement && callbackElementModifyLock) { SPIN_LOCK(*callbackElementModifyLock) { callbackElement=0; callbackElementModifyLock=0; } } parsingExecutor->DeactivateCallback(this); } void RepeatingParsingExecutor::CallbackBase::TextEditPreview(TextEditPreviewStruct& arguments) { } void RepeatingParsingExecutor::CallbackBase::TextEditNotify(const TextEditNotifyStruct& arguments) { } void RepeatingParsingExecutor::CallbackBase::TextCaretChanged(const TextCaretChangedStruct& arguments) { } void RepeatingParsingExecutor::CallbackBase::TextEditFinished(vuint editVersion) { if(callbackElement && callbackElementModifyLock && callbackAutoPushing) { SPIN_LOCK(*callbackElementModifyLock) { RepeatingParsingInput input; input.editVersion=editVersion; input.code=callbackElement->GetLines().GetText(); parsingExecutor->SubmitTask(input); } } } /*********************************************************************** RepeatingParsingExecutor ***********************************************************************/ void RepeatingParsingExecutor::Execute(const RepeatingParsingInput& input) { List> errors; Ptr node=grammarParser->Parse(input.code, grammarRule, errors).Cast(); if(node) { node->InitializeQueryCache(); } RepeatingParsingOutput result; result.node=node; result.editVersion=input.editVersion; result.code=input.code; if(node) { OnContextFinishedAsync(result); for (auto callback : callbacks) { callback->OnParsingFinishedAsync(result); } } } void RepeatingParsingExecutor::PrepareMetaData() { Ptr table=grammarParser->GetTable(); tokenIndexMap.Clear(); semanticIndexMap.Clear(); tokenMetaDatas.Clear(); fieldMetaDatas.Clear(); Dictionary> tokenColorAtts, tokenContextColorAtts, tokenCandidateAtts, tokenAutoCompleteAtts; Dictionary> fieldColorAtts, fieldSemanticAtts; { vint tokenCount=table->GetTokenCount(); for(vint token=ParsingTable::UserTokenStart;tokenGetTokenInfo(token); vint tokenIndex=token-ParsingTable::UserTokenStart; tokenIndexMap.Add(tokenInfo.name, tokenIndex); if(Ptr att=GetColorAttribute(tokenInfo.attributeIndex)) { tokenColorAtts.Add(tokenIndex, att); } if(Ptr att=GetContextColorAttribute(tokenInfo.attributeIndex)) { tokenContextColorAtts.Add(tokenIndex, att); } if(Ptr att=GetCandidateAttribute(tokenInfo.attributeIndex)) { tokenCandidateAtts.Add(tokenIndex, att); } if(Ptr att=GetAutoCompleteAttribute(tokenInfo.attributeIndex)) { tokenAutoCompleteAtts.Add(tokenIndex, att); } } } { vint fieldCount=table->GetTreeFieldInfoCount(); for(vint field=0;fieldGetTreeFieldInfo(field); FieldDesc fieldDesc(fieldInfo.type, fieldInfo.field); if(Ptr att=GetColorAttribute(fieldInfo.attributeIndex)) { fieldColorAtts.Add(fieldDesc, att); } if(Ptr att=GetSemanticAttribute(fieldInfo.attributeIndex)) { fieldSemanticAtts.Add(fieldDesc, att); } } } for (auto att : From(tokenColorAtts.Values()) .Concat(tokenContextColorAtts.Values()) .Concat(fieldColorAtts.Values()) .Concat(fieldSemanticAtts.Values()) ) { for (auto argument : att->arguments) { if(!semanticIndexMap.Contains(argument)) { semanticIndexMap.Add(argument); } } } vint index=0; for (auto tokenIndex : tokenIndexMap.Values()) { TokenMetaData md; md.tableTokenIndex=tokenIndex+ParsingTable::UserTokenStart; md.lexerTokenIndex=tokenIndex; md.defaultColorIndex=-1; md.hasContextColor=false; md.hasAutoComplete=false; md.isCandidate=false; if((index=tokenColorAtts.Keys().IndexOf(tokenIndex))!=-1) { md.defaultColorIndex=semanticIndexMap.IndexOf(tokenColorAtts.Values()[index]->arguments[0]); } md.hasContextColor=tokenContextColorAtts.Keys().Contains(tokenIndex); md.hasAutoComplete=tokenAutoCompleteAtts.Keys().Contains(tokenIndex); if ((md.isCandidate = tokenCandidateAtts.Keys().Contains(tokenIndex))) { const ParsingTable::TokenInfo& tokenInfo = table->GetTokenInfo(md.tableTokenIndex); auto regex = wtou32(tokenInfo.regex); if (IsRegexEscapedLiteralString(regex)) { md.unescapedRegexText = u32tow(UnescapeTextForRegex(regex)); } else { md.isCandidate = false; } } tokenMetaDatas.Add(tokenIndex, md); } { vint fieldCount=table->GetTreeFieldInfoCount(); for(vint field=0;fieldGetTreeFieldInfo(field); FieldDesc fieldDesc(fieldInfo.type, fieldInfo.field); FieldMetaData md; md.colorIndex=-1; if((index=fieldColorAtts.Keys().IndexOf(fieldDesc))!=-1) { md.colorIndex=semanticIndexMap.IndexOf(fieldColorAtts.Values()[index]->arguments[0]); } if((index=fieldSemanticAtts.Keys().IndexOf(fieldDesc))!=-1) { md.semantics=Ptr(new List); for (auto argument : fieldSemanticAtts.Values()[index]->arguments) { md.semantics->Add(semanticIndexMap.IndexOf(argument)); } } fieldMetaDatas.Add(fieldDesc, md); } } } void RepeatingParsingExecutor::OnContextFinishedAsync(RepeatingParsingOutput& context) { if(analyzer) { context.cache = analyzer->CreateCacheAsync(context); } } RepeatingParsingExecutor::RepeatingParsingExecutor(Ptr _grammarParser, const WString& _grammarRule, Ptr _analyzer) :grammarParser(_grammarParser) ,grammarRule(_grammarRule) ,analyzer(_analyzer) ,autoPushingCallback(0) { PrepareMetaData(); if (analyzer) { analyzer->Attach(this); } } RepeatingParsingExecutor::~RepeatingParsingExecutor() { EnsureTaskFinished(); if (analyzer) { analyzer->Detach(this); } } Ptr RepeatingParsingExecutor::GetParser() { return grammarParser; } bool RepeatingParsingExecutor::AttachCallback(ICallback* value) { if(!value) return false; if(callbacks.Contains(value)) return false; callbacks.Add(value); return true; } bool RepeatingParsingExecutor::DetachCallback(ICallback* value) { if(!value) return false; if(!callbacks.Contains(value)) return false; DeactivateCallback(value); callbacks.Remove(value); return true; } bool RepeatingParsingExecutor::ActivateCallback(ICallback* value) { if(!value) return false; if(!callbacks.Contains(value)) return false; if(activatedCallbacks.Contains(value)) return false; activatedCallbacks.Add(value); if(!autoPushingCallback) { autoPushingCallback=value; autoPushingCallback->RequireAutoSubmitTask(true); } return true; } bool RepeatingParsingExecutor::DeactivateCallback(ICallback* value) { if(!value) return false; if(!callbacks.Contains(value)) return false; if(!activatedCallbacks.Contains(value)) return false; if(autoPushingCallback==value) { autoPushingCallback->RequireAutoSubmitTask(false); autoPushingCallback=0; } activatedCallbacks.Remove(value); if(!autoPushingCallback && activatedCallbacks.Count()>0) { autoPushingCallback=activatedCallbacks[0]; autoPushingCallback->RequireAutoSubmitTask(true); } return true; } Ptr RepeatingParsingExecutor::GetAnalyzer() { return analyzer; } vint RepeatingParsingExecutor::GetTokenIndex(const WString& tokenName) { vint index=tokenIndexMap.Keys().IndexOf(tokenName); return index==-1?-1:tokenIndexMap.Values()[index]; } vint RepeatingParsingExecutor::GetSemanticId(const WString& name) { return semanticIndexMap.IndexOf(name); } WString RepeatingParsingExecutor::GetSemanticName(vint id) { return 0<=id&&id RepeatingParsingExecutor::GetAttribute(vint index, const WString& name, vint argumentCount) { if(index!=-1) { Ptr att=grammarParser->GetTable()->GetAttributeInfo(index)->FindFirst(name); if(att && (argumentCount==-1 || att->arguments.Count()==argumentCount)) { return att; } } return 0; } Ptr RepeatingParsingExecutor::GetColorAttribute(vint index) { return GetAttribute(index, L"Color", 1); } Ptr RepeatingParsingExecutor::GetContextColorAttribute(vint index) { return GetAttribute(index, L"ContextColor", 0); } Ptr RepeatingParsingExecutor::GetSemanticAttribute(vint index) { return GetAttribute(index, L"Semantic", -1); } Ptr RepeatingParsingExecutor::GetCandidateAttribute(vint index) { return GetAttribute(index, L"Candidate", 0); } Ptr RepeatingParsingExecutor::GetAutoCompleteAttribute(vint index) { return GetAttribute(index, L"AutoComplete", 0); } } } } /*********************************************************************** .\CONTROLS\TOOLSTRIPPACKAGE\GUIMENUCONTROLS.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { using namespace compositions; /*********************************************************************** IGuiMenuService ***********************************************************************/ const wchar_t* const IGuiMenuService::Identifier = L"vl::presentation::controls::IGuiMenuService"; const wchar_t* const IGuiMenuDropdownProvider::Identifier = L"vl::presentation::controls::IGuiMenuDropdownProvider"; IGuiMenuService::IGuiMenuService() :openingMenu(0) { } void IGuiMenuService::MenuItemExecuted() { if(openingMenu) { openingMenu->Hide(); } if(GetParentMenuService()) { GetParentMenuService()->MenuItemExecuted(); } } GuiMenu* IGuiMenuService::GetOpeningMenu() { return openingMenu; } void IGuiMenuService::MenuOpened(GuiMenu* menu) { if(openingMenu!=menu && openingMenu) { openingMenu->Hide(); } openingMenu=menu; } void IGuiMenuService::MenuClosed(GuiMenu* menu) { if(openingMenu==menu) { openingMenu=0; } } /*********************************************************************** GuiMenu ***********************************************************************/ void GuiMenu::BeforeControlTemplateUninstalled_() { } void GuiMenu::AfterControlTemplateInstalled_(bool initialize) { } IGuiMenuService* GuiMenu::GetParentMenuService() { return parentMenuService; } IGuiMenuService::Direction GuiMenu::GetPreferredDirection() { return IGuiMenuService::Vertical; } theme::ThemeName GuiMenu::GetHostThemeName() { return GetControlThemeName(); } bool GuiMenu::IsActiveState() { return true; } bool GuiMenu::IsSubMenuActivatedByMouseDown() { return false; } void GuiMenu::MenuItemExecuted() { IGuiMenuService::MenuItemExecuted(); Hide(); } void GuiMenu::Moving(NativeRect& bounds, bool fixSizeOnly, bool draggingBorder) { GuiPopup::Moving(bounds, fixSizeOnly, draggingBorder); if (draggingBorder) { if (auto nativeWindow = GetNativeWindow()) { auto newSize = bounds.GetSize(); auto nativeOffset = (nativeWindow->GetBounds().GetSize() - nativeWindow->GetClientSize()); auto preferredNativeSize = nativeWindow->Convert(preferredMenuClientSizeBeforeUpdating) + nativeOffset; if (newSize.x < preferredNativeSize.x) newSize.x = preferredNativeSize.x; if (newSize.y < preferredNativeSize.y) newSize.y = preferredNativeSize.y; preferredMenuClientSize = nativeWindow->Convert(newSize - nativeOffset); } } } void GuiMenu::UpdateClientSizeAfterRendering(Size preferredSize, Size clientSize) { auto size = preferredSize; if (size.x < preferredMenuClientSize.x) size.x = preferredMenuClientSize.x; if (size.y < preferredMenuClientSize.y) size.y = preferredMenuClientSize.y; GuiPopup::UpdateClientSizeAfterRendering(preferredSize, size); } void GuiMenu::OnWindowOpened(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if(parentMenuService) { parentMenuService->MenuOpened(this); } } void GuiMenu::OnDeactivatedAltHost() { if(hideOnDeactivateAltHost) { Hide(); } GuiPopup::OnDeactivatedAltHost(); } void GuiMenu::OnWindowClosed(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if(parentMenuService) { parentMenuService->MenuClosed(this); GuiMenu* openingSubMenu=GetOpeningMenu(); if(openingSubMenu) { openingSubMenu->Hide(); } } } GuiMenu::GuiMenu(theme::ThemeName themeName, GuiControl* _owner) :GuiPopup(themeName, INativeWindow::Menu) , owner(_owner) { UpdateMenuService(); WindowOpened.AttachMethod(this, &GuiMenu::OnWindowOpened); WindowClosed.AttachMethod(this, &GuiMenu::OnWindowClosed); } GuiMenu::~GuiMenu() { } void GuiMenu::UpdateMenuService() { if(owner) { parentMenuService=owner->QueryTypedService(); } } IDescriptable* GuiMenu::QueryService(const WString& identifier) { if(identifier==IGuiMenuService::Identifier) { return (IGuiMenuService*)this; } else { return GuiPopup::QueryService(identifier); } } bool GuiMenu::GetHideOnDeactivateAltHost() { return hideOnDeactivateAltHost; } void GuiMenu::SetHideOnDeactivateAltHost(bool value) { hideOnDeactivateAltHost = value; } Size GuiMenu::GetPreferredMenuClientSize() { return preferredMenuClientSize; } void GuiMenu::SetPreferredMenuClientSize(Size value) { preferredMenuClientSize = value; preferredMenuClientSizeBeforeUpdating = value; } /*********************************************************************** GuiMenuBar ***********************************************************************/ IGuiMenuService* GuiMenuBar::GetParentMenuService() { return GetParent() ? GetParent()->QueryTypedService() : nullptr; } IGuiMenuService::Direction GuiMenuBar::GetPreferredDirection() { return IGuiMenuService::Horizontal; } theme::ThemeName GuiMenuBar::GetHostThemeName() { return GetControlThemeName(); } bool GuiMenuBar::IsActiveState() { return GetOpeningMenu()!=0; } bool GuiMenuBar::IsSubMenuActivatedByMouseDown() { return true; } GuiMenuBar::GuiMenuBar(theme::ThemeName themeName) :GuiControl(themeName) { } GuiMenuBar::~GuiMenuBar() { } IDescriptable* GuiMenuBar::QueryService(const WString& identifier) { if(identifier==IGuiMenuService::Identifier) { return (IGuiMenuService*)this; } else { return GuiControl::QueryService(identifier); } } /*********************************************************************** GuiMenuButton ***********************************************************************/ void GuiMenuButton::BeforeControlTemplateUninstalled_() { auto host = GetSubMenuHost(); host->Clicked.Detach(hostClickedHandler); host->GetBoundsComposition()->GetEventReceiver()->mouseEnter.Detach(hostMouseEnterHandler); hostClickedHandler = nullptr; hostMouseEnterHandler = nullptr; } void GuiMenuButton::AfterControlTemplateInstalled_(bool initialize) { auto ct = TypedControlTemplateObject(true); auto host = GetSubMenuHost(); ct->SetSubMenuOpening(GetSubMenuOpening()); ct->SetLargeImage(largeImage); ct->SetImage(image); ct->SetShortcutText(shortcutText); ct->SetSubMenuExisting(subMenu != nullptr); hostClickedHandler = host->Clicked.AttachMethod(this, &GuiMenuButton::OnClicked); hostMouseEnterHandler = host->GetBoundsComposition()->GetEventReceiver()->mouseEnter.AttachMethod(this, &GuiMenuButton::OnMouseEnter); } bool GuiMenuButton::OpenSubMenuInternal() { if (!GetSubMenuOpening()) { if (ownerMenuService) { GuiMenu* openingSiblingMenu = ownerMenuService->GetOpeningMenu(); if (openingSiblingMenu) { openingSiblingMenu->Hide(); } } BeforeSubMenuOpening.Execute(GetNotifyEventArguments()); if (subMenu) { subMenu->SetClientSize(preferredMenuClientSize); IGuiMenuService::Direction direction = GetSubMenuDirection(); subMenu->ShowPopup(GetSubMenuHost(), direction == IGuiMenuService::Horizontal); AfterSubMenuOpening.Execute(GetNotifyEventArguments()); return true; } } return false; } void GuiMenuButton::OnParentLineChanged() { GuiButton::OnParentLineChanged(); ownerMenuService=QueryTypedService(); if(ownerMenuService) { SetClickOnMouseUp(!ownerMenuService->IsSubMenuActivatedByMouseDown()); } if(subMenu) { subMenu->UpdateMenuService(); } } compositions::IGuiAltActionHost* GuiMenuButton::GetActivatingAltHost() { if (subMenu) { return subMenu->QueryTypedService(); } else { return GuiSelectableButton::GetActivatingAltHost(); } } void GuiMenuButton::OnSubMenuWindowOpened(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { SubMenuOpeningChanged.Execute(GetNotifyEventArguments()); TypedControlTemplateObject(true)->SetSubMenuOpening(true); } void GuiMenuButton::OnSubMenuWindowClosed(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { SubMenuOpeningChanged.Execute(GetNotifyEventArguments()); TypedControlTemplateObject(true)->SetSubMenuOpening(false); } void GuiMenuButton::OnMouseEnter(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if(GetVisuallyEnabled()) { if(cascadeAction && ownerMenuService && ownerMenuService->IsActiveState()) { OpenSubMenuInternal(); } } } void GuiMenuButton::OnClicked(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if(GetVisuallyEnabled()) { if(!OpenSubMenuInternal() && ownerMenuService) { ownerMenuService->MenuItemExecuted(); } } } IGuiMenuService::Direction GuiMenuButton::GetSubMenuDirection() { return ownerMenuService?ownerMenuService->GetPreferredDirection():IGuiMenuService::Horizontal; } void GuiMenuButton::DetachSubMenu() { if (subMenu) { subMenu->WindowOpened.Detach(subMenuWindowOpenedHandler); subMenu->WindowClosed.Detach(subMenuWindowClosedHandler); subMenuWindowOpenedHandler = nullptr; subMenuWindowClosedHandler = nullptr; if (ownedSubMenu) { delete subMenu; } } } GuiMenu* GuiMenuButton::ProvideDropdownMenu() { return GetSubMenu(); } GuiMenuButton::GuiMenuButton(theme::ThemeName themeName) :GuiSelectableButton(themeName) ,subMenu(0) ,ownedSubMenu(false) ,ownerMenuService(0) ,cascadeAction(true) { SetAutoSelection(false); BeforeSubMenuOpening.SetAssociatedComposition(boundsComposition); SubMenuOpeningChanged.SetAssociatedComposition(boundsComposition); LargeImageChanged.SetAssociatedComposition(boundsComposition); ImageChanged.SetAssociatedComposition(boundsComposition); ShortcutTextChanged.SetAssociatedComposition(boundsComposition); } GuiMenuButton::~GuiMenuButton() { if (!subMenuDisposeFlag || !subMenuDisposeFlag->IsDisposed()) { DetachSubMenu(); } } GuiButton* GuiMenuButton::GetSubMenuHost() { GuiButton* button = TypedControlTemplateObject(true)->GetSubMenuHost(); return button ? button : this; } Ptr GuiMenuButton::GetLargeImage() { return largeImage; } void GuiMenuButton::SetLargeImage(Ptr value) { if (largeImage != value) { largeImage = value; TypedControlTemplateObject(true)->SetLargeImage(largeImage); LargeImageChanged.Execute(GetNotifyEventArguments()); } } Ptr GuiMenuButton::GetImage() { return image; } void GuiMenuButton::SetImage(Ptr value) { if (image != value) { image = value; TypedControlTemplateObject(true)->SetImage(image); ImageChanged.Execute(GetNotifyEventArguments()); } } const WString& GuiMenuButton::GetShortcutText() { return shortcutText; } void GuiMenuButton::SetShortcutText(const WString& value) { if (shortcutText != value) { shortcutText = value; TypedControlTemplateObject(true)->SetShortcutText(shortcutText); ShortcutTextChanged.Execute(GetNotifyEventArguments()); } } bool GuiMenuButton::IsSubMenuExists() { return subMenu!=0; } GuiMenu* GuiMenuButton::GetSubMenu() { return subMenu; } GuiMenu* GuiMenuButton::CreateSubMenu(TemplateProperty subMenuTemplate) { if (!subMenu) { GuiMenu* newSubMenu = new GuiMenu(theme::ThemeName::Menu, this); newSubMenu->SetControlTemplate(subMenuTemplate ? subMenuTemplate : TypedControlTemplateObject(true)->GetSubMenuTemplate()); SetSubMenu(newSubMenu, true); } return subMenu; } void GuiMenuButton::SetSubMenu(GuiMenu* value, bool owned) { if (subMenu) { DetachSubMenu(); subMenuDisposeFlag = nullptr; } subMenu = value; ownedSubMenu = owned; if (subMenu) { subMenu->SetPreferredMenuClientSize(preferredMenuClientSize); subMenuDisposeFlag = subMenu->GetDisposedFlag(); subMenuWindowOpenedHandler = subMenu->WindowOpened.AttachMethod(this, &GuiMenuButton::OnSubMenuWindowOpened); subMenuWindowClosedHandler = subMenu->WindowClosed.AttachMethod(this, &GuiMenuButton::OnSubMenuWindowClosed); } TypedControlTemplateObject(true)->SetSubMenuExisting(subMenu != nullptr); } void GuiMenuButton::DestroySubMenu() { if(subMenu) { DetachSubMenu(); subMenu=0; ownedSubMenu=false; TypedControlTemplateObject(true)->SetSubMenuExisting(false); } } bool GuiMenuButton::GetOwnedSubMenu() { return subMenu && ownedSubMenu; } bool GuiMenuButton::GetSubMenuOpening() { if(subMenu) { return subMenu->GetOpening(); } else { return false; } } void GuiMenuButton::SetSubMenuOpening(bool value) { if (subMenu && subMenu->GetOpening() != value) { if (value) { OpenSubMenuInternal(); } else { subMenu->Close(); } } } Size GuiMenuButton::GetPreferredMenuClientSize() { return preferredMenuClientSize; } void GuiMenuButton::SetPreferredMenuClientSize(Size value) { preferredMenuClientSize = value; if (subMenu) { subMenu->SetPreferredMenuClientSize(preferredMenuClientSize); } } bool GuiMenuButton::GetCascadeAction() { return cascadeAction; } void GuiMenuButton::SetCascadeAction(bool value) { cascadeAction=value; } IDescriptable* GuiMenuButton::QueryService(const WString& identifier) { if (identifier == IGuiMenuDropdownProvider::Identifier) { return (IGuiMenuDropdownProvider*)this; } else { return GuiSelectableButton::QueryService(identifier); } } } } } /*********************************************************************** .\CONTROLS\TOOLSTRIPPACKAGE\GUIRIBBONCONTROLS.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { using namespace reflection::description; using namespace collections; using namespace compositions; using namespace theme; using namespace templates; /*********************************************************************** GuiRibbonTab ***********************************************************************/ void GuiRibbonTab::BeforeControlTemplateUninstalled_() { auto ct = TypedControlTemplateObject(false); if (!ct) return; if (auto bhc = ct->GetBeforeHeadersContainer()) { bhc->RemoveChild(beforeHeaders); } if (auto ahc = ct->GetAfterHeadersContainer()) { ahc->RemoveChild(afterHeaders); } } void GuiRibbonTab::AfterControlTemplateInstalled_(bool initialize) { auto ct = TypedControlTemplateObject(true); if (auto bhc = ct->GetBeforeHeadersContainer()) { bhc->AddChild(beforeHeaders); } if (auto ahc = ct->GetAfterHeadersContainer()) { ahc->AddChild(afterHeaders); } } GuiRibbonTab::GuiRibbonTab(theme::ThemeName themeName) :GuiTab(themeName) { beforeHeaders = new GuiBoundsComposition(); beforeHeaders->SetAlignmentToParent(Margin(0, 0, 0, 0)); beforeHeaders->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); afterHeaders = new GuiBoundsComposition(); afterHeaders->SetAlignmentToParent(Margin(0, 0, 0, 0)); afterHeaders->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); } GuiRibbonTab::~GuiRibbonTab() { if (!beforeHeaders->GetParent()) { SafeDeleteComposition(beforeHeaders); } if (!afterHeaders->GetParent()) { SafeDeleteComposition(afterHeaders); } } compositions::GuiGraphicsComposition* GuiRibbonTab::GetBeforeHeaders() { return beforeHeaders; } compositions::GuiGraphicsComposition* GuiRibbonTab::GetAfterHeaders() { return afterHeaders; } /*********************************************************************** GuiRibbonGroupCollection ***********************************************************************/ bool GuiRibbonGroupCollection::QueryInsert(vint index, GuiRibbonGroup* const& value) { return !value->GetBoundsComposition()->GetParent(); } void GuiRibbonGroupCollection::AfterInsert(vint index, GuiRibbonGroup* const& value) { value->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); auto item = new GuiStackItemComposition(); item->AddChild(value->GetBoundsComposition()); tabPage->stack->InsertStackItem(index, item); } void GuiRibbonGroupCollection::AfterRemove(vint index, vint count) { for (vint i = 0; i < count; i++) { auto item = tabPage->stack->GetStackItems()[index]; tabPage->stack->RemoveChild(item); item->RemoveChild(item->Children()[0]); delete item; } } GuiRibbonGroupCollection::GuiRibbonGroupCollection(GuiRibbonTabPage* _tabPage) :tabPage(_tabPage) { } GuiRibbonGroupCollection::~GuiRibbonGroupCollection() { } /*********************************************************************** GuiRibbonTabPage ***********************************************************************/ GuiRibbonTabPage::GuiRibbonTabPage(theme::ThemeName themeName) :GuiTabPage(themeName) , groups(this) { stack = new GuiStackComposition(); stack->SetDirection(GuiStackComposition::Horizontal); stack->SetAlignmentToParent(Margin(2, 2, 2, 2)); stack->SetPadding(2); stack->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); responsiveStack = new GuiResponsiveStackComposition(); responsiveStack->SetDirection(ResponsiveDirection::Horizontal); responsiveStack->SetAlignmentToParent(Margin(0, 0, 0, 0)); responsiveStack->AddChild(stack); responsiveContainer = new GuiResponsiveContainerComposition(); responsiveContainer->SetAlignmentToParent(Margin(0, 0, 0, 0)); responsiveContainer->SetResponsiveTarget(responsiveStack); containerComposition->AddChild(responsiveContainer); HighlightedChanged.SetAssociatedComposition(boundsComposition); } GuiRibbonTabPage::~GuiRibbonTabPage() { } bool GuiRibbonTabPage::GetHighlighted() { return highlighted; } void GuiRibbonTabPage::SetHighlighted(bool value) { if (highlighted != value) { highlighted = value; HighlightedChanged.Execute(GetNotifyEventArguments()); } } collections::ObservableListBase& GuiRibbonTabPage::GetGroups() { return groups; } /*********************************************************************** GuiRibbonGroupItemCollection ***********************************************************************/ bool GuiRibbonGroupItemCollection::QueryInsert(vint index, GuiControl* const& value) { return !value->GetBoundsComposition()->GetParent(); } void GuiRibbonGroupItemCollection::AfterInsert(vint index, GuiControl* const& value) { value->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); auto item = new GuiStackItemComposition(); item->AddChild(value->GetBoundsComposition()); group->stack->InsertStackItem(index, item); } void GuiRibbonGroupItemCollection::AfterRemove(vint index, vint count) { for (vint i = 0; i < count; i++) { auto item = group->stack->GetStackItems()[index]; group->stack->RemoveChild(item); item->RemoveChild(item->Children()[0]); delete item; } } GuiRibbonGroupItemCollection::GuiRibbonGroupItemCollection(GuiRibbonGroup* _group) :group(_group) { } GuiRibbonGroupItemCollection::~GuiRibbonGroupItemCollection() { } /*********************************************************************** GuiRibbonGroup::CommandExecutor ***********************************************************************/ GuiRibbonGroup::CommandExecutor::CommandExecutor(GuiRibbonGroup* _group) :group(_group) { } GuiRibbonGroup::CommandExecutor::~CommandExecutor() { } void GuiRibbonGroup::CommandExecutor::NotifyExpandButtonClicked() { group->ExpandButtonClicked.Execute(group->GetNotifyEventArguments()); } /*********************************************************************** GuiRibbonGroupMenu ***********************************************************************/ class GuiRibbonGroupMenu : public GuiMenu, public Description { private: IGuiMenuService::Direction GetPreferredDirection()override { return IGuiMenuService::Horizontal; } bool IsActiveState()override { return false; } public: GuiRibbonGroupMenu(theme::ThemeName themeName, GuiControl* _owner) :GuiMenu(themeName, _owner) { } }; /*********************************************************************** GuiRibbonGroup ***********************************************************************/ void GuiRibbonGroup::BeforeControlTemplateUninstalled_() { auto ct = TypedControlTemplateObject(false); if (!ct) return; ct->SetCommands(nullptr); } void GuiRibbonGroup::AfterControlTemplateInstalled_(bool initialize) { { auto ct = TypedControlTemplateObject(true); ct->SetExpandable(expandable); ct->SetCollapsed(responsiveView->GetCurrentView() == responsiveFixedButton); ct->SetCommands(commandExecutor.Obj()); dropdownButton->SetControlTemplate(ct->GetLargeDropdownButtonTemplate()); dropdownMenu->SetControlTemplate(ct->GetSubMenuTemplate()); } if (auto ct = dynamic_cast(dropdownMenu->TypedControlTemplateObject(true))) { ct->SetExpandable(expandable); ct->SetCommands(commandExecutor.Obj()); } } bool GuiRibbonGroup::IsAltAvailable() { return alt != L""; } compositions::IGuiAltActionHost* GuiRibbonGroup::GetActivatingAltHost() { if (IsAltAvailable()) { return this; } else { return GuiControl::GetActivatingAltHost(); } } void GuiRibbonGroup::OnCachedBoundsChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { dropdownMenu->GetBoundsComposition()->SetPreferredMinSize(Size(0, containerComposition->GetCachedBounds().Height())); } void GuiRibbonGroup::OnTextChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { dropdownButton->SetText(GetText()); } void GuiRibbonGroup::OnBeforeSubMenuOpening(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if (responsiveView->GetViews().Contains(responsiveFixedButton)) { auto currentDropdown = dropdownMenu; if (items.Count() == 1) { if (auto provider = items[0]->QueryTypedService()) { if (auto menu = provider->ProvideDropdownMenu()) { currentDropdown = menu; } } } dropdownButton->SetSubMenu(currentDropdown, false); } } void GuiRibbonGroup::OnBeforeSwitchingView(compositions::GuiGraphicsComposition* sender, compositions::GuiItemEventArgs& arguments) { if (auto ct = TypedControlTemplateObject(false)) { ct->SetCollapsed(arguments.itemIndex == 1); } if (arguments.itemIndex == 0) { while (responsiveStack->LevelDown()); dropdownMenu->GetContainerComposition()->RemoveChild(stack); responsiveStack->AddChild(stack); dropdownButton->SetSubMenu(nullptr, false); } else { while (responsiveStack->LevelUp()); responsiveStack->RemoveChild(stack); dropdownMenu->GetContainerComposition()->AddChild(stack); } } GuiRibbonGroup::GuiRibbonGroup(theme::ThemeName themeName) :GuiControl(themeName) , items(this) { SetAltComposition(boundsComposition); SetAltControl(this, false); commandExecutor = Ptr(new CommandExecutor(this)); { stack = new GuiStackComposition(); stack->SetDirection(GuiStackComposition::Horizontal); stack->SetAlignmentToParent(Margin(0, 0, 0, 0)); stack->SetPadding(2); stack->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); responsiveStack = new GuiResponsiveStackComposition(); responsiveStack->SetDirection(ResponsiveDirection::Horizontal); responsiveStack->SetAlignmentToParent(Margin(0, 0, 0, 0)); responsiveStack->AddChild(stack); } { dropdownButton = new GuiToolstripButton(theme::ThemeName::RibbonLargeDropdownButton); dropdownButton->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); responsiveFixedButton = new GuiResponsiveFixedComposition(); responsiveFixedButton->SetAlignmentToParent(Margin(0, 0, 0, 0)); responsiveFixedButton->AddChild(dropdownButton->GetBoundsComposition()); dropdownMenu = new GuiRibbonGroupMenu(theme::ThemeName::RibbonGroupMenu, dropdownButton); } responsiveView = new GuiResponsiveViewComposition(); responsiveView->SetAlignmentToParent(Margin(0, 0, 0, 0)); responsiveView->GetViews().Add(responsiveStack); containerComposition->AddChild(responsiveView); ExpandableChanged.SetAssociatedComposition(boundsComposition); ExpandButtonClicked.SetAssociatedComposition(boundsComposition); LargeImageChanged.SetAssociatedComposition(boundsComposition); TextChanged.AttachMethod(this, &GuiRibbonGroup::OnTextChanged); boundsComposition->CachedBoundsChanged.AttachMethod(this, &GuiRibbonGroup::OnCachedBoundsChanged); responsiveView->BeforeSwitchingView.AttachMethod(this, &GuiRibbonGroup::OnBeforeSwitchingView); dropdownButton->BeforeSubMenuOpening.AttachMethod(this, &GuiRibbonGroup::OnBeforeSubMenuOpening); } GuiRibbonGroup::~GuiRibbonGroup() { if (!responsiveView->GetViews().Contains(responsiveFixedButton)) { SafeDeleteComposition(responsiveFixedButton); } delete dropdownMenu; } bool GuiRibbonGroup::GetExpandable() { return expandable; } void GuiRibbonGroup::SetExpandable(bool value) { if (expandable != value) { expandable = value; TypedControlTemplateObject(true)->SetExpandable(expandable); if (auto ct = dynamic_cast(dropdownMenu->TypedControlTemplateObject(true))) { ct->SetExpandable(expandable); } ExpandableChanged.Execute(GetNotifyEventArguments()); } } Ptr GuiRibbonGroup::GetLargeImage() { return largeImage; } void GuiRibbonGroup::SetLargeImage(Ptr value) { if (largeImage != value) { largeImage = value; dropdownButton->SetLargeImage(value); LargeImageChanged.Execute(GetNotifyEventArguments()); if (value) { if (!responsiveView->GetViews().Contains(responsiveFixedButton)) { responsiveView->GetViews().Add(responsiveFixedButton); } } else { if (responsiveView->GetViews().Contains(responsiveFixedButton)) { responsiveView->GetViews().Remove(responsiveFixedButton); } } } } collections::ObservableListBase& GuiRibbonGroup::GetItems() { return items; } /*********************************************************************** GuiRibbonIconLabel ***********************************************************************/ void GuiRibbonIconLabel::BeforeControlTemplateUninstalled_() { } void GuiRibbonIconLabel::AfterControlTemplateInstalled_(bool initialize) { auto ct = TypedControlTemplateObject(true); ct->SetImage(image); } GuiRibbonIconLabel::GuiRibbonIconLabel(theme::ThemeName themeName) :GuiControl(themeName) { ImageChanged.SetAssociatedComposition(boundsComposition); } GuiRibbonIconLabel::~GuiRibbonIconLabel() { } Ptr GuiRibbonIconLabel::GetImage() { return image; } void GuiRibbonIconLabel::SetImage(Ptr value) { if (image != value) { image = value; TypedControlTemplateObject(true)->SetImage(image); ImageChanged.Execute(GetNotifyEventArguments()); } } /*********************************************************************** GuiRibbonButtonsItemCollection ***********************************************************************/ bool GuiRibbonButtonsItemCollection::QueryInsert(vint index, GuiControl* const& value) { return !value->GetBoundsComposition()->GetParent(); } void GuiRibbonButtonsItemCollection::AfterInsert(vint index, GuiControl* const& value) { buttons->responsiveView->GetSharedControls().Add(value); buttons->SetButtonThemeName(buttons->responsiveView->GetCurrentView(), value); for (vint i = 0; i < sizeof(buttons->views) / sizeof(*buttons->views); i++) { if (auto view = buttons->views[i]) { auto stack = dynamic_cast(view->Children()[0]); auto shared = new GuiResponsiveSharedComposition(); shared->SetAlignmentToParent(Margin(0, 0, 0, 0)); shared->SetShared(value); auto item = new GuiStackItemComposition(); item->AddChild(shared); stack->InsertStackItem(index, item); } } } void GuiRibbonButtonsItemCollection::BeforeRemove(vint index, GuiControl* const& value) { CHECK_FAIL(L"GuiRibbonButtonsItemCollection::BeforeRemove(vint, GuiControl* const&)#Controls are not allowed to be removed from GuiRibbonButtons."); } GuiRibbonButtonsItemCollection::GuiRibbonButtonsItemCollection(GuiRibbonButtons* _buttons) :buttons(_buttons) { } GuiRibbonButtonsItemCollection::~GuiRibbonButtonsItemCollection() { } /*********************************************************************** GuiRibbonButtons ***********************************************************************/ void GuiRibbonButtons::BeforeControlTemplateUninstalled_() { } void GuiRibbonButtons::AfterControlTemplateInstalled_(bool initialize) { for (auto button : buttons) { SetButtonThemeName(responsiveView->GetCurrentView(), button); } } void GuiRibbonButtons::OnBeforeSwitchingView(compositions::GuiGraphicsComposition* sender, compositions::GuiItemEventArgs& arguments) { for (auto button : buttons) { SetButtonThemeName(responsiveView->GetViews()[arguments.itemIndex], button); } } void GuiRibbonButtons::SetButtonThemeName(compositions::GuiResponsiveCompositionBase* fixed, GuiControl* button) { if (fixed && button) { auto themeName = button->GetControlThemeName(); vint type = -1; switch (themeName) { case ThemeName::RibbonLargeButton: case ThemeName::RibbonSmallButton: case ThemeName::ToolstripButton: type = 0; break; case ThemeName::RibbonLargeDropdownButton: case ThemeName::RibbonSmallDropdownButton: case ThemeName::ToolstripDropdownButton: type = 1; break; case ThemeName::RibbonLargeSplitButton: case ThemeName::RibbonSmallSplitButton: case ThemeName::ToolstripSplitButton: type = 2; break; case ThemeName::RibbonSmallIconLabel: case ThemeName::RibbonIconLabel: type = 3; break; default:; } if (type != -1) { ThemeName themeName = ThemeName::Unknown; TemplateProperty controlTemplate; if (fixed == views[(vint)RibbonButtonSize::Large]) { switch (type) { case 0: themeName = ThemeName::RibbonLargeButton; break; case 1: themeName = ThemeName::RibbonLargeDropdownButton; break; case 2: themeName = ThemeName::RibbonLargeSplitButton; break; case 3: themeName = ThemeName::RibbonSmallIconLabel; break; } } else if (fixed == views[(vint)RibbonButtonSize::Small]) { switch (type) { case 0: themeName = ThemeName::RibbonSmallButton; break; case 1: themeName = ThemeName::RibbonSmallDropdownButton; break; case 2: themeName = ThemeName::RibbonSmallSplitButton; break; case 3: themeName = ThemeName::RibbonSmallIconLabel; break; } } else if (fixed == views[(vint)RibbonButtonSize::Icon]) { switch (type) { case 0: themeName = ThemeName::ToolstripButton; break; case 1: themeName = ThemeName::ToolstripDropdownButton; break; case 2: themeName = ThemeName::ToolstripSplitButton; break; case 3: themeName = ThemeName::RibbonIconLabel; break; } } if (auto ct = TypedControlTemplateObject(false)) { if (fixed == views[(vint)RibbonButtonSize::Large]) { switch (type) { case 0: controlTemplate = ct->GetLargeButtonTemplate(); break; case 1: controlTemplate = ct->GetLargeDropdownButtonTemplate(); break; case 2: controlTemplate = ct->GetLargeSplitButtonTemplate(); break; case 3: controlTemplate = ct->GetSmallIconLabelTemplate(); break; } } else if (fixed == views[(vint)RibbonButtonSize::Small]) { switch (type) { case 0: controlTemplate = ct->GetSmallButtonTemplate(); break; case 1: controlTemplate = ct->GetSmallDropdownButtonTemplate(); break; case 2: controlTemplate = ct->GetSmallSplitButtonTemplate(); break; case 3: controlTemplate = ct->GetSmallIconLabelTemplate(); break; } } else if (fixed == views[(vint)RibbonButtonSize::Icon]) { switch (type) { case 0: controlTemplate = ct->GetIconButtonTemplate(); break; case 1: controlTemplate = ct->GetIconDropdownButtonTemplate(); break; case 2: controlTemplate = ct->GetIconSplitButtonTemplate(); break; case 3: controlTemplate = ct->GetIconLabelTemplate(); break; } } } button->SetControlThemeNameAndTemplate(themeName, controlTemplate); } } } GuiRibbonButtons::GuiRibbonButtons(theme::ThemeName themeName, RibbonButtonSize _maxSize, RibbonButtonSize _minSize) :GuiControl(themeName) , maxSize(_maxSize) , minSize(_minSize) , buttons(this) { responsiveView = new GuiResponsiveViewComposition(); responsiveView->SetDirection(ResponsiveDirection::Horizontal); responsiveView->SetAlignmentToParent(Margin(0, 0, 0, 0)); responsiveView->BeforeSwitchingView.AttachMethod(this, &GuiRibbonButtons::OnBeforeSwitchingView); auto installButton = [&](GuiTableComposition* table, vint row, vint column, GuiControl* buttonContainer) { auto shared = new GuiResponsiveSharedComposition(); shared->SetAlignmentToParent(Margin(0, 0, 0, 0)); shared->SetShared(buttonContainer); auto cell = new GuiCellComposition(); cell->SetSite(row, column, 1, 1); cell->AddChild(shared); table->AddChild(cell); }; for (vint i = 0; i < sizeof(views) / sizeof(*views); i++) { if ((vint)maxSize <= i && i <= (vint)minSize) { auto stack = new GuiStackComposition(); stack->SetAlignmentToParent(Margin(0, 0, 0, 0)); stack->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); stack->SetDirection(i == 0 ? GuiStackComposition::Horizontal : GuiStackComposition::Vertical); views[i] = new GuiResponsiveFixedComposition(); views[i]->AddChild(stack); responsiveView->GetViews().Add(views[i]); } } auto sharedSizeRootComposition = new GuiSharedSizeRootComposition(); sharedSizeRootComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); sharedSizeRootComposition->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); sharedSizeRootComposition->AddChild(responsiveView); containerComposition->AddChild(sharedSizeRootComposition); } GuiRibbonButtons::~GuiRibbonButtons() { } collections::ObservableListBase& GuiRibbonButtons::GetButtons() { return buttons; } /*********************************************************************** GuiRibbonToolstripsGroupCollection ***********************************************************************/ bool GuiRibbonToolstripsGroupCollection::QueryInsert(vint index, GuiToolstripGroup* const& value) { return !value->GetBoundsComposition()->GetParent(); } void GuiRibbonToolstripsGroupCollection::AfterInsert(vint index, GuiToolstripGroup* const& value) { toolstrips->RearrangeToolstripGroups(); } void GuiRibbonToolstripsGroupCollection::AfterRemove(vint index, vint count) { toolstrips->RearrangeToolstripGroups(); } GuiRibbonToolstripsGroupCollection::GuiRibbonToolstripsGroupCollection(GuiRibbonToolstrips* _toolstrips) :toolstrips(_toolstrips) { } GuiRibbonToolstripsGroupCollection::~GuiRibbonToolstripsGroupCollection() { } /*********************************************************************** GuiRibbonToolstrips ***********************************************************************/ // TODO: (enumerable) foreach #define ARRLEN(X) sizeof(X) / sizeof(*X) void GuiRibbonToolstrips::BeforeControlTemplateUninstalled_() { } void GuiRibbonToolstrips::AfterControlTemplateInstalled_(bool initialize) { auto ct = TypedControlTemplateObject(true); for (vint i = 0; i < ARRLEN(toolbars); i++) { toolbars[i]->SetControlTemplate(ct->GetToolbarTemplate()); } } void GuiRibbonToolstrips::OnBeforeSwitchingView(compositions::GuiGraphicsComposition* sender, compositions::GuiItemEventArgs& arguments) { RearrangeToolstripGroups(arguments.itemIndex); } void GuiRibbonToolstrips::RearrangeToolstripGroups(vint viewIndex) { static_assert(ARRLEN(longContainers) == 2, ""); static_assert(ARRLEN(shortContainers) == 3, ""); if (viewIndex == -1) { viewIndex = responsiveView->GetViews().IndexOf(responsiveView->GetCurrentView()); } for (vint i = 0; i < ARRLEN(longContainers); i++) { longContainers[i]->GetToolstripItems().Clear(); } for (vint i = 0; i < ARRLEN(shortContainers); i++) { shortContainers[i]->GetToolstripItems().Clear(); } vint count = viewIndex == 0 ? 2 : 3; if (groups.Count() <= count) { auto containers = viewIndex == 0 ? longContainers : shortContainers; // TODO: (enumerable) foreach:indexed for (vint i = 0; i < groups.Count(); i++) { containers[i]->GetToolstripItems().Add(groups[i]); } } else if (count == 3) { #define DELTA(POSTFIX) (abs(count1##POSTFIX - count2##POSTFIX) + abs(count2##POSTFIX - count3##POSTFIX) + abs(count3##POSTFIX - count1##POSTFIX)) #define DEFINE_COUNT(POSTFIX, OFFSET_FIRST, OFFSET_LAST) \ vint count1##POSTFIX = count1_o + (OFFSET_FIRST); \ vint count2##POSTFIX = count2_o - (OFFSET_FIRST) - (OFFSET_LAST); \ vint count3##POSTFIX = count3_o + (OFFSET_LAST) #define MIN(a, b) (a)<(b)?(a):(b) vint firstGroupCount = 0; vint lastGroupCount = 0; vint count1_o = 0; vint count2_o = From(groups) .Select([](GuiToolstripGroup* group) {return group->GetToolstripItems().Count(); }) .Aggregate([](vint a, vint b) {return a + b; }); vint count3_o = 0; vint delta_o = DELTA(_o); while (firstGroupCount + lastGroupCount < groups.Count()) { auto newFirstGroup = groups[firstGroupCount]; auto newLastGroup = groups[groups.Count() - lastGroupCount - 1]; DEFINE_COUNT(_f, newFirstGroup->GetToolstripItems().Count(), 0); vint delta_f = DELTA(_f); DEFINE_COUNT(_l, 0, newLastGroup->GetToolstripItems().Count()); vint delta_l = DELTA(_l); vint delta = MIN(delta_o, MIN(delta_f, delta_l)); if (delta == delta_f) { firstGroupCount++; count1_o = count1_f; count2_o = count2_f; count3_o = count3_f; delta_o = delta_f; } else if (delta == delta_l) { lastGroupCount++; count1_o = count1_l; count2_o = count2_l; count3_o = count3_l; delta_o = delta_l; } else { break; } } vint minMiddle = firstGroupCount; vint maxMiddle = groups.Count() - lastGroupCount - 1; // TODO: (enumerable) foreach:indexed for (vint j = 0; j < groups.Count(); j++) { shortContainers[ j < minMiddle ? 0 : j>maxMiddle ? 2 : 1 ]->GetToolstripItems().Add(groups[j]); } #undef MIN #undef DEFINE_COUNT #undef DELTA } else if (count == 2) { vint firstGroupCount = groups.Count(); { vint count1 = 0; vint count2 = From(groups) .Select([](GuiToolstripGroup* group) {return group->GetToolstripItems().Count(); }) .Aggregate([](vint a, vint b) {return a + b; }); vint delta = abs(count2 - count1); // TODO: (enumerable) foreach for (vint i = 0; i < groups.Count(); i++) { auto groupCount = groups[i]->GetToolstripItems().Count(); vint count1_2 = count1 + groupCount; vint count2_2 = count2 - groupCount; vint delta_2 = abs(count2_2 - count1_2); if (delta < delta_2) { firstGroupCount = i; break; } count1 = count1_2; count2 = count2_2; delta = delta_2; } } // TODO: (enumerable) foreach:indexed for (vint j = 0; j < groups.Count(); j++) { longContainers[j < firstGroupCount ? 0 : 1]->GetToolstripItems().Add(groups[j]); } } } GuiRibbonToolstrips::GuiRibbonToolstrips(theme::ThemeName themeName) :GuiControl(themeName) , groups(this) { responsiveView = new GuiResponsiveViewComposition(); responsiveView->SetDirection(ResponsiveDirection::Horizontal); responsiveView->SetAlignmentToParent(Margin(0, 0, 0, 0)); responsiveView->BeforeSwitchingView.AttachMethod(this, &GuiRibbonToolstrips::OnBeforeSwitchingView); vint toolbarIndex = 0; // TODO: (enumerable) foreach:indexed for (vint i = 0; i < sizeof(views) / sizeof(*views); i++) { auto containers = i == 0 ? longContainers : shortContainers; vint count = i == 0 ? 2 : 3; auto table = new GuiTableComposition(); table->SetAlignmentToParent(Margin(0, 0, 0, 0)); table->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); table->SetRowsAndColumns(count * 2 + 1, 1); table->SetColumnOption(0, GuiCellOption::MinSizeOption()); table->SetRowOption(0, GuiCellOption::PercentageOption(1.0)); for (vint j = 0; j < count; j++) { table->SetRowOption(j * 2 + 1, GuiCellOption::MinSizeOption()); table->SetRowOption(j * 2 + 2, GuiCellOption::PercentageOption(1.0)); } for (vint j = 0; j < count; j++) { auto toolbar = new GuiToolstripToolBar(theme::ThemeName::ToolstripToolBar); toolbar->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); toolbars[toolbarIndex++] = toolbar; auto cell = new GuiCellComposition(); cell->SetSite(j * 2 + 1, 0, 1, 1); cell->AddChild(toolbar->GetBoundsComposition()); table->AddChild(cell); auto container = new GuiToolstripGroupContainer(theme::ThemeName::CustomControl); toolbar->GetToolstripItems().Add(container); containers[j] = container; } views[i] = new GuiResponsiveFixedComposition(); views[i]->AddChild(table); responsiveView->GetViews().Add(views[i]); } containerComposition->AddChild(responsiveView); } GuiRibbonToolstrips::~GuiRibbonToolstrips() { } collections::ObservableListBase& GuiRibbonToolstrips::GetGroups() { return groups; } #undef ARRLEN /*********************************************************************** GuiRibbonGallery::CommandExecutor ***********************************************************************/ GuiRibbonGallery::CommandExecutor::CommandExecutor(GuiRibbonGallery* _gallery) :gallery(_gallery) { } GuiRibbonGallery::CommandExecutor::~CommandExecutor() { } void GuiRibbonGallery::CommandExecutor::NotifyScrollUp() { gallery->RequestedScrollUp.Execute(gallery->GetNotifyEventArguments()); } void GuiRibbonGallery::CommandExecutor::NotifyScrollDown() { gallery->RequestedScrollDown.Execute(gallery->GetNotifyEventArguments()); } void GuiRibbonGallery::CommandExecutor::NotifyDropdown() { gallery->RequestedDropdown.Execute(gallery->GetNotifyEventArguments()); } /*********************************************************************** GuiRibbonGallery ***********************************************************************/ void GuiRibbonGallery::BeforeControlTemplateUninstalled_() { auto ct = TypedControlTemplateObject(false); if (!ct) return; ct->SetCommands(nullptr); } void GuiRibbonGallery::AfterControlTemplateInstalled_(bool initialize) { auto ct = TypedControlTemplateObject(true); ct->SetCommands(commandExecutor.Obj()); ct->SetScrollUpEnabled(scrollUpEnabled); ct->SetScrollDownEnabled(scrollDownEnabled); } GuiRibbonGallery::GuiRibbonGallery(theme::ThemeName themeName) :GuiControl(themeName) { commandExecutor = Ptr(new CommandExecutor(this)); ScrollUpEnabledChanged.SetAssociatedComposition(boundsComposition); ScrollDownEnabledChanged.SetAssociatedComposition(boundsComposition); RequestedScrollUp.SetAssociatedComposition(boundsComposition); RequestedScrollDown.SetAssociatedComposition(boundsComposition); RequestedDropdown.SetAssociatedComposition(boundsComposition); } GuiRibbonGallery::~GuiRibbonGallery() { } bool GuiRibbonGallery::GetScrollUpEnabled() { return scrollUpEnabled; } void GuiRibbonGallery::SetScrollUpEnabled(bool value) { if (scrollUpEnabled != value) { scrollUpEnabled = value; TypedControlTemplateObject(true)->SetScrollUpEnabled(value); } } bool GuiRibbonGallery::GetScrollDownEnabled() { return scrollDownEnabled; } void GuiRibbonGallery::SetScrollDownEnabled(bool value) { if (scrollDownEnabled != value) { scrollDownEnabled = value; TypedControlTemplateObject(true)->SetScrollDownEnabled(value); } } /*********************************************************************** GuiRibbonToolstripMenu ***********************************************************************/ void GuiRibbonToolstripMenu::BeforeControlTemplateUninstalled_() { auto ct = TypedControlTemplateObject(false); if (!ct) return; if (auto cc = ct->GetContentComposition()) { cc->RemoveChild(contentComposition); } } void GuiRibbonToolstripMenu::AfterControlTemplateInstalled_(bool initialize) { auto ct = TypedControlTemplateObject(true); if (auto cc = ct->GetContentComposition()) { cc->AddChild(contentComposition); } } GuiRibbonToolstripMenu::GuiRibbonToolstripMenu(theme::ThemeName themeName, GuiControl* owner) :GuiToolstripMenu(themeName, owner) { contentComposition = new GuiBoundsComposition(); contentComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); contentComposition->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); } GuiRibbonToolstripMenu::~GuiRibbonToolstripMenu() { if (!contentComposition->GetParent()) { SafeDeleteComposition(contentComposition); } } compositions::GuiGraphicsComposition* GuiRibbonToolstripMenu::GetContentComposition() { return contentComposition; } } } } /*********************************************************************** .\CONTROLS\TOOLSTRIPPACKAGE\GUIRIBBONGALLERYLIST.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { using namespace reflection::description; using namespace collections; using namespace compositions; using namespace templates; namespace list { /*********************************************************************** list::GalleryGroup ***********************************************************************/ GalleryGroup::GalleryGroup() { } GalleryGroup::~GalleryGroup() { if (eventHandler) { itemValues.Cast()->ItemChanged.Remove(eventHandler); } } WString GalleryGroup::GetName() { return name; } Ptr GalleryGroup::GetItemValues() { return itemValues; } /*********************************************************************** list::GroupedDataSource ***********************************************************************/ void GroupedDataSource::RebuildItemSource() { ignoreGroupChanged = true; joinedItemSource.Clear(); groupedItemSource.Clear(); cachedGroupItemCounts.Clear(); ignoreGroupChanged = false; if (itemSource) { if (GetGroupEnabled()) { for (auto [groupValue, index] : indexed(GetLazyList(itemSource))) { auto group = Ptr(new GalleryGroup); group->name = titleProperty(groupValue); group->itemValues = GetChildren(childrenProperty(groupValue)); AttachGroupChanged(group, index); groupedItemSource.Add(group); } } else { auto group = Ptr(new GalleryGroup); group->itemValues = GetChildren(itemSource); AttachGroupChanged(group, 0); groupedItemSource.Add(group); } } } Ptr GroupedDataSource::GetChildren(Ptr children) { if (!children) { return nullptr; } else if (auto list = children.Cast()) { return list; } else { return IValueList::Create(GetLazyList(children)); } } void GroupedDataSource::AttachGroupChanged(Ptr group, vint index) { if (auto observable = group->itemValues.Cast()) { group->eventHandler = observable->ItemChanged.Add([this, index](vint start, vint oldCount, vint newCount) { OnGroupItemChanged(index, start, oldCount, newCount); }); } } void GroupedDataSource::OnGroupChanged(vint start, vint oldCount, vint newCount) { if (!ignoreGroupChanged) { for (vint i = 0; i < oldCount; i++) { RemoveGroupFromJoined(start); } for (vint i = 0; i < newCount; i++) { InsertGroupToJoined(start + i); } } } void GroupedDataSource::OnGroupItemChanged(vint index, vint start, vint oldCount, vint newCount) { if (!ignoreGroupChanged) { vint countBeforeGroup = GetCountBeforeGroup(index); vint joinedIndex = countBeforeGroup + start; vint minCount = oldCount < newCount ? oldCount : newCount; auto itemValues = groupedItemSource[index]->itemValues; for (vint i = 0; i < minCount; i++) { joinedItemSource.Set(joinedIndex + i, itemValues->Get(start + i)); } if (oldCount < newCount) { for (vint i = minCount; i < newCount; i++) { joinedItemSource.Insert(joinedIndex + i, itemValues->Get(start + i)); } } else if (oldCount > newCount) { for (vint i = minCount; i < oldCount; i++) { joinedItemSource.RemoveAt(joinedIndex + i); } } cachedGroupItemCounts[index] += newCount - oldCount; } } vint GroupedDataSource::GetCountBeforeGroup(vint index) { vint count = 0; for (vint i = 0; i < index; i++) { count += cachedGroupItemCounts[i]; } return count; } void GroupedDataSource::InsertGroupToJoined(vint index) { vint countBeforeGroup = GetCountBeforeGroup(index); auto group = groupedItemSource[index]; vint itemCount = group->itemValues ? group->itemValues->GetCount() : 0; cachedGroupItemCounts.Insert(index, itemCount); if (itemCount > 0) { for (vint i = 0; i < itemCount; i++) { joinedItemSource.Insert(countBeforeGroup + i, group->itemValues->Get(i)); } } } void GroupedDataSource::RemoveGroupFromJoined(vint index) { vint countBeforeGroup = GetCountBeforeGroup(index); joinedItemSource.RemoveRange(countBeforeGroup, cachedGroupItemCounts[index]); cachedGroupItemCounts.RemoveAt(index); } GroupedDataSource::GroupedDataSource(compositions::GuiGraphicsComposition* _associatedComposition) :associatedComposition(_associatedComposition) { GroupEnabledChanged.SetAssociatedComposition(associatedComposition); GroupTitlePropertyChanged.SetAssociatedComposition(associatedComposition); GroupChildrenPropertyChanged.SetAssociatedComposition(associatedComposition); auto vol = UnboxValue>(BoxParameter(groupedItemSource)); groupChangedHandler = vol->ItemChanged.Add(this, &GroupedDataSource::OnGroupChanged); } GroupedDataSource::~GroupedDataSource() { auto vol = UnboxValue>(BoxParameter(joinedItemSource)); vol->ItemChanged.Remove(groupChangedHandler); } Ptr GroupedDataSource::GetItemSource() { return itemSource; } void GroupedDataSource::SetItemSource(Ptr value) { if (itemSource != value) { itemSource = value; RebuildItemSource(); } } bool GroupedDataSource::GetGroupEnabled() { return titleProperty && childrenProperty; } ItemProperty GroupedDataSource::GetGroupTitleProperty() { return titleProperty; } void GroupedDataSource::SetGroupTitleProperty(const ItemProperty& value) { if (titleProperty != value) { titleProperty = value; GroupTitlePropertyChanged.Execute(GuiEventArgs(associatedComposition)); GroupEnabledChanged.Execute(GuiEventArgs(associatedComposition)); RebuildItemSource(); } } ItemProperty> GroupedDataSource::GetGroupChildrenProperty() { return childrenProperty; } void GroupedDataSource::SetGroupChildrenProperty(const ItemProperty>& value) { if (childrenProperty != value) { childrenProperty = value; GroupChildrenPropertyChanged.Execute(GuiEventArgs(associatedComposition)); GroupEnabledChanged.Execute(GuiEventArgs(associatedComposition)); RebuildItemSource(); } } const GroupedDataSource::GalleryGroupList& GroupedDataSource::GetGroups() { return groupedItemSource; } } /*********************************************************************** GuiBindableRibbonGalleryList ***********************************************************************/ void GuiBindableRibbonGalleryList::BeforeControlTemplateUninstalled_() { } void GuiBindableRibbonGalleryList::AfterControlTemplateInstalled_(bool initialize) { auto ct = TypedControlTemplateObject(true); itemList->SetControlTemplate(ct->GetItemListTemplate()); subMenu->SetControlTemplate(ct->GetMenuTemplate()); groupContainer->SetControlTemplate(ct->GetGroupContainerTemplate()); MenuResetGroupTemplate(); UpdateLayoutSizeOffset(); } void GuiBindableRibbonGalleryList::UpdateLayoutSizeOffset() { auto cSize = itemList->GetContainerComposition()->GetCachedBounds(); auto bSize = itemList->GetBoundsComposition()->GetCachedBounds(); layout->SetSizeOffset(Size(bSize.Width() - cSize.Width(), bSize.Height() - cSize.Height())); if (layout->GetItemWidth() > 0) { vint columns = layout->GetVisibleItemCount(); if (columns == 0) columns = 1; vint rows = (visibleItemCount + columns - 1) / columns; vint height = (vint)(layout->GetCachedBounds().Height()*(rows + 0.5)); groupContainer->GetBoundsComposition()->SetPreferredMinSize(Size(0, height)); } else { groupContainer->GetBoundsComposition()->SetPreferredMinSize(Size(0, 0)); } } void GuiBindableRibbonGalleryList::OnItemListSelectionChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { auto pos = IndexToGalleryPos(itemList->GetSelectedItemIndex()); // TODO: (enumerable) foreach for (vint i = 0; i < groupedItemSource.Count(); i++) { auto group = groupedItemSource[i]; if (group->GetItemValues()) { vint count = group->GetItemValues()->GetCount(); for (vint j = 0; j < count; j++) { auto background = MenuGetGroupItemBackground(i, j); background->SetSelected(pos.group == i && pos.item == j); } } } if (!skipItemAppliedEvent && itemList->GetSelectedItemIndex() != -1) { GuiItemEventArgs itemAppliedArgs(boundsComposition); itemAppliedArgs.itemIndex = itemList->GetSelectedItemIndex(); ItemApplied.Execute(itemAppliedArgs); } SelectionChanged.Execute(GetNotifyEventArguments()); } void GuiBindableRibbonGalleryList::OnItemListItemMouseEnter(compositions::GuiGraphicsComposition* sender, compositions::GuiItemEventArgs& arguments) { StartPreview(arguments.itemIndex); } void GuiBindableRibbonGalleryList::OnItemListItemMouseLeave(compositions::GuiGraphicsComposition* sender, compositions::GuiItemEventArgs& arguments) { StopPreview(arguments.itemIndex); } void GuiBindableRibbonGalleryList::OnCachedBoundsChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { UpdateLayoutSizeOffset(); auto bounds = boundsComposition->GetCachedBounds(); subMenu->GetBoundsComposition()->SetPreferredMinSize(Size(bounds.Width() + 20, 1)); // TODO: (enumerable) foreach for (vint i = 0; i < groupedItemSource.Count(); i++) { auto group = groupedItemSource[i]; if (group->GetItemValues()) { vint count = group->GetItemValues()->GetCount(); for (vint j = 0; j < count; j++) { auto background = MenuGetGroupItemBackground(i, j); background->GetBoundsComposition()->SetPreferredMinSize(Size(0, bounds.Height())); } } } } void GuiBindableRibbonGalleryList::OnRequestedDropdown(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { subMenu->ShowPopup(this, Point(0, 0)); } void GuiBindableRibbonGalleryList::OnRequestedScrollUp(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { itemListArranger->ScrollUp(); } void GuiBindableRibbonGalleryList::OnRequestedScrollDown(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { itemListArranger->ScrollDown(); } void GuiBindableRibbonGalleryList::MenuResetGroupTemplate() { groupStack->SetItemTemplate([this](const Value& groupValue)->GuiTemplate* { auto group = UnboxValue>(groupValue); auto groupTemplate = new GuiTemplate; groupTemplate->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); auto groupContentStack = new GuiStackComposition; groupContentStack->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); groupContentStack->SetAlignmentToParent(Margin(0, 0, 0, 0)); groupContentStack->SetDirection(GuiStackComposition::Vertical); { auto item = new GuiStackItemComposition; groupContentStack->AddChild(item); auto header = new GuiControl(theme::ThemeName::RibbonToolstripHeader); header->SetControlTemplate(TypedControlTemplateObject(true)->GetHeaderTemplate()); header->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); header->SetText(group->GetName()); item->AddChild(header->GetBoundsComposition()); } if (itemStyle) { auto item = new GuiStackItemComposition; item->SetPreferredMinSize(Size(1, 1)); groupContentStack->AddChild(item); auto groupItemFlow = new GuiRepeatFlowComposition(); groupItemFlow->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); groupItemFlow->SetAlignmentToParent(Margin(0, 0, 0, 0)); groupItemFlow->SetItemSource(group->GetItemValues()); groupItemFlow->SetItemTemplate([=](const Value& groupItemValue)->GuiTemplate* { auto groupItemTemplate = new GuiTemplate; groupItemTemplate->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); auto backgroundButton = new GuiSelectableButton(theme::ThemeName::ListItemBackground); if (auto style = TypedControlTemplateObject(true)->GetBackgroundTemplate()) { backgroundButton->SetControlTemplate(style); } backgroundButton->SetAutoFocus(false); backgroundButton->SetAutoSelection(false); backgroundButton->Clicked.AttachLambda([=](GuiGraphicsComposition* sender, GuiEventArgs& arguments) { auto groupIndex = groupStack->GetStackItems().IndexOf(dynamic_cast(groupTemplate->GetParent())); auto itemIndex = groupItemFlow->GetFlowItems().IndexOf(dynamic_cast(groupItemTemplate->GetParent())); auto index = GalleryPosToIndex({ groupIndex,itemIndex }); itemList->SetSelected(index, true); itemList->EnsureItemVisible(index); subMenu->Close(); }); backgroundButton->GetBoundsComposition()->GetEventReceiver()->mouseEnter.AttachLambda([=](GuiGraphicsComposition* sender, GuiEventArgs& arguments) { auto groupIndex = groupStack->GetStackItems().IndexOf(dynamic_cast(groupTemplate->GetParent())); auto itemIndex = groupItemFlow->GetFlowItems().IndexOf(dynamic_cast(groupItemTemplate->GetParent())); auto index = GalleryPosToIndex({ groupIndex,itemIndex }); StartPreview(index); }); backgroundButton->GetBoundsComposition()->GetEventReceiver()->mouseLeave.AttachLambda([=](GuiGraphicsComposition* sender, GuiEventArgs& arguments) { auto groupIndex = groupStack->GetStackItems().IndexOf(dynamic_cast(groupTemplate->GetParent())); auto itemIndex = groupItemFlow->GetFlowItems().IndexOf(dynamic_cast(groupItemTemplate->GetParent())); auto index = GalleryPosToIndex({ groupIndex,itemIndex }); StopPreview(index); }); groupItemTemplate->AddChild(backgroundButton->GetBoundsComposition()); auto itemTemplate = itemStyle(groupItemValue); itemTemplate->SetAlignmentToParent(Margin(0, 0, 0, 0)); backgroundButton->GetContainerComposition()->AddChild(itemTemplate); return groupItemTemplate; }); item->AddChild(groupItemFlow); } groupTemplate->AddChild(groupContentStack); return groupTemplate; }); } GuiControl* GuiBindableRibbonGalleryList::MenuGetGroupHeader(vint groupIndex) { CHECK_ERROR(0 <= groupIndex && groupIndex < groupedItemSource.Count(), L"GuiBindableRibbonGalleryList::MenuGetGroupHeader(vint)#Group index out of range"); auto stackItem = groupStack->GetStackItems()[groupIndex]; auto groupTemplate = stackItem->Children()[0]; auto groupContentStack = dynamic_cast(groupTemplate->Children()[0]); auto groupHeaderItem = groupContentStack->GetStackItems()[0]; auto groupHeader = groupHeaderItem->Children()[0]->GetAssociatedControl(); CHECK_ERROR(groupHeader, L"GuiBindableRibbonGalleryList::MenuGetGroupHeader(vint)#Internal error."); return groupHeader; } compositions::GuiRepeatFlowComposition* GuiBindableRibbonGalleryList::MenuGetGroupFlow(vint groupIndex) { CHECK_ERROR(0 <= groupIndex && groupIndex < groupedItemSource.Count(), L"GuiBindableRibbonGalleryList::MenuGetGroupFlow(vint)#Group index out of range"); if (!itemStyle) return nullptr; auto stackItem = groupStack->GetStackItems()[groupIndex]; auto groupTemplate = stackItem->Children()[0]; auto groupContentStack = dynamic_cast(groupTemplate->Children()[0]); auto groupContentItem = groupContentStack->GetStackItems()[1]; auto groupFlow = dynamic_cast(groupContentItem->Children()[0]); CHECK_ERROR(groupFlow, L"GuiBindableRibbonGalleryList::MenuGetGroupHeader(vint)#Internal error."); return groupFlow; } GuiSelectableButton* GuiBindableRibbonGalleryList::MenuGetGroupItemBackground(vint groupIndex, vint itemIndex) { CHECK_ERROR(0 <= groupIndex && groupIndex < groupedItemSource.Count(), L"GuiBindableRibbonGalleryList::MenuGetGroupItemBackground(vint, vint)#Group index out of range"); auto group = groupedItemSource[groupIndex]; CHECK_ERROR(group->GetItemValues() && 0 <= itemIndex && itemIndex < group->GetItemValues()->GetCount(), L"GuiBindableRibbonGalleryList::MenuGetGroupHeader(vint, vint)#Item index out of range"); auto groupFlow = MenuGetGroupFlow(groupIndex); auto groupFlowItem = groupFlow->GetFlowItems()[itemIndex]; auto groupItemTemplate = groupFlowItem->Children()[0]; auto groupItemBackground = dynamic_cast(groupItemTemplate->Children()[0]->GetAssociatedControl()); CHECK_ERROR(groupItemBackground, L"GuiBindableRibbonGalleryList::MenuGetGroupHeader(vint, vint)#Internal error."); return groupItemBackground; } void GuiBindableRibbonGalleryList::StartPreview(vint index) { if (index != itemList->GetSelectedItemIndex()) { GuiItemEventArgs previewArgs(boundsComposition); previewArgs.itemIndex = index; PreviewStarted.Execute(previewArgs); } } void GuiBindableRibbonGalleryList::StopPreview(vint index) { if (index != itemList->GetSelectedItemIndex()) { GuiItemEventArgs previewArgs(boundsComposition); previewArgs.itemIndex = index; PreviewStopped.Execute(previewArgs); } } GuiMenu* GuiBindableRibbonGalleryList::ProvideDropdownMenu() { return GetSubMenu(); } GuiBindableRibbonGalleryList::GuiBindableRibbonGalleryList(theme::ThemeName themeName) :GuiRibbonGallery(themeName) , GroupedDataSource(boundsComposition) { ItemTemplateChanged.SetAssociatedComposition(boundsComposition); SelectionChanged.SetAssociatedComposition(boundsComposition); PreviewStarted.SetAssociatedComposition(boundsComposition); PreviewStopped.SetAssociatedComposition(boundsComposition); ItemApplied.SetAssociatedComposition(boundsComposition); subMenu = new GuiRibbonToolstripMenu(theme::ThemeName::RibbonToolstripMenu, this); { layout = new ribbon_impl::GalleryResponsiveLayout; layout->SetAlignmentToParent(Margin(0, 0, 0, 0)); containerComposition->AddChild(layout); itemListArranger = new ribbon_impl::GalleryItemArranger(this); itemList = new GuiBindableTextList(theme::ThemeName::RibbonGalleryItemList); itemList->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); itemList->SetArranger(Ptr(itemListArranger)); itemList->SetItemSource(UnboxValue>(BoxParameter(joinedItemSource))); itemList->SelectionChanged.AttachMethod(this, &GuiBindableRibbonGalleryList::OnItemListSelectionChanged); itemList->ItemMouseEnter.AttachMethod(this, &GuiBindableRibbonGalleryList::OnItemListItemMouseEnter); itemList->ItemMouseLeave.AttachMethod(this, &GuiBindableRibbonGalleryList::OnItemListItemMouseLeave); layout->AddChild(itemList->GetBoundsComposition()); } { groupContainer = new GuiScrollContainer(theme::ThemeName::ScrollView); groupContainer->SetHorizontalAlwaysVisible(false); groupContainer->SetVerticalAlwaysVisible(false); groupContainer->SetExtendToFullWidth(true); groupContainer->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); subMenu->GetContentComposition()->AddChild(groupContainer->GetBoundsComposition()); groupStack = new GuiRepeatStackComposition(); groupStack->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); groupStack->SetAlignmentToParent(Margin(0, 0, 0, 0)); groupStack->SetDirection(GuiStackComposition::Vertical); groupStack->SetItemSource(UnboxValue>(BoxParameter(groupedItemSource))); groupContainer->GetContainerComposition()->AddChild(groupStack); MenuResetGroupTemplate(); } RequestedScrollUp.AttachMethod(this, &GuiBindableRibbonGalleryList::OnRequestedScrollUp); RequestedScrollDown.AttachMethod(this, &GuiBindableRibbonGalleryList::OnRequestedScrollDown); RequestedDropdown.AttachMethod(this, &GuiBindableRibbonGalleryList::OnRequestedDropdown); boundsComposition->CachedBoundsChanged.AttachMethod(this, &GuiBindableRibbonGalleryList::OnCachedBoundsChanged); itemListArranger->UnblockScrollUpdate(); } GuiBindableRibbonGalleryList::~GuiBindableRibbonGalleryList() { delete subMenu; } GuiBindableRibbonGalleryList::ItemStyleProperty GuiBindableRibbonGalleryList::GetItemTemplate() { return itemStyle; } void GuiBindableRibbonGalleryList::SetItemTemplate(ItemStyleProperty value) { if (itemStyle != value) { itemStyle = value; itemList->SetItemTemplate(value); ItemTemplateChanged.Execute(GetNotifyEventArguments()); } } GalleryPos GuiBindableRibbonGalleryList::IndexToGalleryPos(vint index) { if (0 <= index && index < joinedItemSource.Count()) { for (auto [group, groupIndex] : indexed(groupedItemSource)) { auto itemValues = group->GetItemValues(); vint itemCount = itemValues ? itemValues->GetCount() : 0; if (index >= itemCount) { index -= itemCount; } else { return GalleryPos(groupIndex, index); } } } return {}; } vint GuiBindableRibbonGalleryList::GalleryPosToIndex(GalleryPos pos) { if (0 <= pos.group && pos.group < groupedItemSource.Count()) { auto countBeforeGroup = GetCountBeforeGroup(pos.group); auto itemValues = groupedItemSource[pos.group]->GetItemValues(); vint itemCount = itemValues ? itemValues->GetCount() : 0; if (0 <= pos.item && pos.item < itemCount) { return countBeforeGroup + pos.item; } } return -1; } vint GuiBindableRibbonGalleryList::GetMinCount() { return layout->GetMinCount(); } void GuiBindableRibbonGalleryList::SetMinCount(vint value) { layout->SetMinCount(value); } vint GuiBindableRibbonGalleryList::GetMaxCount() { return layout->GetMaxCount(); } void GuiBindableRibbonGalleryList::SetMaxCount(vint value) { layout->SetMaxCount(value); } vint GuiBindableRibbonGalleryList::GetSelectedIndex() { return itemList->GetSelectedItemIndex(); } description::Value GuiBindableRibbonGalleryList::GetSelectedItem() { vint index = itemList->GetSelectedItemIndex(); if (index == -1) return Value(); auto pos = IndexToGalleryPos(index); return groupedItemSource[pos.group]->GetItemValues()->Get(pos.item); } void GuiBindableRibbonGalleryList::ApplyItem(vint index) { if (index == -1) { itemList->ClearSelection(); } else { itemList->SetSelected(index, true); itemList->EnsureItemVisible(index); } } void GuiBindableRibbonGalleryList::SelectItem(vint index) { skipItemAppliedEvent = true; ApplyItem(index); skipItemAppliedEvent = false; } vint GuiBindableRibbonGalleryList::GetVisibleItemCount() { return visibleItemCount; } void GuiBindableRibbonGalleryList::SetVisibleItemCount(vint value) { if (visibleItemCount != value) { visibleItemCount = value; UpdateLayoutSizeOffset(); } } GuiToolstripMenu* GuiBindableRibbonGalleryList::GetSubMenu() { return subMenu; } IDescriptable* GuiBindableRibbonGalleryList::QueryService(const WString& identifier) { if (identifier == IGuiMenuDropdownProvider::Identifier) { return (IGuiMenuDropdownProvider*)this; } else { return GuiRibbonGallery::QueryService(identifier); } } } } } /*********************************************************************** .\CONTROLS\TOOLSTRIPPACKAGE\GUIRIBBONIMPL.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { using namespace compositions; /*********************************************************************** GalleryItemArrangerRepeatComposition ***********************************************************************/ namespace ribbon_impl { void GalleryItemArrangerRepeatComposition::Layout_BeginPlaceItem(bool firstPhase, Rect newBounds, vint& newStartIndex) { if (firstPhase) { pim_itemWidth = itemWidth; newStartIndex = firstIndex; } } compositions::VirtualRepeatPlaceItemResult GalleryItemArrangerRepeatComposition::Layout_PlaceItem(bool firstPhase, bool newCreatedStyle, vint index, ItemStyleRecord style, Rect viewBounds, Rect& bounds, Margin& alignmentToParent) { alignmentToParent = Margin(-1, 0, -1, 0); bounds = Rect(Point((index - firstIndex) * itemWidth, 0), Size(itemWidth, 0)); if (firstPhase) { vint styleWidth = Layout_GetStylePreferredSize(style).x; if (pim_itemWidth < styleWidth) { pim_itemWidth = styleWidth; } } if (bounds.Right() + pim_itemWidth > viewBounds.Right()) { return VirtualRepeatPlaceItemResult::HitLastItem; } else { return VirtualRepeatPlaceItemResult::None; } } compositions::VirtualRepeatEndPlaceItemResult GalleryItemArrangerRepeatComposition::Layout_EndPlaceItem(bool firstPhase, Rect newBounds, vint newStartIndex) { bool result = false; if (firstPhase) { if (pim_itemWidth != itemWidth) { itemWidth = pim_itemWidth; result = true; } } if (!blockScrollUpdate) { UnblockScrollUpdate(); } return result ? VirtualRepeatEndPlaceItemResult::TotalSizeUpdated : VirtualRepeatEndPlaceItemResult::None; } void GalleryItemArrangerRepeatComposition::Layout_EndLayout(bool totalSizeUpdated) { } void GalleryItemArrangerRepeatComposition::Layout_InvalidateItemSizeCache() { itemWidth = 1; } void GalleryItemArrangerRepeatComposition::Layout_CalculateTotalSize(Size& full, Size& minimum) { full = Size(1, 1); minimum = Size(1, 1); } Size GalleryItemArrangerRepeatComposition::Layout_GetAdoptedSize(Size expectedSize) { return Size(1, 1); } GalleryItemArrangerRepeatComposition::GalleryItemArrangerRepeatComposition(GuiBindableRibbonGalleryList* _owner) :owner(_owner) { } GalleryItemArrangerRepeatComposition::~GalleryItemArrangerRepeatComposition() { } vint GalleryItemArrangerRepeatComposition::FindItemByVirtualKeyDirection(vint itemIndex, compositions::KeyDirection key) { vint count = itemSource->GetCount(); if (itemIndex < 0 || itemIndex >= count) return -1; vint groupCount = viewBounds.Width() / itemWidth; if (groupCount == 0) groupCount = 1; switch (key) { case KeyDirection::Left: itemIndex--; break; case KeyDirection::Right: itemIndex++; break; case KeyDirection::Home: itemIndex = 0; break; case KeyDirection::End: itemIndex = count; break; case KeyDirection::PageUp: itemIndex -= groupCount; break; case KeyDirection::PageDown: itemIndex += groupCount; break; default: return -1; } if (itemIndex < 0) return 0; else if (itemIndex >= count) return count - 1; else return itemIndex; } compositions::VirtualRepeatEnsureItemVisibleResult GalleryItemArrangerRepeatComposition::EnsureItemVisible(vint itemIndex) { if (!itemSource) return VirtualRepeatEnsureItemVisibleResult::NotMoved; if (itemIndex < 0 || itemIndex >= itemSource->GetCount()) { return VirtualRepeatEnsureItemVisibleResult::ItemNotExists; } vint groupCount = viewBounds.Width() / itemWidth; if (groupCount == 0) groupCount = 1; if (itemIndex < firstIndex) { firstIndex = itemIndex; InvalidateLayout(); } else if (itemIndex >= firstIndex + groupCount) { firstIndex = itemIndex - groupCount + 1; InvalidateLayout(); } return VirtualRepeatEnsureItemVisibleResult::NotMoved; } void GalleryItemArrangerRepeatComposition::ScrollUp() { vint count = itemSource->GetCount(); vint groupCount = viewBounds.Width() / itemWidth; if (groupCount == 0) groupCount = 1; if (count > groupCount) { firstIndex -= groupCount; if (firstIndex < 0) { firstIndex = 0; } InvalidateLayout(); } } void GalleryItemArrangerRepeatComposition::ScrollDown() { vint count = itemSource->GetCount(); vint groupCount = viewBounds.Width() / itemWidth; if (groupCount == 0) groupCount = 1; if (count > groupCount) { firstIndex += groupCount; if (firstIndex > count - groupCount) { firstIndex = count - groupCount; } InvalidateLayout(); } } void GalleryItemArrangerRepeatComposition::UnblockScrollUpdate() { blockScrollUpdate = false; vint count = itemSource->GetCount(); vint groupCount = viewBounds.Width() / pim_itemWidth; if (groupCount == 0) groupCount = 1; owner->SetScrollUpEnabled(firstIndex > 0); owner->SetScrollDownEnabled(firstIndex + groupCount < count); if (owner->layout->GetItemWidth() != pim_itemWidth) { owner->layout->SetItemWidth(pim_itemWidth); owner->UpdateLayoutSizeOffset(); } } /*********************************************************************** GalleryItemArranger ***********************************************************************/ GalleryItemArranger::GalleryItemArranger(GuiBindableRibbonGalleryList* _owner) : TBase(new TBase::ArrangerRepeatComposition(this, _owner)) { } GalleryItemArranger::~GalleryItemArranger() { } void GalleryItemArranger::ScrollUp() { GetRepeatComposition()->ScrollUp(); } void GalleryItemArranger::ScrollDown() { GetRepeatComposition()->ScrollDown(); } void GalleryItemArranger::UnblockScrollUpdate() { GetRepeatComposition()->UnblockScrollUpdate(); } /*********************************************************************** GalleryResponsiveLayout ***********************************************************************/ void GalleryResponsiveLayout::UpdateMinSize() { SetPreferredMinSize(Size(itemCount * itemWidth + sizeOffset.x, sizeOffset.y)); } GalleryResponsiveLayout::GalleryResponsiveLayout() { SetDirection(ResponsiveDirection::Horizontal); } GalleryResponsiveLayout::~GalleryResponsiveLayout() { } vint GalleryResponsiveLayout::GetMinCount() { return minCount; } vint GalleryResponsiveLayout::GetMaxCount() { return maxCount; } vint GalleryResponsiveLayout::GetItemWidth() { return itemWidth; } Size GalleryResponsiveLayout::GetSizeOffset() { return sizeOffset; } vint GalleryResponsiveLayout::GetVisibleItemCount() { return itemCount; } void GalleryResponsiveLayout::SetMinCount(vint value) { vint oldCount = GetLevelCount(); vint oldLevel = GetCurrentLevel(); if (minCount != value) { if (value < 0) value = 0; minCount = value; if (maxCount < minCount) maxCount = minCount; if (itemCount < minCount) itemCount = minCount; UpdateMinSize(); } bool countChanged = oldCount != GetLevelCount(); bool levelChanged = oldLevel != GetCurrentLevel(); if (countChanged) LevelCountChanged.Execute(GuiEventArgs(this)); if (levelChanged) CurrentLevelChanged.Execute(GuiEventArgs(this)); if (countChanged || levelChanged) OnResponsiveChildLevelUpdated(); } void GalleryResponsiveLayout::SetMaxCount(vint value) { vint oldCount = GetLevelCount(); vint oldLevel = GetCurrentLevel(); if (maxCount != value) { if (value < 0) value = 0; maxCount = value; if (minCount > maxCount) minCount = maxCount; if (itemCount > maxCount) itemCount = maxCount; UpdateMinSize(); } if (oldCount != GetLevelCount()) LevelCountChanged.Execute(GuiEventArgs(this)); if (oldLevel != GetCurrentLevel()) CurrentLevelChanged.Execute(GuiEventArgs(this)); } void GalleryResponsiveLayout::SetItemWidth(vint value) { if (itemWidth != value) { itemWidth = value; UpdateMinSize(); } } void GalleryResponsiveLayout::SetSizeOffset(Size value) { if (sizeOffset != value) { sizeOffset = value; UpdateMinSize(); } } vint GalleryResponsiveLayout::GetLevelCount() { return maxCount - minCount + 1; } vint GalleryResponsiveLayout::GetCurrentLevel() { return itemCount - minCount; } bool GalleryResponsiveLayout::LevelDown() { if (itemCount > minCount) { itemCount--; UpdateMinSize(); CurrentLevelChanged.Execute(GuiEventArgs(this)); return true; } return false; } bool GalleryResponsiveLayout::LevelUp() { if (itemCount < maxCount) { itemCount++; UpdateMinSize(); CurrentLevelChanged.Execute(GuiEventArgs(this)); return true; } return false; } } } } } /*********************************************************************** .\CONTROLS\TOOLSTRIPPACKAGE\GUITOOLSTRIPCOMMAND.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { using namespace collections; using namespace compositions; using namespace regex; /*********************************************************************** GuiToolstripCommand ***********************************************************************/ void GuiToolstripCommand::OnShortcutKeyItemExecuted(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if (enabled) { Executed.ExecuteWithNewSender(arguments, sender); } } void GuiToolstripCommand::OnRenderTargetChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { UpdateShortcutOwner(); } void GuiToolstripCommand::InvokeDescriptionChanged() { GuiEventArgs arguments; DescriptionChanged.Execute(arguments); } compositions::IGuiShortcutKeyManager* GuiToolstripCommand::GetShortcutManagerFromBuilder(Ptr builder) { if (builder->global) { return GetApplication()->GetGlobalShortcutKeyManager(); } else { if (attachedControlHost) { if (!attachedControlHost->GetShortcutKeyManager()) { attachedControlHost->SetShortcutKeyManager(new GuiShortcutKeyManager()); } return attachedControlHost->GetShortcutKeyManager(); } } return nullptr; } void GuiToolstripCommand::RemoveShortcut() { if (shortcutKeyItem) { shortcutKeyItem->Executed.Detach(shortcutKeyItemExecutedHandler); shortcutKeyItem->GetManager()->DestroyShortcut(shortcutKeyItem); } shortcutKeyItem = nullptr; shortcutKeyItemExecutedHandler = nullptr; } void GuiToolstripCommand::ReplaceShortcut(compositions::IGuiShortcutKeyItem* value) { if (shortcutKeyItem != value) { RemoveShortcut(); if (value) { shortcutKeyItem = value; shortcutKeyItemExecutedHandler = shortcutKeyItem->Executed.AttachMethod(this, &GuiToolstripCommand::OnShortcutKeyItemExecuted); } InvokeDescriptionChanged(); } } void GuiToolstripCommand::BuildShortcut(const WString& builderText) { List errors; if (auto parser = GetParserManager()->GetParser(L"SHORTCUT")) { if (auto builder = parser->ParseInternal(builderText, errors)) { shortcutBuilder = builder; if (auto shortcutKeyManager = GetShortcutManagerFromBuilder(builder)) { if (auto item = shortcutKeyManager->CreateShortcutIfNotExist(builder->ctrl, builder->shift, builder->alt, builder->key)) { ReplaceShortcut(item); } } } } } void GuiToolstripCommand::UpdateShortcutOwner() { GuiControlHost* host = nullptr; if (auto control = dynamic_cast(attachedRootObject)) { host = control->GetRelatedControlHost(); } else if (auto composition = dynamic_cast(attachedRootObject)) { host = composition->GetRelatedControlHost(); } if (attachedControlHost != host) { attachedControlHost = host; if (shortcutBuilder && !shortcutBuilder->global) { if (shortcutKeyItem) { ReplaceShortcut(nullptr); } BuildShortcut(shortcutBuilder->text); } } } GuiToolstripCommand::GuiToolstripCommand() { } GuiToolstripCommand::~GuiToolstripCommand() { RemoveShortcut(); shortcutBuilder = nullptr; } void GuiToolstripCommand::Attach(GuiInstanceRootObject* rootObject) { GuiGraphicsComposition* rootComposition = nullptr; if (attachedRootObject != rootObject) { if (attachedRootObject) { if (auto control = dynamic_cast(attachedRootObject)) { control->ControlSignalTrigerred.Detach(renderTargetChangedHandler); } else if (auto composition = dynamic_cast(attachedRootObject)) { composition->GetEventReceiver()->renderTargetChanged.Detach(renderTargetChangedHandler); } renderTargetChangedHandler = nullptr; } attachedRootObject = rootObject; if (attachedRootObject) { if (auto control = dynamic_cast(attachedRootObject)) { renderTargetChangedHandler = control->ControlSignalTrigerred.AttachLambda( [=](GuiGraphicsComposition* sender, GuiControlSignalEventArgs& arguments) { OnRenderTargetChanged(sender, arguments); }); } else if (auto composition = dynamic_cast(attachedRootObject)) { renderTargetChangedHandler = composition->GetEventReceiver()->renderTargetChanged.AttachMethod(this, &GuiToolstripCommand::OnRenderTargetChanged); } } UpdateShortcutOwner(); } } void GuiToolstripCommand::Detach(GuiInstanceRootObject* rootObject) { Attach(nullptr); } Ptr GuiToolstripCommand::GetLargeImage() { return largeImage; } void GuiToolstripCommand::SetLargeImage(Ptr value) { if (largeImage != value) { largeImage = value; InvokeDescriptionChanged(); } } Ptr GuiToolstripCommand::GetImage() { return image; } void GuiToolstripCommand::SetImage(Ptr value) { if(image!=value) { image=value; InvokeDescriptionChanged(); } } const WString& GuiToolstripCommand::GetText() { return text; } void GuiToolstripCommand::SetText(const WString& value) { if(text!=value) { text=value; InvokeDescriptionChanged(); } } compositions::IGuiShortcutKeyItem* GuiToolstripCommand::GetShortcut() { return shortcutKeyItem; } WString GuiToolstripCommand::GetShortcutBuilder() { return shortcutBuilder ? shortcutBuilder->text : L""; } void GuiToolstripCommand::SetShortcutBuilder(const WString& value) { BuildShortcut(value); } bool GuiToolstripCommand::GetEnabled() { return enabled; } void GuiToolstripCommand::SetEnabled(bool value) { if(enabled!=value) { enabled=value; InvokeDescriptionChanged(); } } bool GuiToolstripCommand::GetSelected() { return selected; } void GuiToolstripCommand::SetSelected(bool value) { if(selected!=value) { selected=value; InvokeDescriptionChanged(); } } /*********************************************************************** GuiToolstripCommand::ShortcutBuilder Parser ***********************************************************************/ class GuiToolstripCommandShortcutParser : public Object, public IGuiParser { typedef GuiToolstripCommand::ShortcutBuilder ShortcutBuilder; public: Regex regexShortcut; const vint _global; const vint _ctrl; const vint _shift; const vint _alt; const vint _key; GuiToolstripCommandShortcutParser() : regexShortcut(L"((global:))?((Ctrl)/+|(Shift)/+|(Alt)/+)*(/.+)") , _global(regexShortcut.CaptureNames().IndexOf(L"global")) , _ctrl(regexShortcut.CaptureNames().IndexOf(L"ctrl")) , _shift(regexShortcut.CaptureNames().IndexOf(L"shift")) , _alt(regexShortcut.CaptureNames().IndexOf(L"alt")) , _key(regexShortcut.CaptureNames().IndexOf(L"key")) { } Ptr ParseInternal(const WString& text, collections::List& errors)override { Ptr match=regexShortcut.MatchHead(text); if (match && match->Result().Length() != text.Length()) { glr::ParsingError error; error.message = L"Failed to parse a shortcut \"" + text + L"\"."; errors.Add(error); return nullptr; } auto builder = Ptr(new ShortcutBuilder); builder->text = text; builder->global = match->Groups().Contains(_global); builder->ctrl = match->Groups().Contains(_ctrl); builder->shift = match->Groups().Contains(_shift); builder->alt = match->Groups().Contains(_alt); WString name = match->Groups()[_key][0].Value(); builder->key = GetCurrentController()->InputService()->GetKey(name); return builder->key == VKEY::KEY_UNKNOWN ? nullptr : builder; } }; /*********************************************************************** GuiToolstripCommandPlugin ***********************************************************************/ class GuiToolstripCommandPlugin : public Object, public IGuiPlugin { public: GUI_PLUGIN_NAME(GacUI_Compiler_ShortcutParser) { GUI_PLUGIN_DEPEND(GacUI_Parser); } void Load(bool controllerUnrelatedPlugins, bool controllerRelatedPlugins)override { if (controllerUnrelatedPlugins) { IGuiParserManager* manager = GetParserManager(); manager->SetParser(L"SHORTCUT", Ptr(new GuiToolstripCommandShortcutParser)); } } void Unload(bool controllerUnrelatedPlugins, bool controllerRelatedPlugins)override { } }; GUI_REGISTER_PLUGIN(GuiToolstripCommandPlugin) } } } /*********************************************************************** .\CONTROLS\TOOLSTRIPPACKAGE\GUITOOLSTRIPMENU.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace controls { using namespace collections; using namespace compositions; /*********************************************************************** GuiToolstripCollectionBase ***********************************************************************/ const wchar_t* const IToolstripUpdateLayoutInvoker::Identifier = L"vl::presentation::controls::IToolstripUpdateLayoutInvoker"; void GuiToolstripCollectionBase::InvokeUpdateLayout() { if(contentCallback) { contentCallback->UpdateLayout(); } } bool GuiToolstripCollectionBase::QueryInsert(vint index, GuiControl* const& child) { return !items.Contains(child) && !child->GetBoundsComposition()->GetParent(); } void GuiToolstripCollectionBase::BeforeRemove(vint index, GuiControl* const& child) { if (auto invoker = child->QueryTypedService()) { invoker->SetCallback(nullptr); } InvokeUpdateLayout(); } void GuiToolstripCollectionBase::AfterInsert(vint index, GuiControl* const& child) { if (auto invoker = child->QueryTypedService()) { invoker->SetCallback(contentCallback); } InvokeUpdateLayout(); } void GuiToolstripCollectionBase::AfterRemove(vint index, vint count) { InvokeUpdateLayout(); } GuiToolstripCollectionBase::GuiToolstripCollectionBase(IToolstripUpdateLayout* _contentCallback) :contentCallback(_contentCallback) { } GuiToolstripCollectionBase::~GuiToolstripCollectionBase() { } /*********************************************************************** GuiToolstripCollection ***********************************************************************/ void GuiToolstripCollection::UpdateItemVisibility(vint index, GuiControl* child) { auto stackItem = stackComposition->GetStackItems()[index]; if (child->GetVisible()) { stackItem->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); child->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); } else { stackItem->SetMinSizeLimitation(GuiGraphicsComposition::NoLimit); child->GetBoundsComposition()->SetAlignmentToParent(Margin(-1, -1, -1, -1)); } } void GuiToolstripCollection::OnItemVisibleChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { auto child = sender->GetRelatedControl(); vint index = IndexOf(child); UpdateItemVisibility(index, child); InvokeUpdateLayout(); } void GuiToolstripCollection::BeforeRemove(vint index, GuiControl* const& child) { GuiStackItemComposition* stackItem = stackComposition->GetStackItems().Get(index); auto eventHandler = eventHandlers[index]; child->VisibleChanged.Detach(eventHandler); eventHandlers.RemoveAt(index); GuiToolstripCollectionBase::BeforeRemove(index, child); } void GuiToolstripCollection::AfterInsert(vint index, GuiControl* const& child) { { GuiStackItemComposition* stackItem = new GuiStackItemComposition; stackItem->AddChild(child->GetBoundsComposition()); stackComposition->InsertStackItem(index, stackItem); } { auto eventHandler = child->VisibleChanged.AttachMethod(this, &GuiToolstripCollection::OnItemVisibleChanged); eventHandlers.Insert(index, eventHandler); } UpdateItemVisibility(index, child); GuiToolstripCollectionBase::AfterInsert(index, child); } void GuiToolstripCollection::AfterRemove(vint index, vint count) { for (vint i = 0; i < count; i++) { auto stackItem = stackComposition->GetStackItems().Get(index); stackComposition->RemoveChild(stackItem); SafeDeleteComposition(stackItem); } GuiToolstripCollectionBase::AfterRemove(index, count); } GuiToolstripCollection::GuiToolstripCollection(IToolstripUpdateLayout* _contentCallback, compositions::GuiStackComposition* _stackComposition) :GuiToolstripCollectionBase(_contentCallback) ,stackComposition(_stackComposition) { stackComposition->SetPreferredMinSize(Size(1, 1)); } GuiToolstripCollection::~GuiToolstripCollection() { } /*********************************************************************** GuiToolstripMenu ***********************************************************************/ void GuiToolstripMenu::UpdateLayout() { sharedSizeRootComposition->ForceCalculateSizeImmediately(); } GuiToolstripMenu::GuiToolstripMenu(theme::ThemeName themeName, GuiControl* _owner) :GuiMenu(themeName, _owner) { sharedSizeRootComposition = new GuiSharedSizeRootComposition(); sharedSizeRootComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); sharedSizeRootComposition->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); containerComposition->AddChild(sharedSizeRootComposition); stackComposition=new GuiStackComposition; stackComposition->SetDirection(GuiStackComposition::Vertical); stackComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); stackComposition->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); sharedSizeRootComposition->AddChild(stackComposition); toolstripItems = Ptr(new GuiToolstripCollection(this, stackComposition)); } GuiToolstripMenu::~GuiToolstripMenu() { } collections::ObservableListBase& GuiToolstripMenu::GetToolstripItems() { return *toolstripItems.Obj(); } /*********************************************************************** GuiToolstripMenuBar ***********************************************************************/ GuiToolstripMenuBar::GuiToolstripMenuBar(theme::ThemeName themeName) :GuiMenuBar(themeName) { stackComposition=new GuiStackComposition; stackComposition->SetDirection(GuiStackComposition::Horizontal); stackComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); stackComposition->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); containerComposition->AddChild(stackComposition); toolstripItems=Ptr(new GuiToolstripCollection(nullptr, stackComposition)); } GuiToolstripMenuBar::~GuiToolstripMenuBar() { } collections::ObservableListBase& GuiToolstripMenuBar::GetToolstripItems() { return *toolstripItems.Obj(); } /*********************************************************************** GuiToolstripToolBar ***********************************************************************/ IGuiMenuService* GuiToolstripToolBar::GetParentMenuService() { return GetParent() ? GetParent()->QueryTypedService() : nullptr; } IGuiMenuService::Direction GuiToolstripToolBar::GetPreferredDirection() { return IGuiMenuService::Horizontal; } theme::ThemeName GuiToolstripToolBar::GetHostThemeName() { return GetControlThemeName(); } bool GuiToolstripToolBar::IsActiveState() { return GetOpeningMenu() != nullptr; } bool GuiToolstripToolBar::IsSubMenuActivatedByMouseDown() { return false; } GuiToolstripToolBar::GuiToolstripToolBar(theme::ThemeName themeName) :GuiControl(themeName) { stackComposition=new GuiStackComposition; stackComposition->SetDirection(GuiStackComposition::Horizontal); stackComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); stackComposition->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); containerComposition->AddChild(stackComposition); toolstripItems=Ptr(new GuiToolstripCollection(nullptr, stackComposition)); } GuiToolstripToolBar::~GuiToolstripToolBar() { } collections::ObservableListBase& GuiToolstripToolBar::GetToolstripItems() { return *toolstripItems.Obj(); } IDescriptable* GuiToolstripToolBar::QueryService(const WString& identifier) { if (identifier == IGuiMenuService::Identifier) { return (IGuiMenuService*)this; } else { return GuiControl::QueryService(identifier); } } /*********************************************************************** GuiToolstripButton ***********************************************************************/ void GuiToolstripButton::SetCallback(IToolstripUpdateLayout* _callback) { callback = _callback; } void GuiToolstripButton::OnActiveAlt() { auto host = GetSubMenuHost(); if (host == this) { GuiMenuButton::OnActiveAlt(); } else { host->QueryTypedService()->OnActiveAlt(); } } void GuiToolstripButton::UpdateCommandContent() { if(command) { SetLargeImage(command->GetLargeImage()); SetImage(command->GetImage()); SetText(command->GetText()); SetEnabled(command->GetEnabled()); SetSelected(command->GetSelected()); if(command->GetShortcut()) { SetShortcutText(command->GetShortcut()->GetName()); } else { SetShortcutText(L""); } } else { SetLargeImage(nullptr); SetImage(nullptr); SetText(L""); SetEnabled(true); SetSelected(false); SetShortcutText(L""); } } void GuiToolstripButton::OnLayoutAwaredPropertyChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if (callback) { callback->UpdateLayout(); } } void GuiToolstripButton::OnClicked(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { if(command) { command->Executed.ExecuteWithNewSender(arguments, sender); } } void GuiToolstripButton::OnCommandDescriptionChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) { UpdateCommandContent(); } GuiToolstripButton::GuiToolstripButton(theme::ThemeName themeName) :GuiMenuButton(themeName) ,command(0) { SetAutoFocus(false); Clicked.AttachMethod(this, &GuiToolstripButton::OnClicked); TextChanged.AttachMethod(this, &GuiToolstripButton::OnLayoutAwaredPropertyChanged); ShortcutTextChanged.AttachMethod(this, &GuiToolstripButton::OnLayoutAwaredPropertyChanged); } GuiToolstripButton::~GuiToolstripButton() { } GuiToolstripCommand* GuiToolstripButton::GetCommand() { return command; } void GuiToolstripButton::SetCommand(GuiToolstripCommand* value) { if(command!=value) { if(command) { command->DescriptionChanged.Detach(descriptionChangedHandler); } command=0; descriptionChangedHandler=0; if(value) { command=value; descriptionChangedHandler=command->DescriptionChanged.AttachMethod(this, &GuiToolstripButton::OnCommandDescriptionChanged); } UpdateCommandContent(); } } GuiToolstripMenu* GuiToolstripButton::GetToolstripSubMenu() { return dynamic_cast(GetSubMenu()); } GuiToolstripMenu* GuiToolstripButton::EnsureToolstripSubMenu() { if (!GetSubMenu()) { CreateToolstripSubMenu({}); } return dynamic_cast(GetSubMenu()); } void GuiToolstripButton::CreateToolstripSubMenu(TemplateProperty subMenuTemplate) { if (!subMenu) { auto newSubMenu = new GuiToolstripMenu(theme::ThemeName::Menu, this); if (subMenuTemplate) { newSubMenu->SetControlTemplate(subMenuTemplate); } else { newSubMenu->SetControlTemplate(TypedControlTemplateObject(true)->GetSubMenuTemplate()); } SetSubMenu(newSubMenu, true); } } IDescriptable* GuiToolstripButton::QueryService(const WString& identifier) { if (identifier == IToolstripUpdateLayoutInvoker::Identifier) { return (IToolstripUpdateLayoutInvoker*)this; } else { return GuiMenuButton::QueryService(identifier); } } /*********************************************************************** GuiToolstripNestedContainer ***********************************************************************/ void GuiToolstripNestedContainer::UpdateLayout() { // It could happen if a GuiToolstripGroupContainer contains something other that GuiToolstripGroup if (callback && callback != this) { callback->UpdateLayout(); } } void GuiToolstripNestedContainer::SetCallback(IToolstripUpdateLayout* _callback) { callback = _callback; } GuiToolstripNestedContainer::GuiToolstripNestedContainer(theme::ThemeName themeName) :GuiControl(themeName) { } GuiToolstripNestedContainer::~GuiToolstripNestedContainer() { } IDescriptable* GuiToolstripNestedContainer::QueryService(const WString& identifier) { if (identifier == IToolstripUpdateLayoutInvoker::Identifier) { return (IToolstripUpdateLayoutInvoker*)this; } else { return GuiControl::QueryService(identifier); } } /*********************************************************************** GuiToolstripGroupContainer::GroupCollection ***********************************************************************/ void GuiToolstripGroupContainer::GroupCollection::BeforeRemove(vint index, GuiControl* const& child) { auto controlStackItem = container->stackComposition->GetStackItems()[index * 2]; CHECK_ERROR(controlStackItem->RemoveChild(child->GetBoundsComposition()), L"GuiToolstripGroupContainer::GroupCollection::BeforeRemove(vint, GuiControl* const&)#Internal error"); } void GuiToolstripGroupContainer::GroupCollection::AfterInsert(vint index, GuiControl* const& child) { auto controlStackItem = new GuiStackItemComposition; child->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); CHECK_ERROR(controlStackItem->AddChild(child->GetBoundsComposition()), L"GuiToolstripGroupContainer::GroupCollection::AfterInsert(vint, GuiControl* const&)#Internal error"); if (container->stackComposition->GetStackItems().Count() > 0) { auto splitterStackItem = new GuiStackItemComposition; auto splitter = new GuiControl(container->splitterThemeName); if (splitterTemplate) { splitter->SetControlTemplate(splitterTemplate); } splitter->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); splitterStackItem->AddChild(splitter->GetBoundsComposition()); container->stackComposition->InsertStackItem(index == 0 ? 0 : index * 2 - 1, splitterStackItem); } container->stackComposition->InsertStackItem(index * 2, controlStackItem); GuiToolstripCollectionBase::AfterInsert(index, child); } void GuiToolstripGroupContainer::GroupCollection::AfterRemove(vint index, vint count) { vint min = index * 2; vint max = (index + count - 1) * 2; for (vint i = min; i <= max; i++) { auto stackItem = container->stackComposition->GetStackItems()[min]; container->stackComposition->RemoveChild(stackItem); SafeDeleteComposition(stackItem); } GuiToolstripCollectionBase::AfterRemove(index, count); } GuiToolstripGroupContainer::GroupCollection::GroupCollection(GuiToolstripGroupContainer* _container) :GuiToolstripCollectionBase(_container) , container(_container) { } GuiToolstripGroupContainer::GroupCollection::~GroupCollection() { } GuiToolstripGroupContainer::ControlTemplatePropertyType GuiToolstripGroupContainer::GroupCollection::GetSplitterTemplate() { return splitterTemplate; } void GuiToolstripGroupContainer::GroupCollection::SetSplitterTemplate(const ControlTemplatePropertyType& value) { splitterTemplate = value; RebuildSplitters(); } void GuiToolstripGroupContainer::GroupCollection::RebuildSplitters() { auto stack = container->stackComposition; vint count = stack->GetStackItems().Count(); for (vint i = 1; i < count; i += 2) { auto stackItem = stack->GetStackItems()[i]; { auto control = stackItem->Children()[0]->GetAssociatedControl(); CHECK_ERROR(control != nullptr, L"GuiToolstripGroupContainer::GroupCollection::RebuildSplitters()#Internal error"); stackItem->RemoveChild(control->GetBoundsComposition()); delete control; } { auto control = new GuiControl(container->splitterThemeName); if (splitterTemplate) { control->SetControlTemplate(splitterTemplate); } control->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); stackItem->AddChild(control->GetBoundsComposition()); } } } /*********************************************************************** GuiToolstripGroupContainer ***********************************************************************/ void GuiToolstripGroupContainer::OnParentLineChanged() { auto newDirection = GuiStackComposition::Horizontal; auto newTheme = theme::ThemeName::ToolstripSplitter; if (auto service = QueryTypedService()) { if (service->GetPreferredDirection() == IGuiMenuService::Vertical) { newTheme = theme::ThemeName::MenuSplitter; newDirection = GuiStackComposition::Vertical; } switch (service->GetHostThemeName()) { case theme::ThemeName::MenuBar: newTheme = theme::ThemeName::MenuSplitter; break; case theme::ThemeName::ToolstripToolBar: newTheme = theme::ThemeName::ToolstripSplitter; break; case theme::ThemeName::ToolstripToolBarInMenu: newTheme = theme::ThemeName::ToolstripSplitterInMenu; break; default:; } } if (newDirection != stackComposition->GetDirection() || newTheme != splitterThemeName) { splitterThemeName = newTheme; stackComposition->SetDirection(newDirection); groupCollection->RebuildSplitters(); UpdateLayout(); } GuiControl::OnParentLineChanged(); } GuiToolstripGroupContainer::GuiToolstripGroupContainer(theme::ThemeName themeName) :GuiToolstripNestedContainer(themeName) , splitterThemeName(theme::ThemeName::ToolstripSplitter) { stackComposition = new GuiStackComposition; stackComposition->SetDirection(GuiStackComposition::Horizontal); stackComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); stackComposition->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); containerComposition->AddChild(stackComposition); groupCollection = Ptr(new GroupCollection(this)); } GuiToolstripGroupContainer::~GuiToolstripGroupContainer() { } GuiToolstripGroupContainer::ControlTemplatePropertyType GuiToolstripGroupContainer::GetSplitterTemplate() { return groupCollection->GetSplitterTemplate(); } void GuiToolstripGroupContainer::SetSplitterTemplate(const ControlTemplatePropertyType& value) { groupCollection->SetSplitterTemplate(value); } collections::ObservableListBase& GuiToolstripGroupContainer::GetToolstripItems() { return *groupCollection.Obj(); } /*********************************************************************** GuiToolstripGroup ***********************************************************************/ void GuiToolstripGroup::OnParentLineChanged() { auto direction = GuiStackComposition::Horizontal; if (auto service = QueryTypedService()) { if (service->GetPreferredDirection() == IGuiMenuService::Vertical) { direction = GuiStackComposition::Vertical; } } if (direction != stackComposition->GetDirection()) { stackComposition->SetDirection(direction); UpdateLayout(); } GuiControl::OnParentLineChanged(); } GuiToolstripGroup::GuiToolstripGroup(theme::ThemeName themeName) :GuiToolstripNestedContainer(themeName) { stackComposition = new GuiStackComposition; stackComposition->SetDirection(GuiStackComposition::Horizontal); stackComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); stackComposition->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); containerComposition->AddChild(stackComposition); toolstripItems = Ptr(new GuiToolstripCollection(nullptr, stackComposition)); } GuiToolstripGroup::~GuiToolstripGroup() { } collections::ObservableListBase& GuiToolstripGroup::GetToolstripItems() { return *toolstripItems.Obj(); } } } } /*********************************************************************** .\GRAPHICSCOMPOSITION\GUIGRAPHICSAXIS.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace compositions { /*********************************************************************** GuiDefaultAxis ***********************************************************************/ GuiDefaultAxis::GuiDefaultAxis() { } GuiDefaultAxis::~GuiDefaultAxis() { } Size GuiDefaultAxis::RealSizeToVirtualSize(Size size) { return size; } Size GuiDefaultAxis::VirtualSizeToRealSize(Size size) { return size; } Point GuiDefaultAxis::RealPointToVirtualPoint(Size realFullSize, Point point) { return point; } Point GuiDefaultAxis::VirtualPointToRealPoint(Size realFullSize, Point point) { return point; } Rect GuiDefaultAxis::RealRectToVirtualRect(Size realFullSize, Rect rect) { return rect; } Rect GuiDefaultAxis::VirtualRectToRealRect(Size realFullSize, Rect rect) { return rect; } Margin GuiDefaultAxis::RealMarginToVirtualMargin(Margin margin) { return margin; } Margin GuiDefaultAxis::VirtualMarginToRealMargin(Margin margin) { return margin; } KeyDirection GuiDefaultAxis::RealKeyDirectionToVirtualKeyDirection(KeyDirection key) { return key; } /*********************************************************************** GuiAxis ***********************************************************************/ GuiAxis::GuiAxis(AxisDirection _axisDirection) :axisDirection(_axisDirection) { } GuiAxis::~GuiAxis() { } AxisDirection GuiAxis::GetDirection() { return axisDirection; } Size GuiAxis::RealSizeToVirtualSize(Size size) { switch(axisDirection) { case AxisDirection::LeftDown: case AxisDirection::RightDown: case AxisDirection::LeftUp: case AxisDirection::RightUp: return Size(size.x, size.y); case AxisDirection::DownLeft: case AxisDirection::DownRight: case AxisDirection::UpLeft: case AxisDirection::UpRight: return Size(size.y, size.x); } return size; } Size GuiAxis::VirtualSizeToRealSize(Size size) { return RealSizeToVirtualSize(size); } Point GuiAxis::RealPointToVirtualPoint(Size realFullSize, Point point) { Rect rect(point, Size(0, 0)); return RealRectToVirtualRect(realFullSize, rect).LeftTop(); } Point GuiAxis::VirtualPointToRealPoint(Size realFullSize, Point point) { Rect rect(point, Size(0, 0)); return VirtualRectToRealRect(realFullSize, rect).LeftTop(); } Rect GuiAxis::RealRectToVirtualRect(Size realFullSize, Rect rect) { vint x1=rect.x1; vint x2=realFullSize.x-rect.x2; vint y1=rect.y1; vint y2=realFullSize.y-rect.y2; vint w=rect.Width(); vint h=rect.Height(); switch(axisDirection) { case AxisDirection::LeftDown: return Rect(Point(x2, y1), Size(w, h)); case AxisDirection::RightDown: return Rect(Point(x1, y1), Size(w, h)); case AxisDirection::LeftUp: return Rect(Point(x2, y2), Size(w, h)); case AxisDirection::RightUp: return Rect(Point(x1, y2), Size(w, h)); case AxisDirection::DownLeft: return Rect(Point(y1, x2), Size(h, w)); case AxisDirection::DownRight: return Rect(Point(y1, x1), Size(h, w)); case AxisDirection::UpLeft: return Rect(Point(y2, x2), Size(h, w)); case AxisDirection::UpRight: return Rect(Point(y2, x1), Size(h, w)); } return rect; } Rect GuiAxis::VirtualRectToRealRect(Size realFullSize, Rect rect) { realFullSize=RealSizeToVirtualSize(realFullSize); vint x1=rect.x1; vint x2=realFullSize.x-rect.x2; vint y1=rect.y1; vint y2=realFullSize.y-rect.y2; vint w=rect.Width(); vint h=rect.Height(); switch(axisDirection) { case AxisDirection::LeftDown: return Rect(Point(x2, y1), Size(w, h)); case AxisDirection::RightDown: return Rect(Point(x1, y1), Size(w, h)); case AxisDirection::LeftUp: return Rect(Point(x2, y2), Size(w, h)); case AxisDirection::RightUp: return Rect(Point(x1, y2), Size(w, h)); case AxisDirection::DownLeft: return Rect(Point(y2, x1), Size(h, w)); case AxisDirection::DownRight: return Rect(Point(y1, x1), Size(h, w)); case AxisDirection::UpLeft: return Rect(Point(y2, x2), Size(h, w)); case AxisDirection::UpRight: return Rect(Point(y1, x2), Size(h, w)); } return rect; } Margin GuiAxis::RealMarginToVirtualMargin(Margin margin) { vint x1=margin.left; vint x2=margin.right; vint y1=margin.top; vint y2=margin.bottom; switch(axisDirection) { case AxisDirection::LeftDown: return Margin(x2, y1, x1, y2); case AxisDirection::RightDown: return Margin(x1, y1, x2, y2); case AxisDirection::LeftUp: return Margin(x2, y2, x1, y1); case AxisDirection::RightUp: return Margin(x1, y2, x2, y1); case AxisDirection::DownLeft: return Margin(y1, x2, y2, x1); case AxisDirection::DownRight: return Margin(y1, x1, y2, x2); case AxisDirection::UpLeft: return Margin(y2, x2, y1, x1); case AxisDirection::UpRight: return Margin(y2, x1, y1, x2); } return margin; } Margin GuiAxis::VirtualMarginToRealMargin(Margin margin) { vint x1=margin.left; vint x2=margin.right; vint y1=margin.top; vint y2=margin.bottom; switch(axisDirection) { case AxisDirection::LeftDown: return Margin(x2, y1, x1, y2); case AxisDirection::RightDown: return Margin(x1, y1, x2, y2); case AxisDirection::LeftUp: return Margin(x2, y2, x1, y1); case AxisDirection::RightUp: return Margin(x1, y2, x2, y1); case AxisDirection::DownLeft: return Margin(y2, x1, y1, x2); case AxisDirection::DownRight: return Margin(y1, x1, y2, x2); case AxisDirection::UpLeft: return Margin(y2, x2, y1, x1); case AxisDirection::UpRight: return Margin(y1, x2, y2, x1); default:; } return margin; } KeyDirection GuiAxis::RealKeyDirectionToVirtualKeyDirection(KeyDirection key) { bool pageKey=false; switch(key) { case KeyDirection::PageUp: pageKey=true; key=KeyDirection::Up; break; case KeyDirection::PageDown: pageKey=true; key=KeyDirection::Down; break; case KeyDirection::PageLeft: pageKey=true; key=KeyDirection::Left; break; case KeyDirection::PageRight: pageKey=true; key=KeyDirection::Right; break; default:; } switch(key) { case KeyDirection::Up: switch(axisDirection) { case AxisDirection::LeftDown: key=KeyDirection::Up; break; case AxisDirection::RightDown: key=KeyDirection::Up; break; case AxisDirection::LeftUp: key=KeyDirection::Down; break; case AxisDirection::RightUp: key=KeyDirection::Down; break; case AxisDirection::DownLeft: key=KeyDirection::Left; break; case AxisDirection::DownRight: key=KeyDirection::Left; break; case AxisDirection::UpLeft: key=KeyDirection::Right; break; case AxisDirection::UpRight: key=KeyDirection::Right; break; } break; case KeyDirection::Down: switch(axisDirection) { case AxisDirection::LeftDown: key=KeyDirection::Down; break; case AxisDirection::RightDown: key=KeyDirection::Down; break; case AxisDirection::LeftUp: key=KeyDirection::Up; break; case AxisDirection::RightUp: key=KeyDirection::Up; break; case AxisDirection::DownLeft: key=KeyDirection::Right; break; case AxisDirection::DownRight: key=KeyDirection::Right; break; case AxisDirection::UpLeft: key=KeyDirection::Left; break; case AxisDirection::UpRight: key=KeyDirection::Left; break; } break; case KeyDirection::Left: switch(axisDirection) { case AxisDirection::LeftDown: key=KeyDirection::Right; break; case AxisDirection::RightDown: key=KeyDirection::Left; break; case AxisDirection::LeftUp: key=KeyDirection::Right; break; case AxisDirection::RightUp: key=KeyDirection::Left; break; case AxisDirection::DownLeft: key=KeyDirection::Down; break; case AxisDirection::DownRight: key=KeyDirection::Up; break; case AxisDirection::UpLeft: key=KeyDirection::Down; break; case AxisDirection::UpRight: key=KeyDirection::Up; break; } break; case KeyDirection::Right: switch(axisDirection) { case AxisDirection::LeftDown: key=KeyDirection::Left; break; case AxisDirection::RightDown: key=KeyDirection::Right; break; case AxisDirection::LeftUp: key=KeyDirection::Left; break; case AxisDirection::RightUp: key=KeyDirection::Right; break; case AxisDirection::DownLeft: key=KeyDirection::Up; break; case AxisDirection::DownRight: key=KeyDirection::Down; break; case AxisDirection::UpLeft: key=KeyDirection::Up; break; case AxisDirection::UpRight: key=KeyDirection::Down; break; } break; case KeyDirection::Home: switch(axisDirection) { case AxisDirection::LeftDown: key=KeyDirection::Home; break; case AxisDirection::RightDown: key=KeyDirection::Home; break; case AxisDirection::LeftUp: key=KeyDirection::End; break; case AxisDirection::RightUp: key=KeyDirection::End; break; case AxisDirection::DownLeft: key=KeyDirection::Home; break; case AxisDirection::DownRight: key=KeyDirection::Home; break; case AxisDirection::UpLeft: key=KeyDirection::End; break; case AxisDirection::UpRight: key=KeyDirection::End; break; } break; case KeyDirection::End: switch(axisDirection) { case AxisDirection::LeftDown: key=KeyDirection::End; break; case AxisDirection::RightDown: key=KeyDirection::End; break; case AxisDirection::LeftUp: key=KeyDirection::Home; break; case AxisDirection::RightUp: key=KeyDirection::Home; break; case AxisDirection::DownLeft: key=KeyDirection::End; break; case AxisDirection::DownRight: key=KeyDirection::End; break; case AxisDirection::UpLeft: key=KeyDirection::Home; break; case AxisDirection::UpRight: key=KeyDirection::Home; break; } break; default:; } if(pageKey) { switch(key) { case KeyDirection::Up: key=KeyDirection::PageUp; break; case KeyDirection::Down: key=KeyDirection::PageDown; break; case KeyDirection::Left: key=KeyDirection::PageLeft; break; case KeyDirection::Right: key=KeyDirection::PageRight; break; default:; } } return key; } } } } /*********************************************************************** .\GRAPHICSCOMPOSITION\GUIGRAPHICSFLOWCOMPOSITION.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace compositions { using namespace collections; /*********************************************************************** GuiFlowComposition ***********************************************************************/ void GuiFlowComposition::Layout_UpdateFlowItemLayout(vint maxVirtualWidth) { for (auto item : layout_flowItems) { item->Layout_SetCachedMinSize(item->Layout_CalculateMinSizeHelper()); } if (layout_lastVirtualWidth != maxVirtualWidth) { layout_invalid = true; layout_lastVirtualWidth = maxVirtualWidth; } if (!layout_invalid) return; layout_invalid = false; vint currentIndex = 0; vint rowTop = 0; while (currentIndex < layout_flowItems.Count()) { auto currentItemVirtualMinSize = axis->RealSizeToVirtualSize(layout_flowItems[currentIndex]->GetCachedMinSize()); vint rowWidth = currentItemVirtualMinSize.x; vint rowHeight = currentItemVirtualMinSize.y; vint rowItemCount = 1; for (vint i = currentIndex + 1; i < layout_flowItems.Count(); i++) { auto itemSize = axis->RealSizeToVirtualSize(layout_flowItems[i]->GetCachedMinSize()); vint itemWidth = itemSize.x + columnPadding; if (rowWidth + itemWidth > maxVirtualWidth) { break; } rowWidth += itemWidth; if (rowHeight < itemSize.y) { rowHeight = itemSize.y; } rowItemCount++; } vint baseLine = 0; Array itemBaseLines(rowItemCount); for (vint i = 0; i < rowItemCount; i++) { vint index = currentIndex + i; vint itemBaseLine = 0; auto itemSize = axis->RealSizeToVirtualSize(layout_flowItems[index]->GetCachedMinSize()); auto option = layout_flowItems[index]->GetFlowOption(); switch (option.baseline) { case GuiFlowOption::FromTop: itemBaseLine = option.distance; break; case GuiFlowOption::FromBottom: itemBaseLine = itemSize.y - option.distance; break; case GuiFlowOption::Percentage: itemBaseLine = (vint)(itemSize.y*option.percentage); break; } itemBaseLines[i] = itemBaseLine; if (baseLine < itemBaseLine) { baseLine = itemBaseLine; } } vint rowUsedWidth = 0; for (vint i = 0; i < rowItemCount; i++) { vint index = currentIndex + i; auto itemSize = axis->RealSizeToVirtualSize(layout_flowItems[index]->GetCachedMinSize()); vint itemLeft = 0; vint itemTop = rowTop + baseLine - itemBaseLines[i]; switch (alignment) { case FlowAlignment::Left: itemLeft = rowUsedWidth + i * columnPadding; break; case FlowAlignment::Center: itemLeft = rowUsedWidth + i * columnPadding + (maxVirtualWidth - rowWidth) / 2; break; case FlowAlignment::Right: itemLeft = rowUsedWidth + i * columnPadding + (maxVirtualWidth - rowWidth); break; case FlowAlignment::Extend: if (i == 0) { itemLeft = rowUsedWidth; } else { itemLeft = rowUsedWidth + (vint)((double)(maxVirtualWidth - rowWidth) * i / (rowItemCount - 1)) + i * columnPadding; } break; } layout_flowItems[index]->layout_virtualBounds = Rect({ itemLeft,itemTop }, itemSize); rowUsedWidth += itemSize.x; } rowTop += rowHeight + rowPadding; currentIndex += rowItemCount; } layout_minVirtualHeight = rowTop == 0 ? 0 : rowTop - rowPadding; } Size GuiFlowComposition::Layout_UpdateFlowItemLayoutByConstraint(Size constraintSize) { Size extraSize( extraMargin.left + extraMargin.right, extraMargin.top + extraMargin.bottom ); constraintSize.x -= extraSize.x; constraintSize.y -= extraSize.y; if (constraintSize.x < 0) constraintSize.x = 0; if (constraintSize.y < 0) constraintSize.y = 0; vint maxVirtualWidth = axis->RealSizeToVirtualSize(constraintSize).x; Layout_UpdateFlowItemLayout(maxVirtualWidth); return extraSize; } void GuiFlowComposition::OnChildInserted(GuiGraphicsComposition* child) { GuiBoundsComposition::OnChildInserted(child); auto item = dynamic_cast(child); if (item && !layout_flowItems.Contains(item)) { layout_flowItems.Add(item); } } void GuiFlowComposition::OnChildRemoved(GuiGraphicsComposition* child) { GuiBoundsComposition::OnChildRemoved(child); auto item = dynamic_cast(child); if (item) { layout_flowItems.Remove(item); } } void GuiFlowComposition::OnCompositionStateChanged() { GuiBoundsComposition::OnCompositionStateChanged(); layout_invalid = true; } Size GuiFlowComposition::Layout_CalculateMinSize() { Size minSize = GuiBoundsComposition::Layout_CalculateMinSize(); if (GetMinSizeLimitation() == GuiGraphicsComposition::LimitToElementAndChildren && layout_flowItems.Count() > 0) { Size cachedSize = cachedBounds.GetSize(); Size constraintSize( minSize.x > cachedSize.x ? minSize.x : cachedSize.x, minSize.y > cachedSize.y ? minSize.y : cachedSize.y ); Size extraSize = Layout_UpdateFlowItemLayoutByConstraint(constraintSize); Size minFlowSize = axis->VirtualSizeToRealSize(Size(0, layout_minVirtualHeight)); minFlowSize.x += extraSize.x; minFlowSize.y += extraSize.y; if (minSize.x < minFlowSize.x) minSize.x = minFlowSize.x; if (minSize.y < minFlowSize.y) minSize.y = minFlowSize.y; } return minSize; } Rect GuiFlowComposition::Layout_CalculateBounds(Size parentSize) { Rect bounds = GuiBoundsComposition::Layout_CalculateBounds(parentSize); Size extraSize = Layout_UpdateFlowItemLayoutByConstraint(bounds.GetSize()); Size contentSize( bounds.Width() - extraSize.x, bounds.Height() - extraSize.y ); for (auto item : layout_flowItems) { item->Layout_SetFlowItemBounds(contentSize, item->layout_virtualBounds); } return bounds; } const GuiFlowComposition::ItemCompositionList& GuiFlowComposition::GetFlowItems() { return layout_flowItems; } bool GuiFlowComposition::InsertFlowItem(vint index, GuiFlowItemComposition* item) { index = layout_flowItems.Insert(index, item); if (AddChild(item)) { return true; } layout_flowItems.RemoveAt(index); return false; } Margin GuiFlowComposition::GetExtraMargin() { return extraMargin; } void GuiFlowComposition::SetExtraMargin(Margin value) { if (extraMargin != value) { extraMargin = value; InvokeOnCompositionStateChanged(); } } vint GuiFlowComposition::GetRowPadding() { return rowPadding; } void GuiFlowComposition::SetRowPadding(vint value) { if (rowPadding != value) { rowPadding = value; InvokeOnCompositionStateChanged(); } } vint GuiFlowComposition::GetColumnPadding() { return columnPadding; } void GuiFlowComposition::SetColumnPadding(vint value) { if (columnPadding != value) { columnPadding = value; InvokeOnCompositionStateChanged(); } } Ptr GuiFlowComposition::GetAxis() { return axis; } void GuiFlowComposition::SetAxis(Ptr value) { if (value) { axis = value; InvokeOnCompositionStateChanged(); } } FlowAlignment GuiFlowComposition::GetAlignment() { return alignment; } void GuiFlowComposition::SetAlignment(FlowAlignment value) { if (alignment != value) { alignment = value; InvokeOnCompositionStateChanged(); } } /*********************************************************************** GuiFlowItemComposition ***********************************************************************/ void GuiFlowItemComposition::Layout_SetFlowItemBounds(Size contentSize, Rect virtualBounds) { Rect result = layout_flowParent->axis->VirtualRectToRealRect(contentSize, virtualBounds); result.x1 += layout_flowParent->extraMargin.left; result.x2 += layout_flowParent->extraMargin.left; result.y1 += layout_flowParent->extraMargin.top; result.y2 += layout_flowParent->extraMargin.top; result.x1 -= extraMargin.left; result.y1 -= extraMargin.top; result.x2 += extraMargin.right; result.y2 += extraMargin.bottom; Layout_SetCachedBounds(result); } void GuiFlowItemComposition::OnParentLineChanged() { layout_flowParent = dynamic_cast(GetParent()); GuiGraphicsComposition::OnParentLineChanged(); } GuiFlowItemComposition::GuiFlowItemComposition() { SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); CachedMinSizeChanged.AttachLambda([this](GuiGraphicsComposition* sender, GuiEventArgs& arguments) { if (layout_flowParent) layout_flowParent->layout_invalid = true; }); } Margin GuiFlowItemComposition::GetExtraMargin() { return extraMargin; } void GuiFlowItemComposition::SetExtraMargin(Margin value) { if (extraMargin != value) { extraMargin = value; InvokeOnCompositionStateChanged(); } } GuiFlowOption GuiFlowItemComposition::GetFlowOption() { return option; } void GuiFlowItemComposition::SetFlowOption(GuiFlowOption value) { if (option != value) { option = value; if (layout_flowParent) layout_flowParent->layout_invalid = true; InvokeOnCompositionStateChanged(); } } } } } /*********************************************************************** .\GRAPHICSCOMPOSITION\GUIGRAPHICSREPEATCOMPOSITION.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace compositions { using namespace reflection::description; /*********************************************************************** GuiRepeatCompositionBase ***********************************************************************/ GuiRepeatCompositionBase::GuiRepeatCompositionBase() { } GuiRepeatCompositionBase::~GuiRepeatCompositionBase() { if (itemChangedHandler) { itemSource.Cast()->ItemChanged.Remove(itemChangedHandler); } } GuiRepeatCompositionBase::ItemStyleProperty GuiRepeatCompositionBase::GetItemTemplate() { return itemTemplate; } void GuiRepeatCompositionBase::SetItemTemplate(ItemStyleProperty value) { OnClearItems(); itemTemplate = value; if (itemTemplate && itemSource) { OnInstallItems(); } } Ptr GuiRepeatCompositionBase::GetItemSource() { return itemSource; } void GuiRepeatCompositionBase::SetItemSource(Ptr value) { if (value != itemSource) { if (itemChangedHandler) { itemSource.Cast()->ItemChanged.Remove(itemChangedHandler); itemChangedHandler = {}; } OnClearItems(); itemSource = value.Cast(); if (!itemSource && value) { itemSource = IValueList::Create(GetLazyList(value)); } if (itemTemplate && itemSource) { OnInstallItems(); } if (auto observable = itemSource.Cast()) { itemChangedHandler = observable->ItemChanged.Add(this, &GuiRepeatCompositionBase::OnItemChanged); } } } description::Value GuiRepeatCompositionBase::GetContext() { return itemContext; } void GuiRepeatCompositionBase::SetContext(const description::Value& value) { if (itemContext != value) { itemContext = value; OnUpdateContext(); GuiEventArgs arguments(dynamic_cast(this)); ContextChanged.Execute(arguments); } } } } } /*********************************************************************** .\GRAPHICSCOMPOSITION\GUIGRAPHICSREPEATCOMPOSITION_NONVIRTIAL.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace compositions { /*********************************************************************** GuiNonVirtialRepeatCompositionBase ***********************************************************************/ void GuiNonVirtialRepeatCompositionBase::OnItemChanged(vint index, vint oldCount, vint newCount) { if (itemTemplate && itemSource) { for (vint i = oldCount - 1; i >= 0; i--) { RemoveItem(index + i); } for (vint i = 0; i < newCount; i++) { InstallItem(index + i); } } } void GuiNonVirtialRepeatCompositionBase::OnClearItems() { vint count = GetRepeatCompositionCount(); for (vint i = count - 1; i >= 0; i--) { RemoveItem(i); } } void GuiNonVirtialRepeatCompositionBase::OnInstallItems() { if (itemTemplate && itemSource) { vint count = itemSource->GetCount(); for (vint i = 0; i < count; i++) { InstallItem(i); } } } void GuiNonVirtialRepeatCompositionBase::OnUpdateContext() { vint count = GetRepeatCompositionCount(); for (vint i = 0; i < count; i++) { auto rc = GetRepeatComposition(i); auto it = dynamic_cast(rc->Children()[0]); it->SetContext(itemContext); } } void GuiNonVirtialRepeatCompositionBase::RemoveItem(vint index) { GuiItemEventArgs arguments(dynamic_cast(this)); arguments.itemIndex = index; ItemRemoved.Execute(arguments); auto item = RemoveRepeatComposition(index); SafeDeleteComposition(item); } void GuiNonVirtialRepeatCompositionBase::InstallItem(vint index) { auto source = itemSource->Get(index); auto templateItem = itemTemplate(source); auto item = InsertRepeatComposition(index); templateItem->SetAlignmentToParent(Margin(0, 0, 0, 0)); templateItem->SetContext(itemContext); item->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); item->AddChild(templateItem); GuiItemEventArgs arguments(dynamic_cast(this)); arguments.itemIndex = index; ItemInserted.Execute(arguments); } GuiNonVirtialRepeatCompositionBase::GuiNonVirtialRepeatCompositionBase() { } GuiNonVirtialRepeatCompositionBase::~GuiNonVirtialRepeatCompositionBase() { } /*********************************************************************** GuiRepeatStackComposition ***********************************************************************/ vint GuiRepeatStackComposition::GetRepeatCompositionCount() { return GetStackItems().Count(); } GuiGraphicsComposition* GuiRepeatStackComposition::GetRepeatComposition(vint index) { return GetStackItems()[index]; } GuiGraphicsComposition* GuiRepeatStackComposition::InsertRepeatComposition(vint index) { CHECK_ERROR(0 <= index && index <= GetStackItems().Count(), L"GuiRepeatStackComposition::InsertRepeatComposition(vint)#Index out of range."); auto item = new GuiStackItemComposition; InsertStackItem(index, item); return item; } GuiGraphicsComposition* GuiRepeatStackComposition::RemoveRepeatComposition(vint index) { auto item = GetStackItems()[index]; RemoveChild(item); return item; } GuiRepeatStackComposition::GuiRepeatStackComposition() { ItemInserted.SetAssociatedComposition(this); ItemRemoved.SetAssociatedComposition(this); ContextChanged.SetAssociatedComposition(this); } GuiRepeatStackComposition::~GuiRepeatStackComposition() { } /*********************************************************************** GuiRepeatFlowComposition ***********************************************************************/ vint GuiRepeatFlowComposition::GetRepeatCompositionCount() { return GetFlowItems().Count(); } GuiGraphicsComposition* GuiRepeatFlowComposition::GetRepeatComposition(vint index) { return GetFlowItems()[index]; } GuiGraphicsComposition* GuiRepeatFlowComposition::InsertRepeatComposition(vint index) { CHECK_ERROR(0 <= index && index <= GetFlowItems().Count(), L"GuiRepeatStackComposition::InsertRepeatComposition(vint)#Index out of range."); auto item = new GuiFlowItemComposition; InsertFlowItem(index, item); return item; } GuiGraphicsComposition* GuiRepeatFlowComposition::RemoveRepeatComposition(vint index) { auto item = GetFlowItems()[index]; RemoveChild(item); return item; } GuiRepeatFlowComposition::GuiRepeatFlowComposition() { ItemInserted.SetAssociatedComposition(this); ItemRemoved.SetAssociatedComposition(this); ContextChanged.SetAssociatedComposition(this); } GuiRepeatFlowComposition::~GuiRepeatFlowComposition() { } } } } /*********************************************************************** .\GRAPHICSCOMPOSITION\GUIGRAPHICSREPEATCOMPOSITION_VIRTUAL.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace compositions { using namespace collections; /*********************************************************************** GuiVirtualRepeatCompositionBase ***********************************************************************/ void GuiVirtualRepeatCompositionBase::Layout_UpdateIndex(ItemStyleRecord style, vint index) { } void GuiVirtualRepeatCompositionBase::Layout_UpdateViewBounds(Rect value, bool forceUpdateTotalSize) { auto old = GetViewLocation(); Rect oldBounds = viewBounds; viewBounds = value; OnViewChangedInternal(oldBounds, value, forceUpdateTotalSize); if (old != GetViewLocation()) { ViewLocationChanged.Execute(GuiEventArgs(this)); } } void GuiVirtualRepeatCompositionBase::Layout_UpdateViewLocation(Point value) { Layout_UpdateViewBounds(Rect(value, viewBounds.GetSize()), false); } Rect GuiVirtualRepeatCompositionBase::Layout_CalculateBounds(Size parentSize) { auto bounds = GuiBoundsComposition::Layout_CalculateBounds(parentSize); auto size = axis->RealSizeToVirtualSize(bounds.GetSize()); if (size != viewBounds.GetSize() || itemSourceUpdated) { itemSourceUpdated = false; Layout_UpdateViewBounds(Rect(viewBounds.LeftTop(), size), true); } return bounds; } void GuiVirtualRepeatCompositionBase::Layout_ResetLayout() { viewBounds = Rect({ 0,0 }, { 0,0 }); ViewLocationChanged.Execute(GuiEventArgs(this)); OnResetViewLocation(); itemSourceUpdated = true; Layout_InvalidateItemSizeCache(); AdoptedSizeInvalidated.Execute(GuiEventArgs(this)); } void GuiVirtualRepeatCompositionBase::Layout_SetStyleAlignmentToParent(ItemStyleRecord style, Margin value) { style->SetAlignmentToParent(axis->VirtualMarginToRealMargin(value)); } Size GuiVirtualRepeatCompositionBase::Layout_GetStylePreferredSize(ItemStyleRecord style) { return axis->RealSizeToVirtualSize(style->GetCachedMinSize()); } Rect GuiVirtualRepeatCompositionBase::Layout_GetStyleBounds(ItemStyleRecord style) { return axis->RealRectToVirtualRect(axis->VirtualSizeToRealSize(viewBounds.GetSize()), style->GetCachedBounds()); } void GuiVirtualRepeatCompositionBase::Layout_SetStyleBounds(ItemStyleRecord style, Rect value) { return style->SetExpectedBounds(axis->VirtualRectToRealRect(axis->VirtualSizeToRealSize(viewBounds.GetSize()), value)); } void GuiVirtualRepeatCompositionBase::OnStyleCachedMinSizeChanged(GuiGraphicsComposition* sender, GuiEventArgs& arguments) { InvalidateLayout(); } void GuiVirtualRepeatCompositionBase::AttachEventHandler(GuiGraphicsComposition* itemStyle) { eventHandlers.Add(itemStyle, itemStyle->CachedMinSizeChanged.AttachMethod(this, &GuiVirtualRepeatCompositionBase::OnStyleCachedMinSizeChanged)); } void GuiVirtualRepeatCompositionBase::DetachEventHandler(GuiGraphicsComposition* itemStyle) { vint index = eventHandlers.Keys().IndexOf(itemStyle); if (index != -1) { auto eventHandler = eventHandlers.Values()[index]; itemStyle->CachedBoundsChanged.Detach(eventHandler); eventHandlers.Remove(itemStyle); } } void GuiVirtualRepeatCompositionBase::OnChildRemoved(GuiGraphicsComposition* child) { DetachEventHandler(child); GuiBoundsComposition::OnChildRemoved(child); } void GuiVirtualRepeatCompositionBase::OnItemChanged(vint start, vint oldCount, vint newCount) { itemSourceUpdated = true; InvokeOnCompositionStateChanged(); vint visibleCount = visibleStyles.Count(); vint itemCount = itemSource->GetCount(); SortedList reusedStyles; for (vint i = 0; i < visibleCount; i++) { vint index = startIndex + i; if (index >= itemCount) { break; } vint oldIndex = -1; if (index < start) { oldIndex = index; } else if (index >= start + newCount) { oldIndex = index - newCount + oldCount; } if (oldIndex != -1) { if (oldIndex >= startIndex && oldIndex < startIndex + visibleCount) { auto style = visibleStyles[oldIndex - startIndex]; reusedStyles.Add(style); visibleStyles.Add(style); } else { oldIndex = -1; } } if (oldIndex == -1) { visibleStyles.Add(CreateStyle(index)); } } for (vint i = 0; i < visibleCount; i++) { auto style = visibleStyles[i]; if (!reusedStyles.Contains(style)) { DeleteStyle(style); } } visibleStyles.RemoveRange(0, visibleCount); for (auto [style, i] : indexed(visibleStyles)) { Layout_UpdateIndex(style, startIndex + i); } } void GuiVirtualRepeatCompositionBase::OnClearItems() { startIndex = 0; for (auto style : visibleStyles) { DeleteStyle(style); } visibleStyles.Clear(); Layout_ResetLayout(); } void GuiVirtualRepeatCompositionBase::OnInstallItems() { // nothing needs to be done here // visibleStyles will be recreated in the next round of layout } void GuiVirtualRepeatCompositionBase::OnUpdateContext() { for (auto style : visibleStyles) { style->SetContext(itemContext); } } void GuiVirtualRepeatCompositionBase::OnResetViewLocation() { } GuiVirtualRepeatCompositionBase::ItemStyleRecord GuiVirtualRepeatCompositionBase::CreateStyleInternal(vint index) { auto source = itemSource->Get(index); auto itemStyle = itemTemplate(source); itemStyle->SetContext(itemContext); return itemStyle; } void GuiVirtualRepeatCompositionBase::DeleteStyleInternal(ItemStyleRecord style) { SafeDeleteComposition(style); } vint GuiVirtualRepeatCompositionBase::CalculateAdoptedSize(vint expectedSize, vint count, vint itemSize) { vint visibleCount = expectedSize / itemSize; if (count < visibleCount) { visibleCount = count; } else if (count > visibleCount) { vint deltaA = expectedSize - visibleCount * itemSize; vint deltaB = itemSize - deltaA; if (deltaB < deltaA) { visibleCount++; } } return visibleCount * itemSize; } GuiVirtualRepeatCompositionBase::ItemStyleRecord GuiVirtualRepeatCompositionBase::CreateStyle(vint index) { auto itemStyle = CreateStyleInternal(index); AddChild(itemStyle); itemStyle->ForceCalculateSizeImmediately(); AttachEventHandler(itemStyle); return itemStyle; } void GuiVirtualRepeatCompositionBase::DeleteStyle(ItemStyleRecord style) { DetachEventHandler(style); DeleteStyleInternal(style); } void GuiVirtualRepeatCompositionBase::UpdateFullSize() { Size fullSize, minimumSize; Layout_CalculateTotalSize(fullSize, minimumSize); realFullSize = axis->VirtualSizeToRealSize(fullSize); realMinimumFullSize = axis->VirtualSizeToRealSize(minimumSize); } void GuiVirtualRepeatCompositionBase::OnViewChangedInternal(Rect oldBounds, Rect newBounds, bool forceUpdateTotalSize) { bool needToUpdateTotalSize = forceUpdateTotalSize; if (itemTemplate && itemSource) { while (true) { bool needRestart = false; vint endIndex = startIndex + visibleStyles.Count() - 1; vint newStartIndex = 0; vint itemCount = itemSource->GetCount(); Layout_BeginPlaceItem(true, newBounds, newStartIndex); if (newStartIndex < 0) newStartIndex = 0; StyleList newVisibleStyles; for (vint i = newStartIndex; i < itemCount; i++) { bool reuseOldStyle = startIndex <= i && i <= endIndex; auto style = reuseOldStyle ? visibleStyles[i - startIndex] : CreateStyle(i); newVisibleStyles.Add(style); Rect bounds; Margin alignmentToParent; auto placeItemResult = Layout_PlaceItem(true, !reuseOldStyle, i, style, newBounds, bounds, alignmentToParent); if (placeItemResult != VirtualRepeatPlaceItemResult::None) { needRestart = placeItemResult == VirtualRepeatPlaceItemResult::Restart; break; } } vint newEndIndex = newStartIndex + newVisibleStyles.Count() - 1; for (auto [style, i] : indexed(visibleStyles)) { vint index = startIndex + i; if (index < newStartIndex || index > newEndIndex) { DeleteStyle(visibleStyles[i]); } } CopyFrom(visibleStyles, newVisibleStyles); needToUpdateTotalSize = (Layout_EndPlaceItem(true, newBounds, newStartIndex) == VirtualRepeatEndPlaceItemResult::TotalSizeUpdated) || needToUpdateTotalSize; startIndex = newStartIndex; if (!needRestart) break; } { vint newStartIndex = startIndex; Layout_BeginPlaceItem(false, viewBounds, newStartIndex); for (auto [style, i] : indexed(visibleStyles)) { Rect bounds; Margin alignmentToParent(-1, -1, -1, -1); Layout_PlaceItem(false, false, startIndex + i, style, viewBounds, bounds, alignmentToParent); bounds.x1 -= viewBounds.x1; bounds.x2 -= viewBounds.x1; bounds.y1 -= viewBounds.y1; bounds.y2 -= viewBounds.y1; Layout_SetStyleAlignmentToParent(style, alignmentToParent); Layout_SetStyleBounds(style, bounds); } needToUpdateTotalSize = (Layout_EndPlaceItem(false, viewBounds, startIndex) == VirtualRepeatEndPlaceItemResult::TotalSizeUpdated) || needToUpdateTotalSize; } } else if (realFullSize != Size(0, 0)) { needToUpdateTotalSize = true; } if (needToUpdateTotalSize) { UpdateFullSize(); TotalSizeChanged.Execute(GuiEventArgs(this)); AdoptedSizeInvalidated.Execute(GuiEventArgs(this)); } Layout_EndLayout(needToUpdateTotalSize); } GuiVirtualRepeatCompositionBase::GuiVirtualRepeatCompositionBase() { AxisChanged.SetAssociatedComposition(this); TotalSizeChanged.SetAssociatedComposition(this); ViewLocationChanged.SetAssociatedComposition(this); AdoptedSizeInvalidated.SetAssociatedComposition(this); } GuiVirtualRepeatCompositionBase::~GuiVirtualRepeatCompositionBase() { for (auto [style, eventHandler] : eventHandlers) { style->CachedMinSizeChanged.Detach(eventHandler); } eventHandlers.Clear(); } Ptr GuiVirtualRepeatCompositionBase::GetAxis() { return axis; } void GuiVirtualRepeatCompositionBase::SetAxis(Ptr value) { if (axis != value) { OnClearItems(); if (!value) value = Ptr(new GuiDefaultAxis); axis = value; if (itemTemplate && itemSource) { OnInstallItems(); } AxisChanged.Execute(GuiEventArgs(this)); } } Size GuiVirtualRepeatCompositionBase::GetTotalSize() { return useMinimumFullSize ? realMinimumFullSize : realFullSize; } bool GuiVirtualRepeatCompositionBase::GetUseMinimumTotalSize() { return useMinimumFullSize; } void GuiVirtualRepeatCompositionBase::SetUseMinimumTotalSize(bool value) { if (useMinimumFullSize != value) { useMinimumFullSize = value; UpdateFullSize(); TotalSizeChanged.Execute(GuiEventArgs(this)); } } Point GuiVirtualRepeatCompositionBase::GetViewLocation() { return axis->VirtualRectToRealRect(realFullSize, viewBounds).LeftTop(); } void GuiVirtualRepeatCompositionBase::SetViewLocation(Point value) { Size realSize = axis->VirtualSizeToRealSize(viewBounds.GetSize()); Rect realBounds = Rect(value, realSize); Layout_UpdateViewBounds(axis->RealRectToVirtualRect(realFullSize, realBounds), false); OnResetViewLocation(); } GuiVirtualRepeatCompositionBase::ItemStyleRecord GuiVirtualRepeatCompositionBase::GetVisibleStyle(vint itemIndex) { if (startIndex <= itemIndex && itemIndex < startIndex + visibleStyles.Count()) { return visibleStyles[itemIndex - startIndex]; } else { return nullptr; } } vint GuiVirtualRepeatCompositionBase::GetVisibleIndex(ItemStyleRecord style) { for (auto [s, i] : indexed(visibleStyles)) { if (s == style) { return i + startIndex; } } return -1; } void GuiVirtualRepeatCompositionBase::ResetLayout(bool recreateVisibleStyles) { if (recreateVisibleStyles) { OnClearItems(); } else { Layout_ResetLayout(); } } void GuiVirtualRepeatCompositionBase::InvalidateLayout() { itemSourceUpdated = true; } Size GuiVirtualRepeatCompositionBase::GetAdoptedSize(Size expectedSize) { Size expectedViewSize = axis->RealSizeToVirtualSize(expectedSize); Size adoptedViewSize = Layout_GetAdoptedSize(expectedViewSize); Size adoptedSize = axis->VirtualSizeToRealSize(adoptedViewSize); return adoptedSize; } vint GuiVirtualRepeatCompositionBase::FindItemByRealKeyDirection(vint itemIndex, compositions::KeyDirection key) { return FindItemByVirtualKeyDirection(itemIndex, axis->RealKeyDirectionToVirtualKeyDirection(key)); } /*********************************************************************** GuiRepeatFreeHeightItemComposition ***********************************************************************/ void GuiRepeatFreeHeightItemComposition::EnsureOffsetForItem(vint itemIndex) { if (heights.Count() == 0) return; if (availableOffsetCount == 0) { availableOffsetCount = 1; offsets[0] = 0; } for (vint i = availableOffsetCount; i < itemIndex && i < heights.Count(); i++) { offsets[i] = offsets[i - 1] + heights[i - 1]; } } void GuiRepeatFreeHeightItemComposition::Layout_BeginPlaceItem(bool firstPhase, Rect newBounds, vint& newStartIndex) { pi_heightUpdated = false; EnsureOffsetForItem(heights.Count() - 1); if (firstPhase) { // TODO: (enumerable) foreach:indexed for (vint i = 0; i < heights.Count(); i++) { if (heights[i] == 1 && startIndex <= i && i < startIndex + visibleStyles.Count() && visibleStyles[i - startIndex]) { vint h = visibleStyles[i - startIndex]->GetCachedMinSize().y; if (h > 1) { heights[i] = h; } } if (offsets[i] + heights[i] > newBounds.Top()) { newStartIndex = i; break; } } } } VirtualRepeatPlaceItemResult GuiRepeatFreeHeightItemComposition::Layout_PlaceItem(bool firstPhase, bool newCreatedStyle, vint index, ItemStyleRecord style, Rect viewBounds, Rect& bounds, Margin& alignmentToParent) { vint styleHeight = heights[index]; { vint newStyleHeight = Layout_GetStylePreferredSize(style).y; if (!newCreatedStyle || styleHeight < newStyleHeight) { styleHeight = newStyleHeight; } } if (heights[index] != styleHeight) { heights[index] = styleHeight; pi_heightUpdated = true; } vint styleOffset = index == 0 ? 0 : offsets[index - 1] + heights[index - 1]; if (availableOffsetCount <= index || offsets[index] != styleOffset) { offsets[index] = styleOffset; availableOffsetCount = index; } bounds = Rect(Point(0, offsets[index]), Size(viewBounds.Width(), heights[index])); if (bounds.Bottom() >= viewBounds.Bottom()) { return VirtualRepeatPlaceItemResult::HitLastItem; } else { return VirtualRepeatPlaceItemResult::None; } } VirtualRepeatEndPlaceItemResult GuiRepeatFreeHeightItemComposition::Layout_EndPlaceItem(bool firstPhase, Rect newBounds, vint newStartIndex) { return pi_heightUpdated ? VirtualRepeatEndPlaceItemResult::TotalSizeUpdated : VirtualRepeatEndPlaceItemResult::None; } void GuiRepeatFreeHeightItemComposition::Layout_EndLayout(bool totalSizeUpdated) { } void GuiRepeatFreeHeightItemComposition::Layout_InvalidateItemSizeCache() { availableOffsetCount = 0; for (vint i = 0; i < heights.Count(); i++) { heights[i] = 1; } } void GuiRepeatFreeHeightItemComposition::Layout_CalculateTotalSize(Size& full, Size& minimum) { if (heights.Count() == 0) { full = minimum = Size(0, 0); return; } EnsureOffsetForItem(heights.Count()); vint w = viewBounds.Width(); vint h = offsets[heights.Count() - 1] + heights[heights.Count() - 1]; full = Size(w, h); minimum = Size(0, h); } Size GuiRepeatFreeHeightItemComposition::Layout_GetAdoptedSize(Size expectedSize) { vint h = expectedSize.x * 2; if (expectedSize.y < h) expectedSize.y = h; return expectedSize; } void GuiRepeatFreeHeightItemComposition::OnItemChanged(vint start, vint oldCount, vint newCount) { availableOffsetCount = start; vint itemCount = heights.Count() + newCount - oldCount; if (oldCount < newCount) { heights.Resize(itemCount); if (start + newCount < itemCount) { memmove(&heights[start + newCount], &heights[start + oldCount], sizeof(vint) * (itemCount - start - newCount)); } } else if (oldCount > newCount) { if (start + newCount < itemCount) { memmove(&heights[start + newCount], &heights[start + oldCount], sizeof(vint) * (itemCount - start - newCount)); } heights.Resize(itemCount); } for (vint i = 0; i < newCount; i++) { heights[start + i] = 1; } offsets.Resize(itemCount); GuiVirtualRepeatCompositionBase::OnItemChanged(start, oldCount, newCount); } void GuiRepeatFreeHeightItemComposition::OnInstallItems() { heights.Resize(itemSource->GetCount()); Layout_InvalidateItemSizeCache(); offsets.Resize(itemSource->GetCount()); EnsureOffsetForItem(heights.Count() - 1); GuiVirtualRepeatCompositionBase::OnInstallItems(); } vint GuiRepeatFreeHeightItemComposition::FindItemByVirtualKeyDirection(vint itemIndex, compositions::KeyDirection key) { if (!itemSource) return -1; vint count = itemSource->GetCount(); if (itemIndex < 0 || itemIndex >= count) return -1; switch (key) { case KeyDirection::Up: itemIndex--; break; case KeyDirection::Down: itemIndex++; break; case KeyDirection::Home: itemIndex = 0; break; case KeyDirection::End: itemIndex = count - 1; break; case KeyDirection::PageUp: itemIndex -= visibleStyles.Count(); break; case KeyDirection::PageDown: itemIndex += visibleStyles.Count(); break; default: return -1; } if (itemIndex < 0) return 0; else if (itemIndex >= count) return count - 1; else return itemIndex; } VirtualRepeatEnsureItemVisibleResult GuiRepeatFreeHeightItemComposition::EnsureItemVisible(vint itemIndex) { if (!itemSource) return VirtualRepeatEnsureItemVisibleResult::NotMoved; bool moved = false; while (true) { if (itemIndex < 0 || itemIndex >= itemSource->GetCount()) { return VirtualRepeatEnsureItemVisibleResult::ItemNotExists; } EnsureOffsetForItem(itemIndex); vint offset = viewBounds.y1; vint top = offsets[itemIndex]; vint bottom = top + heights[itemIndex]; vint height = viewBounds.Height(); Point location = viewBounds.LeftTop(); if (offset >= top && offset + height <= bottom) { break; } else if (offset > top) { location.y = top; } else if (offset < bottom - height) { location.y = bottom - height; } else { break; } auto oldLeftTop = viewBounds.LeftTop(); Layout_UpdateViewLocation(location); moved |= viewBounds.LeftTop() != oldLeftTop; if (viewBounds.LeftTop() != location) break; } return moved ? VirtualRepeatEnsureItemVisibleResult::Moved : VirtualRepeatEnsureItemVisibleResult::NotMoved; } /*********************************************************************** GuiRepeatFixedHeightItemComposition ***********************************************************************/ void GuiRepeatFixedHeightItemComposition::Layout_BeginPlaceItem(bool firstPhase, Rect newBounds, vint& newStartIndex) { pi_width = itemWidth; pi_yoffset = itemYOffset; if (firstPhase) { pi_rowHeight = rowHeight; newStartIndex = newBounds.Top() / pi_rowHeight; } } VirtualRepeatPlaceItemResult GuiRepeatFixedHeightItemComposition::Layout_PlaceItem(bool firstPhase, bool newCreatedStyle, vint index, ItemStyleRecord style, Rect viewBounds, Rect& bounds, Margin& alignmentToParent) { if (firstPhase) { vint styleHeight = Layout_GetStylePreferredSize(style).y; if (pi_rowHeight < styleHeight) { pi_rowHeight = styleHeight; } } vint top = pi_yoffset + index * pi_rowHeight; if (pi_width == -1) { alignmentToParent = Margin(0, -1, 0, -1); bounds = Rect(Point(0, top), Size(0, pi_rowHeight)); } else { alignmentToParent = Margin(-1, -1, -1, -1); bounds = Rect(Point(0, top), Size(pi_width, pi_rowHeight)); } if (bounds.Bottom() >= viewBounds.Bottom()) { return VirtualRepeatPlaceItemResult::HitLastItem; } else { return VirtualRepeatPlaceItemResult::None; } } VirtualRepeatEndPlaceItemResult GuiRepeatFixedHeightItemComposition::Layout_EndPlaceItem(bool firstPhase, Rect newBounds, vint newStartIndex) { if (firstPhase) { if (pi_rowHeight != rowHeight) { rowHeight = pi_rowHeight; return VirtualRepeatEndPlaceItemResult::TotalSizeUpdated; } } return VirtualRepeatEndPlaceItemResult::None; } void GuiRepeatFixedHeightItemComposition::Layout_EndLayout(bool totalSizeUpdated) { } void GuiRepeatFixedHeightItemComposition::Layout_InvalidateItemSizeCache() { rowHeight = 1; } void GuiRepeatFixedHeightItemComposition::Layout_CalculateTotalSize(Size& full, Size& minimum) { if (!itemSource || itemSource->GetCount() == 0) { full = minimum = Size(0, 0); return; } vint w = itemWidth; vint w1 = w == -1 ? viewBounds.Width() : w; vint w2 = w == -1 ? 0 : w; vint h = rowHeight * itemSource->GetCount() + itemYOffset; full = Size(w1, h); minimum = Size(w2, h); } Size GuiRepeatFixedHeightItemComposition::Layout_GetAdoptedSize(Size expectedSize) { if (!itemSource) return expectedSize; vint y = expectedSize.y - itemYOffset; vint itemCount = itemSource->GetCount(); return Size(expectedSize.x, itemYOffset + CalculateAdoptedSize(y, itemCount, rowHeight)); } vint GuiRepeatFixedHeightItemComposition::FindItemByVirtualKeyDirection(vint itemIndex, compositions::KeyDirection key) { vint count = itemSource->GetCount(); if (itemIndex < 0 || itemIndex >= count) return -1; vint groupCount = viewBounds.Height() / rowHeight; if (groupCount == 0) groupCount = 1; switch (key) { case KeyDirection::Up: itemIndex--; break; case KeyDirection::Down: itemIndex++; break; case KeyDirection::Home: itemIndex = 0; break; case KeyDirection::End: itemIndex = count; break; case KeyDirection::PageUp: itemIndex -= groupCount; break; case KeyDirection::PageDown: itemIndex += groupCount; break; default: return -1; } if (itemIndex < 0) return 0; else if (itemIndex >= count) return count - 1; else return itemIndex; } VirtualRepeatEnsureItemVisibleResult GuiRepeatFixedHeightItemComposition::EnsureItemVisible(vint itemIndex) { if (!itemSource) return VirtualRepeatEnsureItemVisibleResult::NotMoved; if (itemIndex < 0 || itemIndex >= itemSource->GetCount()) { return VirtualRepeatEnsureItemVisibleResult::ItemNotExists; } vint viewY1 = viewBounds.y1 + itemYOffset; vint viewY2 = viewBounds.y2; vint itemY1 = itemIndex * rowHeight + itemYOffset; vint itemY2 = itemY1 + rowHeight; if (viewY2 - viewY1 < rowHeight) { if (itemY1 < viewY2 && itemY2 > viewY1) { return VirtualRepeatEnsureItemVisibleResult::NotMoved; } } else { if (itemY1 >= viewY1 && itemY2 <= viewY2) { return VirtualRepeatEnsureItemVisibleResult::NotMoved; } if (itemY1 < viewY1 && itemY2 > viewY1) { Layout_UpdateViewLocation({ viewBounds.x1,viewBounds.y1 + itemY1 - viewY1 }); return VirtualRepeatEnsureItemVisibleResult::Moved; } if (itemY1 < viewY2 && itemY2 > viewY2) { Layout_UpdateViewLocation({ viewBounds.x1,viewBounds.y1 + itemY2 - viewY2 }); return VirtualRepeatEnsureItemVisibleResult::Moved; } } bool up = itemY1 < viewY1; while (true) { if (up) { if (itemY1 >= viewY1) break; Layout_UpdateViewLocation({ viewBounds.x1,viewBounds.y1 + itemY1 - viewY1 }); } else { if (itemY2 <= viewY2) break; Layout_UpdateViewLocation({ viewBounds.x1,viewBounds.y1 + itemY2 - viewY2 }); } viewY1 = viewBounds.y1 + itemYOffset; viewY2 = viewBounds.y2; itemY1 = itemIndex * rowHeight + itemYOffset; itemY2 = itemY1 + rowHeight; } return VirtualRepeatEnsureItemVisibleResult::Moved; } vint GuiRepeatFixedHeightItemComposition::GetItemWidth() { return itemWidth; } void GuiRepeatFixedHeightItemComposition::SetItemWidth(vint value) { if (value < -1) value = -1; if (itemWidth != value) { itemWidth = value; InvalidateLayout(); } } vint GuiRepeatFixedHeightItemComposition::GetItemYOffset() { return itemYOffset; } void GuiRepeatFixedHeightItemComposition::SetItemYOffset(vint value) { if (value < 0) value = 0; if (itemYOffset != value) { itemYOffset = value; InvalidateLayout(); } } /*********************************************************************** GuiRepeatFixedSizeMultiColumnItemComposition ***********************************************************************/ void GuiRepeatFixedSizeMultiColumnItemComposition::Layout_BeginPlaceItem(bool firstPhase, Rect newBounds, vint& newStartIndex) { if (firstPhase) { pi_itemSize = itemSize; vint rows = newBounds.Top() / pi_itemSize.y; if (rows < 0) rows = 0; vint cols = newBounds.Width() / pi_itemSize.x; if (cols < 1) cols = 1; newStartIndex = rows * cols; } } VirtualRepeatPlaceItemResult GuiRepeatFixedSizeMultiColumnItemComposition::Layout_PlaceItem(bool firstPhase, bool newCreatedStyle, vint index, ItemStyleRecord style, Rect viewBounds, Rect& bounds, Margin& alignmentToParent) { if (firstPhase) { Size styleSize = Layout_GetStylePreferredSize(style); if (pi_itemSize.x < styleSize.x) pi_itemSize.x = styleSize.x; if (pi_itemSize.y < styleSize.y) pi_itemSize.y = styleSize.y; } vint rowItems = viewBounds.Width() / pi_itemSize.x; if (rowItems < 1) rowItems = 1; vint row = index / rowItems; vint col = index % rowItems; bounds = Rect(Point(col * pi_itemSize.x, row * pi_itemSize.y), pi_itemSize); if (col == rowItems - 1 && bounds.Bottom() >= viewBounds.Bottom()) { return VirtualRepeatPlaceItemResult::HitLastItem; } else { return VirtualRepeatPlaceItemResult::None; } } VirtualRepeatEndPlaceItemResult GuiRepeatFixedSizeMultiColumnItemComposition::Layout_EndPlaceItem(bool firstPhase, Rect newBounds, vint newStartIndex) { if (firstPhase) { if (pi_itemSize != itemSize) { itemSize = pi_itemSize; return VirtualRepeatEndPlaceItemResult::TotalSizeUpdated; } } return VirtualRepeatEndPlaceItemResult::None; } void GuiRepeatFixedSizeMultiColumnItemComposition::Layout_EndLayout(bool totalSizeUpdated) { } void GuiRepeatFixedSizeMultiColumnItemComposition::Layout_InvalidateItemSizeCache() { itemSize = Size(1, 1); } void GuiRepeatFixedSizeMultiColumnItemComposition::Layout_CalculateTotalSize(Size& full, Size& minimum) { if (!itemSource || itemSource->GetCount() == 0) { full = minimum = Size(0, 0); return; } vint rowItems = viewBounds.Width() / itemSize.x; if (rowItems < 1) rowItems = 1; vint rows = itemSource->GetCount() / rowItems; if (itemSource->GetCount() % rowItems) rows++; vint h = itemSize.y * rows; full = Size(itemSize.x * rowItems, h); minimum = Size(itemSize.x, h); } Size GuiRepeatFixedSizeMultiColumnItemComposition::Layout_GetAdoptedSize(Size expectedSize) { if (!itemSource) return expectedSize; vint count = itemSource->GetCount(); vint columnCount = viewBounds.Width() / itemSize.x; vint rowCount = count / columnCount; if (count % columnCount != 0) rowCount++; if (columnCount == 0) columnCount = 1; if (rowCount == 0) rowCount = 1; return Size( CalculateAdoptedSize(expectedSize.x, columnCount, itemSize.x), CalculateAdoptedSize(expectedSize.y, rowCount, itemSize.y) ); } vint GuiRepeatFixedSizeMultiColumnItemComposition::FindItemByVirtualKeyDirection(vint itemIndex, compositions::KeyDirection key) { vint count = itemSource->GetCount(); if (itemIndex < 0 || itemIndex >= count) return -1; vint columnCount = viewBounds.Width() / itemSize.x; if (columnCount == 0) columnCount = 1; vint rowCount = viewBounds.Height() / itemSize.y; if (rowCount == 0) rowCount = 1; switch (key) { case KeyDirection::Up: itemIndex -= columnCount; break; case KeyDirection::Down: itemIndex += columnCount; break; case KeyDirection::Left: itemIndex--; break; case KeyDirection::Right: itemIndex++; break; case KeyDirection::Home: itemIndex = 0; break; case KeyDirection::End: itemIndex = count; break; case KeyDirection::PageUp: itemIndex -= columnCount * rowCount; break; case KeyDirection::PageDown: itemIndex += columnCount * rowCount; break; case KeyDirection::PageLeft: itemIndex -= itemIndex % columnCount; break; case KeyDirection::PageRight: itemIndex += columnCount - itemIndex % columnCount - 1; break; default: return -1; } if (itemIndex < 0) return 0; else if (itemIndex >= count) return count - 1; else return itemIndex; } VirtualRepeatEnsureItemVisibleResult GuiRepeatFixedSizeMultiColumnItemComposition::EnsureItemVisible(vint itemIndex) { if (!itemSource) return VirtualRepeatEnsureItemVisibleResult::NotMoved; if (itemIndex < 0 || itemIndex >= itemSource->GetCount()) { return VirtualRepeatEnsureItemVisibleResult::ItemNotExists; } bool moved = false; while (true) { vint rowHeight = itemSize.y; vint columnCount = viewBounds.Width() / itemSize.x; if (columnCount == 0) columnCount = 1; vint rowIndex = itemIndex / columnCount; vint top = rowIndex * rowHeight; vint bottom = top + rowHeight; if (viewBounds.Height() < rowHeight) { if (viewBounds.Top() < bottom && top < viewBounds.Bottom()) { break; } } Point location = viewBounds.LeftTop(); if (top < viewBounds.Top()) { location.y = top; } else if (viewBounds.Bottom() < bottom) { location.y = bottom - viewBounds.Height(); } else { break; } auto oldLeftTop = viewBounds.LeftTop(); Layout_UpdateViewLocation(location); moved |= viewBounds.LeftTop() != oldLeftTop; if (viewBounds.LeftTop() != location) break; } return moved ? VirtualRepeatEnsureItemVisibleResult::Moved : VirtualRepeatEnsureItemVisibleResult::NotMoved; } /*********************************************************************** GuiRepeatFixedHeightMultiColumnItemComposition ***********************************************************************/ void GuiRepeatFixedHeightMultiColumnItemComposition::FixColumnWidth(vint index) { vint c = index / pi_rows - pi_firstColumn; vint r = index % pi_rows; vint w = pi_visibleItemWidths[index - pi_firstColumn * pi_rows]; if (r == 0) { while (pi_visibleColumnWidths.Count() <= c) pi_visibleColumnWidths.Add(0); while (pi_visibleColumnOffsets.Count() <= c) pi_visibleColumnOffsets.Add(0); pi_visibleColumnWidths[c] = w; if (c == 0) { pi_visibleColumnOffsets[c] = 0; } else { pi_visibleColumnOffsets[c] = pi_visibleColumnOffsets[c - 1] + pi_visibleColumnWidths[c - 1]; } } else { if (pi_visibleColumnWidths[c] < w) { pi_visibleColumnWidths[c] = w; } } } void GuiRepeatFixedHeightMultiColumnItemComposition::Layout_BeginPlaceItem(bool firstPhase, Rect newBounds, vint& newStartIndex) { if (firstPhase) { pi_firstColumn = newBounds.Width() == 0 ? 0 : newBounds.x1 / newBounds.Width(); pi_itemHeight = itemHeight; pi_visibleItemWidths.Clear(); pi_visibleColumnWidths.Clear(); pi_visibleColumnOffsets.Clear(); pi_rows = newBounds.Height() / itemHeight; if (pi_rows < 1) pi_rows = 1; if (pi_firstColumn < 0) { pi_firstColumn = 0; } else if (pi_firstColumn * pi_rows >= itemSource->GetCount()) { pi_firstColumn = (itemSource->GetCount() + pi_rows - 1) / pi_rows - 1; } newStartIndex = pi_firstColumn * pi_rows; } } VirtualRepeatPlaceItemResult GuiRepeatFixedHeightMultiColumnItemComposition::Layout_PlaceItem(bool firstPhase, bool newCreatedStyle, vint index, ItemStyleRecord style, Rect viewBounds, Rect& bounds, Margin& alignmentToParent) { #define ERROR_MESSAGE_INTERNAL_ERROR L"vl::presentation::compositions::GuiRepeatFixedHeightMultiColumnItemComposition::Layout_PlaceItem(...)#Internal error." vint visibleColumn = index / pi_rows - pi_firstColumn; vint visibleRow = index % pi_rows; if (firstPhase) { Size styleSize = Layout_GetStylePreferredSize(style); if (pi_itemHeight < styleSize.y) { pi_itemHeight = styleSize.y; vint newRows = viewBounds.Height() / pi_itemHeight; if (newRows != pi_rows) { CHECK_ERROR(newRows < pi_rows, ERROR_MESSAGE_INTERNAL_ERROR); vint oldFirstIndex = pi_firstColumn * pi_rows; pi_rows = newRows > 0 ? newRows : 1; vint newFirstIndex = pi_firstColumn * pi_rows; if (oldFirstIndex == newFirstIndex) { for (vint i = newFirstIndex; i < index; i++) { FixColumnWidth(i + newFirstIndex); } } else { CHECK_ERROR(oldFirstIndex > newFirstIndex, ERROR_MESSAGE_INTERNAL_ERROR); return VirtualRepeatPlaceItemResult::Restart; } visibleColumn = index / pi_rows - pi_firstColumn; visibleRow = index % pi_rows; } } pi_visibleItemWidths.Add(styleSize.x); FixColumnWidth(index); } vint x = viewBounds.x1 + pi_visibleColumnOffsets[visibleColumn]; vint y = pi_itemHeight * visibleRow; vint w = pi_visibleItemWidths[index - pi_firstColumn * pi_rows]; bounds = Rect({ x,y }, { w,pi_itemHeight }); if (visibleRow == pi_rows - 1 && pi_visibleColumnOffsets[visibleColumn] + pi_visibleColumnWidths[visibleColumn] >= viewBounds.Width()) { return VirtualRepeatPlaceItemResult::HitLastItem; } else { return VirtualRepeatPlaceItemResult::None; } #undef ERROR_MESSAGE_INTERNAL_ERROR } VirtualRepeatEndPlaceItemResult GuiRepeatFixedHeightMultiColumnItemComposition::Layout_EndPlaceItem(bool firstPhase, Rect newBounds, vint newStartIndex) { if (firstPhase) { bool itemHeightUpdated = pi_itemHeight != itemHeight; firstColumn = pi_firstColumn; itemHeight = pi_itemHeight; if (pi_visibleColumnOffsets.Count() <= 1) { fullVisibleColumns = pi_visibleColumnOffsets.Count(); } else { vint c = pi_visibleColumnOffsets.Count() - 1; vint x = pi_visibleColumnOffsets[c] + pi_visibleColumnWidths[c]; if (x <= viewBounds.Width()) { fullVisibleColumns = c + 1; } else { fullVisibleColumns = c; } } if (itemHeightUpdated) { return VirtualRepeatEndPlaceItemResult::TotalSizeUpdated; } } return VirtualRepeatEndPlaceItemResult::None; } void GuiRepeatFixedHeightMultiColumnItemComposition::Layout_EndLayout(bool totalSizeUpdated) { } void GuiRepeatFixedHeightMultiColumnItemComposition::Layout_InvalidateItemSizeCache() { itemHeight = 1; } void GuiRepeatFixedHeightMultiColumnItemComposition::Layout_CalculateTotalSize(Size& full, Size& minimum) { if (!itemSource || itemSource->GetCount() == 0) { full = minimum = Size(0, 0); return; } vint rows = viewBounds.Height() / itemHeight; if (rows < 1) rows = 1; vint columns = (itemSource->GetCount() + rows - 1) / rows; vint w = viewBounds.Width() * (columns + 1); vint h = rows * itemHeight; full = Size(w, h); minimum = Size(w, 0); } Size GuiRepeatFixedHeightMultiColumnItemComposition::Layout_GetAdoptedSize(Size expectedSize) { if (!itemSource) return expectedSize; vint count = itemSource->GetCount(); vint rowCount = viewBounds.Height() / itemHeight; if (rowCount > count) rowCount = count; if (rowCount == 0) rowCount = 1; return Size(expectedSize.x, CalculateAdoptedSize(expectedSize.y, rowCount, itemHeight)); } vint GuiRepeatFixedHeightMultiColumnItemComposition::FindItemByVirtualKeyDirection(vint itemIndex, compositions::KeyDirection key) { vint count = itemSource->GetCount(); if (itemIndex < 0 || itemIndex >= count) return -1; vint rowCount = viewBounds.Height() / itemHeight; if (rowCount == 0) rowCount = 1; switch (key) { case KeyDirection::Up: itemIndex--; break; case KeyDirection::Down: itemIndex++; break; case KeyDirection::Left: itemIndex -= rowCount; break; case KeyDirection::Right: itemIndex += rowCount; break; case KeyDirection::Home: itemIndex = 0; break; case KeyDirection::End: itemIndex = count; break; case KeyDirection::PageUp: itemIndex -= itemIndex % rowCount; break; case KeyDirection::PageDown: itemIndex += rowCount - itemIndex % rowCount - 1; break; default: return -1; } if (itemIndex < 0) return 0; else if (itemIndex >= count) return count - 1; else return itemIndex; } VirtualRepeatEnsureItemVisibleResult GuiRepeatFixedHeightMultiColumnItemComposition::EnsureItemVisible(vint itemIndex) { if (!itemSource) return VirtualRepeatEnsureItemVisibleResult::NotMoved; if (itemIndex < 0 || itemIndex >= itemSource->GetCount()) { return VirtualRepeatEnsureItemVisibleResult::ItemNotExists; } bool moved = false; while (true) { vint rowCount = viewBounds.Height() / itemHeight; if (rowCount == 0) rowCount = 1; vint column = itemIndex / rowCount; Point location = viewBounds.LeftTop(); if (column < firstColumn) { location.x = viewBounds.Width() * column; } else if (column >= firstColumn + fullVisibleColumns) { location.x = viewBounds.Width() * (column - fullVisibleColumns + 1); } else { break; } auto oldLeftTop = viewBounds.LeftTop(); Layout_UpdateViewLocation(location); moved |= viewBounds.LeftTop() != oldLeftTop; if (viewBounds.LeftTop() != location) break; } return moved ? VirtualRepeatEnsureItemVisibleResult::Moved : VirtualRepeatEnsureItemVisibleResult::NotMoved; } } } } /*********************************************************************** .\GRAPHICSCOMPOSITION\GUIGRAPHICSRESPONSIVECOMPOSITION.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace compositions { using namespace collections; using namespace controls; /*********************************************************************** GuiResponsiveCompositionBase ***********************************************************************/ void GuiResponsiveCompositionBase::OnParentLineChanged() { GuiBoundsComposition::OnParentLineChanged(); GuiResponsiveCompositionBase* responsive = nullptr; { auto parent = GetParent(); while (parent) { if ((responsive = dynamic_cast(parent))) { break; } parent = parent->GetParent(); } } if (responsiveParent != responsive) { if (responsiveParent) { responsiveParent->OnResponsiveChildRemoved(this); responsiveParent->OnResponsiveChildLevelUpdated(); } responsiveParent = responsive; if (responsiveParent) { responsiveParent->OnResponsiveChildInserted(this); responsiveParent->OnResponsiveChildLevelUpdated(); } } } void GuiResponsiveCompositionBase::OnResponsiveChildInserted(GuiResponsiveCompositionBase* child) { } void GuiResponsiveCompositionBase::OnResponsiveChildRemoved(GuiResponsiveCompositionBase* child) { } void GuiResponsiveCompositionBase::OnResponsiveChildLevelUpdated() { if (responsiveParent) { responsiveParent->OnResponsiveChildLevelUpdated(); } else { InvokeOnCompositionStateChanged(); } } GuiResponsiveCompositionBase::GuiResponsiveCompositionBase() { SetMinSizeLimitation(LimitToElementAndChildren); SetPreferredMinSize(Size(1, 1)); LevelCountChanged.SetAssociatedComposition(this); CurrentLevelChanged.SetAssociatedComposition(this); } ResponsiveDirection GuiResponsiveCompositionBase::GetDirection() { return direction; } void GuiResponsiveCompositionBase::SetDirection(ResponsiveDirection value) { if (direction != value) { direction = value; OnResponsiveChildLevelUpdated(); } } /*********************************************************************** GuiResponsiveSharedCollection ***********************************************************************/ void GuiResponsiveSharedCollection::BeforeInsert(vint index, controls::GuiControl* const& value) { CHECK_ERROR(!value->GetBoundsComposition()->GetParent(), L"GuiResponsiveSharedCollection::BeforeInsert(vint, GuiResponsiveSharedCollection* const&)#Cannot insert a shared control that is currently in use."); } void GuiResponsiveSharedCollection::AfterInsert(vint index, controls::GuiControl* const& value) { view->OnResponsiveChildLevelUpdated(); } void GuiResponsiveSharedCollection::BeforeRemove(vint index, controls::GuiControl* const& value) { CHECK_ERROR(!value->GetBoundsComposition()->GetParent(), L"GuiResponsiveSharedCollection::BeforeRemove(vint, GuiResponsiveSharedCollection* const&)#Cannot remove a shared control that is currently in use."); } void GuiResponsiveSharedCollection::AfterRemove(vint index, vint count) { view->OnResponsiveChildLevelUpdated(); } GuiResponsiveSharedCollection::GuiResponsiveSharedCollection(GuiResponsiveViewComposition* _view) :view(_view) { } /*********************************************************************** GuiResponsiveViewCollection ***********************************************************************/ void GuiResponsiveViewCollection::BeforeInsert(vint index, GuiResponsiveCompositionBase* const& value) { CHECK_ERROR(!value->GetParent(), L"GuiResponsiveViewCollection::BeforeRemove(vint, GuiResponsiveCompositionBase* const&)#Cannot insert a view that is currently in use."); } void GuiResponsiveViewCollection::AfterInsert(vint index, GuiResponsiveCompositionBase* const& value) { if (!view->currentView) { view->skipUpdatingLevels = true; view->currentView = value; view->currentView->SetAlignmentToParent(Margin(0, 0, 0, 0)); view->AddChild(view->currentView); view->skipUpdatingLevels = false; } view->OnResponsiveChildLevelUpdated(); } void GuiResponsiveViewCollection::BeforeRemove(vint index, GuiResponsiveCompositionBase* const& value) { CHECK_ERROR(!value->GetParent(), L"GuiResponsiveViewCollection::BeforeRemove(vint, GuiResponsiveCompositionBase* const&)#Cannot remove a view that is currently in use."); } void GuiResponsiveViewCollection::AfterRemove(vint index, vint count) { view->OnResponsiveChildLevelUpdated(); } GuiResponsiveViewCollection::GuiResponsiveViewCollection(GuiResponsiveViewComposition* _view) :view(_view) { } /*********************************************************************** GuiResponsiveSharedComposition ***********************************************************************/ void GuiResponsiveSharedComposition::SetSharedControl() { if (shared && view && !view->destructing) { auto sharedParent = shared->GetBoundsComposition()->GetParent(); CHECK_ERROR(view->sharedControls.Contains(shared), L"GuiResponsiveSharedComposition::SetSharedControl()#The specified shared control is not in GuiResponsiveViewComposition::GetSharedControls()."); CHECK_ERROR(!sharedParent || sharedParent == this, L"GuiResponsiveSharedComposition::SetSharedControl()#The specified shared control has not been released. This usually means this control is not in GuiResponsiveViewComposition::GetSharedControls()."); if (!sharedParent) { shared->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); AddChild(shared->GetBoundsComposition()); view->usedSharedControls.Add(shared); } } } void GuiResponsiveSharedComposition::OnParentLineChanged() { GuiBoundsComposition::OnParentLineChanged(); if (view && view->destructing) { return; } GuiResponsiveViewComposition* currentView = nullptr; { auto parent = GetParent(); while (parent) { if ((currentView = dynamic_cast(parent))) { break; } parent = parent->GetParent(); } } if (currentView != view && view && shared) { RemoveChild(shared->GetBoundsComposition()); view->usedSharedControls.Remove(shared); } view = currentView; SetSharedControl(); } GuiResponsiveSharedComposition::GuiResponsiveSharedComposition() { SetMinSizeLimitation(LimitToElementAndChildren); } controls::GuiControl* GuiResponsiveSharedComposition::GetShared() { return shared; } void GuiResponsiveSharedComposition::SetShared(controls::GuiControl* value) { if (shared != value) { CHECK_ERROR(!shared || !shared->GetBoundsComposition()->GetParent(), L"GuiResponsiveSharedComposition::SetShared(GuiControl*)#Cannot replace a shared control that is currently in use."); shared = value; SetSharedControl(); } } /*********************************************************************** GuiResponsiveViewComposition ***********************************************************************/ bool GuiResponsiveViewComposition::CalculateLevelCount() { vint old = levelCount; if (views.Count() == 0) { levelCount = 1; } else { levelCount = 0; // TODO: (enumerable) foreach for (vint i = 0; i < views.Count(); i++) { auto view = views[i]; if (((vint)direction & (vint)view->GetDirection()) != 0) { levelCount += view->GetLevelCount(); } else { levelCount += 1; } } } if (old != levelCount) { LevelCountChanged.Execute(GuiEventArgs(this)); return true; } return false; } bool GuiResponsiveViewComposition::CalculateCurrentLevel() { vint old = currentLevel; currentLevel = 0; // TODO: (enumerable) foreach:reversed for (vint i = views.Count() - 1; i >= 0; i--) { auto view = views[i]; if (((vint)direction & (vint)view->GetDirection()) != 0) { if (currentView == view) { currentLevel += view->GetCurrentLevel() + 1; break; } else { currentLevel += view->GetLevelCount(); } } else { currentLevel++; } } currentLevel--; if (old != currentLevel) { CurrentLevelChanged.Execute(GuiEventArgs(this)); return true; } return false; } void GuiResponsiveViewComposition::OnResponsiveChildLevelUpdated() { if (!skipUpdatingLevels) { CalculateLevelCount(); CalculateCurrentLevel(); GuiResponsiveCompositionBase::OnResponsiveChildLevelUpdated(); } } GuiResponsiveViewComposition::GuiResponsiveViewComposition() :sharedControls(this) , views(this) { BeforeSwitchingView.SetAssociatedComposition(this); } GuiResponsiveViewComposition::~GuiResponsiveViewComposition() { destructing = true; for (auto view : views) { if (view != currentView) { SafeDeleteComposition(view); } } for (auto shared : From(sharedControls).Except(usedSharedControls)) { SafeDeleteControl(shared); } } vint GuiResponsiveViewComposition::GetLevelCount() { return levelCount; } vint GuiResponsiveViewComposition::GetCurrentLevel() { return currentLevel; } bool GuiResponsiveViewComposition::LevelDown() { skipUpdatingLevels = true; if (((vint)direction & (vint)currentView->GetDirection()) != 0 && !currentView->LevelDown()) { vint index = views.IndexOf(currentView); if (index < views.Count() - 1) { RemoveChild(currentView); currentView = views[index + 1]; currentView->SetAlignmentToParent(Margin(0, 0, 0, 0)); { GuiItemEventArgs arguments(this); arguments.itemIndex = views.IndexOf(currentView); BeforeSwitchingView.Execute(arguments); } AddChild(currentView); } } skipUpdatingLevels = false; auto x = CalculateLevelCount(); auto y = CalculateCurrentLevel(); if (!x && !y) return false; InvokeOnCompositionStateChanged(); return true; } bool GuiResponsiveViewComposition::LevelUp() { skipUpdatingLevels = true; if (((vint)direction & (vint)currentView->GetDirection()) != 0 && !currentView->LevelUp()) { vint index = views.IndexOf(currentView); if (index > 0) { RemoveChild(currentView); currentView = views[index - 1]; currentView->SetAlignmentToParent(Margin(0, 0, 0, 0)); { GuiItemEventArgs arguments(this); arguments.itemIndex = views.IndexOf(currentView); BeforeSwitchingView.Execute(arguments); } AddChild(currentView); } } skipUpdatingLevels = false; auto x = CalculateLevelCount(); auto y = CalculateCurrentLevel(); if (!x && !y) return false; InvokeOnCompositionStateChanged(); return true; } GuiResponsiveCompositionBase* GuiResponsiveViewComposition::GetCurrentView() { return currentView; } collections::ObservableListBase& GuiResponsiveViewComposition::GetSharedControls() { return sharedControls; } collections::ObservableListBase& GuiResponsiveViewComposition::GetViews() { return views; } /*********************************************************************** GuiResponsiveFixedComposition ***********************************************************************/ void GuiResponsiveFixedComposition::OnResponsiveChildLevelUpdated() { InvokeOnCompositionStateChanged(); } vint GuiResponsiveFixedComposition::GetLevelCount() { return 1; } vint GuiResponsiveFixedComposition::GetCurrentLevel() { return 0; } bool GuiResponsiveFixedComposition::LevelDown() { return false; } bool GuiResponsiveFixedComposition::LevelUp() { return false; } /*********************************************************************** GuiResponsiveStackComposition ***********************************************************************/ #define DEFINE_AVAILABLE \ auto availables = From(responsiveChildren) \ .Where([=](GuiResponsiveCompositionBase* child) \ { \ return ((vint)direction & (vint)child->GetDirection()) != 0; \ }) \ bool GuiResponsiveStackComposition::CalculateLevelCount() { vint old = levelCount; DEFINE_AVAILABLE; if (availables.IsEmpty()) { levelCount = 1; } else { levelCount = availables .Select([](GuiResponsiveCompositionBase* child) { return child->GetLevelCount() - 1; }) .Aggregate([](vint a, vint b) { return a + b; }) + 1; } if (old != levelCount) { LevelCountChanged.Execute(GuiEventArgs(this)); return true; } return false; } bool GuiResponsiveStackComposition::CalculateCurrentLevel() { vint old = currentLevel; DEFINE_AVAILABLE; if (availables.IsEmpty()) { currentLevel = 0; } else { currentLevel = availables .Select([](GuiResponsiveCompositionBase* child) { return child->GetCurrentLevel(); }) .Aggregate([](vint a, vint b) { return a + b; }); } if (old != currentLevel) { CurrentLevelChanged.Execute(GuiEventArgs(this)); return true; } return false; } void GuiResponsiveStackComposition::OnResponsiveChildInserted(GuiResponsiveCompositionBase* child) { responsiveChildren.Add(child); } void GuiResponsiveStackComposition::OnResponsiveChildRemoved(GuiResponsiveCompositionBase* child) { responsiveChildren.Remove(child); } void GuiResponsiveStackComposition::OnResponsiveChildLevelUpdated() { CalculateLevelCount(); CalculateCurrentLevel(); GuiResponsiveCompositionBase::OnResponsiveChildLevelUpdated(); } bool GuiResponsiveStackComposition::ChangeLevel(bool levelDown) { DEFINE_AVAILABLE; SortedList ignored; while (true) { GuiResponsiveCompositionBase* selected = nullptr; vint size = 0; for (auto child : availables) { if (!ignored.Contains(child)) { Size childSize = child->GetCachedBounds().GetSize(); vint childSizeToCompare = direction == ResponsiveDirection::Horizontal ? childSize.x : direction == ResponsiveDirection::Vertical ? childSize.y : childSize.x * childSize.y; if (!selected || (levelDown ? size < childSizeToCompare : size > childSizeToCompare)) { selected = child; size = childSizeToCompare; } } } if (!selected) { break; } else if (levelDown ? selected->LevelDown() : selected->LevelUp()) { break; } else { ignored.Add(selected); } } if (!CalculateCurrentLevel()) return false; InvokeOnCompositionStateChanged(); return true; } vint GuiResponsiveStackComposition::GetLevelCount() { return levelCount; } vint GuiResponsiveStackComposition::GetCurrentLevel() { return currentLevel; } bool GuiResponsiveStackComposition::LevelDown() { return ChangeLevel(true); } bool GuiResponsiveStackComposition::LevelUp() { return ChangeLevel(false); } /*********************************************************************** GuiResponsiveGroupComposition ***********************************************************************/ bool GuiResponsiveGroupComposition::CalculateLevelCount() { vint old = levelCount; DEFINE_AVAILABLE; if (availables.IsEmpty()) { levelCount = 1; } else { levelCount = availables .Select([](GuiResponsiveCompositionBase* child) { return child->GetLevelCount(); }) .Max(); } if (old != levelCount) { LevelCountChanged.Execute(GuiEventArgs(this)); return true; } return false; } bool GuiResponsiveGroupComposition::CalculateCurrentLevel() { vint old = currentLevel; DEFINE_AVAILABLE; if (availables.IsEmpty()) { currentLevel = 0; } else { currentLevel = availables .Select([](GuiResponsiveCompositionBase* child) { return child->GetCurrentLevel(); }) .Max(); } if (old != currentLevel) { CurrentLevelChanged.Execute(GuiEventArgs(this)); return true; } return false; } void GuiResponsiveGroupComposition::OnResponsiveChildInserted(GuiResponsiveCompositionBase* child) { responsiveChildren.Add(child); } void GuiResponsiveGroupComposition::OnResponsiveChildRemoved(GuiResponsiveCompositionBase* child) { responsiveChildren.Remove(child); } void GuiResponsiveGroupComposition::OnResponsiveChildLevelUpdated() { CalculateLevelCount(); CalculateCurrentLevel(); GuiResponsiveCompositionBase::OnResponsiveChildLevelUpdated(); } vint GuiResponsiveGroupComposition::GetLevelCount() { return levelCount; } vint GuiResponsiveGroupComposition::GetCurrentLevel() { return currentLevel; } bool GuiResponsiveGroupComposition::LevelDown() { DEFINE_AVAILABLE; vint level = currentLevel; for (auto child : availables) { if (child->GetCurrentLevel() >= level) { if (!child->LevelDown()) { break; } } } if (!CalculateCurrentLevel()) return false; InvokeOnCompositionStateChanged(); return true; } bool GuiResponsiveGroupComposition::LevelUp() { DEFINE_AVAILABLE; vint level = currentLevel; for (auto child : availables) { while (child->GetCurrentLevel() <= level) { if (!child->LevelUp()) { break; } } } if (!CalculateCurrentLevel()) return false; InvokeOnCompositionStateChanged(); return true; } #undef DEFINE_AVAILABLE /*********************************************************************** GuiResponsiveContainerComposition ***********************************************************************/ std::strong_ordering GuiResponsiveContainerComposition::Layout_CompareSize(Size first, Size second) { auto ordX = testX ? first.x <=> second.x : std::strong_ordering::equivalent; auto ordY = testY ? first.y <=> second.y : std::strong_ordering::equivalent; if (ordX == std::strong_ordering::less || ordY == std::strong_ordering::less) { return std::strong_ordering::less; } if (ordX == std::strong_ordering::greater || ordY == std::strong_ordering::greater) { return std::strong_ordering::greater; } return std::strong_ordering::equivalent; } void GuiResponsiveContainerComposition::Layout_AdjustLevelUp(Size containerSize) { while (true) { if (responsiveTarget->GetCurrentLevel() == responsiveTarget->GetLevelCount() - 1) break; if (Layout_CompareSize(minSizeLowerBound, minSizeUpperBound) == std::strong_ordering::less) { if (Layout_CompareSize(containerSize, minSizeUpperBound) == std::strong_ordering::less) break; } else { if (Layout_CompareSize(containerSize, minSizeUpperBound) != std::strong_ordering::greater) break; } if (!responsiveTarget->LevelUp()) break; responsiveTarget->Layout_UpdateMinSize(); minSizeUpperBound = responsiveTarget->GetCachedMinSize(); if (Layout_CompareSize(containerSize, minSizeUpperBound) == std::strong_ordering::less) { responsiveTarget->LevelDown(); responsiveTarget->Layout_UpdateMinSize(); break; } minSizeLowerBound = minSizeUpperBound; } } void GuiResponsiveContainerComposition::Layout_AdjustLevelDown(Size containerSize) { while (true) { if (responsiveTarget->GetCurrentLevel() == 0) break; if (Layout_CompareSize(containerSize, minSizeLowerBound) != std::strong_ordering::less) break; if (!responsiveTarget->LevelDown()) break; responsiveTarget->Layout_UpdateMinSize(); minSizeUpperBound = minSizeLowerBound; minSizeLowerBound = responsiveTarget->GetCachedMinSize(); } } Rect GuiResponsiveContainerComposition::Layout_CalculateBounds(Size parentSize) { auto bounds = GuiBoundsComposition::Layout_CalculateBounds(parentSize); if (responsiveTarget) { bool needAdjust = false; auto containerSize = bounds.GetSize(); auto ordering = std::strong_ordering::equivalent; if (containerSize != cachedBounds.GetSize()) { ordering = Layout_CompareSize(containerSize, cachedBounds.GetSize()); needAdjust = true; } if (responsiveTarget) { if (responsiveTarget->GetCachedMinSize() != minSizeLowerBound) { minSizeLowerBound = responsiveTarget->GetCachedMinSize(); if (minSizeUpperBound.x < minSizeLowerBound.x) minSizeUpperBound.x = minSizeLowerBound.x; if (minSizeUpperBound.y < minSizeLowerBound.y) minSizeUpperBound.y = minSizeLowerBound.y; } if (Layout_CompareSize(containerSize, minSizeLowerBound) == std::strong_ordering::less) { ordering = std::strong_ordering::less; needAdjust = true; } } if (needAdjust) { if (ordering == std::strong_ordering::less) { Layout_AdjustLevelDown(containerSize); } else if (ordering == std::strong_ordering::greater) { Layout_AdjustLevelUp(containerSize); } } } return bounds; } GuiResponsiveContainerComposition::GuiResponsiveContainerComposition() { } GuiResponsiveCompositionBase* GuiResponsiveContainerComposition::GetResponsiveTarget() { return responsiveTarget; } void GuiResponsiveContainerComposition::SetResponsiveTarget(GuiResponsiveCompositionBase* value) { if (responsiveTarget != value) { if (responsiveTarget) { RemoveChild(responsiveTarget); } responsiveTarget = value; if (responsiveTarget) { responsiveTarget->SetAlignmentToParent(Margin(0, 0, 0, 0)); while (responsiveTarget->LevelUp()); AddChild(responsiveTarget); responsiveTarget->Layout_UpdateMinSize(); minSizeUpperBound = responsiveTarget->GetCachedMinSize(); minSizeLowerBound = responsiveTarget->GetCachedMinSize(); testX = (vint)responsiveTarget->GetDirection() & (vint)ResponsiveDirection::Horizontal; testY = (vint)responsiveTarget->GetDirection() & (vint)ResponsiveDirection::Vertical; Layout_AdjustLevelDown(cachedBounds.GetSize()); } else { minSizeUpperBound = {}; minSizeLowerBound = {}; testX = false; testY = false; } } } } } } /*********************************************************************** .\GRAPHICSCOMPOSITION\GUIGRAPHICSSHAREDSIZECOMPOSITION.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace compositions { using namespace reflection::description; using namespace collections; using namespace controls; using namespace elements; /*********************************************************************** GuiSharedSizeItemComposition ***********************************************************************/ void GuiSharedSizeItemComposition::OnParentLineChanged() { GuiBoundsComposition::OnParentLineChanged(); if (parentRoot) { parentRoot->childItems.Remove(this); parentRoot = nullptr; } auto current = GetParent(); while (current) { if (auto item = dynamic_cast(current)) { break; } else if (auto root = dynamic_cast(current)) { parentRoot = root; break; } current = current->GetParent(); } if (parentRoot) { parentRoot->childItems.Add(this); } } Size GuiSharedSizeItemComposition::Layout_CalculateMinSize() { if (parentRoot) { return cachedMinSize; } else { return GuiBoundsComposition::Layout_CalculateMinSize(); } } Size GuiSharedSizeItemComposition::Layout_CalculateOriginalMinSize() { return GuiBoundsComposition::Layout_CalculateMinSize(); } GuiSharedSizeItemComposition::GuiSharedSizeItemComposition() { SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); } const WString& GuiSharedSizeItemComposition::GetGroup() { return group; } void GuiSharedSizeItemComposition::SetGroup(const WString& value) { if (group != value) { group = value; InvokeOnCompositionStateChanged(); } } bool GuiSharedSizeItemComposition::GetSharedWidth() { return sharedWidth; } void GuiSharedSizeItemComposition::SetSharedWidth(bool value) { if (sharedWidth != value) { sharedWidth = value; InvokeOnCompositionStateChanged(); } } bool GuiSharedSizeItemComposition::GetSharedHeight() { return sharedHeight; } void GuiSharedSizeItemComposition::SetSharedHeight(bool value) { if (sharedHeight != value) { sharedHeight = value; InvokeOnCompositionStateChanged(); } } /*********************************************************************** GuiSharedSizeRootComposition ***********************************************************************/ void GuiSharedSizeRootComposition::AddSizeComponent(collections::Dictionary& sizes, const WString& group, vint sizeComponent) { vint index = sizes.Keys().IndexOf(group); if (index == -1) { sizes.Add(group, sizeComponent); } else if (sizes.Values().Get(index) < sizeComponent) { sizes.Set(group, sizeComponent); } } void GuiSharedSizeRootComposition::CalculateOriginalMinSizes() { for (auto item : childItems) { item->originalMinSize = item->Layout_CalculateOriginalMinSize(); } } void GuiSharedSizeRootComposition::CollectSizes(collections::Dictionary& widths, collections::Dictionary& heights) { for (auto item : childItems) { auto group = item->GetGroup(); if (item->GetSharedWidth()) { AddSizeComponent(widths, group, item->originalMinSize.x); } if (item->GetSharedHeight()) { AddSizeComponent(heights, group, item->originalMinSize.y); } } } void GuiSharedSizeRootComposition::AlignSizes(collections::Dictionary& widths, collections::Dictionary& heights) { for (auto item : childItems) { auto group = item->GetGroup(); auto size = item->originalMinSize; if (item->GetSharedWidth()) { size.x = widths[group]; } if (item->GetSharedHeight()) { size.y = heights[group]; } item->Layout_SetCachedMinSize(size); } } Size GuiSharedSizeRootComposition::Layout_CalculateMinSize() { itemWidths.Clear(); itemHeights.Clear(); CalculateOriginalMinSizes(); CollectSizes(itemWidths, itemHeights); AlignSizes(itemWidths, itemHeights); return GuiBoundsComposition::Layout_CalculateMinSize(); } } } } /*********************************************************************** .\GRAPHICSCOMPOSITION\GUIGRAPHICSSPECIALIZEDCOMPOSITION.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace compositions { /*********************************************************************** GuiSideAlignedComposition ***********************************************************************/ Rect GuiSideAlignedComposition::Layout_CalculateBounds(Size parentSize) { Rect result; if (auto parent = GetParent()) { Rect bounds({}, parentSize); vint w = (vint)(bounds.Width() * maxRatio); vint h = (vint)(bounds.Height() * maxRatio); if (w > maxLength) w = maxLength; if (h > maxLength) h = maxLength; switch (direction) { case Left: { bounds.x2 = bounds.x1 + w; } break; case Top: { bounds.y2 = bounds.y1 + h; } break; case Right: { bounds.x1 = bounds.x2 - w; } break; case Bottom: { bounds.y1 = bounds.y2 - h; } break; } result = bounds; } return result; } GuiSideAlignedComposition::Direction GuiSideAlignedComposition::GetDirection() { return direction; } void GuiSideAlignedComposition::SetDirection(Direction value) { if (direction != value) { direction = value; InvokeOnCompositionStateChanged(); } } vint GuiSideAlignedComposition::GetMaxLength() { return maxLength; } void GuiSideAlignedComposition::SetMaxLength(vint value) { if (value < 0) value = 0; if (maxLength != value) { maxLength = value; InvokeOnCompositionStateChanged(); } } double GuiSideAlignedComposition::GetMaxRatio() { return maxRatio; } void GuiSideAlignedComposition::SetMaxRatio(double value) { if (value < 0) value = 0; else if (value > 1) value = 1; if (maxRatio != value) { maxRatio = value; InvokeOnCompositionStateChanged(); } } /*********************************************************************** GuiPartialViewComposition ***********************************************************************/ Rect GuiPartialViewComposition::Layout_CalculateBounds(Size parentSize) { Rect result; if (auto parent = GetParent()) { Rect bounds({}, parentSize); vint w = bounds.Width(); vint h = bounds.Height(); vint pw = (vint)(wPageSize * w); vint ph = (vint)(hPageSize * h); vint ow = preferredMinSize.x - pw; if (ow < 0) ow = 0; vint oh = preferredMinSize.y - ph; if (oh < 0) oh = 0; w -= ow; h -= oh; pw += ow; ph += oh; result = Rect(Point((vint)(wRatio * w), (vint)(hRatio * h)), Size(pw, ph)); } return result; } double GuiPartialViewComposition::GetWidthRatio() { return wRatio; } double GuiPartialViewComposition::GetWidthPageSize() { return wPageSize; } double GuiPartialViewComposition::GetHeightRatio() { return hRatio; } double GuiPartialViewComposition::GetHeightPageSize() { return hPageSize; } void GuiPartialViewComposition::SetWidthRatio(double value) { if (wRatio != value) { wRatio = value; InvokeOnCompositionStateChanged(); } } void GuiPartialViewComposition::SetWidthPageSize(double value) { if (wPageSize != value) { wPageSize = value; InvokeOnCompositionStateChanged(); } } void GuiPartialViewComposition::SetHeightRatio(double value) { if (hRatio != value) { hRatio = value; InvokeOnCompositionStateChanged(); } } void GuiPartialViewComposition::SetHeightPageSize(double value) { if (hPageSize != value) { hPageSize = value; InvokeOnCompositionStateChanged(); } } } } } /*********************************************************************** .\GRAPHICSCOMPOSITION\GUIGRAPHICSSTACKCOMPOSITION.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace compositions { /*********************************************************************** GuiStackComposition ***********************************************************************/ void GuiStackComposition::Layout_UpdateStackItemMinSizes() { for (auto item : layout_stackItems) { item->Layout_SetCachedMinSize(item->Layout_CalculateMinSizeHelper()); } if (!layout_invalid) return; layout_invalid = false; Point virtualOffset; layout_stackItemTotalSize = Size(0, 0); for (auto [item, index] : indexed(layout_stackItems)) { Size itemSize = item->GetCachedMinSize(); switch (direction) { case GuiStackComposition::Horizontal: case GuiStackComposition::ReversedHorizontal: { if (layout_stackItemTotalSize.y < itemSize.y) { layout_stackItemTotalSize.y = itemSize.y; } if (index > 0) { layout_stackItemTotalSize.x += padding; } layout_stackItemTotalSize.x += itemSize.x; } break; case GuiStackComposition::Vertical: case GuiStackComposition::ReversedVertical: { if (layout_stackItemTotalSize.x < itemSize.x) { layout_stackItemTotalSize.x = itemSize.x; } if (index > 0) { layout_stackItemTotalSize.y += padding; } layout_stackItemTotalSize.y += itemSize.y; } break; } item->layout_virtualOffset = virtualOffset; virtualOffset.x += itemSize.x + padding; virtualOffset.y += itemSize.y + padding; } } void GuiStackComposition::Layout_UpdateStackItemBounds(Rect contentBounds) { if (layout_ensuringVisibleStackItem) { Rect itemBounds( layout_ensuringVisibleStackItem->layout_virtualOffset, layout_ensuringVisibleStackItem->GetCachedMinSize() ); switch (direction) { case Horizontal: case ReversedHorizontal: if (itemBounds.x1 <= 0) { layout_adjustment = -itemBounds.x1; } else { vint overflow = itemBounds.x2 - contentBounds.x2; if (overflow > 0) { layout_adjustment = -overflow; } } break; case Vertical: case ReversedVertical: if (itemBounds.y1 <= 0) { layout_adjustment = -itemBounds.y1; } else { vint overflow = itemBounds.y2 - contentBounds.y2; if (overflow > 0) { layout_adjustment = -overflow; } } break; } } for (auto item : layout_stackItems) { item->Layout_SetStackItemBounds(contentBounds, item->layout_virtualOffset); } } void GuiStackComposition::OnChildInserted(GuiGraphicsComposition* child) { GuiBoundsComposition::OnChildInserted(child); GuiStackItemComposition* item = dynamic_cast(child); if (item) { if (!layout_stackItems.Contains(item)) { layout_stackItems.Add(item); } } } void GuiStackComposition::OnChildRemoved(GuiGraphicsComposition* child) { GuiBoundsComposition::OnChildRemoved(child); GuiStackItemComposition* item = dynamic_cast(child); if (item) { layout_stackItems.Remove(item); if (item == layout_ensuringVisibleStackItem) { layout_ensuringVisibleStackItem = nullptr; } } } void GuiStackComposition::OnCompositionStateChanged() { GuiBoundsComposition::OnCompositionStateChanged(); layout_invalid = true; } Size GuiStackComposition::Layout_CalculateMinSize() { Layout_UpdateStackItemMinSizes(); Size minStackSize; if (GetMinSizeLimitation() == GuiGraphicsComposition::LimitToElementAndChildren) { if (!layout_ensuringVisibleStackItem || direction == Vertical || direction == ReversedVertical) { minStackSize.x = layout_stackItemTotalSize.x; } if (!layout_ensuringVisibleStackItem || direction == Horizontal || direction == ReversedHorizontal) { minStackSize.y = layout_stackItemTotalSize.y; } } minStackSize.x += extraMargin.left; minStackSize.x += extraMargin.right; minStackSize.y += extraMargin.top; minStackSize.y += extraMargin.bottom; Size minClientSize = GuiBoundsComposition::Layout_CalculateMinSize(); return Size( minStackSize.x > minClientSize.x ? minStackSize.x : minClientSize.x, minStackSize.y > minClientSize.y ? minStackSize.y : minClientSize.y ); return minStackSize; } Rect GuiStackComposition::Layout_CalculateBounds(Size parentSize) { Rect bounds = GuiBoundsComposition::Layout_CalculateBounds(parentSize); Rect contentBounds( extraMargin.left, extraMargin.top, bounds.Width() - extraMargin.right, bounds.Height() - extraMargin.bottom ); Layout_UpdateStackItemBounds(contentBounds); return bounds; } const GuiStackComposition::ItemCompositionList& GuiStackComposition::GetStackItems() { return layout_stackItems; } bool GuiStackComposition::InsertStackItem(vint index, GuiStackItemComposition* item) { index = layout_stackItems.Insert(index, item); if (AddChild(item)) { return true; } layout_stackItems.RemoveAt(index); return false; } GuiStackComposition::Direction GuiStackComposition::GetDirection() { return direction; } void GuiStackComposition::SetDirection(Direction value) { if (direction != value) { direction = value; InvokeOnCompositionStateChanged(); } } vint GuiStackComposition::GetPadding() { return padding; } void GuiStackComposition::SetPadding(vint value) { if (padding != value) { padding = value; InvokeOnCompositionStateChanged(); } } Margin GuiStackComposition::GetExtraMargin() { return extraMargin; } void GuiStackComposition::SetExtraMargin(Margin value) { if (value.left < 0) value.left = 0; if (value.top < 0) value.top = 0; if (value.right < 0) value.right = 0; if (value.bottom < 0) value.bottom = 0; if (extraMargin != value) { extraMargin = value; InvokeOnCompositionStateChanged(); } } bool GuiStackComposition::IsStackItemClipped() { Rect clientArea = GetCachedClientArea(); switch(direction) { case Horizontal: case ReversedHorizontal: { vint width = layout_stackItemTotalSize.x + extraMargin.left + extraMargin.right; return width > clientArea.Width(); } break; case Vertical: case ReversedVertical: { vint height = layout_stackItemTotalSize.y + extraMargin.top + extraMargin.bottom; return height > clientArea.Height(); } break; } return false; } bool GuiStackComposition::EnsureVisible(vint index) { if (0 <= index && index < layout_stackItems.Count()) { layout_ensuringVisibleStackItem = layout_stackItems[index]; } else { layout_ensuringVisibleStackItem = nullptr; } InvokeOnCompositionStateChanged(); return layout_ensuringVisibleStackItem != nullptr; } /*********************************************************************** GuiStackItemComposition ***********************************************************************/ void GuiStackItemComposition::Layout_SetStackItemBounds(Rect contentBounds, Point virtualOffset) { vint x = 0; vint y = 0; switch (layout_stackParent->direction) { case GuiStackComposition::Horizontal: x = contentBounds.x1 + virtualOffset.x; y = contentBounds.y1; break; case GuiStackComposition::ReversedHorizontal: x = contentBounds.x2 - virtualOffset.x - cachedMinSize.x; y = contentBounds.y1; break; case GuiStackComposition::Vertical: x = contentBounds.x1; y = contentBounds.y1 + virtualOffset.y; break; case GuiStackComposition::ReversedVertical: x = contentBounds.x1; y = contentBounds.y2 - virtualOffset.y - cachedMinSize.y; break; } switch (layout_stackParent->direction) { case GuiStackComposition::Horizontal: x += layout_stackParent->layout_adjustment; break; case GuiStackComposition::ReversedHorizontal: x -= layout_stackParent->layout_adjustment; break; case GuiStackComposition::Vertical: y += layout_stackParent->layout_adjustment; break; case GuiStackComposition::ReversedVertical: y -= layout_stackParent->layout_adjustment; break; } vint w = 0; vint h = 0; switch (layout_stackParent->direction) { case GuiStackComposition::Horizontal: case GuiStackComposition::ReversedHorizontal: w = cachedMinSize.x; h = contentBounds.Height(); break; case GuiStackComposition::Vertical: case GuiStackComposition::ReversedVertical: w = contentBounds.Width(); h = cachedMinSize.y; break; } Rect result( x - extraMargin.left, y - extraMargin.top, x + w + extraMargin.right, y + h + extraMargin.bottom ); Layout_SetCachedBounds(result); } void GuiStackItemComposition::OnParentLineChanged() { layout_stackParent = dynamic_cast(GetParent()); GuiGraphicsComposition::OnParentLineChanged(); } GuiStackItemComposition::GuiStackItemComposition() { SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); CachedMinSizeChanged.AttachLambda([this](GuiGraphicsComposition* sender, GuiEventArgs& arguments) { if (layout_stackParent) layout_stackParent->layout_invalid = true; }); } Margin GuiStackItemComposition::GetExtraMargin() { return extraMargin; } void GuiStackItemComposition::SetExtraMargin(Margin value) { if (extraMargin != value) { extraMargin = value; InvokeOnCompositionStateChanged(); } } } } } /*********************************************************************** .\GRAPHICSCOMPOSITION\GUIGRAPHICSTABLECOMPOSITION.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace compositions { using namespace collections; using namespace controls; using namespace elements; /*********************************************************************** GuiTableComposition ***********************************************************************/ namespace update_cell_bounds_helpers { vint First(vint a, vint b) { return a; } vint Second(vint a, vint b) { return b; } vint X(Size s) { return s.x; } vint Y(Size s) { return s.y; } vint RL(GuiCellComposition* cell) { return cell->GetRow(); } vint CL(GuiCellComposition* cell) { return cell->GetColumn(); } vint RS(GuiCellComposition* cell) { return cell->GetRowSpan(); } vint CS(GuiCellComposition* cell) { return cell->GetColumnSpan(); } } using namespace update_cell_bounds_helpers; Rect GuiTableComposition::Layout_CalculateCellArea(Rect tableBounds) { Rect bounds(Point(0, 0), tableBounds.GetSize()); vint borderThickness = borderVisible ? cellPadding : 0; bounds.x1 += borderThickness; bounds.y1 += borderThickness; bounds.x2 -= borderThickness; bounds.y2 -= borderThickness; if (bounds.x2 < bounds.x1) bounds.x2 = bounds.x1; if (bounds.y2 < bounds.y1) bounds.y2 = bounds.y1; return bounds; } void GuiTableComposition::Layout_UpdateCellBoundsInternal( collections::Array& dimSizes, vint& dimSize, vint& dimSizeWithPercentage, collections::Array& dimOptions, vint GuiTableComposition::* dim1, vint GuiTableComposition::* dim2, vint(*getSize)(Size), vint(*getLocation)(GuiCellComposition*), vint(*getSpan)(GuiCellComposition*), vint(*getRow)(vint, vint), vint(*getCol)(vint, vint) ) { for (vint i = 0; i < this->*dim1; i++) { dimSizes[i] = 0; } for (vint i = 0; i < this->*dim1; i++) { GuiCellOption option = dimOptions[i]; switch (option.composeType) { case GuiCellOption::Absolute: { dimSizes[i] = option.absolute; } break; case GuiCellOption::MinSize: { for (vint j = 0; j < this->*dim2; j++) { if (auto cell = GetSitedCell(getRow(i, j), getCol(i, j))) { if (getSpan(cell) == 1) { vint size = getSize(cell->GetCachedMinSize()); vint span = getSpan(cell); for (vint k = 1; k < span; k++) { size -= dimSizes[i - k] + cellPadding; } if (dimSizes[i] < size) { dimSizes[i] = size; } } } } } break; default:; } } { for (vint i = 0; i < this->*dim1; i++) { GuiCellOption option = dimOptions[i]; if (option.composeType == GuiCellOption::Percentage) { for (vint j = 0; j < this->*dim2; j++) { GuiCellComposition* cell = GetSitedCell(getRow(i, j), getCol(i, j)); if (cell) { vint size = getSize(cell->GetCachedMinSize()); vint start = getLocation(cell); vint span = getSpan(cell); size -= (span - 1)*cellPadding; double totalPercentage = 0; for (vint k = start; k < start + span; k++) { if (dimOptions[k].composeType == GuiCellOption::Percentage) { totalPercentage += dimOptions[k].percentage; } else { size -= dimSizes[k]; } } size = (vint)ceil(size*option.percentage / totalPercentage); if (dimSizes[i] < size) { dimSizes[i] = size; } } } } } double totalPercentage = 0; for (vint i = 0; i < this->*dim1; i++) { GuiCellOption option = dimOptions[i]; if (option.composeType == GuiCellOption::Percentage) { totalPercentage += option.percentage; } } vint percentageTotalSize = 0; for (vint i = 0; i < this->*dim1; i++) { GuiCellOption option = dimOptions[i]; if (option.composeType == GuiCellOption::Percentage) { vint size = (vint)ceil(dimSizes[i] * totalPercentage / option.percentage); if (percentageTotalSize < size) { percentageTotalSize = size; } } } for (vint i = 0; i < this->*dim1; i++) { GuiCellOption option = dimOptions[i]; if (option.composeType == GuiCellOption::Percentage) { vint size = (vint)ceil(percentageTotalSize * option.percentage / totalPercentage); if (dimSizes[i] < size) { dimSizes[i] = size; } } } } for (vint i = 0; i < this->*dim1; i++) { if (dimOptions[i].composeType != GuiCellOption::Percentage) { dimSize += dimSizes[i]; } dimSizeWithPercentage += dimSizes[i]; } } void GuiTableComposition::Layout_UpdateCellBoundsPercentages( collections::Array& dimSizes, vint dimSize, vint maxDimSize, collections::Array& dimOptions ) { if (maxDimSize > dimSize) { double totalPercentage = 0; vint percentageCount = 0; // TODO: (enumerable) foreach for (vint i = 0; i < dimOptions.Count(); i++) { GuiCellOption option = dimOptions[i]; if (option.composeType == GuiCellOption::Percentage) { totalPercentage += option.percentage; percentageCount++; } } if (percentageCount > 0 && totalPercentage > 0) { // TODO: (enumerable) foreach for (vint i = 0; i < dimOptions.Count(); i++) { GuiCellOption option = dimOptions[i]; if (option.composeType == GuiCellOption::Percentage) { dimSizes[i] = (vint)((maxDimSize - dimSize)*option.percentage / totalPercentage); } } } } } vint GuiTableComposition::GetSiteIndex(vint _rows, vint _columns, vint _row, vint _column) { return _row * _columns + _column; } void GuiTableComposition::SetSitedCell(vint _row, vint _column, GuiCellComposition* cell) { layout_cellCompositions[GetSiteIndex(rows, columns, _row, _column)] = cell; layout_invalid = true; } void GuiTableComposition::OnCompositionStateChanged() { GuiBoundsComposition::OnCompositionStateChanged(); ConfigChanged.Execute(GuiEventArgs(this)); layout_invalid = true; } Size GuiTableComposition::Layout_CalculateMinSize() { for (auto child : Children()) { if (auto cell = dynamic_cast(child)) { cell->Layout_SetCachedMinSize(cell->Layout_CalculateMinSizeHelper()); } } if (layout_invalid) { layout_invalid = false; layout_invalidCellBounds = true; layout_rowOffsets.Resize(rows); layout_rowSizes.Resize(rows); layout_columnOffsets.Resize(columns); layout_columnSizes.Resize(columns); layout_rowTotal = (rows - 1) * cellPadding; layout_columnTotal = (columns - 1) * cellPadding; layout_rowTotalWithPercentage = layout_rowTotal; layout_columnTotalWithPercentage = layout_columnTotal; Layout_UpdateCellBoundsInternal( layout_rowSizes, layout_rowTotal, layout_rowTotalWithPercentage, rowOptions, &GuiTableComposition::rows, &GuiTableComposition::columns, &Y, &RL, &RS, &First, &Second ); Layout_UpdateCellBoundsInternal( layout_columnSizes, layout_columnTotal, layout_columnTotalWithPercentage, columnOptions, &GuiTableComposition::columns, &GuiTableComposition::rows, &X, &CL, &CS, &Second, &First ); } Size minTableSize; if (GetMinSizeLimitation() == GuiGraphicsComposition::LimitToElementAndChildren) { vint offset = (borderVisible ? 2 * cellPadding : 0); minTableSize.x = layout_columnTotalWithPercentage + offset; minTableSize.y = layout_rowTotalWithPercentage + offset; } Size minClientSize = GuiBoundsComposition::Layout_CalculateMinSize(); return Size( minTableSize.x > minClientSize.x ? minTableSize.x : minClientSize.x, minTableSize.y > minClientSize.y ? minTableSize.y : minClientSize.y ); } Rect GuiTableComposition::Layout_CalculateBounds(Size parentSize) { Rect bounds = GuiBoundsComposition::Layout_CalculateBounds(parentSize); if (layout_lastTableSize != bounds.GetSize()) { layout_invalidCellBounds = true; layout_lastTableSize = bounds.GetSize(); } if (layout_invalidCellBounds) { layout_invalidCellBounds = false; Size area = Layout_CalculateCellArea(bounds).GetSize(); Layout_UpdateCellBoundsPercentages( layout_rowSizes, layout_rowTotal, area.y, rowOptions ); Layout_UpdateCellBoundsPercentages( layout_columnSizes, layout_columnTotal, area.x, columnOptions ); layout_rowExtending = Layout_UpdateCellBoundsOffsets(layout_rowOffsets, layout_rowSizes, area.y); layout_columnExtending = Layout_UpdateCellBoundsOffsets(layout_columnOffsets, layout_columnSizes, area.x); for (vint i = 0; i < rows; i++) { for (vint j = 0; j < columns; j++) { vint index = GetSiteIndex(rows, columns, i, j); layout_cellBounds[index] = Rect(Point(layout_columnOffsets[j], layout_rowOffsets[i]), Size(layout_columnSizes[j], layout_rowSizes[i])); } } for (auto child : Children()) { if (auto cell = dynamic_cast(child)) { cell->Layout_SetCellBounds(); } } } return bounds; } vint GuiTableComposition::Layout_UpdateCellBoundsOffsets( collections::Array& offsets, collections::Array& sizes, vint max ) { offsets[0] = 0; for (vint i = 1; i < offsets.Count(); i++) { offsets[i] = offsets[i - 1] + cellPadding + sizes[i - 1]; } vint last = offsets.Count() - 1; vint right = offsets[last] + sizes[last]; return max > right ? max - right : 0; } GuiTableComposition::GuiTableComposition() { ConfigChanged.SetAssociatedComposition(this); SetRowsAndColumns(1, 1); } vint GuiTableComposition::GetRows() { return rows; } vint GuiTableComposition::GetColumns() { return columns; } bool GuiTableComposition::SetRowsAndColumns(vint _rows, vint _columns) { if (_rows <= 0 || _columns <= 0) return false; rowOptions.Resize(_rows); columnOptions.Resize(_columns); layout_cellCompositions.Resize(_rows*_columns); layout_cellBounds.Resize(_rows*_columns); for (vint i = 0; i < _rows*_columns; i++) { layout_cellCompositions[i] = nullptr; layout_cellBounds[i] = Rect(); } rows = _rows; columns = _columns; // TODO: (enumerable) foreach vint childCount = Children().Count(); for (vint i = 0; i < childCount; i++) { GuiCellComposition* cell = dynamic_cast(Children().Get(i)); if (cell) { cell->OnTableRowsAndColumnsChanged(); } } InvokeOnCompositionStateChanged(); return true; } GuiCellComposition* GuiTableComposition::GetSitedCell(vint _row, vint _column) { return layout_cellCompositions[GetSiteIndex(rows, columns, _row, _column)]; } GuiCellOption GuiTableComposition::GetRowOption(vint _row) { return rowOptions[_row]; } void GuiTableComposition::SetRowOption(vint _row, GuiCellOption option) { if (option.composeType == GuiCellOption::Percentage && option.percentage < 0.001) { option.percentage = 0; } if (rowOptions[_row] != option) { rowOptions[_row] = option; InvokeOnCompositionStateChanged(); } } GuiCellOption GuiTableComposition::GetColumnOption(vint _column) { return columnOptions[_column]; } void GuiTableComposition::SetColumnOption(vint _column, GuiCellOption option) { if (option.composeType == GuiCellOption::Percentage && option.percentage < 0.001) { option.percentage = 0; } if (columnOptions[_column] != option) { columnOptions[_column] = option; InvokeOnCompositionStateChanged(); } } vint GuiTableComposition::GetCellPadding() { return cellPadding; } void GuiTableComposition::SetCellPadding(vint value) { if (value < 0) value = 0; if (cellPadding != value) { cellPadding = value; InvokeOnCompositionStateChanged(); } } bool GuiTableComposition::GetBorderVisible() { return borderVisible; } void GuiTableComposition::SetBorderVisible(bool value) { if (borderVisible != value) { borderVisible = value; InvokeOnCompositionStateChanged(); } } /*********************************************************************** GuiCellComposition ***********************************************************************/ void GuiCellComposition::ClearSitedCells(GuiTableComposition* table) { if (row != -1 && column != -1) { for (vint r = 0; r < rowSpan; r++) { for (vint c = 0; c < columnSpan; c++) { table->SetSitedCell(row + r, column + c, nullptr); } } } } void GuiCellComposition::SetSitedCells(GuiTableComposition* table) { for (vint r = 0; r < rowSpan; r++) { for (vint c = 0; c < columnSpan; c++) { table->SetSitedCell(row + r, column + c, this); } } } void GuiCellComposition::ResetSiteInternal() { row = -1; column = -1; rowSpan = 1; columnSpan = 1; } bool GuiCellComposition::SetSiteInternal(vint _row, vint _column, vint _rowSpan, vint _columnSpan) { if (layout_tableParent) { if (_row < 0 || _row >= layout_tableParent->rows || _column < 0 || _column >= layout_tableParent->columns) return false; if (_rowSpan<1 || _row + _rowSpan>layout_tableParent->rows || _columnSpan<1 || _column + _columnSpan>layout_tableParent->columns) return false; for (vint r = 0; r < _rowSpan; r++) { for (vint c = 0; c < _columnSpan; c++) { GuiCellComposition* cell = layout_tableParent->GetSitedCell(_row + r, _column + c); if (cell && cell != this) { return false; } } } ClearSitedCells(layout_tableParent); } row = _row; column = _column; rowSpan = _rowSpan; columnSpan = _columnSpan; if (layout_tableParent) { SetSitedCells(layout_tableParent); } return true; } void GuiCellComposition::OnParentChanged(GuiGraphicsComposition* oldParent, GuiGraphicsComposition* newParent) { GuiGraphicsComposition::OnParentChanged(oldParent, newParent); if (layout_tableParent) { ClearSitedCells(layout_tableParent); } layout_tableParent = dynamic_cast(newParent); if (!layout_tableParent || !SetSiteInternal(row, column, rowSpan, columnSpan)) { ResetSiteInternal(); } if (layout_tableParent) { if (row != -1 && column != -1) { SetSiteInternal(row, column, rowSpan, columnSpan); } } } void GuiCellComposition::OnTableRowsAndColumnsChanged() { if(!SetSiteInternal(row, column, rowSpan, columnSpan)) { ResetSiteInternal(); } } void GuiCellComposition::Layout_SetCellBounds() { Rect result; if (layout_tableParent && row != -1 && column != -1) { Rect bounds1, bounds2; { vint index = layout_tableParent->GetSiteIndex(layout_tableParent->rows, layout_tableParent->columns, row, column); bounds1 = layout_tableParent->layout_cellBounds[index]; } { vint index = layout_tableParent->GetSiteIndex(layout_tableParent->rows, layout_tableParent->columns, row + rowSpan - 1, column + columnSpan - 1); bounds2 = layout_tableParent->layout_cellBounds[index]; if (row + rowSpan == layout_tableParent->rows) { bounds2.y2 += layout_tableParent->layout_rowExtending; } if (column + columnSpan == layout_tableParent->columns) { bounds2.x2 += layout_tableParent->layout_columnExtending; } } vint offset = layout_tableParent->borderVisible ? layout_tableParent->cellPadding : 0; result = Rect(bounds1.x1 + offset, bounds1.y1 + offset, bounds2.x2 + offset, bounds2.y2 + offset); } else { result = Rect(); } Layout_SetCachedBounds(result); } GuiCellComposition::GuiCellComposition() { SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); CachedMinSizeChanged.AttachLambda([this](GuiGraphicsComposition* sender, GuiEventArgs& arguments) { if (layout_tableParent) layout_tableParent->layout_invalid = true; }); } GuiTableComposition* GuiCellComposition::GetTableParent() { return layout_tableParent; } vint GuiCellComposition::GetRow() { return row; } vint GuiCellComposition::GetRowSpan() { return rowSpan; } vint GuiCellComposition::GetColumn() { return column; } vint GuiCellComposition::GetColumnSpan() { return columnSpan; } bool GuiCellComposition::SetSite(vint _row, vint _column, vint _rowSpan, vint _columnSpan) { if (!SetSiteInternal(_row, _column, _rowSpan, _columnSpan)) { return false; } InvokeOnCompositionStateChanged(); return true; } /*********************************************************************** GuiTableSplitterCompositionBase ***********************************************************************/ void GuiTableSplitterCompositionBase::OnParentChanged(GuiGraphicsComposition* oldParent, GuiGraphicsComposition* newParent) { GuiGraphicsComposition::OnParentChanged(oldParent, newParent); tableParent = dynamic_cast(newParent); } void GuiTableSplitterCompositionBase::OnLeftButtonDown(GuiGraphicsComposition* sender, GuiMouseEventArgs& arguments) { dragging = true; draggingPoint = Point(arguments.x, arguments.y); } void GuiTableSplitterCompositionBase::OnLeftButtonUp(GuiGraphicsComposition* sender, GuiMouseEventArgs& arguments) { dragging = false; } void GuiTableSplitterCompositionBase::OnMouseMoveHelper( vint cellsBefore, vint GuiTableComposition::* cells, collections::Array& cellSizes, vint offset, GuiCellOption(GuiTableComposition::*getOption)(vint), void(GuiTableComposition::*setOption)(vint, GuiCellOption) ) { if (dragging) { if (tableParent) { if (0 < cellsBefore && cellsBefore < tableParent->*cells) { auto o1 = (tableParent->*getOption)(cellsBefore - 1); auto o2 = (tableParent->*getOption)(cellsBefore); vint indexStart = -1; vint indexEnd = -1; vint indexStep = -1; vint max = 0; if (offset < 0) { indexStart = cellsBefore - 1; indexEnd = -1; indexStep = -1; } else if (offset > 0) { indexStart = cellsBefore; indexEnd = tableParent->*cells; indexStep = 1; } else { return; } { auto o = (tableParent->*getOption)(indexStart); if (o.composeType == GuiCellOption::Absolute) { max = o.absolute - 1; } else { for (vint i = indexStart; i != indexEnd; i += indexStep) { o = (tableParent->*getOption)(i); if (o.composeType == GuiCellOption::Absolute) { break; } else if (o.composeType == GuiCellOption::Percentage) { max += cellSizes[i] - 1; } } } if (max <= 0) { return; } } if (offset < 0) { if (max < -offset) { offset = -max; } } else { if (max < offset) { offset = max; } } if (o1.composeType == GuiCellOption::Absolute) { o1.absolute += offset; (tableParent->*setOption)(cellsBefore - 1, o1); } if (o2.composeType == GuiCellOption::Absolute) { o2.absolute -= offset; (tableParent->*setOption)(cellsBefore, o2); } tableParent->ForceCalculateSizeImmediately(); } } } } Rect GuiTableSplitterCompositionBase::GetBoundsHelper( vint cellsBefore, vint GuiTableComposition::* cells, vint(Rect::* dimSize)()const, collections::Array& cellOffsets, vint Rect::* dimU1, vint Rect::* dimU2, vint Rect::* dimV1, vint Rect::* dimV2 ) { Rect result; if (tableParent) { if (0 < cellsBefore && cellsBefore < tableParent->*cells) { vint offset = tableParent->borderVisible ? tableParent->cellPadding : 0; result.*dimU1 = offset; result.*dimU2 = offset + (tableParent->Layout_CalculateCellArea(tableParent->GetCachedBounds()).*dimSize)(); result.*dimV1 = offset + cellOffsets[cellsBefore] - tableParent->cellPadding; result.*dimV2 = (result.*dimV1) + tableParent->cellPadding; } } return result; } GuiTableSplitterCompositionBase::GuiTableSplitterCompositionBase() { GetEventReceiver()->leftButtonDown.AttachMethod(this, &GuiTableSplitterCompositionBase::OnLeftButtonDown); GetEventReceiver()->leftButtonUp.AttachMethod(this, &GuiTableSplitterCompositionBase::OnLeftButtonUp); } GuiTableComposition* GuiTableSplitterCompositionBase::GetTableParent() { return tableParent; } /*********************************************************************** GuiRowSplitterComposition ***********************************************************************/ void GuiRowSplitterComposition::OnMouseMove(GuiGraphicsComposition* sender, GuiMouseEventArgs& arguments) { OnMouseMoveHelper( rowsToTheTop, &GuiTableComposition::rows, tableParent->layout_rowSizes, arguments.y - draggingPoint.y, &GuiTableComposition::GetRowOption, &GuiTableComposition::SetRowOption ); } Rect GuiRowSplitterComposition::Layout_CalculateBounds(Size parentSize) { return GetBoundsHelper( rowsToTheTop, &GuiTableComposition::rows, &Rect::Width, tableParent->layout_rowOffsets, &Rect::x1, &Rect::x2, &Rect::y1, &Rect::y2 ); } GuiRowSplitterComposition::GuiRowSplitterComposition() { if (auto controller = GetCurrentController()) { SetAssociatedCursor(controller->ResourceService()->GetSystemCursor(INativeCursor::SizeNS)); } GetEventReceiver()->mouseMove.AttachMethod(this, &GuiRowSplitterComposition::OnMouseMove); } vint GuiRowSplitterComposition::GetRowsToTheTop() { return rowsToTheTop; } void GuiRowSplitterComposition::SetRowsToTheTop(vint value) { if (rowsToTheTop != value) { rowsToTheTop = value; InvokeOnCompositionStateChanged(); } } /*********************************************************************** GuiColumnSplitterComposition ***********************************************************************/ void GuiColumnSplitterComposition::OnMouseMove(GuiGraphicsComposition* sender, GuiMouseEventArgs& arguments) { OnMouseMoveHelper( columnsToTheLeft, &GuiTableComposition::columns, tableParent->layout_columnSizes, arguments.x - draggingPoint.x, &GuiTableComposition::GetColumnOption, &GuiTableComposition::SetColumnOption ); } Rect GuiColumnSplitterComposition::Layout_CalculateBounds(Size parentSize) { return GetBoundsHelper( columnsToTheLeft, &GuiTableComposition::columns, &Rect::Height, tableParent->layout_columnOffsets, &Rect::y1, &Rect::y2, &Rect::x1, &Rect::x2 ); } GuiColumnSplitterComposition::GuiColumnSplitterComposition() { if (auto controller = GetCurrentController()) { SetAssociatedCursor(controller->ResourceService()->GetSystemCursor(INativeCursor::SizeWE)); } GetEventReceiver()->mouseMove.AttachMethod(this, &GuiColumnSplitterComposition::OnMouseMove); } vint GuiColumnSplitterComposition::GetColumnsToTheLeft() { return columnsToTheLeft; } void GuiColumnSplitterComposition::SetColumnsToTheLeft(vint value) { if (columnsToTheLeft != value) { columnsToTheLeft = value; InvokeOnCompositionStateChanged(); } } } } } /*********************************************************************** .\GRAPHICSELEMENT\GUIGRAPHICSDOCUMENTELEMENT.CPP ***********************************************************************/ namespace vl { using namespace collections; namespace presentation { namespace elements { /*********************************************************************** SetPropertiesVisitor ***********************************************************************/ namespace visitors { class SetPropertiesVisitor : public Object, public DocumentRun::IVisitor { typedef GuiDocumentElement::GuiDocumentElementRenderer Renderer; typedef DocumentModel::ResolvedStyle ResolvedStyle; public: vint start; vint length; vint selectionBegin; vint selectionEnd; List styles; DocumentModel* model; Renderer* renderer; Ptr cache; IGuiGraphicsParagraph* paragraph; SetPropertiesVisitor(DocumentModel* _model, Renderer* _renderer, Ptr _cache, vint _selectionBegin, vint _selectionEnd) :start(0) ,length(0) ,model(_model) ,renderer(_renderer) ,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(styleStartselectionBegin?styleStart:selectionBegin; vint s3=selectionEndGetStyle(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->size; properties.baseline=run->baseline; properties.breakCondition=IGuiGraphicsParagraph::Alone; properties.backgroundImage = element; paragraph->SetInlineObject(start, length, properties); if(startGetStyle(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 = renderer->nameCallbackIdMap.Keys().IndexOf(run->name); if (index != -1) { auto id = renderer->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 Renderer::EmbeddedObject); eo->name = run->name; eo->size = Size(0, 0); eo->start = start; vint id = -1; vint count = renderer->freeCallbackIds.Count(); if (count > 0) { id = renderer->freeCallbackIds[count - 1]; renderer->freeCallbackIds.RemoveAt(count - 1); } else { id = renderer->usedCallbackIds++; } renderer->nameCallbackIdMap.Add(eo->name, id); cache->embeddedObjects.Add(id, eo); properties.callbackId = id; } } paragraph->SetInlineObject(start, length, properties); if(startGetStyle(DocumentModel::SelectionStyleName, style); ApplyColor(start, length, selectionStyle); } start+=length; } void Visit(DocumentParagraphRun* run)override { VisitContainer(run); } static vint SetProperty(DocumentModel* model, Renderer* renderer, Ptr cache, Ptr run, vint selectionBegin, vint selectionEnd) { SetPropertiesVisitor visitor(model, renderer, cache, selectionBegin, selectionEnd); run->Accept(&visitor); return visitor.length; } }; } using namespace visitors; /*********************************************************************** GuiDocumentElement::GuiDocumentElementRenderer ***********************************************************************/ Size GuiDocumentElement::GuiDocumentElementRenderer::OnRenderInlineObject(vint callbackId, Rect location) { if (element->callback) { auto cache = paragraphCaches[renderingParagraph]; auto relativeLocation = Rect(Point(location.x1 + renderingParagraphOffset.x, location.y1 + renderingParagraphOffset.y), location.GetSize()); auto eo = cache->embeddedObjects[callbackId]; auto size = element->callback->OnRenderEmbeddedObject(eo->name, relativeLocation); eo->resized = eo->size != size; eo->size = size; return eo->size; } else { return Size(); } } void GuiDocumentElement::GuiDocumentElementRenderer::InitializeInternal() { } void GuiDocumentElement::GuiDocumentElementRenderer::FinalizeInternal() { } void GuiDocumentElement::GuiDocumentElementRenderer::RenderTargetChangedInternal(IGuiGraphicsRenderTarget* oldRenderTarget, IGuiGraphicsRenderTarget* newRenderTarget) { // TODO: (enumerable) foreach for(vint i=0;igraphicsParagraph=0; } } } Ptr GuiDocumentElement::GuiDocumentElementRenderer::EnsureAndGetCache(vint paragraphIndex, bool createParagraph) { if(paragraphIndex<0 || paragraphIndex>=paragraphCaches.Count()) return 0; Ptr paragraph=element->document->paragraphs[paragraphIndex]; Ptr cache=paragraphCaches[paragraphIndex]; if(!cache) { cache=Ptr(new ParagraphCache); cache->fullText=paragraph->GetText(false); paragraphCaches[paragraphIndex]=cache; } if(createParagraph) { if(!cache->graphicsParagraph) { cache->graphicsParagraph=layoutProvider->CreateParagraph(cache->fullText, renderTarget, this); cache->graphicsParagraph->SetParagraphAlignment(paragraph->alignment ? paragraph->alignment.Value() : Alignment::Left); SetPropertiesVisitor::SetProperty(element->document.Obj(), this, cache, paragraph, cache->selectionBegin, cache->selectionEnd); } if(cache->graphicsParagraph->GetMaxWidth()!=lastMaxWidth) { cache->graphicsParagraph->SetMaxWidth(lastMaxWidth); } vint paragraphHeight=paragraphHeights[paragraphIndex]; vint height=cache->graphicsParagraph->GetHeight(); if(paragraphHeight!=height) { cachedTotalHeight+=height-paragraphHeight; paragraphHeight=height; paragraphHeights[paragraphIndex]=paragraphHeight; minSize=Size(0, cachedTotalHeight); } } return cache; } bool GuiDocumentElement::GuiDocumentElementRenderer::GetParagraphIndexFromPoint(Point point, vint& top, vint& index) { vint y=0; // TODO: (enumerable) foreach for(vint i=0;iGetLayoutProvider()) ,lastCaret(-1, -1) ,lastCaretFrontSide(false) { } void GuiDocumentElement::GuiDocumentElementRenderer::Render(Rect bounds) { if (element->callback) { element->callback->OnStartRender(); } renderTarget->PushClipper(bounds, element); if(!renderTarget->IsClipperCoverWholeTarget()) { vint maxWidth=bounds.Width(); Rect clipper=renderTarget->GetClipper(); vint cx=bounds.Left(); vint cy=bounds.Top(); vint y1=clipper.Top()-bounds.Top(); vint y2=y1+clipper.Height(); vint y=0; lastMaxWidth=maxWidth; // TODO: (enumerable) foreach for(vint i=0;i=y2) { break; } else { Ptr paragraph=element->document->paragraphs[i]; Ptr cache=paragraphCaches[i]; bool created=cache && cache->graphicsParagraph; cache=EnsureAndGetCache(i, true); if(!created && i==lastCaret.row && element->caretVisible) { cache->graphicsParagraph->OpenCaret(lastCaret.column, lastCaretColor, lastCaretFrontSide); } paragraphHeight=cache->graphicsParagraph->GetHeight(); renderingParagraph = i; renderingParagraphOffset = Point(cx - bounds.x1, cy + y - bounds.y1); cache->graphicsParagraph->Render(Rect(Point(cx, cy+y), Size(maxWidth, paragraphHeight))); renderingParagraph = -1; bool resized = false; // TODO: (enumerable) foreach for (vint j = 0; j < cache->embeddedObjects.Count(); j++) { auto eo = cache->embeddedObjects.Values()[j]; if (eo->resized) { eo->resized = false; resized = true; } } if (resized) { cache->graphicsParagraph = 0; } } y+=paragraphHeight+paragraphDistance; } } renderTarget->PopClipper(element); if (element->callback) { element->callback->OnFinishRender(); } } void GuiDocumentElement::GuiDocumentElementRenderer::OnElementStateChanged() { if (element->document && element->document->paragraphs.Count() > 0) { vint defaultSize = GetCurrentController()->ResourceService()->GetDefaultFont().size; paragraphDistance = defaultSize; vint defaultHeight = defaultSize; paragraphCaches.Resize(element->document->paragraphs.Count()); paragraphHeights.Resize(element->document->paragraphs.Count()); for (vint i = 0; i < paragraphCaches.Count(); i++) { paragraphCaches[i] = 0; } for (vint i = 0; i < paragraphHeights.Count(); i++) { paragraphHeights[i] = defaultHeight; } cachedTotalHeight = paragraphHeights.Count() * (defaultHeight + paragraphDistance); if (paragraphHeights.Count()>0) { cachedTotalHeight -= paragraphDistance; } minSize = Size(0, cachedTotalHeight); } else { paragraphCaches.Resize(0); paragraphHeights.Resize(0); cachedTotalHeight = 0; minSize = Size(0, 0); } nameCallbackIdMap.Clear(); freeCallbackIds.Clear(); usedCallbackIds = 0; } void GuiDocumentElement::GuiDocumentElementRenderer::NotifyParagraphUpdated(vint index, vint oldCount, vint newCount, bool updatedText) { if (0 <= index && index < paragraphCaches.Count() && 0 <= oldCount && index + oldCount <= paragraphCaches.Count() && 0 <= newCount) { vint paragraphCount = element->document->paragraphs.Count(); CHECK_ERROR(updatedText || oldCount == newCount, L"GuiDocumentlement::GuiDocumentElementRenderer::NotifyParagraphUpdated(vint, vint, vint, bool)#Illegal values of oldCount and newCount."); CHECK_ERROR(paragraphCount - paragraphCaches.Count() == newCount - oldCount, L"GuiDocumentElement::GuiDocumentElementRenderer::NotifyParagraphUpdated(vint, vint, vint, bool)#Illegal values of oldCount and newCount."); ParagraphCacheArray oldCaches; CopyFrom(oldCaches, paragraphCaches); paragraphCaches.Resize(paragraphCount); ParagraphHeightArray oldHeights; CopyFrom(oldHeights, paragraphHeights); paragraphHeights.Resize(paragraphCount); vint defaultHeight = GetCurrentController()->ResourceService()->GetDefaultFont().size; cachedTotalHeight = 0; for (vint i = 0; i < paragraphCount; i++) { if (i < index) { paragraphCaches[i] = oldCaches[i]; paragraphHeights[i] = oldHeights[i]; } else if (i < index + newCount) { paragraphCaches[i] = 0; paragraphHeights[i] = defaultHeight; if (!updatedText && i < index + oldCount) { auto cache = oldCaches[i]; if(cache) { cache->graphicsParagraph = 0; } paragraphCaches[i] = cache; paragraphHeights[i] = oldHeights[i]; } } else { paragraphCaches[i] = oldCaches[i - (newCount - oldCount)]; paragraphHeights[i] = oldHeights[i - (newCount - oldCount)]; } cachedTotalHeight += paragraphHeights[i] + paragraphDistance; } if (paragraphCount > 0) { cachedTotalHeight -= paragraphDistance; } if (updatedText) { vint count = oldCount < newCount ? oldCount : newCount; for (vint i = 0; i < count; i++) { if (auto cache = oldCaches[index + i]) { // 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); } } } } } } Ptr GuiDocumentElement::GuiDocumentElementRenderer::GetHyperlinkFromPoint(Point point) { if (!renderTarget) return nullptr; vint top=0; vint index=-1; if(GetParagraphIndexFromPoint(point, top, index)) { Ptr cache=EnsureAndGetCache(index, true); Point paragraphPoint(point.x, point.y-top); vint start=-1; vint length=0; if(cache->graphicsParagraph->GetInlineObjectFromPoint(paragraphPoint, start, length)) { return element->document->GetHyperlink(index, start, start+length); } vint caret=cache->graphicsParagraph->GetCaretFromPoint(paragraphPoint); return element->document->GetHyperlink(index, caret, caret); } return nullptr; } void GuiDocumentElement::GuiDocumentElementRenderer::OpenCaret(TextPos caret, Color color, bool frontSide) { CloseCaret(caret); lastCaret=caret; lastCaretColor=color; lastCaretFrontSide=frontSide; Ptr cache=paragraphCaches[lastCaret.row]; if(cache && cache->graphicsParagraph) { cache->graphicsParagraph->OpenCaret(lastCaret.column, lastCaretColor, lastCaretFrontSide); } } void GuiDocumentElement::GuiDocumentElementRenderer::CloseCaret(TextPos caret) { if(lastCaret!=TextPos(-1, -1)) { if(0<=lastCaret.row && lastCaret.row cache=paragraphCaches[lastCaret.row]; if(cache && cache->graphicsParagraph) { cache->graphicsParagraph->CloseCaret(); } } } lastCaret=caret; } void GuiDocumentElement::GuiDocumentElementRenderer::SetSelection(TextPos begin, TextPos end) { if(begin>end) { TextPos t=begin; begin=end; end=t; } if(begin==end) { begin=TextPos(-1, -1); end=TextPos(-1, -1); } if (!renderTarget) return; // TODO: (enumerable) foreach:indexed for(vint i=0;i cache=EnsureAndGetCache(i, false); vint newBegin=i==begin.row?begin.column:0; vint newEnd=i==end.row?end.column:cache->fullText.Length(); if(cache->selectionBegin!=newBegin || cache->selectionEnd!=newEnd) { cache->selectionBegin=newBegin; cache->selectionEnd=newEnd; NotifyParagraphUpdated(i, 1, 1, false); } } else { Ptr cache=paragraphCaches[i]; if(cache) { if(cache->selectionBegin!=-1 || cache->selectionEnd!=-1) { cache->selectionBegin=-1; cache->selectionEnd=-1; NotifyParagraphUpdated(i, 1, 1, false); } } } } } TextPos GuiDocumentElement::GuiDocumentElementRenderer::CalculateCaret(TextPos comparingCaret, IGuiGraphicsParagraph::CaretRelativePosition position, bool& preferFrontSide) { if (!renderTarget) return comparingCaret; Ptr cache=EnsureAndGetCache(comparingCaret.row, true); if(cache) { switch(position) { case IGuiGraphicsParagraph::CaretFirst: { preferFrontSide=false; vint caret=cache->graphicsParagraph->GetCaret(0, IGuiGraphicsParagraph::CaretFirst, preferFrontSide); return TextPos(comparingCaret.row, caret); } case IGuiGraphicsParagraph::CaretLast: { preferFrontSide=true; vint caret=cache->graphicsParagraph->GetCaret(0, IGuiGraphicsParagraph::CaretLast, preferFrontSide); return TextPos(comparingCaret.row, caret); } case IGuiGraphicsParagraph::CaretLineFirst: { preferFrontSide=false; vint caret=cache->graphicsParagraph->GetCaret(comparingCaret.column, IGuiGraphicsParagraph::CaretLineFirst, preferFrontSide); return TextPos(comparingCaret.row, caret); } case IGuiGraphicsParagraph::CaretLineLast: { preferFrontSide=true; vint caret=cache->graphicsParagraph->GetCaret(comparingCaret.column, IGuiGraphicsParagraph::CaretLineLast, preferFrontSide); return TextPos(comparingCaret.row, caret); } case IGuiGraphicsParagraph::CaretMoveUp: { vint caret=cache->graphicsParagraph->GetCaret(comparingCaret.column, IGuiGraphicsParagraph::CaretMoveUp, preferFrontSide); if(caret==comparingCaret.column && comparingCaret.row>0) { Rect caretBounds=cache->graphicsParagraph->GetCaretBounds(comparingCaret.column, preferFrontSide); Ptr anotherCache=EnsureAndGetCache(comparingCaret.row-1, true); vint height=anotherCache->graphicsParagraph->GetHeight(); caret=anotherCache->graphicsParagraph->GetCaretFromPoint(Point(caretBounds.x1, height)); return TextPos(comparingCaret.row-1, caret); } else { return TextPos(comparingCaret.row, caret); } } case IGuiGraphicsParagraph::CaretMoveDown: { vint caret=cache->graphicsParagraph->GetCaret(comparingCaret.column, IGuiGraphicsParagraph::CaretMoveDown, preferFrontSide); if(caret==comparingCaret.column && comparingCaret.rowgraphicsParagraph->GetCaretBounds(comparingCaret.column, preferFrontSide); Ptr anotherCache=EnsureAndGetCache(comparingCaret.row+1, true); caret=anotherCache->graphicsParagraph->GetCaretFromPoint(Point(caretBounds.x1, 0)); return TextPos(comparingCaret.row+1, caret); } else { return TextPos(comparingCaret.row, caret); } } case IGuiGraphicsParagraph::CaretMoveLeft: { preferFrontSide=false; vint caret=cache->graphicsParagraph->GetCaret(comparingCaret.column, IGuiGraphicsParagraph::CaretMoveLeft, preferFrontSide); if(caret==comparingCaret.column && comparingCaret.row>0) { Ptr anotherCache=EnsureAndGetCache(comparingCaret.row-1, true); caret=anotherCache->graphicsParagraph->GetCaret(0, IGuiGraphicsParagraph::CaretLast, preferFrontSide); return TextPos(comparingCaret.row-1, caret); } else { return TextPos(comparingCaret.row, caret); } } case IGuiGraphicsParagraph::CaretMoveRight: { preferFrontSide=true; vint caret=cache->graphicsParagraph->GetCaret(comparingCaret.column, IGuiGraphicsParagraph::CaretMoveRight, preferFrontSide); if(caret==comparingCaret.column && comparingCaret.row anotherCache=EnsureAndGetCache(comparingCaret.row+1, true); caret=anotherCache->graphicsParagraph->GetCaret(0, IGuiGraphicsParagraph::CaretFirst, preferFrontSide); return TextPos(comparingCaret.row+1, caret); } else { return TextPos(comparingCaret.row, caret); } } } } return comparingCaret; } TextPos GuiDocumentElement::GuiDocumentElementRenderer::CalculateCaretFromPoint(Point point) { if (!renderTarget) return TextPos(-1, -1); vint top=0; vint index=-1; if(GetParagraphIndexFromPoint(point, top, index)) { Ptr cache=EnsureAndGetCache(index, true); Point paragraphPoint(point.x, point.y-top); vint caret=cache->graphicsParagraph->GetCaretFromPoint(paragraphPoint); return TextPos(index, caret); } return TextPos(-1, -1); } Rect GuiDocumentElement::GuiDocumentElementRenderer::GetCaretBounds(TextPos caret, bool frontSide) { if (!renderTarget) return Rect(); Ptr cache=EnsureAndGetCache(caret.row, true); if(cache) { Rect bounds=cache->graphicsParagraph->GetCaretBounds(caret.column, frontSide); if(bounds!=Rect()) { vint y=0; for(vint i=0;i(); if (elementRenderer) { elementRenderer->SetSelection(caretBegin, caretEnd); if (caretVisible) { elementRenderer->OpenCaret(caretEnd, caretColor, caretFrontSide); } else { elementRenderer->CloseCaret(caretEnd); } InvokeOnCompositionStateChanged(); } } GuiDocumentElement::GuiDocumentElement() :caretVisible(false) ,caretFrontSide(false) { } GuiDocumentElement::ICallback* GuiDocumentElement::GetCallback() { return callback; } void GuiDocumentElement::SetCallback(ICallback* value) { callback = value; } Ptr GuiDocumentElement::GetDocument() { return document; } void GuiDocumentElement::SetDocument(Ptr value) { document=value; InvokeOnElementStateChanged(); SetCaret(TextPos(), TextPos(), false); } TextPos GuiDocumentElement::GetCaretBegin() { return caretBegin; } TextPos GuiDocumentElement::GetCaretEnd() { return caretEnd; } bool GuiDocumentElement::IsCaretEndPreferFrontSide() { return caretFrontSide; } void GuiDocumentElement::SetCaret(TextPos begin, TextPos end, bool frontSide) { caretBegin=begin; caretEnd=end; if(caretBegincaretEnd) { caretFrontSide=false; } else { caretFrontSide=frontSide; } UpdateCaret(); } bool GuiDocumentElement::GetCaretVisible() { return caretVisible; } void GuiDocumentElement::SetCaretVisible(bool value) { caretVisible=value; UpdateCaret(); } Color GuiDocumentElement::GetCaretColor() { return caretColor; } void GuiDocumentElement::SetCaretColor(Color value) { caretColor=value; UpdateCaret(); } TextPos GuiDocumentElement::CalculateCaret(TextPos comparingCaret, IGuiGraphicsParagraph::CaretRelativePosition position, bool& preferFrontSide) { if (auto elementRenderer = renderer.Cast()) { TextPos caret=elementRenderer->CalculateCaret(comparingCaret, position, preferFrontSide); return caret.column==-1?comparingCaret:caret; } else { return comparingCaret; } } TextPos GuiDocumentElement::CalculateCaretFromPoint(Point point) { if (auto elementRenderer = renderer.Cast()) { return elementRenderer->CalculateCaretFromPoint(point); } else { return TextPos(0, 0); } } Rect GuiDocumentElement::GetCaretBounds(TextPos caret, bool frontSide) { if (auto elementRenderer = renderer.Cast()) { return elementRenderer->GetCaretBounds(caret, frontSide); } else { return Rect(); } } void GuiDocumentElement::NotifyParagraphUpdated(vint index, vint oldCount, vint newCount, bool updatedText) { if (auto elementRenderer = renderer.Cast()) { elementRenderer->NotifyParagraphUpdated(index, oldCount, newCount, updatedText); InvokeOnCompositionStateChanged(); } } void GuiDocumentElement::EditRun(TextPos begin, TextPos end, Ptr model, bool copy) { if (auto elementRenderer = renderer.Cast()) { if (begin > end) { TextPos temp = begin; begin = end; end = temp; } vint newRows = document->EditRun(begin, end, model, copy); if (newRows != -1) { elementRenderer->NotifyParagraphUpdated(begin.row, end.row - begin.row + 1, newRows, true); } InvokeOnCompositionStateChanged(); } } void GuiDocumentElement::EditText(TextPos begin, TextPos end, bool frontSide, const collections::Array& text) { if (auto elementRenderer = renderer.Cast()) { if (begin > end) { TextPos temp = begin; begin = end; end = temp; } vint newRows = document->EditText(begin, end, frontSide, text); if (newRows != -1) { elementRenderer->NotifyParagraphUpdated(begin.row, end.row - begin.row + 1, newRows, true); } InvokeOnCompositionStateChanged(); } } void GuiDocumentElement::EditStyle(TextPos begin, TextPos end, Ptr style) { if (auto elementRenderer = renderer.Cast()) { if (begin > end) { TextPos temp = begin; begin = end; end = temp; } if (document->EditStyle(begin, end, style)) { elementRenderer->NotifyParagraphUpdated(begin.row, end.row - begin.row + 1, end.row - begin.row + 1, false); } InvokeOnCompositionStateChanged(); } } void GuiDocumentElement::EditImage(TextPos begin, TextPos end, Ptr image) { if (auto elementRenderer = renderer.Cast()) { if (begin > end) { TextPos temp = begin; begin = end; end = temp; } if (document->EditImage(begin, end, image)) { elementRenderer->NotifyParagraphUpdated(begin.row, end.row - begin.row + 1, 1, true); } InvokeOnCompositionStateChanged(); } } void GuiDocumentElement::EditHyperlink(vint paragraphIndex, vint begin, vint end, const WString& reference, const WString& normalStyleName, const WString& activeStyleName) { if (auto elementRenderer = renderer.Cast()) { if (begin > end) { vint temp = begin; begin = end; end = temp; } if (document->EditHyperlink(paragraphIndex, begin, end, reference, normalStyleName, activeStyleName)) { elementRenderer->NotifyParagraphUpdated(paragraphIndex, 1, 1, false); } InvokeOnCompositionStateChanged(); } } void GuiDocumentElement::RemoveHyperlink(vint paragraphIndex, vint begin, vint end) { if (auto elementRenderer = renderer.Cast()) { if (begin > end) { vint temp = begin; begin = end; end = temp; } if (document->RemoveHyperlink(paragraphIndex, begin, end)) { elementRenderer->NotifyParagraphUpdated(paragraphIndex, 1, 1, false); } InvokeOnCompositionStateChanged(); } } void GuiDocumentElement::EditStyleName(TextPos begin, TextPos end, const WString& styleName) { if (auto elementRenderer = renderer.Cast()) { if (begin > end) { TextPos temp = begin; begin = end; end = temp; } if (document->EditStyleName(begin, end, styleName)) { elementRenderer->NotifyParagraphUpdated(begin.row, end.row - begin.row + 1, end.row - begin.row + 1, false); } InvokeOnCompositionStateChanged(); } } void GuiDocumentElement::RemoveStyleName(TextPos begin, TextPos end) { if (auto elementRenderer = renderer.Cast()) { if (begin > end) { TextPos temp = begin; begin = end; end = temp; } if (document->RemoveStyleName(begin, end)) { elementRenderer->NotifyParagraphUpdated(begin.row, end.row - begin.row + 1, end.row - begin.row + 1, false); } InvokeOnCompositionStateChanged(); } } void GuiDocumentElement::RenameStyle(const WString& oldStyleName, const WString& newStyleName) { if (auto elementRenderer = renderer.Cast()) { document->RenameStyle(oldStyleName, newStyleName); } } void GuiDocumentElement::ClearStyle(TextPos begin, TextPos end) { if (auto elementRenderer = renderer.Cast()) { if (begin > end) { TextPos temp = begin; begin = end; end = temp; } if (document->ClearStyle(begin, end)) { elementRenderer->NotifyParagraphUpdated(begin.row, end.row - begin.row + 1, end.row - begin.row + 1, false); } InvokeOnCompositionStateChanged(); } } Ptr GuiDocumentElement::SummarizeStyle(TextPos begin, TextPos end) { if (auto elementRenderer = renderer.Cast()) { if (begin > end) { TextPos temp = begin; begin = end; end = temp; } return document->SummarizeStyle(begin, end); } return nullptr; } Nullable GuiDocumentElement::SummarizeStyleName(TextPos begin, TextPos end) { if (auto elementRenderer = renderer.Cast()) { if (begin > end) { TextPos temp = begin; begin = end; end = temp; } return document->SummarizeStyleName(begin, end); } return {}; } void GuiDocumentElement::SetParagraphAlignment(TextPos begin, TextPos end, const collections::Array>& alignments) { if (auto elementRenderer = renderer.Cast()) { vint first = begin.row; vint last = end.row; if (first > last) { vint temp = first; first = last; last = temp; } if (0 <= first && first < document->paragraphs.Count() && 0 <= last && last < document->paragraphs.Count() && last - first + 1 == alignments.Count()) { for (vint i = first; i <= last; i++) { document->paragraphs[i]->alignment = alignments[i - first]; } elementRenderer->NotifyParagraphUpdated(first, alignments.Count(), alignments.Count(), false); } InvokeOnCompositionStateChanged(); } } Nullable GuiDocumentElement::SummarizeParagraphAlignment(TextPos begin, TextPos end) { if (auto elementRenderer = renderer.Cast()) { if (begin > end) { TextPos temp = begin; begin = end; end = temp; } return document->SummarizeParagraphAlignment(begin, end); } return {}; } Ptr GuiDocumentElement::GetHyperlinkFromPoint(Point point) { if (auto elementRenderer = renderer.Cast()) { return elementRenderer->GetHyperlinkFromPoint(point); } return nullptr; } } } } /*********************************************************************** .\GRAPHICSELEMENT\GUIGRAPHICSELEMENT.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace elements { using namespace collections; /*********************************************************************** GuiFocusRectangleElement ***********************************************************************/ GuiFocusRectangleElement::GuiFocusRectangleElement() { } /*********************************************************************** GuiSolidBorderElement ***********************************************************************/ GuiSolidBorderElement::GuiSolidBorderElement() :color(0, 0, 0) { } Color GuiSolidBorderElement::GetColor() { return color; } void GuiSolidBorderElement::SetColor(Color value) { if(color!=value) { color=value; InvokeOnElementStateChanged(); } } ElementShape GuiSolidBorderElement::GetShape() { return shape; } void GuiSolidBorderElement::SetShape(ElementShape value) { shape=value; } /*********************************************************************** Gui3DBorderElement ***********************************************************************/ Gui3DBorderElement::Gui3DBorderElement() { } Color Gui3DBorderElement::GetColor1() { return color1; } void Gui3DBorderElement::SetColor1(Color value) { SetColors(value, color2); } Color Gui3DBorderElement::GetColor2() { return color2; } void Gui3DBorderElement::SetColor2(Color value) { SetColors(color1, value); } void Gui3DBorderElement::SetColors(Color value1, Color value2) { if(color1!=value1 || color2!=value2) { color1=value1; color2=value2; InvokeOnElementStateChanged(); } } /*********************************************************************** Gui3DSplitterElement ***********************************************************************/ Gui3DSplitterElement::Gui3DSplitterElement() :direction(Horizontal) { } Color Gui3DSplitterElement::GetColor1() { return color1; } void Gui3DSplitterElement::SetColor1(Color value) { SetColors(value, color2); } Color Gui3DSplitterElement::GetColor2() { return color2; } void Gui3DSplitterElement::SetColor2(Color value) { SetColors(color1, value); } void Gui3DSplitterElement::SetColors(Color value1, Color value2) { if(color1!=value1 || color2!=value2) { color1=value1; color2=value2; InvokeOnElementStateChanged(); } } Gui3DSplitterElement::Direction Gui3DSplitterElement::GetDirection() { return direction; } void Gui3DSplitterElement::SetDirection(Direction value) { if(direction!=value) { direction=value; InvokeOnElementStateChanged(); } } /*********************************************************************** GuiSolidBackgroundElement ***********************************************************************/ GuiSolidBackgroundElement::GuiSolidBackgroundElement() :color(255, 255, 255) { } Color GuiSolidBackgroundElement::GetColor() { return color; } void GuiSolidBackgroundElement::SetColor(Color value) { if(color!=value) { color=value; InvokeOnElementStateChanged(); } } ElementShape GuiSolidBackgroundElement::GetShape() { return shape; } void GuiSolidBackgroundElement::SetShape(ElementShape value) { shape=value; } /*********************************************************************** GuiGradientBackgroundElement ***********************************************************************/ GuiGradientBackgroundElement::GuiGradientBackgroundElement() :direction(Horizontal) { } Color GuiGradientBackgroundElement::GetColor1() { return color1; } void GuiGradientBackgroundElement::SetColor1(Color value) { SetColors(value, color2); } Color GuiGradientBackgroundElement::GetColor2() { return color2; } void GuiGradientBackgroundElement::SetColor2(Color value) { SetColors(color1, value); } void GuiGradientBackgroundElement::SetColors(Color value1, Color value2) { if(color1!=value1 || color2!=value2) { color1=value1; color2=value2; InvokeOnElementStateChanged(); } } GuiGradientBackgroundElement::Direction GuiGradientBackgroundElement::GetDirection() { return direction; } void GuiGradientBackgroundElement::SetDirection(Direction value) { if(direction!=value) { direction=value; InvokeOnElementStateChanged(); } } ElementShape GuiGradientBackgroundElement::GetShape() { return shape; } void GuiGradientBackgroundElement::SetShape(ElementShape value) { shape=value; } /*********************************************************************** GuiInnerShadowElement ***********************************************************************/ GuiInnerShadowElement::GuiInnerShadowElement() { } Color GuiInnerShadowElement::GetColor() { return color; } void GuiInnerShadowElement::SetColor(Color value) { if (color != value) { color = value; InvokeOnElementStateChanged(); } } vint GuiInnerShadowElement::GetThickness() { return thickness; } void GuiInnerShadowElement::SetThickness(vint value) { if (thickness != value) { thickness = value; InvokeOnElementStateChanged(); } } /*********************************************************************** GuiSolidLabelElement ***********************************************************************/ GuiSolidLabelElement::GuiSolidLabelElement() :color(0, 0, 0) ,hAlignment(Alignment::Left) ,vAlignment(Alignment::Top) ,wrapLine(false) ,ellipse(false) ,multiline(false) ,wrapLineHeightCalculation(false) { fontProperties.fontFamily=L"Lucida Console"; fontProperties.size=12; } Color GuiSolidLabelElement::GetColor() { return color; } void GuiSolidLabelElement::SetColor(Color value) { if(color!=value) { color=value; InvokeOnElementStateChanged(); } } const FontProperties& GuiSolidLabelElement::GetFont() { return fontProperties; } void GuiSolidLabelElement::SetFont(const FontProperties& value) { if(fontProperties!=value) { fontProperties=value; InvokeOnElementStateChanged(); } } const WString& GuiSolidLabelElement::GetText() { return text; } void GuiSolidLabelElement::SetText(const WString& value) { if(text!=value) { text=value; InvokeOnElementStateChanged(); } } Alignment GuiSolidLabelElement::GetHorizontalAlignment() { return hAlignment; } Alignment GuiSolidLabelElement::GetVerticalAlignment() { return vAlignment; } void GuiSolidLabelElement::SetHorizontalAlignment(Alignment value) { SetAlignments(value, vAlignment); } void GuiSolidLabelElement::SetVerticalAlignment(Alignment value) { SetAlignments(hAlignment, value); } void GuiSolidLabelElement::SetAlignments(Alignment horizontal, Alignment vertical) { if(hAlignment!=horizontal || vAlignment!=vertical) { hAlignment=horizontal; vAlignment=vertical; InvokeOnElementStateChanged(); } } bool GuiSolidLabelElement::GetWrapLine() { return wrapLine; } void GuiSolidLabelElement::SetWrapLine(bool value) { if(wrapLine!=value) { wrapLine=value; InvokeOnElementStateChanged(); } } bool GuiSolidLabelElement::GetEllipse() { return ellipse; } void GuiSolidLabelElement::SetEllipse(bool value) { if(ellipse!=value) { ellipse=value; InvokeOnElementStateChanged(); } } bool GuiSolidLabelElement::GetMultiline() { return multiline; } void GuiSolidLabelElement::SetMultiline(bool value) { if(multiline!=value) { multiline=value; InvokeOnElementStateChanged(); } } bool GuiSolidLabelElement::GetWrapLineHeightCalculation() { return wrapLineHeightCalculation; } void GuiSolidLabelElement::SetWrapLineHeightCalculation(bool value) { if(wrapLineHeightCalculation!=value) { wrapLineHeightCalculation=value; InvokeOnElementStateChanged(); } } /*********************************************************************** GuiImageFrameElement ***********************************************************************/ GuiImageFrameElement::GuiImageFrameElement() : frameIndex(0) , hAlignment(Alignment::Left) , vAlignment(Alignment::Top) , stretch(false) , enabled(true) { } Ptr GuiImageFrameElement::GetImage() { return image; } vint GuiImageFrameElement::GetFrameIndex() { return frameIndex; } void GuiImageFrameElement::SetImage(Ptr value) { SetImage(value, frameIndex); } void GuiImageFrameElement::SetFrameIndex(vint value) { SetImage(image, value); } void GuiImageFrameElement::SetImage(Ptr _image, vint _frameIndex) { if (image != _image || frameIndex != _frameIndex) { if (!_image) { image = nullptr; frameIndex = 0; } else if (0 <= _frameIndex) { // do not check frame count because // on remote protocol metadata could have not been loaded yet image = _image; frameIndex = _frameIndex; } InvokeOnElementStateChanged(); } } Alignment GuiImageFrameElement::GetHorizontalAlignment() { return hAlignment; } Alignment GuiImageFrameElement::GetVerticalAlignment() { return vAlignment; } void GuiImageFrameElement::SetHorizontalAlignment(Alignment value) { SetAlignments(value, vAlignment); } void GuiImageFrameElement::SetVerticalAlignment(Alignment value) { SetAlignments(hAlignment, value); } void GuiImageFrameElement::SetAlignments(Alignment horizontal, Alignment vertical) { if (hAlignment != horizontal || vAlignment != vertical) { hAlignment = horizontal; vAlignment = vertical; InvokeOnElementStateChanged(); } } bool GuiImageFrameElement::GetStretch() { return stretch; } void GuiImageFrameElement::SetStretch(bool value) { if (stretch != value) { stretch = value; InvokeOnElementStateChanged(); } } bool GuiImageFrameElement::GetEnabled() { return enabled; } void GuiImageFrameElement::SetEnabled(bool value) { if (enabled != value) { enabled = value; InvokeOnElementStateChanged(); } } /*********************************************************************** GuiPolygonElement ***********************************************************************/ GuiPolygonElement::GuiPolygonElement() { } Size GuiPolygonElement::GetSize() { return size; } void GuiPolygonElement::SetSize(Size value) { if(size!=value) { size=value; InvokeOnElementStateChanged(); } } const Point& GuiPolygonElement::GetPoint(vint index) { return points[index]; } vint GuiPolygonElement::GetPointCount() { return points.Count(); } void GuiPolygonElement::SetPoints(const Point* p, vint count) { points.Resize(count); if(count>0) { memcpy(&points[0], p, sizeof(*p)*count); } InvokeOnElementStateChanged(); } const GuiPolygonElement::PointArray& GuiPolygonElement::GetPointsArray() { return points; } void GuiPolygonElement::SetPointsArray(const PointArray& value) { CopyFrom(points, value); InvokeOnElementStateChanged(); } Color GuiPolygonElement::GetBorderColor() { return borderColor; } void GuiPolygonElement::SetBorderColor(Color value) { if(borderColor!=value) { borderColor=value; InvokeOnElementStateChanged(); } } Color GuiPolygonElement::GetBackgroundColor() { return backgroundColor; } void GuiPolygonElement::SetBackgroundColor(Color value) { if(backgroundColor!=value) { backgroundColor=value; InvokeOnElementStateChanged(); } } } } } /*********************************************************************** .\GRAPHICSELEMENT\GUIGRAPHICSELEMENTINTERFACES.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace elements { using namespace collections; /*********************************************************************** GuiGraphicsRenderTarget ***********************************************************************/ bool GuiGraphicsRenderTarget::IsInHostedRendering() { return hostedRendering; } void GuiGraphicsRenderTarget::StartHostedRendering() { CHECK_ERROR(!hostedRendering && !rendering, L"vl::presentation::elements::GuiGraphicsRenderTarget::StartHostedRendering()#Wrong timing to call this function."); hostedRendering = true; StartRenderingOnNativeWindow(); } RenderTargetFailure GuiGraphicsRenderTarget::StopHostedRendering() { CHECK_ERROR(hostedRendering && !rendering, L"vl::presentation::elements::GuiGraphicsRenderTarget::StopHostedRendering()#Wrong timing to call this function."); hostedRendering = false; return StopRenderingOnNativeWindow(); } void GuiGraphicsRenderTarget::StartRendering() { CHECK_ERROR(!rendering, L"vl::presentation::elements::GuiGraphicsRenderTarget::StartRendering()#Wrong timing to call this function."); rendering = true; if (!hostedRendering) { StartRenderingOnNativeWindow(); } } RenderTargetFailure GuiGraphicsRenderTarget::StopRendering() { CHECK_ERROR(rendering, L"vl::presentation::elements::GuiGraphicsRenderTarget::StopRendering()#Wrong timing to call this function."); rendering = false; if (!hostedRendering) { return StopRenderingOnNativeWindow(); } return RenderTargetFailure::None; } void GuiGraphicsRenderTarget::PushClipper(Rect clipper, reflection::DescriptableObject* generator) { if (clipperCoverWholeTargetCounter > 0) { clipperCoverWholeTargetCounter++; } else { Rect currentClipper = GetClipper().Intersect(clipper); if (currentClipper.x1 < currentClipper.x2 && currentClipper.y1 < currentClipper.y2) { clippers.Add(currentClipper); AfterPushedClipper(clipper, currentClipper, generator); } else { clipperCoverWholeTargetCounter++; AfterPushedClipperAndBecameInvalid(clipper, generator); } } } void GuiGraphicsRenderTarget::PopClipper(reflection::DescriptableObject* generator) { if (clipperCoverWholeTargetCounter > 0) { clipperCoverWholeTargetCounter--; if (clipperCoverWholeTargetCounter == 0) { AfterPoppedClipperAndBecameValid(GetClipper(), clippers.Count() > 0, generator); } } else if (clippers.Count() > 0) { clippers.RemoveAt(clippers.Count() - 1); AfterPoppedClipper(GetClipper(), clippers.Count() > 0, generator); } } Rect GuiGraphicsRenderTarget::GetClipper() { if (clipperCoverWholeTargetCounter > 0) { return { {-1,-1},{0,0} }; } else if (clippers.Count() == 0) { return Rect(Point(0, 0), GetCanvasSize()); } else { return clippers[clippers.Count() - 1]; } } bool GuiGraphicsRenderTarget::IsClipperCoverWholeTarget() { return clipperCoverWholeTargetCounter > 0; } } } } /*********************************************************************** .\GRAPHICSELEMENT\GUIGRAPHICSRESOURCEMANAGER.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace elements { using namespace collections; /*********************************************************************** GuiGraphicsResourceManager ***********************************************************************/ GuiGraphicsResourceManager::GuiGraphicsResourceManager() { } GuiGraphicsResourceManager::~GuiGraphicsResourceManager() { } vint GuiGraphicsResourceManager::RegisterElementType(const WString& elementTypeName) { CHECK_ERROR(!elementTypes.Contains(elementTypeName), L"GuiGraphicsResourceManager::RegisterElementType(const WString&)#This element type has already been registered."); return elementTypes.Add(elementTypeName); } void GuiGraphicsResourceManager::RegisterRendererFactory(vint elementType, Ptr factory) { if (rendererFactories.Count() <= elementType) { rendererFactories.Resize(elementType + 1); rendererFactories[elementType] = factory; } else { CHECK_ERROR(!rendererFactories[elementType], L"GuiGraphicsResourceManager::RegisterRendererFactory(vint, Ptr)#This element type has already been binded a renderer factory."); rendererFactories[elementType] = factory; } } IGuiGraphicsRendererFactory* GuiGraphicsResourceManager::GetRendererFactory(vint elementType) { return rendererFactories.Count() > elementType ? rendererFactories[elementType].Obj() : nullptr; } IGuiGraphicsResourceManager* guiGraphicsResourceManager = nullptr; IGuiGraphicsResourceManager* GetGuiGraphicsResourceManager() { return guiGraphicsResourceManager; } void SetGuiGraphicsResourceManager(IGuiGraphicsResourceManager* resourceManager) { guiGraphicsResourceManager=resourceManager; } } } } /*********************************************************************** .\GRAPHICSELEMENT\GUIGRAPHICSTEXTELEMENT.CPP ***********************************************************************/ namespace vl { using namespace collections; namespace presentation { namespace elements { namespace text { /*********************************************************************** text::TextLine ***********************************************************************/ TextLine::TextLine() :text(0) ,att(0) ,availableOffsetCount(0) ,bufferLength(0) ,dataLength(0) ,lexerFinalState(-1) ,contextFinalState(-1) { } TextLine::~TextLine() { } vint TextLine::CalculateBufferLength(vint dataLength) { if(dataLength<1)dataLength=1; vint bufferLength=dataLength-dataLength%BlockSize; if(bufferLengthdataLength || inputCount<0) return false; vint newDataLength=dataLength-count+inputCount; vint newBufferLength=CalculateBufferLength(newDataLength); if(newBufferLength!=bufferLength) { wchar_t* newText=new wchar_t[newBufferLength]; memcpy(newText, text, start*sizeof(wchar_t)); memcpy(newText+start, input, inputCount*sizeof(wchar_t)); memcpy(newText+start+inputCount, text+start+count, (dataLength-start-count)*sizeof(wchar_t)); CharAtt* newAtt=new CharAtt[newBufferLength]; memcpy(newAtt, att, start*sizeof(CharAtt)); memset(newAtt+start, 0, inputCount*sizeof(CharAtt)); memcpy(newAtt+start+inputCount, att+start+count, (dataLength-start-count)*sizeof(CharAtt)); delete[] text; delete[] att; text=newText; att=newAtt; } else { memmove(text+start+inputCount, text+start+count, (dataLength-start-count)*sizeof(wchar_t)); memmove(att+start+inputCount, att+start+count, (dataLength-start-count)*sizeof(CharAtt)); memcpy(text+start, input, inputCount*sizeof(wchar_t)); memset(att+start, 0, inputCount*sizeof(CharAtt)); } dataLength=newDataLength; bufferLength=newBufferLength; if(availableOffsetCount>start) { availableOffsetCount=start; } return true; } TextLine TextLine::Split(vint index) { if(index<0 || index>dataLength) return TextLine(); vint count=dataLength-index; TextLine line; line.Initialize(); line.Modify(0, 0, text+index, count); memcpy(line.att, att+index, count*sizeof(CharAtt)); Modify(index, count, L"", 0); return line; } void TextLine::AppendAndFinalize(TextLine& line) { vint oldDataLength=dataLength; Modify(oldDataLength, 0, line.text, line.dataLength); memcpy(att+oldDataLength, line.att, line.dataLength*sizeof(CharAtt)); line.Finalize(); } /*********************************************************************** text::CharMeasurer ***********************************************************************/ CharMeasurer::CharMeasurer(vint _rowHeight) :rowHeight(_rowHeight) { memset(widths, 0, sizeof(widths)); } CharMeasurer::~CharMeasurer() { } void CharMeasurer::SetRenderTarget(IGuiGraphicsRenderTarget* value) { if(oldRenderTarget!=value) { oldRenderTarget=value; rowHeight=GetRowHeightInternal(oldRenderTarget); memset(widths, 0, sizeof(widths)); } } vint CharMeasurer::MeasureWidth(UnicodeCodePoint codePoint) { vuint32_t index = codePoint.GetCodePoint(); if (0 <= index && index < 65536) { vint w = widths[index]; if (w == 0) { widths[index] = w = MeasureWidthInternal(codePoint, oldRenderTarget); } return w; } else if (index < 0x110000) { return MeasureWidthInternal(codePoint, oldRenderTarget); } else { return 0; } } vint CharMeasurer::GetRowHeight() { return rowHeight; } /*********************************************************************** text::TextLines ***********************************************************************/ TextLines::TextLines(GuiColorizedTextElement* _ownerElement) :ownerElement(_ownerElement) ,charMeasurer(0) ,renderTarget(0) ,tabWidth(1) ,tabSpaceCount(4) ,passwordChar(L'\0') { TextLine line; line.Initialize(); lines.Add(line); } TextLines::~TextLines() { RemoveLines(0, lines.Count()); } //-------------------------------------------------------- vint TextLines::GetCount() { return lines.Count(); } TextLine& TextLines::GetLine(vint row) { return lines[row]; } CharMeasurer* TextLines::GetCharMeasurer() { return charMeasurer; } void TextLines::SetCharMeasurer(CharMeasurer* value) { charMeasurer=value; if(charMeasurer) charMeasurer->SetRenderTarget(renderTarget); ClearMeasurement(); } IGuiGraphicsRenderTarget* TextLines::GetRenderTarget() { return renderTarget; } void TextLines::SetRenderTarget(IGuiGraphicsRenderTarget* value) { renderTarget=value; if(charMeasurer) charMeasurer->SetRenderTarget(renderTarget); ClearMeasurement(); } WString TextLines::GetText(TextPos start, TextPos end) { if(!IsAvailable(start) || !IsAvailable(end) || start>end) return L""; if(start.row==end.row) { return WString::CopyFrom(lines[start.row].text+start.column, end.column-start.column); } vint count=0; for(vint i=start.row+1;i buffer; buffer.Resize(count+(end.row-start.row)*2); wchar_t* writing=&buffer[0]; for(vint i=start.row;i<=end.row;i++) { wchar_t* text=lines[i].text; vint chars=0; if(i==start.row) { text+=start.column; chars=lines[i].dataLength-start.column; } else if(i==end.row) { chars=end.column; } else { chars=lines[i].dataLength; } if(i!=start.row) { *writing++=L'\r'; *writing++=L'\n'; } memcpy(writing, text, chars*sizeof(wchar_t)); writing+=chars; } return WString::CopyFrom(&buffer[0], buffer.Count()); } WString TextLines::GetText() { return GetText(TextPos(0, 0), TextPos(lines.Count()-1, lines[lines.Count()-1].dataLength)); } void TextLines::SetText(const WString& value) { Modify(TextPos(0, 0), TextPos(lines.Count()-1, lines[lines.Count()-1].dataLength), value); } //-------------------------------------------------------- bool TextLines::RemoveLines(vint start, vint count) { if(start<0 || count<0 || start+count>lines.Count()) return false; for(vint i=start+count-1;i>=start;i--) { lines[i].Finalize(); } lines.RemoveRange(start, count); return true; } bool TextLines::IsAvailable(TextPos pos) { return 0<=pos.row && pos.row=lines.Count()) { return TextPos(lines.Count()-1, lines[lines.Count()-1].dataLength); } else { TextLine& line=lines[pos.row]; if(pos.column<0) { return TextPos(pos.row, 0); } else if(pos.column>line.dataLength) { return TextPos(pos.row, line.dataLength); } else { return pos; } } } TextPos TextLines::Modify(TextPos start, TextPos end, const wchar_t** inputs, vint* inputCounts, vint rows) { if(!IsAvailable(start) || !IsAvailable(end) || start>end) return TextPos(-1, -1); if (ownerElement) { ownerElement->InvokeOnElementStateChanged(); } if(rows==1) { if(start.row==end.row) { lines[start.row].Modify(start.column, end.column-start.column, inputs[0], inputCounts[0]); } else { if(end.row-start.row>1) { RemoveLines(start.row+1, end.row-start.row-1); } vint modifyCount=lines[start.row].dataLength-start.column+end.column; lines[start.row].AppendAndFinalize(lines[start.row+1]); lines.RemoveAt(start.row+1); lines[start.row].Modify(start.column, modifyCount, inputs[0], inputCounts[0]); } return TextPos(start.row, start.column+inputCounts[0]); } if(start.row==end.row) { TextLine newLine=lines[start.row].Split(end.column); lines.Insert(start.row+1, newLine); end=TextPos(start.row+1, 0); } vint oldMiddleLines=end.row-start.row-1; vint newMiddleLines=rows-2; if(oldMiddleLinesnewMiddleLines) { RemoveLines(start.row+newMiddleLines+1, oldMiddleLines-newMiddleLines); } end.row+=newMiddleLines-oldMiddleLines; lines[start.row].Modify(start.column, lines[start.row].dataLength-start.column, inputs[0], inputCounts[0]); lines[end.row].Modify(0, end.column, inputs[rows-1], inputCounts[rows-1]); for(vint i=1;i inputs; List inputCounts; const wchar_t* previous=input; const wchar_t* current=input; while(true) { if(current==input+inputCount) { inputs.Add(previous); inputCounts.Add(current-previous); break; } else if(*current==L'\r' || *current==L'\n') { inputs.Add(previous); inputCounts.Add(current-previous); previous=current+(current[1]==L'\n'?2:1); current=previous; } else { current++; } } return Modify(start, end, &inputs[0], &inputCounts[0], inputs.Count()); } TextPos TextLines::Modify(TextPos start, TextPos end, const wchar_t* input) { return Modify(start, end, input, wcslen(input)); } TextPos TextLines::Modify(TextPos start, TextPos end, const WString& input) { return Modify(start, end, input.Buffer(), input.Length()); } void TextLines::Clear() { RemoveLines(0, lines.Count()); TextLine line; line.Initialize(); lines.Add(line); if (ownerElement) { ownerElement->InvokeOnElementStateChanged(); } } //-------------------------------------------------------- void TextLines::ClearMeasurement() { for (vint i = 0; i < lines.Count(); i++) { lines[i].availableOffsetCount = 0; } tabWidth = tabSpaceCount * (charMeasurer ? charMeasurer->MeasureWidth({ L' ' }) : 1); if (tabWidth == 0) { tabWidth = 1; } if (ownerElement) { ownerElement->InvokeOnElementStateChanged(); } } vint TextLines::GetTabSpaceCount() { return tabSpaceCount; } void TextLines::SetTabSpaceCount(vint value) { if(value<1) value=1; if(tabSpaceCount!=value) { tabSpaceCount=value; ClearMeasurement(); } } void TextLines::MeasureRow(vint row) { TextLine& line = lines[row]; vint offset = 0; if (line.availableOffsetCount) { offset = line.att[line.availableOffsetCount - 1].rightOffset; } for (vint i = line.availableOffsetCount; i < line.dataLength; i++) { CharAtt& att = line.att[i]; wchar_t c = line.text[i]; vint width = 0; vint passwordWidth = 0; if (passwordChar) { passwordWidth = charMeasurer ? charMeasurer->MeasureWidth({ passwordChar }) : 1; } if (c == L'\t') { width = tabWidth - offset % tabWidth; } #if defined VCZH_MSVC else if (UTF16SPFirst(c) && (i + 1 < line.dataLength) && UTF16SPSecond(line.text[i + 1])) { width = passwordChar ? passwordWidth : (charMeasurer ? charMeasurer->MeasureWidth({ c, line.text[i + 1] }) : 1); offset += width; att.rightOffset = (int)offset; line.att[i + 1].rightOffset = (int)offset; i++; continue; } #endif else { width = passwordChar ? passwordWidth : (charMeasurer ? charMeasurer->MeasureWidth({ c }) : 1); } offset += width; att.rightOffset = (int)offset; } line.availableOffsetCount = line.dataLength; } vint TextLines::GetRowWidth(vint row) { if(row<0 || row>=lines.Count()) return -1; TextLine& line=lines[row]; if(line.dataLength==0) { return 0; } else { MeasureRow(row); return line.att[line.dataLength-1].rightOffset; } } vint TextLines::GetRowHeight() { return charMeasurer ? charMeasurer->GetRowHeight() : 1; } vint TextLines::GetMaxWidth() { vint width=0; for(vint i=0;i=h*lines.Count()) { point.y=h*lines.Count()-1; } vint row=point.y/h; if(point.x<0) { return TextPos(row, 0); } else if(point.x>=GetRowWidth(row)) { return TextPos(row, lines[row].dataLength); } TextLine& line=lines[row]; vint i1=0, i2=line.dataLength; vint p1=0, p2=line.att[line.dataLength-1].rightOffset; while(i2-i1>1) { vint i=(i1+i2)/2; vint p=i==0?0:line.att[i-1].rightOffset; if(point.x 0 && UTF16SPFirst(line.text[i1 - 1])) { i1--; } #endif return TextPos(row, i1); } Point TextLines::GetPointFromTextPos(TextPos pos) { if(IsAvailable(pos)) { vint y = pos.row * GetRowHeight(); if(pos.column==0) { return Point(0, y); } else { MeasureRow(pos.row); TextLine& line=lines[pos.row]; return Point(line.att[pos.column-1].rightOffset, y); } } else { return Point(-1, -1); } } Rect TextLines::GetRectFromTextPos(TextPos pos) { Point point=GetPointFromTextPos(pos); if(point==Point(-1, -1)) { return Rect(-1, -1, -1, -1); } else { vint h = GetRowHeight(); TextLine& line=lines[pos.row]; if(pos.column==line.dataLength) { return Rect(point, Size(h/2, h)); } else { return Rect(point, Size(line.att[pos.column].rightOffset-point.x, h)); } } } //-------------------------------------------------------- wchar_t TextLines::GetPasswordChar() { return passwordChar; } void TextLines::SetPasswordChar(wchar_t value) { passwordChar=value; ClearMeasurement(); } } using namespace text; /*********************************************************************** GuiColorizedTextElement ***********************************************************************/ GuiColorizedTextElement::GuiColorizedTextElement() :callback(0) ,isVisuallyEnabled(true) ,isFocused(false) ,caretVisible(false) ,lines(this) { } text::TextLines& GuiColorizedTextElement::GetLines() { return lines; } GuiColorizedTextElement::ICallback* GuiColorizedTextElement::GetCallback() { return callback; } void GuiColorizedTextElement::SetCallback(ICallback* value) { callback=value; if(!callback) { lines.SetCharMeasurer(0); } } const GuiColorizedTextElement::ColorArray& GuiColorizedTextElement::GetColors() { return colors; } void GuiColorizedTextElement::SetColors(const ColorArray& value) { CopyFrom(colors, value); if(callback) callback->ColorChanged(); InvokeOnElementStateChanged(); } void GuiColorizedTextElement::ResetTextColorIndex(vint index) { vint lineCount = lines.GetCount(); for (vint i = 0; i < lineCount; i++) { auto& line = lines.GetLine(i); line.lexerFinalState = -1; line.contextFinalState = -1; for (vint j = 0; j < line.dataLength; j++) { line.att[j].colorIndex = (vuint32_t)index; } } } const FontProperties& GuiColorizedTextElement::GetFont() { return font; } void GuiColorizedTextElement::SetFont(const FontProperties& value) { if(font!=value) { font=value; if(callback) { callback->FontChanged(); } InvokeOnElementStateChanged(); } } wchar_t GuiColorizedTextElement::GetPasswordChar() { return lines.GetPasswordChar(); } void GuiColorizedTextElement::SetPasswordChar(wchar_t value) { if(lines.GetPasswordChar()!=value) { lines.SetPasswordChar(value); InvokeOnElementStateChanged(); } } Point GuiColorizedTextElement::GetViewPosition() { return viewPosition; } void GuiColorizedTextElement::SetViewPosition(Point value) { if(viewPosition!=value) { viewPosition=value; InvokeOnElementStateChanged(); } } bool GuiColorizedTextElement::GetVisuallyEnabled() { return isVisuallyEnabled; } void GuiColorizedTextElement::SetVisuallyEnabled(bool value) { if(isVisuallyEnabled!=value) { isVisuallyEnabled=value; InvokeOnElementStateChanged(); } } bool GuiColorizedTextElement::GetFocused() { return isFocused; } void GuiColorizedTextElement::SetFocused(bool value) { if(isFocused!=value) { isFocused=value; InvokeOnElementStateChanged(); } } TextPos GuiColorizedTextElement::GetCaretBegin() { return caretBegin; } void GuiColorizedTextElement::SetCaretBegin(TextPos value) { caretBegin=value; InvokeOnElementStateChanged(); } TextPos GuiColorizedTextElement::GetCaretEnd() { return caretEnd; } void GuiColorizedTextElement::SetCaretEnd(TextPos value) { caretEnd=value; InvokeOnElementStateChanged(); } bool GuiColorizedTextElement::GetCaretVisible() { return caretVisible; } void GuiColorizedTextElement::SetCaretVisible(bool value) { caretVisible=value; InvokeOnElementStateChanged(); } Color GuiColorizedTextElement::GetCaretColor() { return caretColor; } void GuiColorizedTextElement::SetCaretColor(Color value) { if(caretColor!=value) { caretColor=value; InvokeOnElementStateChanged(); } } } } } /*********************************************************************** .\NATIVEWINDOW\GUINATIVEWINDOW.CPP ***********************************************************************/ namespace vl { namespace presentation { const NativeWindowFrameConfig NativeWindowFrameConfig::Default = {}; /*********************************************************************** INativeWindowListener ***********************************************************************/ INativeWindowListener::HitTestResult INativeWindowListener::HitTest(NativePoint location) { return INativeWindowListener::NoDecision; } void INativeWindowListener::Moving(NativeRect& bounds, bool fixSizeOnly, bool draggingBorder) { } void INativeWindowListener::Moved() { } void INativeWindowListener::DpiChanged(bool preparing) { } void INativeWindowListener::Enabled() { } void INativeWindowListener::Disabled() { } void INativeWindowListener::GotFocus() { } void INativeWindowListener::LostFocus() { } void INativeWindowListener::RenderingAsActivated() { } void INativeWindowListener::RenderingAsDeactivated() { } void INativeWindowListener::Opened() { } void INativeWindowListener::BeforeClosing(bool& cancel) { } void INativeWindowListener::AfterClosing() { } void INativeWindowListener::Closed() { } void INativeWindowListener::Paint() { } void INativeWindowListener::Destroying() { } void INativeWindowListener::Destroyed() { } void INativeWindowListener::LeftButtonDown(const NativeWindowMouseInfo& info) { } void INativeWindowListener::LeftButtonUp(const NativeWindowMouseInfo& info) { } void INativeWindowListener::LeftButtonDoubleClick(const NativeWindowMouseInfo& info) { } void INativeWindowListener::RightButtonDown(const NativeWindowMouseInfo& info) { } void INativeWindowListener::RightButtonUp(const NativeWindowMouseInfo& info) { } void INativeWindowListener::RightButtonDoubleClick(const NativeWindowMouseInfo& info) { } void INativeWindowListener::MiddleButtonDown(const NativeWindowMouseInfo& info) { } void INativeWindowListener::MiddleButtonUp(const NativeWindowMouseInfo& info) { } void INativeWindowListener::MiddleButtonDoubleClick(const NativeWindowMouseInfo& info) { } void INativeWindowListener::HorizontalWheel(const NativeWindowMouseInfo& info) { } void INativeWindowListener::VerticalWheel(const NativeWindowMouseInfo& info) { } void INativeWindowListener::MouseMoving(const NativeWindowMouseInfo& info) { } void INativeWindowListener::MouseEntered() { } void INativeWindowListener::MouseLeaved() { } void INativeWindowListener::KeyDown(const NativeWindowKeyInfo& info) { } void INativeWindowListener::KeyUp(const NativeWindowKeyInfo& info) { } void INativeWindowListener::Char(const NativeWindowCharInfo& info) { } bool INativeWindowListener::NeedRefresh() { return false; } void INativeWindowListener::ForceRefresh(bool handleFailure, bool& updated, bool& failureByResized, bool& failureByLostDevice) { } void INativeWindowListener::AssignFrameConfig(const NativeWindowFrameConfig& config) { } /*********************************************************************** INativeControllerListener ***********************************************************************/ void INativeControllerListener::GlobalTimer() { } void INativeControllerListener::ClipboardUpdated() { } void INativeControllerListener::GlobalShortcutKeyActivated(vint id) { } void INativeControllerListener::NativeWindowCreated(INativeWindow* window) { } void INativeControllerListener::NativeWindowDestroying(INativeWindow* window) { } /*********************************************************************** Native Window Provider ***********************************************************************/ class SubstitutableController; INativeController* nativeController = nullptr; SubstitutableController* substitutableController = nullptr; class SubstitutableController : public Object , public INativeController , public INativeServiceSubstitution { public: WString GetExecutablePath() override { return nativeController->GetExecutablePath(); } // Substitutable Service template struct Substitution { T* service = nullptr; bool optional = false; bool requested = false; bool unsubstituted = false; void Substitute(T* _service, bool _optional) { CHECK_ERROR( !requested, L"The service cannot be substituted because it has been used." ); service = _service; optional = _optional; } void Unsubstitute(T* _service) { if (service == _service) { if (requested) { unsubstituted = true; } service = nullptr; } } T* GetService() { CHECK_ERROR( !unsubstituted, L"The service cannot be used because it has been unsubstituted." ); requested = true; auto nativeService = (nativeController->*Getter)(); if (service && (!nativeService || !optional)) { return service; } CHECK_ERROR( nativeService != nullptr, L"Required service does not exist." ); return nativeService; } }; // Unsubstitutable Service template T* GetUnsubstitutableService() { auto nativeService = (nativeController->*Getter)(); CHECK_ERROR( nativeService != nullptr, L"Required service does not exist." ); return nativeService; } // INativeServiceSubstitution and INativeController #define GET_SUBSTITUTABLE_SERVICE(NAME) \ Substitution< \ INative##NAME##Service, \ &INativeController::NAME##Service \ > substituted##NAME; \ \ void Substitute(INative##NAME##Service* service, bool optional) override \ { \ substituted##NAME.Substitute(service, optional); \ } \ \ void Unsubstitute(INative##NAME##Service* service) override \ { \ substituted##NAME.Unsubstitute(service); \ } \ \ INative##NAME##Service* NAME##Service() override \ { \ return substituted##NAME.GetService(); \ } \ GUI_SUBSTITUTABLE_SERVICES(GET_SUBSTITUTABLE_SERVICE) #undef GET_SUBSTITUTABLE_SERVICE #define GET_UNSUBSTITUTABLE_SERVICE(NAME) \ INative##NAME##Service* NAME##Service() override \ { \ return GetUnsubstitutableService< \ INative##NAME##Service, \ &INativeController::NAME##Service \ >(); \ } \ GUI_UNSUBSTITUTABLE_SERVICES(GET_UNSUBSTITUTABLE_SERVICE) #undef GET_UNSUBSTITUTABLE_SERVICE }; INativeServiceSubstitution* GetNativeServiceSubstitution() { return substitutableController; } INativeController* GetCurrentController() { return substitutableController; } void SetNativeController(INativeController* controller) { nativeController = controller; if (nativeController) { if (!substitutableController) { substitutableController = new SubstitutableController(); } } else { if (substitutableController) { delete substitutableController; substitutableController = 0; } } } /*********************************************************************** Helper Functions ***********************************************************************/ NativeImageFrameBase::NativeImageFrameBase() { } NativeImageFrameBase::~NativeImageFrameBase() { // TODO: (enumerable) foreach for (vint i = 0; i < caches.Count(); i++) { caches.Values().Get(i)->OnDetach(this); } } bool NativeImageFrameBase::SetCache(void* key, Ptr cache) { vint index = caches.Keys().IndexOf(key); if (index != -1) { return false; } caches.Add(key, cache); cache->OnAttach(this); return true; } Ptr NativeImageFrameBase::GetCache(void* key) { vint index = caches.Keys().IndexOf(key); return index == -1 ? nullptr : caches.Values().Get(index); } Ptr NativeImageFrameBase::RemoveCache(void* key) { vint index = caches.Keys().IndexOf(key); if (index == -1) { return 0; } Ptr cache = caches.Values().Get(index); cache->OnDetach(this); caches.Remove(key); return cache; } /*********************************************************************** Helper Functions ***********************************************************************/ INativeCursor* GetCursorFromHitTest(INativeWindowListener::HitTestResult hitTestResult, INativeResourceService* resourceService) { switch (hitTestResult) { case INativeWindowListener::BorderLeft: case INativeWindowListener::BorderRight: return resourceService->GetSystemCursor(INativeCursor::SizeWE); case INativeWindowListener::BorderTop: case INativeWindowListener::BorderBottom: return resourceService->GetSystemCursor(INativeCursor::SizeNS); case INativeWindowListener::BorderLeftTop: case INativeWindowListener::BorderRightBottom: return resourceService->GetSystemCursor(INativeCursor::SizeNWSE); case INativeWindowListener::BorderRightTop: case INativeWindowListener::BorderLeftBottom: return resourceService->GetSystemCursor(INativeCursor::SizeNESW); default: return nullptr; } } } } /*********************************************************************** .\PLATFORMPROVIDERS\GACGEN\GACGENCONTROLLER.CPP ***********************************************************************/ using namespace vl; using namespace vl::stream; using namespace vl::reflection::description; using namespace vl::presentation; class GacGenNativeController : public Object , public INativeController , protected INativeCallbackService , protected INativeResourceService , protected INativeImageService , protected INativeInputService { public: INativeCallbackService* CallbackService() override { return this; } INativeResourceService* ResourceService() override { return this; } INativeAsyncService* AsyncService() override { CHECK_FAIL(L"Not implemented!"); } INativeClipboardService* ClipboardService() override { CHECK_FAIL(L"Not implemented!"); } INativeImageService* ImageService() override { return this; } INativeScreenService* ScreenService() override { CHECK_FAIL(L"Not implemented!"); } INativeWindowService* WindowService() override { CHECK_FAIL(L"Not implemented!"); } INativeInputService* InputService() override { return this; } INativeDialogService* DialogService() override { CHECK_FAIL(L"Not implemented!"); } WString GetExecutablePath() override { CHECK_FAIL(L"Not implemented!"); } //////////////////////////////////////////////////////////////////// // INativeCallbackService //////////////////////////////////////////////////////////////////// bool InstallListener(INativeControllerListener* listener) override { return true; } bool UninstallListener(INativeControllerListener* listener) override { return true; } INativeCallbackInvoker* Invoker() override { CHECK_FAIL(L"Not implemented!"); } //////////////////////////////////////////////////////////////////// // INativeResourceService //////////////////////////////////////////////////////////////////// INativeCursor* GetSystemCursor(INativeCursor::SystemCursorType type) override { CHECK_FAIL(L"Not implemented!"); } INativeCursor* GetDefaultSystemCursor() override { CHECK_FAIL(L"Not implemented!"); } FontProperties GetDefaultFont() override { FontProperties font; font.fontFamily = L"GacGen"; font.size = 12; font.bold = false; font.italic = false; font.underline = false; font.strikeline = false; font.antialias = false; font.verticalAntialias = false; return font; } void SetDefaultFont(const FontProperties& value) override { CHECK_FAIL(L"Not implemented!"); } void EnumerateFonts(collections::List& fonts) override { CHECK_FAIL(L"Not implemented!"); } //////////////////////////////////////////////////////////////////// // INativeImageService //////////////////////////////////////////////////////////////////// class NativeImage : public Object, public INativeImage { protected: INativeImageService* imageService; MemoryStream memoryStream; public: NativeImage(INativeImageService* _imageService, IStream& inputStream) : imageService(_imageService) { CopyStream(inputStream, memoryStream); } INativeImageService* GetImageService() override { return imageService; } FormatType GetFormat() override { CHECK_FAIL(L"Not implemented!"); } vint GetFrameCount() override { return 0; } INativeImageFrame* GetFrame(vint index) override { CHECK_FAIL(L"Not implemented!"); } void SaveToStream(stream::IStream& imageStream, FormatType formatType) override { CHECK_ERROR(formatType == FormatType::Unknown, L"Not Implemented!"); memoryStream.SeekFromBegin(0); CopyStream(memoryStream, imageStream); } }; Ptr CreateImageFromFile(const WString& path) override { FileStream imageStream(path, FileStream::ReadOnly); if (!imageStream.IsAvailable()) return nullptr; return Ptr(new NativeImage(this, imageStream)); } Ptr CreateImageFromMemory(void* buffer, vint length) override { MemoryWrapperStream imageStream(buffer, length); return Ptr(new NativeImage(this, imageStream)); } Ptr CreateImageFromStream(stream::IStream& imageStream) override { return Ptr(new NativeImage(this, imageStream)); } //////////////////////////////////////////////////////////////////// // INativeInputService //////////////////////////////////////////////////////////////////// void StartTimer() override { } void StopTimer() override { } bool IsTimerEnabled() override { CHECK_FAIL(L"Not implemented!"); } bool IsKeyPressing(VKEY code) override { CHECK_FAIL(L"Not implemented!"); } bool IsKeyToggled(VKEY code) override { CHECK_FAIL(L"Not implemented!"); } WString GetKeyName(VKEY code) override { CHECK_FAIL(L"Not implemented!"); } VKEY GetKey(const WString& name) override { CHECK_FAIL(L"Not implemented!"); } vint RegisterGlobalShortcutKey(bool ctrl, bool shift, bool alt, VKEY key) override { CHECK_FAIL(L"Not Implemented!"); } bool UnregisterGlobalShortcutKey(vint id) override { CHECK_FAIL(L"Not Implemented!"); } }; extern void GuiApplicationMain(); int SetupGacGenNativeController() { GacGenNativeController controller; SetNativeController(&controller); GuiApplicationMain(); SetNativeController(nullptr); return 0; } /*********************************************************************** .\PLATFORMPROVIDERS\HOSTED\GUIHOSTEDAPPLICATION.CPP ***********************************************************************/ namespace vl::presentation { IGuiHostedApplication* hostedApplication = nullptr; IGuiHostedApplication* GetHostedApplication() { return hostedApplication; } void SetHostedApplication(IGuiHostedApplication* _hostedApp) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::SetHostedApplication(IGuiHostedApplication*)#" if (_hostedApp) { CHECK_ERROR(!hostedApplication, ERROR_MESSAGE_PREFIX L"IGuiHostedApplication instance already exists during initializing."); } else { CHECK_ERROR(hostedApplication, ERROR_MESSAGE_PREFIX L"IGuiHostedApplication instance does not exist during finalizing."); } hostedApplication = _hostedApp; #undef ERROR_MESSAGE_PREFIX } } /*********************************************************************** .\PLATFORMPROVIDERS\HOSTED\GUIHOSTEDCONTROLLER.CPP ***********************************************************************/ namespace vl { namespace presentation { using namespace collections; /*********************************************************************** GuiHostedController ***********************************************************************/ NativePoint GuiHostedController::GetPointInClientSpace(NativePoint location) { auto windowBounds = nativeWindow->GetBounds(); auto clientBounds = nativeWindow->GetClientBoundsInScreen(); location.x.value += windowBounds.x1.value - clientBounds.x1.value; location.y.value += windowBounds.y1.value - clientBounds.y1.value; return location; } GuiHostedWindow* GuiHostedController::HitTestInClientSpace(NativePoint location) { auto window = wmManager->HitTest(location); return window ? window->id : nullptr; } void GuiHostedController::UpdateHoveringWindow(Nullable location) { if (location) { hoveringLocation = location.Value(); } hoveringWindow = HitTestInClientSpace(hoveringLocation); } void GuiHostedController::UpdateEnteringWindow(GuiHostedWindow* window) { if (enteringWindow != window) { if (enteringWindow) { for (auto listener : enteringWindow->listeners) { listener->MouseLeaved(); } } enteringWindow = window; if (enteringWindow) { for (auto listener : enteringWindow->listeners) { listener->MouseEntered(); } } } } /*********************************************************************** GuiHostedController::WindowManager ***********************************************************************/ void GuiHostedController::OnOpened(hosted_window_manager::Window* window) { if (!mainWindow || window != &mainWindow->wmWindow) { for (auto listener : window->id->listeners) { listener->Opened(); } } } void GuiHostedController::OnClosed(hosted_window_manager::Window* window) { for (auto listener : window->id->listeners) { listener->Closed(); } } void GuiHostedController::OnEnabled(hosted_window_manager::Window* window) { for (auto listener : window->id->listeners) { listener->Enabled(); } } void GuiHostedController::OnDisabled(hosted_window_manager::Window* window) { for (auto listener : window->id->listeners) { listener->Disabled(); } } void GuiHostedController::OnGotFocus(hosted_window_manager::Window* window) { window->id->BecomeFocusedWindow(); for (auto listener : window->id->listeners) { listener->GotFocus(); } } void GuiHostedController::OnLostFocus(hosted_window_manager::Window* window) { for (auto listener : window->id->listeners) { listener->LostFocus(); } } void GuiHostedController::OnActivated(hosted_window_manager::Window* window) { for (auto listener : window->id->listeners) { listener->RenderingAsActivated(); } } void GuiHostedController::OnDeactivated(hosted_window_manager::Window* window) { for (auto listener : window->id->listeners) { listener->RenderingAsDeactivated(); } } /*********************************************************************** GuiHostedController::INativeWindowListener ***********************************************************************/ INativeWindowListener::HitTestResult GuiHostedController::HitTest(NativePoint location) { if (mainWindow && mainWindow->IsEnabled()) { auto point = GetPointInClientSpace(location); auto window = HitTestInClientSpace(point); if (window == mainWindow) { return PerformHitTest(From(mainWindow->listeners), point); } } return INativeWindowListener::HitTestResult::NoDecision; } void GuiHostedController::Moving(NativeRect& bounds, bool fixSizeOnly, bool draggingBorder) { if (mainWindow) { auto windowBounds = nativeWindow->GetBounds(); auto clientBounds = nativeWindow->GetClientBoundsInScreen(); auto w = clientBounds.Width().value - windowBounds.Width().value; auto h = clientBounds.Height().value - windowBounds.Height().value; NativeRect mainBounds; mainBounds.x2 = bounds.Width().value - w; mainBounds.y2 = bounds.Height().value - h; for (auto listener : mainWindow->listeners) { listener->Moving(mainBounds, fixSizeOnly, draggingBorder); } bounds.x1.value += mainBounds.x1.value; bounds.y1.value += mainBounds.y1.value; bounds.x2.value = bounds.x1.value + mainBounds.Width().value + w; bounds.y2.value = bounds.y1.value + mainBounds.Height().value + h; } } void GuiHostedController::Moved() { if (mainWindow) { auto size = mainWindow->GetBounds().GetSize(); auto clientSize = nativeWindow->GetClientSize(); if (size != clientSize) { mainWindow->SetBounds({ {},clientSize }); } for (auto listener : mainWindow->listeners) { listener->Moved(); } } } void GuiHostedController::DpiChanged(bool preparing) { if (!preparing) { hostedResourceManager->nativeManager->RecreateRenderTarget(nativeWindow); wmManager->needRefresh = true; } for (auto hostedWindow : createdWindows) { for (auto listener : hostedWindow->listeners) { listener->DpiChanged(preparing); } } } void GuiHostedController::GotFocus() { if (lastFocusedWindow) { lastFocusedWindow->wmWindow.Activate(); lastFocusedWindow = nullptr; } else if (mainWindow) { mainWindow->wmWindow.Activate(); } } void GuiHostedController::Opened() { if (mainWindow) { for (auto listener : mainWindow->wmWindow.id->listeners) { listener->Opened(); } } } void GuiHostedController::LostFocus() { lastFocusedWindow = wmManager->activeWindow ? wmManager->activeWindow->id : nullptr; while (wmManager->activeWindow) { wmManager->activeWindow->Deactivate(); } } void GuiHostedController::BeforeClosing(bool& cancel) { if (mainWindow) { for (auto listener : mainWindow->listeners) { listener->BeforeClosing(cancel); if (cancel) return; } } } void GuiHostedController::AfterClosing() { if (mainWindow) { for (auto listener : mainWindow->listeners) { listener->AfterClosing(); } } } void GuiHostedController::Paint() { } /*********************************************************************** GuiHostedController::INativeWindowListener (GetSelectedWindow) ***********************************************************************/ GuiHostedWindow* GuiHostedController::GetSelectedWindow_MouseDown(const NativeWindowMouseInfo& info) { if (!capturingWindow) { SortedList survivedPopups; auto current = hoveringWindow; while (current) { if (current->IsVisible() && current->GetWindowMode() != INativeWindow::Normal) { survivedPopups.Add(current); } current = current->wmWindow.parent ? current->wmWindow.parent->id : nullptr; } List closingPopups; CopyFrom( closingPopups, From(wmManager->ordinaryWindowsInOrder) .Concat(wmManager->topMostedWindowsInOrder) .Select([](auto window) { return window->id; }) .Where([&](auto window) { return window->GetWindowMode() != INativeWindow::Normal && !survivedPopups.Contains(window); }) ); for (auto popupWindow : closingPopups) { popupWindow->Hide(false); } } auto selectedWindow = capturingWindow ? capturingWindow : hoveringWindow; return selectedWindow; } GuiHostedWindow* GuiHostedController::GetSelectedWindow_MouseMoving(const NativeWindowMouseInfo& info) { UpdateHoveringWindow({ { info.x,info.y } }); auto selectedWindow = capturingWindow ? capturingWindow : hoveringWindow; UpdateEnteringWindow(selectedWindow); return selectedWindow; } GuiHostedWindow* GuiHostedController::GetSelectedWindow_Other(const NativeWindowMouseInfo& info) { auto selectedWindow = capturingWindow ? capturingWindow : hoveringWindow; return selectedWindow; } /*********************************************************************** GuiHostedController::INativeWindowListener (PreAction) ***********************************************************************/ void GuiHostedController::PreAction_LeftButtonDown(const NativeWindowMouseInfo& info) { PreAction_MouseDown(info); if (!capturingWindow && !wmWindow && hoveringWindow && hoveringWindow != mainWindow && hoveringWindow->IsEnabled()) { auto x = info.x.value - hoveringWindow->wmWindow.bounds.x1.value; auto y = info.y.value - hoveringWindow->wmWindow.bounds.y1.value; auto hitTestResult = PerformHitTest(From(hoveringWindow->listeners), { {x},{y} }); switch (hitTestResult) { #define HANDLE_HIT_TEST_RESULT(NAME)\ case INativeWindowListener::NAME: \ wmOperation = WindowManagerOperation::NAME; \ break; \ HANDLE_HIT_TEST_RESULT(Title) HANDLE_HIT_TEST_RESULT(BorderLeft) HANDLE_HIT_TEST_RESULT(BorderRight) HANDLE_HIT_TEST_RESULT(BorderTop) HANDLE_HIT_TEST_RESULT(BorderBottom) HANDLE_HIT_TEST_RESULT(BorderLeftTop) HANDLE_HIT_TEST_RESULT(BorderRightBottom) HANDLE_HIT_TEST_RESULT(BorderRightTop) HANDLE_HIT_TEST_RESULT(BorderLeftBottom) default:; #undef HANDLE_HIT_TEST_RESULT } switch (wmOperation) { case WindowManagerOperation::None: return; case WindowManagerOperation::Title: if (!hoveringWindow->GetTitleBar()) { wmOperation = WindowManagerOperation::None; return; } break; default: if (!hoveringWindow->GetSizeBox()) { wmOperation = WindowManagerOperation::None; return; } } wmWindow = hoveringWindow; nativeWindow->RequireCapture(); switch (wmOperation) { case WindowManagerOperation::Title: case WindowManagerOperation::BorderLeft: case WindowManagerOperation::BorderLeftTop: case WindowManagerOperation::BorderLeftBottom: wmRelative.x.value = x; break; case WindowManagerOperation::BorderTop: case WindowManagerOperation::BorderBottom: wmRelative.x.value = wmWindow->wmWindow.bounds.Width().value / 2; break; case WindowManagerOperation::BorderRight: case WindowManagerOperation::BorderRightTop: case WindowManagerOperation::BorderRightBottom: wmRelative.x.value = wmWindow->wmWindow.bounds.Width().value - x; break; default:; } switch (wmOperation) { case WindowManagerOperation::Title: case WindowManagerOperation::BorderTop: case WindowManagerOperation::BorderLeftTop: case WindowManagerOperation::BorderRightTop: wmRelative.y.value = y; break; case WindowManagerOperation::BorderLeft: case WindowManagerOperation::BorderRight: wmRelative.y.value = wmWindow->wmWindow.bounds.Height().value / 2; break; case WindowManagerOperation::BorderBottom: case WindowManagerOperation::BorderLeftBottom: case WindowManagerOperation::BorderRightBottom: wmRelative.y.value = wmWindow->wmWindow.bounds.Height().value - y; break; default:; } } } void GuiHostedController::PreAction_MouseDown(const NativeWindowMouseInfo& info) { if (!capturingWindow && !wmWindow && hoveringWindow && hoveringWindow->IsEnabled() && hoveringWindow->IsEnabledActivate()) { hoveringWindow->SetActivate(); } } void GuiHostedController::PreAction_MouseMoving(const NativeWindowMouseInfo& info) { if (!capturingWindow && !wmWindow && hoveringWindow && hoveringWindow != mainWindow && hoveringWindow->IsEnabled() && hoveringWindow->GetSizeBox()) { auto x = info.x.value - hoveringWindow->wmWindow.bounds.x1.value; auto y = info.y.value - hoveringWindow->wmWindow.bounds.y1.value; auto hitTestResult = PerformHitTest(From(hoveringWindow->listeners), { {x},{y} }); auto cursor = GetCursorFromHitTest(hitTestResult, ResourceService()); if (cursor == nullptr) { cursor = hoveringWindow->GetWindowCursor(); } nativeWindow->SetWindowCursor(cursor); } if (wmWindow) { auto oldBounds = wmWindow->wmWindow.bounds; auto newBounds = oldBounds; vint mouseX = info.x.value; vint mouseY = info.y.value; vint displayX = mainWindow->wmWindow.bounds.Width().value; vint displayY = mainWindow->wmWindow.bounds.Height().value; if (mouseX < 0) { mouseX = 0; } else if (mouseX >= displayX) { mouseX = displayX - 1; } if (mouseY < 0) { mouseY = 0; } else if (mouseY >= displayY) { mouseY = displayY - 1; } if (wmOperation == WindowManagerOperation::Title) { newBounds = { { {mouseX - wmRelative.x.value}, {mouseY - wmRelative.y.value} }, oldBounds.GetSize() }; } else { switch (wmOperation) { case WindowManagerOperation::BorderLeft: case WindowManagerOperation::BorderLeftTop: case WindowManagerOperation::BorderLeftBottom: newBounds.x1.value = mouseX - wmRelative.x.value; break; case WindowManagerOperation::BorderRight: case WindowManagerOperation::BorderRightTop: case WindowManagerOperation::BorderRightBottom: newBounds.x2.value = mouseX + wmRelative.x.value; break; default:; } switch (wmOperation) { case WindowManagerOperation::BorderTop: case WindowManagerOperation::BorderLeftTop: case WindowManagerOperation::BorderRightTop: newBounds.y1.value = mouseY - wmRelative.y.value; break; case WindowManagerOperation::BorderBottom: case WindowManagerOperation::BorderLeftBottom: case WindowManagerOperation::BorderRightBottom: newBounds.y2.value = mouseY + wmRelative.y.value; break; default:; } } for (auto listener : wmWindow->listeners) { listener->Moving(newBounds, false, wmOperation != WindowManagerOperation::Title); } wmWindow->wmWindow.SetBounds(newBounds); for (auto listener : wmWindow->listeners) { listener->Moved(); } } } void GuiHostedController::PreAction_Other(const NativeWindowMouseInfo& info) { } /*********************************************************************** GuiHostedController::INativeWindowListener (PostAction) ***********************************************************************/ void GuiHostedController::PostAction_LeftButtonUp(GuiHostedWindow* selectedWindow, const NativeWindowMouseInfo& info) { if (!capturingWindow && !wmWindow && selectedWindow && selectedWindow != mainWindow && selectedWindow->IsEnabled()) { auto x = info.x.value - hoveringWindow->wmWindow.bounds.x1.value; auto y = info.y.value - hoveringWindow->wmWindow.bounds.y1.value; auto hitTestResult = PerformHitTest(From(hoveringWindow->listeners), { {x},{y} }); if (hitTestResult == INativeWindowListener::ButtonClose) { hoveringWindow->Hide(true); } } if (wmWindow) { wmWindow = nullptr; wmOperation = WindowManagerOperation::None; nativeWindow->ReleaseCapture(); } } void GuiHostedController::PostAction_Other(GuiHostedWindow* selectedWindow, const NativeWindowMouseInfo& info) { } /*********************************************************************** GuiHostedController::INativeWindowListener (Template) ***********************************************************************/ template< void (GuiHostedController::* PreAction)(const NativeWindowMouseInfo&), GuiHostedWindow*(GuiHostedController::* GetSelectedWindow)(const NativeWindowMouseInfo&), void (GuiHostedController::* PostAction)(GuiHostedWindow*, const NativeWindowMouseInfo&), void (INativeWindowListener::* Callback)(const NativeWindowMouseInfo&) > void GuiHostedController::HandleMouseCallback(const NativeWindowMouseInfo& info) { (this->*PreAction)(info); auto postActionWindow = hoveringWindow; if (!wmWindow) { if (auto selectedWindow = (this->*GetSelectedWindow)(info)) { postActionWindow = selectedWindow; if (!selectedWindow->IsEnabled()) return; auto adjustedInfo = info; adjustedInfo.x.value -= selectedWindow->wmWindow.bounds.x1.value; adjustedInfo.y.value -= selectedWindow->wmWindow.bounds.y1.value; for (auto listener : selectedWindow->listeners) { (listener->*Callback)(adjustedInfo); } } } (this->*PostAction)(postActionWindow, info); } template< typename TInfo, void (INativeWindowListener::* Callback)(const TInfo&) > void GuiHostedController::HandleKeyboardCallback(const TInfo& info) { if (wmManager->activeWindow && !wmWindow) { auto hostedWindow = wmManager->activeWindow->id; for (auto listener : hostedWindow->listeners) { (listener->*Callback)(info); } } } /*********************************************************************** GuiHostedController::INativeWindowListener (IO Event Handling) ***********************************************************************/ #define IMPLEMENT_MOUSE_CALLBACK(NAME, PREACTION, POLICY, POSTACTION) \ void GuiHostedController::NAME(const NativeWindowMouseInfo& info) \ { \ HandleMouseCallback< \ &GuiHostedController::PreAction_##PREACTION, \ &GuiHostedController::GetSelectedWindow_##POLICY, \ &GuiHostedController::PostAction_##POSTACTION, \ &INativeWindowListener::NAME \ >(info); \ } \ IMPLEMENT_MOUSE_CALLBACK(LeftButtonDown, LeftButtonDown, MouseDown, Other ) IMPLEMENT_MOUSE_CALLBACK(LeftButtonUp, Other, Other, LeftButtonUp ) IMPLEMENT_MOUSE_CALLBACK(LeftButtonDoubleClick, Other, Other, Other ) IMPLEMENT_MOUSE_CALLBACK(RightButtonDown, MouseDown, MouseDown, Other ) IMPLEMENT_MOUSE_CALLBACK(RightButtonUp, Other, Other, Other ) IMPLEMENT_MOUSE_CALLBACK(RightButtonDoubleClick, Other, Other, Other ) IMPLEMENT_MOUSE_CALLBACK(MiddleButtonDown, MouseDown, MouseDown, Other ) IMPLEMENT_MOUSE_CALLBACK(MiddleButtonUp, Other, Other, Other ) IMPLEMENT_MOUSE_CALLBACK(MiddleButtonDoubleClick, Other, Other, Other ) IMPLEMENT_MOUSE_CALLBACK(HorizontalWheel, Other, Other, Other ) IMPLEMENT_MOUSE_CALLBACK(VerticalWheel, Other, Other, Other ) IMPLEMENT_MOUSE_CALLBACK(MouseMoving, MouseMoving, MouseMoving, Other ) #undef IMPLEMENT_MOUSE_CALLBACK void GuiHostedController::MouseEntered() { } void GuiHostedController::MouseLeaved() { #define ERROR_MESSAGE_PREFIX L"vl::presentation::GuiHostedController::MouseLeaved()#" UpdateEnteringWindow(nullptr); #undef ERROR_MESSAGE_PREFIX } #define IMPLEMENT_KEY_CALLBACK(NAME, TYPE) \ void GuiHostedController::NAME(const NativeWindow##TYPE##Info& info) \ { \ HandleKeyboardCallback< \ NativeWindow##TYPE##Info, \ &INativeWindowListener::NAME \ >(info); \ } \ IMPLEMENT_KEY_CALLBACK(KeyDown, Key) IMPLEMENT_KEY_CALLBACK(KeyUp, Key) IMPLEMENT_KEY_CALLBACK(Char, Char) #undef IMPLEMENT_KEY_CALLBACK /*********************************************************************** GuiHostedController::INativeControllerListener ***********************************************************************/ void GuiHostedController::GlobalTimer() { callbackService.InvokeGlobalTimer(); if (hostedResourceManager && nativeWindow && nativeWindow->IsVisible()) { auto renderTarget = hostedResourceManager->nativeManager->GetRenderTarget(nativeWindow); if (renderTarget->IsInHostedRendering()) { return; } for (auto visibleWindow : From(wmManager->ordinaryWindowsInOrder).Concat(wmManager->topMostedWindowsInOrder)) { for (auto listener : visibleWindow->id->listeners) { if (listener->NeedRefresh()) { wmManager->needRefresh = true; goto NEED_REFRESH; } } } if (!wmManager->needRefresh && !windowsUpdatedInLastFrame) { return; } NEED_REFRESH: wmManager->needRefresh = false; windowsUpdatedInLastFrame = false; while (true) { renderTarget->StartHostedRendering(); bool failureByResized = false; bool failureByLostDevice = false; auto forceRefreshWindows = [&](List*>& windows) { // TODO: (enumerable) foreach:reversed for (vint i = windows.Count() - 1; i >= 0; i--) { auto hostedWindow = windows[i]->id; for (auto listener : hostedWindow->listeners) { bool updated = false; listener->ForceRefresh(false, updated, failureByResized, failureByLostDevice); windowsUpdatedInLastFrame |= updated; if (failureByResized || failureByLostDevice) { return false; } } } return true; }; if (!forceRefreshWindows(wmManager->ordinaryWindowsInOrder)) goto STOP_RENDERING; if (!forceRefreshWindows(wmManager->topMostedWindowsInOrder)) goto STOP_RENDERING; STOP_RENDERING: switch (renderTarget->StopHostedRendering()) { case elements::RenderTargetFailure::LostDevice: failureByLostDevice = true; break; case elements::RenderTargetFailure::ResizeWhileRendering: failureByResized = true; break; default:; } if (failureByLostDevice) { hostedResourceManager->nativeManager->RecreateRenderTarget(nativeWindow); wmManager->needRefresh = true; } else if (failureByResized) { hostedResourceManager->nativeManager->ResizeRenderTarget(nativeWindow); wmManager->needRefresh = true; } else { nativeWindow->RedrawContent(); break; } } } } void GuiHostedController::ClipboardUpdated() { callbackService.InvokeClipboardUpdated(); } void GuiHostedController::GlobalShortcutKeyActivated(vint id) { callbackService.InvokeGlobalShortcutKeyActivated(id); } void GuiHostedController::NativeWindowDestroying(INativeWindow* window) { if (nativeWindow == window) { DestroyHostedWindowsAfterRunning(); nativeWindow->UninstallListener(this); nativeWindow = nullptr; } } /*********************************************************************** GuiHostedController::INativeAsyncService ***********************************************************************/ bool GuiHostedController::IsInMainThread(INativeWindow* window) { return nativeController->AsyncService()->IsInMainThread(nativeWindow); } void GuiHostedController::InvokeAsync(const Func& proc) { return nativeController->AsyncService()->InvokeAsync(proc); } void GuiHostedController::InvokeInMainThread(INativeWindow* window, const Func& proc) { return nativeController->AsyncService()->InvokeInMainThread(nativeWindow, proc); } bool GuiHostedController::InvokeInMainThreadAndWait(INativeWindow* window, const Func& proc, vint milliseconds) { return nativeController->AsyncService()->InvokeInMainThreadAndWait(nativeWindow, proc, milliseconds); } Ptr GuiHostedController::DelayExecute(const Func& proc, vint milliseconds) { return nativeController->AsyncService()->DelayExecute(proc, milliseconds); } Ptr GuiHostedController::DelayExecuteInMainThread(const Func& proc, vint milliseconds) { return nativeController->AsyncService()->DelayExecuteInMainThread(proc, milliseconds); } /*********************************************************************** GuiHostedController::INativeScreenService ***********************************************************************/ vint GuiHostedController::GetScreenCount() { return 1; } INativeScreen* GuiHostedController::GetScreen(vint index) { CHECK_ERROR(index == 0, L"vl::presentation::GuiHostedController::GetScreen(vint)#Index out of range."); return this; } INativeScreen* GuiHostedController::GetScreen(INativeWindow* window) { return this; } /*********************************************************************** GuiHostedController::INativeScreen ***********************************************************************/ NativeRect GuiHostedController::GetBounds() { if (nativeWindow->IsCustomFrameModeEnabled()) { return { {},nativeWindow->GetBounds().GetSize() }; } else { return { {},nativeWindow->GetClientSize() }; } } NativeRect GuiHostedController::GetClientBounds() { return { {},nativeWindow->GetClientSize() }; } WString GuiHostedController::GetName() { return WString::Unmanaged(L"GacUI Virtual Screen"); } bool GuiHostedController::IsPrimary() { return true; } double GuiHostedController::GetScalingX() { return nativeController->ScreenService()->GetScreen(nativeWindow)->GetScalingX(); } double GuiHostedController::GetScalingY() { return nativeController->ScreenService()->GetScreen(nativeWindow)->GetScalingY(); } /*********************************************************************** GuiHostedController::INativeWindowService ***********************************************************************/ const NativeWindowFrameConfig& GuiHostedController::GetMainWindowFrameConfig() { return nativeController->WindowService()->GetMainWindowFrameConfig(); } const NativeWindowFrameConfig& GuiHostedController::GetNonMainWindowFrameConfig() { static const NativeWindowFrameConfig config = { .MaximizedBoxOption = BoolOption::AlwaysFalse, .MinimizedBoxOption = BoolOption::AlwaysFalse, .CustomFrameEnabled = BoolOption::AlwaysTrue, }; return config; } INativeWindow* GuiHostedController::CreateNativeWindow(INativeWindow::WindowMode windowMode) { auto hostedWindow = Ptr(new GuiHostedWindow(this, windowMode)); createdWindows.Add(hostedWindow); wmManager->RegisterWindow(&hostedWindow->wmWindow); callbackService.InvokeNativeWindowCreated(hostedWindow.Obj()); if (mainWindow) { hostedWindow->BecomeNonMainWindow(); } return hostedWindow.Obj(); } void GuiHostedController::DestroyNativeWindow(INativeWindow* window) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::GuiHostedController::DestroyNativeWindow(INativeWindow*)#" auto hostedWindow = dynamic_cast(window); CHECK_ERROR(hostedWindow, ERROR_MESSAGE_PREFIX L"The window is not created by GuiHostedController."); vint index = createdWindows.IndexOf(hostedWindow); CHECK_ERROR(index != -1, ERROR_MESSAGE_PREFIX L"The window has been destroyed."); if (hostedWindow == enteringWindow) enteringWindow = nullptr; if (hostedWindow == hoveringWindow) hoveringWindow = nullptr; if (hostedWindow == lastFocusedWindow) enteringWindow = nullptr; if (hostedWindow == capturingWindow) { capturingWindow->ReleaseCapture(); } if (hostedWindow == wmWindow) { wmOperation = WindowManagerOperation::None; wmWindow = nullptr; nativeWindow->ReleaseCapture(); } for (auto listener : hostedWindow->listeners) { listener->Destroying(); } callbackService.InvokeNativeWindowDestroying(hostedWindow); wmManager->UnregisterWindow(&hostedWindow->wmWindow); createdWindows.RemoveAt(index); UpdateHoveringWindow({}); #undef ERROR_MESSAGE_PREFIX } INativeWindow* GuiHostedController::GetMainWindow() { return mainWindow; } INativeWindow* GuiHostedController::GetWindow(NativePoint location) { auto wmWindow = wmManager->HitTest(location); return wmWindow ? wmWindow->id : nullptr; } void GuiHostedController::SettingHostedWindowsBeforeRunning() { if (nativeWindow) { for (auto window : createdWindows) { if (window == mainWindow) { window->BecomeMainWindow(); } else { window->BecomeNonMainWindow(); } } if (auto screen = nativeController->ScreenService()->GetScreen(nativeWindow)) { auto screenBounds = screen->GetClientBounds(); auto windowSize = nativeWindow->GetBounds().GetSize(); nativeWindow->SetBounds({ { screenBounds.Left() + (screenBounds.Width() - windowSize.x) / 2, screenBounds.Top() + (screenBounds.Height() - windowSize.y) / 2 }, windowSize }); } wmManager->Start(&mainWindow->wmWindow); } } void GuiHostedController::DestroyHostedWindowsAfterRunning() { if (nativeWindow) { if (wmManager->mainWindow) { wmManager->Stop(); } // TODO: (enumerable) foreach:indexed(alterable(reversed)) for (vint i = createdWindows.Count() - 1; i >= 0; i--) { auto hostedWindow = createdWindows[i]; if (hostedWindow != mainWindow) { DestroyNativeWindow(hostedWindow.Obj()); } } if (mainWindow) { DestroyNativeWindow(mainWindow); mainWindow = nullptr; } } } void GuiHostedController::Run(INativeWindow* window) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::GuiHostedController::Run(INativeWindow*)#" CHECK_ERROR(!mainWindow, ERROR_MESSAGE_PREFIX L"This function has been called."); auto hostedWindow = dynamic_cast(window); CHECK_ERROR(hostedWindow, ERROR_MESSAGE_PREFIX L"The window is not created by GuiHostedController."); mainWindow = hostedWindow; SettingHostedWindowsBeforeRunning(); wmManager->needRefresh = true; try { nativeController->WindowService()->Run(nativeWindow); } catch (const Exception& e) { (void)e; DestroyHostedWindowsAfterRunning(); throw; } catch (const Error& e) { (void)e; DestroyHostedWindowsAfterRunning(); throw; } catch (const unittest::UnitTestAssertError& e) { (void)e; DestroyHostedWindowsAfterRunning(); throw; } catch (...) { DestroyHostedWindowsAfterRunning(); throw; } CHECK_ERROR((nativeWindow == nullptr) == (mainWindow == nullptr), ERROR_MESSAGE_PREFIX L"Hosted windows should have been destroyed if the native windows is destroyed."); DestroyHostedWindowsAfterRunning(); #undef ERROR_MESSAGE_PREFIX } bool GuiHostedController::RunOneCycle() { return nativeController->WindowService()->RunOneCycle(); } /*********************************************************************** GuiHostedController::IGuiHostedApplication ***********************************************************************/ INativeWindow* GuiHostedController::GetNativeWindowHost() { return nativeWindow; } /*********************************************************************** GuiHostedController ***********************************************************************/ GuiHostedController::GuiHostedController(INativeController* _nativeController) : nativeController(_nativeController) { wmManager = this; nativeController->CallbackService()->InstallListener(this); } GuiHostedController::~GuiHostedController() { } IGuiHostedApplication* GuiHostedController::GetHostedApplication() { return this; } void GuiHostedController::Initialize() { #define ERROR_MESSAGE_PREFIX L"vl::presentation::GuiHostedController()::Initialize()#" CHECK_ERROR(!nativeWindow, ERROR_MESSAGE_PREFIX L"Initialize() has been called"); CHECK_ERROR(!nativeWindowDestroyed, ERROR_MESSAGE_PREFIX L"Finalize() has been called."); nativeController->CallbackService()->InstallListener(this); nativeWindow = nativeController->WindowService()->CreateNativeWindow(INativeWindow::WindowMode::Normal); nativeWindow->InstallListener(this); #undef ERROR_MESSAGE_PREFIX } void GuiHostedController::Finalize() { #define ERROR_MESSAGE_PREFIX L"vl::presentation::GuiHostedController()::Finalize()#" CHECK_ERROR(!nativeWindowDestroyed, ERROR_MESSAGE_PREFIX L"Finalize() has been called."); if (nativeWindow) { nativeController->WindowService()->DestroyNativeWindow(nativeWindow); } nativeController->CallbackService()->UninstallListener(this); nativeWindowDestroyed = true; #undef ERROR_MESSAGE_PREFIX } void GuiHostedController::RequestRefresh() { wmManager->needRefresh = true; } /*********************************************************************** GuiHostedController::INativeController ***********************************************************************/ INativeCallbackService* GuiHostedController::CallbackService() { return &callbackService; } INativeResourceService* GuiHostedController::ResourceService() { return nativeController->ResourceService(); } INativeAsyncService* GuiHostedController::AsyncService() { return this; } INativeClipboardService* GuiHostedController::ClipboardService() { return nativeController->ClipboardService(); } INativeImageService* GuiHostedController::ImageService() { return nativeController->ImageService(); } INativeInputService* GuiHostedController::InputService() { return nativeController->InputService(); } INativeDialogService* GuiHostedController::DialogService() { // Use FakeDialogServiceBase return nullptr; } WString GuiHostedController::GetExecutablePath() { return nativeController->GetExecutablePath(); } INativeScreenService* GuiHostedController::ScreenService() { return this; } INativeWindowService* GuiHostedController::WindowService() { return this; } } } /*********************************************************************** .\PLATFORMPROVIDERS\HOSTED\GUIHOSTEDGRAPHICS.CPP ***********************************************************************/ namespace vl { namespace presentation { namespace elements { /*********************************************************************** GuiHostedGraphicsResourceManager ***********************************************************************/ GuiHostedGraphicsResourceManager::GuiHostedGraphicsResourceManager(GuiHostedController* _hostedController, IGuiGraphicsResourceManager* _nativeManager) : hostedController(_hostedController) , nativeManager(_nativeManager) { CHECK_ERROR( !hostedController->hostedResourceManager, L"vl::presentation::elements::GuiHostedGraphicsResourceManager::GuiHostedGraphicsResourceManager(GuiHostedController*, IGuiGraphicsResourceManager*)#" L"GuiHostedGraphicsResourceManager has been created for the same GuiHostedController"); hostedController->hostedResourceManager = this; } GuiHostedGraphicsResourceManager::~GuiHostedGraphicsResourceManager() { hostedController->hostedResourceManager = nullptr; } vint GuiHostedGraphicsResourceManager::RegisterElementType(const WString& elementTypeName) { return nativeManager->RegisterElementType(elementTypeName); } void GuiHostedGraphicsResourceManager::RegisterRendererFactory(vint elementType, Ptr factory) { nativeManager->RegisterRendererFactory(elementType, factory); } IGuiGraphicsRendererFactory* GuiHostedGraphicsResourceManager::GetRendererFactory(vint elementType) { return nativeManager->GetRendererFactory(elementType); } IGuiGraphicsRenderTarget* GuiHostedGraphicsResourceManager::GetRenderTarget(INativeWindow* window) { return nativeManager->GetRenderTarget(hostedController->nativeWindow); } void GuiHostedGraphicsResourceManager::RecreateRenderTarget(INativeWindow* window) { } void GuiHostedGraphicsResourceManager::ResizeRenderTarget(INativeWindow* window) { } IGuiGraphicsLayoutProvider* GuiHostedGraphicsResourceManager::GetLayoutProvider() { return nativeManager->GetLayoutProvider(); } } } } /*********************************************************************** .\PLATFORMPROVIDERS\HOSTED\GUIHOSTEDWINDOW.CPP ***********************************************************************/ namespace vl { namespace presentation { /*********************************************************************** GuiHostedWindow ***********************************************************************/ void GuiHostedWindow::BecomeMainWindow() { proxy = CreateMainHostedWindowProxy(this, controller->nativeWindow); proxy->CheckAndSyncProperties(); } void GuiHostedWindow::BecomeNonMainWindow() { proxy = CreateNonMainHostedWindowProxy(this, controller->nativeWindow); proxy->CheckAndSyncProperties(); } void GuiHostedWindow::BecomeFocusedWindow() { CHECK_ERROR(&wmWindow == controller->wmManager->activeWindow, L"vl::presentation::GuiHostedWindow::BecomeFocusedWindow()#Wrong timing to call this function."); controller->nativeWindow->SetCaretPoint(windowCaretPoint + GetRenderingOffset()); } void GuiHostedWindow::BecomeHoveringWindow() { CHECK_ERROR(this == controller->hoveringWindow, L"vl::presentation::GuiHostedWindow::BecomeFocusedWindow()#Wrong timing to call this function."); controller->nativeWindow->SetWindowCursor(windowCursor); } GuiHostedWindow::GuiHostedWindow(GuiHostedController* _controller, INativeWindow::WindowMode _windowMode) : GuiHostedWindowData(_controller, this, _windowMode) { wmWindow.bounds = { {0,0},{1,1} }; proxy = CreatePlaceholderHostedWindowProxy(this); proxy->CheckAndSyncProperties(); } GuiHostedWindow::~GuiHostedWindow() { for (auto listener : listeners) { listener->Destroyed(); } } bool GuiHostedWindow::IsActivelyRefreshing() { return false; } NativeSize GuiHostedWindow::GetRenderingOffset() { auto pos = wmWindow.bounds.LeftTop(); return { pos.x,pos.y }; } Point GuiHostedWindow::Convert(NativePoint value) { return controller->nativeWindow->Convert(value); } NativePoint GuiHostedWindow::Convert(Point value) { return controller->nativeWindow->Convert(value); } Size GuiHostedWindow::Convert(NativeSize value) { return controller->nativeWindow->Convert(value); } NativeSize GuiHostedWindow::Convert(Size value) { return controller->nativeWindow->Convert(value); } Margin GuiHostedWindow::Convert(NativeMargin value) { return controller->nativeWindow->Convert(value); } NativeMargin GuiHostedWindow::Convert(Margin value) { return controller->nativeWindow->Convert(value); } NativeRect GuiHostedWindow::GetBounds() { return wmWindow.bounds; } void GuiHostedWindow::SetBounds(const NativeRect& bounds) { auto fixedBounds = proxy->FixBounds(bounds); if (wmWindow.bounds == fixedBounds) return; wmWindow.SetBounds(fixedBounds); proxy->UpdateBounds(); } NativeSize GuiHostedWindow::GetClientSize() { return GetBounds().GetSize(); } void GuiHostedWindow::SetClientSize(NativeSize size) { SetBounds({ GetBounds().LeftTop(),size }); } NativeRect GuiHostedWindow::GetClientBoundsInScreen() { return GetBounds(); } WString GuiHostedWindow::GetTitle() { return windowTitle; } void GuiHostedWindow::SetTitle(const WString& title) { if (windowTitle == title) return; windowTitle = title; proxy->UpdateTitle(); } INativeCursor* GuiHostedWindow::GetWindowCursor() { return windowCursor; } void GuiHostedWindow::SetWindowCursor(INativeCursor* cursor) { if (windowCursor == cursor) return; windowCursor = cursor; if (this == controller->hoveringWindow) { controller->nativeWindow->SetWindowCursor(windowCursor); } } NativePoint GuiHostedWindow::GetCaretPoint() { return windowCaretPoint; } void GuiHostedWindow::SetCaretPoint(NativePoint point) { if (windowCaretPoint == point) return; windowCaretPoint = point; if (&wmWindow == controller->wmManager->activeWindow) { controller->nativeWindow->SetCaretPoint(windowCaretPoint + GetRenderingOffset()); } } INativeWindow* GuiHostedWindow::GetParent() { return wmWindow.parent ? wmWindow.parent->id : nullptr; } void GuiHostedWindow::SetParent(INativeWindow* parent) { auto hostedWindow = dynamic_cast(parent); CHECK_ERROR(!parent || hostedWindow, L"vl::presentation::GuiHostedWindow::SetParent(INativeWindow*)#The window is not created by GuiHostedController."); auto parentWindow = hostedWindow ? &hostedWindow->wmWindow : nullptr; if (wmWindow.parent == parentWindow) return; wmWindow.SetParent(parentWindow); } INativeWindow::WindowMode GuiHostedWindow::GetWindowMode() { return windowMode; } void GuiHostedWindow::EnableCustomFrameMode() { if (windowCustomFrameMode) return; windowCustomFrameMode = true; proxy->UpdateCustomFrameMode(); } void GuiHostedWindow::DisableCustomFrameMode() { if (!windowCustomFrameMode) return; windowCustomFrameMode = false; proxy->UpdateCustomFrameMode(); } bool GuiHostedWindow::IsCustomFrameModeEnabled() { return windowCustomFrameMode; } NativeMargin GuiHostedWindow::GetCustomFramePadding() { return controller->nativeWindow->GetCustomFramePadding(); } Ptr GuiHostedWindow::GetIcon() { if (windowIcon) return windowIcon; if (controller->nativeWindow)return controller->nativeWindow->GetIcon(); return nullptr; } void GuiHostedWindow::SetIcon(Ptr icon) { if (windowIcon == icon) return; windowIcon = icon; proxy->UpdateIcon(); } INativeWindow::WindowSizeState GuiHostedWindow::GetSizeState() { return windowSizeState; } void GuiHostedWindow::Show() { EnableActivate(); proxy->Show(); } void GuiHostedWindow::ShowDeactivated() { proxy->ShowDeactivated(); } void GuiHostedWindow::ShowRestored() { proxy->ShowRestored(); } void GuiHostedWindow::ShowMaximized() { proxy->ShowMaximized(); } void GuiHostedWindow::ShowMinimized() { proxy->ShowMinimized(); } void GuiHostedWindow::Hide(bool closeWindow) { if (!wmWindow.visible) return; if (this != controller->mainWindow) { // when the main window is being closed // the underlying INativeWindow will run the process // so we don't need to worry about it here bool cancel = false; for (auto listener : listeners) { listener->BeforeClosing(cancel); if (cancel) return; } for (auto listener : listeners) { listener->AfterClosing(); } } if (closeWindow) { proxy->Close(); } else { proxy->Hide(); } } bool GuiHostedWindow::IsVisible() { return wmWindow.visible; } void GuiHostedWindow::Enable() { if (wmWindow.enabled) return; wmWindow.SetEnabled(true); proxy->UpdateEnabled(); } void GuiHostedWindow::Disable() { if (!wmWindow.enabled) return; wmWindow.SetEnabled(false); proxy->UpdateEnabled(); } bool GuiHostedWindow::IsEnabled() { return wmWindow.enabled; } void GuiHostedWindow::SetActivate() { EnableActivate(); proxy->SetFocus(); } bool GuiHostedWindow::IsActivated() { return wmWindow.active; } bool GuiHostedWindow::IsRenderingAsActivated() { return wmWindow.renderedAsActive; } void GuiHostedWindow::ShowInTaskBar() { if (windowShowInTaskBar) return; windowShowInTaskBar = true; proxy->UpdateShowInTaskBar(); } void GuiHostedWindow::HideInTaskBar() { if (!windowShowInTaskBar) return; windowShowInTaskBar = false; proxy->UpdateShowInTaskBar(); } bool GuiHostedWindow::IsAppearedInTaskBar() { return windowShowInTaskBar; } void GuiHostedWindow::EnableActivate() { if (windowEnabledActivate) return; windowEnabledActivate = true; proxy->UpdateEnabledActivate(); } void GuiHostedWindow::DisableActivate() { if (!windowEnabledActivate) return; windowEnabledActivate = false; proxy->UpdateEnabledActivate(); } bool GuiHostedWindow::IsEnabledActivate() { return windowEnabledActivate; } bool GuiHostedWindow::RequireCapture() { if (controller->capturingWindow) return false; controller->capturingWindow = this; controller->nativeWindow->RequireCapture(); return true; } bool GuiHostedWindow::ReleaseCapture() { if (controller->capturingWindow != this) return false; controller->capturingWindow = nullptr; controller->nativeWindow->ReleaseCapture(); controller->UpdateEnteringWindow(controller->hoveringWindow); return true; } bool GuiHostedWindow::IsCapturing() { return controller->capturingWindow == this; } bool GuiHostedWindow::GetMaximizedBox() { return windowMaximizedBox; } void GuiHostedWindow::SetMaximizedBox(bool visible) { if (windowMaximizedBox == visible) return; windowMaximizedBox = visible; proxy->UpdateMaximizedBox(); } bool GuiHostedWindow::GetMinimizedBox() { return windowMinimizedBox; } void GuiHostedWindow::SetMinimizedBox(bool visible) { if (windowMinimizedBox == visible) return; windowMinimizedBox = visible; proxy->UpdateMinimizedBox(); } bool GuiHostedWindow::GetBorder() { return windowBorder; } void GuiHostedWindow::SetBorder(bool visible) { if (windowBorder == visible) return; windowBorder = visible; proxy->UpdateBorderVisible(); } bool GuiHostedWindow::GetSizeBox() { return windowSizeBox; } void GuiHostedWindow::SetSizeBox(bool visible) { if (windowSizeBox == visible) return; windowSizeBox = visible; proxy->UpdateSizeBox(); } bool GuiHostedWindow::GetIconVisible() { return windowIconVisible; } void GuiHostedWindow::SetIconVisible(bool visible) { if (windowIconVisible == visible) return; windowIconVisible = visible; proxy->UpdateIconVisible(); } bool GuiHostedWindow::GetTitleBar() { return windowTitleBar; } void GuiHostedWindow::SetTitleBar(bool visible) { if (windowTitleBar == visible) return; windowTitleBar = visible; proxy->UpdateTitleBar(); } bool GuiHostedWindow::GetTopMost() { return wmWindow.topMost; } void GuiHostedWindow::SetTopMost(bool topmost) { if (wmWindow.topMost == topmost) return; wmWindow.SetTopMost(topmost); proxy->UpdateTopMost(); } void GuiHostedWindow::SupressAlt() { controller->nativeWindow->SupressAlt(); } bool GuiHostedWindow::InstallListener(INativeWindowListener* listener) { if (listeners.Contains(listener)) { return false; } else { listeners.Add(listener); return true; } } bool GuiHostedWindow::UninstallListener(INativeWindowListener* listener) { if (listeners.Contains(listener)) { listeners.Remove(listener); return true; } else { return false; } } void GuiHostedWindow::RedrawContent() { } } } /*********************************************************************** .\PLATFORMPROVIDERS\HOSTED\GUIHOSTEDWINDOWPROXY_MAIN.CPP ***********************************************************************/ namespace vl { namespace presentation { /*********************************************************************** GuiMainHostedWindowProxy ***********************************************************************/ class GuiMainHostedWindowProxy : public Object , public virtual IGuiHostedWindowProxy { protected: GuiHostedWindowData* data = nullptr; INativeWindow* nativeWindow = nullptr; public: GuiMainHostedWindowProxy(GuiHostedWindowData* _data, INativeWindow* _nativeWindow) : data(_data) , nativeWindow(_nativeWindow) { } void CheckAndSyncProperties() override { for (auto listener : data->listeners) { listener->AssignFrameConfig(data->controller->WindowService()->GetMainWindowFrameConfig()); } if (!data->wmWindow.visible) { data->wmWindow.Show(); } data->wmWindow.SetBounds(FixBounds(data->wmWindow.bounds)); UpdateBounds(); UpdateTitle(); UpdateIcon(); UpdateEnabled(); UpdateTopMost(); UpdateMaximizedBox(); UpdateMinimizedBox(); UpdateBorderVisible(); UpdateSizeBox(); UpdateIconVisible(); UpdateTitleBar(); UpdateShowInTaskBar(); UpdateEnabledActivate(); UpdateCustomFrameMode(); } /*********************************************************************** Visible Properties ***********************************************************************/ NativeRect FixBounds(const NativeRect& bounds) override { return { {},bounds.GetSize() }; } void UpdateBounds() override { nativeWindow->SetClientSize(data->wmWindow.bounds.GetSize()); } void UpdateTitle() override { nativeWindow->SetTitle(data->windowTitle); } void UpdateIcon() override { nativeWindow->SetIcon(data->windowIcon); } void UpdateEnabled() override { // Disabling the main window will not disable the native window // otherwise the whole application is disabled } void UpdateTopMost() override { nativeWindow->SetTopMost(data->wmWindow.topMost); } /*********************************************************************** Border Properties ***********************************************************************/ void UpdateMaximizedBox() override { nativeWindow->SetMaximizedBox(data->windowMaximizedBox); } void UpdateMinimizedBox() override { nativeWindow->SetMinimizedBox(data->windowMinimizedBox); } void UpdateBorderVisible() override { nativeWindow->SetBorder(data->windowBorder); } void UpdateSizeBox() override { nativeWindow->SetSizeBox(data->windowSizeBox); } void UpdateIconVisible() override { nativeWindow->SetIconVisible(data->windowIconVisible); } void UpdateTitleBar() override { nativeWindow->SetTitleBar(data->windowTitleBar); } /*********************************************************************** Behavior Properties ***********************************************************************/ void UpdateShowInTaskBar() override { if (data->windowShowInTaskBar) { nativeWindow->ShowInTaskBar(); } else { nativeWindow->HideInTaskBar(); } } void UpdateEnabledActivate() override { // In hosted mode, the native window is always activatable } void UpdateCustomFrameMode() override { if (data->windowCustomFrameMode) { nativeWindow->EnableCustomFrameMode(); } else { nativeWindow->DisableCustomFrameMode(); } } /*********************************************************************** Show/Hide/Focus In hosted mode, the main window is never closed. Closing the main window causes the native window to be closed. ***********************************************************************/ void Show() override { data->wmWindow.Activate(); nativeWindow->Show(); } void ShowDeactivated() override { data->wmWindow.Deactivate(); nativeWindow->ShowDeactivated(); } void ShowRestored() override { nativeWindow->ShowRestored(); } void ShowMaximized() override { nativeWindow->ShowMaximized(); } void ShowMinimized() override { nativeWindow->ShowMinimized(); } void Hide() override { nativeWindow->Hide(false); } void Close() override { nativeWindow->Hide(true); } void SetFocus() override { data->wmWindow.Activate(); nativeWindow->SetActivate(); } }; /*********************************************************************** Helper ***********************************************************************/ Ptr CreateMainHostedWindowProxy(GuiHostedWindowData* data, INativeWindow* nativeWindow) { return Ptr(new GuiMainHostedWindowProxy(data, nativeWindow)); } } } /*********************************************************************** .\PLATFORMPROVIDERS\HOSTED\GUIHOSTEDWINDOWPROXY_NONMAIN.CPP ***********************************************************************/ namespace vl { namespace presentation { /*********************************************************************** GuiNonMainHostedWindowProxy ***********************************************************************/ class GuiNonMainHostedWindowProxy : public Object , public virtual IGuiHostedWindowProxy { protected: GuiHostedWindowData* data = nullptr; INativeWindow* nativeWindow = nullptr; bool calledAssignFrameConfig = false; public: GuiNonMainHostedWindowProxy(GuiHostedWindowData* _data, INativeWindow* _nativeWindow) : data(_data) , nativeWindow(_nativeWindow) { } void EnsureNoSystemBorderWhenVisible() { if (!data->wmWindow.visible) return; if (data->windowCustomFrameMode) return; CHECK_ERROR( !data->windowBorder && !data->windowSizeBox && !data->windowTitleBar, L"vl::presentation::GuiNonMainHostedWindowProxy::EnsureNoSystemBorder()#" L"For non main window in hosted mode, when custom frame mode is disabled" L"the following window features should also be disabled: " L"Border, SizeBox, TitleBar."); } void CallAssignFrameConfigIfNever() { if (calledAssignFrameConfig) return; for (auto listener : data->listeners) { listener->AssignFrameConfig(data->controller->WindowService()->GetNonMainWindowFrameConfig()); calledAssignFrameConfig = true; } } void CheckAndSyncProperties() override { data->windowMaximizedBox = false; data->windowMinimizedBox = false; CallAssignFrameConfigIfNever(); EnsureNoSystemBorderWhenVisible(); } /*********************************************************************** Visible Properties ***********************************************************************/ NativeRect FixBounds(const NativeRect& bounds) override { auto w = bounds.Width().value; auto h = bounds.Height().value; if (w < 1) w = 1; if (h < 1) h = 1; return { bounds.LeftTop(),{{w},{h}} }; } void UpdateBounds() override { } void UpdateTitle() override { } void UpdateIcon() override { } void UpdateEnabled() override { } void UpdateTopMost() override { } /*********************************************************************** Border Properties ***********************************************************************/ void UpdateMaximizedBox() override { if (data->windowMaximizedBox) { data->windowMaximizedBox = false; } } void UpdateMinimizedBox() override { if (data->windowMinimizedBox) { data->windowMinimizedBox = false; } } void UpdateBorderVisible() override { EnsureNoSystemBorderWhenVisible(); } void UpdateSizeBox() override { EnsureNoSystemBorderWhenVisible(); } void UpdateIconVisible() override { } void UpdateTitleBar() override { EnsureNoSystemBorderWhenVisible(); } /*********************************************************************** Behavior Properties ***********************************************************************/ void UpdateShowInTaskBar() override { } void UpdateEnabledActivate() override { } void UpdateCustomFrameMode() override { EnsureNoSystemBorderWhenVisible(); } /*********************************************************************** Show/Hide/Focus Maximized and Minimized are not available ***********************************************************************/ void Show() override { CallAssignFrameConfigIfNever(); data->wmWindow.SetVisible(true); data->wmWindow.Activate(); EnsureNoSystemBorderWhenVisible(); } void ShowDeactivated() override { CallAssignFrameConfigIfNever(); data->wmWindow.SetVisible(true); EnsureNoSystemBorderWhenVisible(); } void ShowRestored() override { Show(); } void ShowMaximized() override { Show(); } void ShowMinimized() override { Show(); } void Hide() override { data->wmWindow.SetVisible(false); } void Close() override { Hide(); } void SetFocus() override { if (data->wmWindow.visible) { data->wmWindow.Activate(); nativeWindow->SetActivate(); } } }; /*********************************************************************** Helper ***********************************************************************/ Ptr CreateNonMainHostedWindowProxy(GuiHostedWindowData* data, INativeWindow* nativeWindow) { return Ptr(new GuiNonMainHostedWindowProxy(data, nativeWindow)); } } } /*********************************************************************** .\PLATFORMPROVIDERS\HOSTED\GUIHOSTEDWINDOWPROXY_PLACEHOLDER.CPP ***********************************************************************/ namespace vl { namespace presentation { /*********************************************************************** GuiPlaceholderHostedWindowProxy ***********************************************************************/ class GuiPlaceholderHostedWindowProxy : public Object , public virtual IGuiHostedWindowProxy { protected: GuiHostedWindowData* data = nullptr; public: GuiPlaceholderHostedWindowProxy(GuiHostedWindowData* _data) : data(_data) { } void CheckAndSyncProperties() override { } /*********************************************************************** Visible Properties ***********************************************************************/ NativeRect FixBounds(const NativeRect& bounds) override { return bounds; } void UpdateBounds() override { } void UpdateTitle() override { } void UpdateIcon() override { } void UpdateEnabled() override { } void UpdateTopMost() override { } /*********************************************************************** Border Properties ***********************************************************************/ void UpdateMaximizedBox() override { } void UpdateMinimizedBox() override { } void UpdateBorderVisible() override { } void UpdateSizeBox() override { } void UpdateIconVisible() override { } void UpdateTitleBar() override { } /*********************************************************************** Behavior Properties ***********************************************************************/ void UpdateShowInTaskBar() override { } void UpdateEnabledActivate() override { } void UpdateCustomFrameMode() override { } /*********************************************************************** Show/Hide/Focus ***********************************************************************/ void Show() override { CHECK_FAIL(L"vl::presentation::GuiPlaceholderHostedWindowProxy::Show()#This function should not be called."); } void ShowDeactivated() override { CHECK_FAIL(L"vl::presentation::GuiPlaceholderHostedWindowProxy::ShowDeactivated()#This function should not be called."); } void ShowRestored() override { CHECK_FAIL(L"vl::presentation::GuiPlaceholderHostedWindowProxy::ShowRestored()#This function should not be called."); } void ShowMaximized() override { CHECK_FAIL(L"vl::presentation::GuiPlaceholderHostedWindowProxy::ShowMaximized()#This function should not be called."); } void ShowMinimized() override { CHECK_FAIL(L"vl::presentation::GuiPlaceholderHostedWindowProxy::ShowMinimized()#This function should not be called."); } void Hide() override { } void Close() override { } void SetFocus() override { } }; /*********************************************************************** Helper ***********************************************************************/ Ptr CreatePlaceholderHostedWindowProxy(GuiHostedWindowData* data) { return Ptr(new GuiPlaceholderHostedWindowProxy(data)); } } } /*********************************************************************** .\PLATFORMPROVIDERS\REMOTE\GUIREMOTECONTROLLER.CPP ***********************************************************************/ namespace vl::presentation { using namespace collections; /*********************************************************************** GuiRemoteCursor ***********************************************************************/ class GuiRemoteCursor : public Object, public virtual INativeCursor { protected: INativeCursor::SystemCursorType cursorType; public: GuiRemoteCursor(INativeCursor::SystemCursorType _cursorType) : cursorType(_cursorType) {} bool IsSystemCursor() { return true; } SystemCursorType GetSystemCursorType() { return cursorType; } }; /*********************************************************************** GuiRemoteController::INativeResourceService ***********************************************************************/ INativeCursor* GuiRemoteController::GetSystemCursor(INativeCursor::SystemCursorType type) { vint index = cursors.Keys().IndexOf(type); if (index == -1) { auto cursor = Ptr(new GuiRemoteCursor(type)); cursors.Add(type, cursor); return cursor.Obj(); } else { return cursors.Values()[index].Obj(); } } INativeCursor* GuiRemoteController::GetDefaultSystemCursor() { return GetSystemCursor(INativeCursor::SystemCursorType::Arrow); } FontProperties GuiRemoteController::GetDefaultFont() { return remoteFontConfig.defaultFont; } void GuiRemoteController::SetDefaultFont(const FontProperties& value) { remoteFontConfig.defaultFont = value; } void GuiRemoteController::EnumerateFonts(collections::List& fonts) { if (remoteFontConfig.supportedFonts) { CopyFrom(fonts, *remoteFontConfig.supportedFonts.Obj()); } } /*********************************************************************** GuiRemoteController::INativeInputService ***********************************************************************/ void GuiRemoteController::StartTimer() { timerEnabled = true; } void GuiRemoteController::StopTimer() { timerEnabled = false; } bool GuiRemoteController::IsTimerEnabled() { return timerEnabled; } bool GuiRemoteController::IsKeyPressing(VKEY code) { vint idIsKeyPressing = remoteMessages.RequestIOIsKeyPressing(code); bool disconnected = false; remoteMessages.Submit(disconnected); if (disconnected) return false; bool result = remoteMessages.RetrieveIOIsKeyPressing(idIsKeyPressing); return result; } bool GuiRemoteController::IsKeyToggled(VKEY code) { vint idIsKeyToggled = remoteMessages.RequestIOIsKeyToggled(code); bool disconnected = false; remoteMessages.Submit(disconnected); if (disconnected) return false; bool result = remoteMessages.RetrieveIOIsKeyToggled(idIsKeyToggled); return result; } void GuiRemoteController::EnsureKeyInitialized() { if (keyInitialized) return; keyInitialized = true; #define INITIALIZE_KEY_NAME(NAME, TEXT)\ keyNames.Add(VKEY::KEY_ ## NAME, WString::Unmanaged(TEXT));\ if (!keyCodes.Keys().Contains(WString::Unmanaged(TEXT))) keyCodes.Add(WString::Unmanaged(TEXT), VKEY::KEY_ ## NAME);\ GUI_DEFINE_KEYBOARD_WINDOWS_NAME(INITIALIZE_KEY_NAME) #undef INITIALIZE_KEY_NAME } WString GuiRemoteController::GetKeyName(VKEY code) { EnsureKeyInitialized(); vint index = keyNames.Keys().IndexOf(code); return index == -1 ? WString::Unmanaged(L"?") : keyNames.Values()[index]; } VKEY GuiRemoteController::GetKey(const WString& name) { EnsureKeyInitialized(); vint index = keyCodes.Keys().IndexOf(name); return index == -1 ? VKEY::KEY_UNKNOWN : keyCodes.Values()[index]; } void GuiRemoteController::UpdateGlobalShortcutKey() { auto hotKeys = Ptr(new List); for (auto [id, entry] : hotKeyIds) { remoteprotocol::GlobalShortcutKey key; key.id = id; key.ctrl = entry.get<0>(); key.shift = entry.get<1>(); key.alt = entry.get<2>(); key.code = entry.get<3>(); hotKeys->Add(key); } remoteMessages.RequestIOUpdateGlobalShortcutKey(hotKeys); } vint GuiRemoteController::RegisterGlobalShortcutKey(bool ctrl, bool shift, bool alt, VKEY key) { HotKeyEntry entry = { ctrl,shift,alt,key }; if (hotKeySet.Contains(entry)) return (vint)NativeGlobalShortcutKeyResult::Occupied; vint id = ++usedHotKeys; hotKeySet.Add(entry); hotKeyIds.Add(id, entry); UpdateGlobalShortcutKey(); bool disconnected = false; remoteMessages.Submit(disconnected); // there is no result from this request, assuming succeeded return id; } bool GuiRemoteController::UnregisterGlobalShortcutKey(vint id) { vint index = hotKeyIds.Keys().IndexOf(id); if (index == -1) return false; auto entry = hotKeyIds.Values()[index]; hotKeyIds.Remove(id); hotKeySet.Remove(entry); UpdateGlobalShortcutKey(); bool disconnected = false; remoteMessages.Submit(disconnected); // there is no result from this request, assuming succeeded return true; } /*********************************************************************** GuiRemoteController::INativeScreenService ***********************************************************************/ vint GuiRemoteController::GetScreenCount() { return 1; } INativeScreen* GuiRemoteController::GetScreen(vint index) { CHECK_ERROR(index == 0, L"vl::presentation::GuiRemoteController::GetScreen(vint)#Index out of range."); return this; } INativeScreen* GuiRemoteController::GetScreen(INativeWindow* window) { return this; } /*********************************************************************** GuiHostedController::INativeScreen ***********************************************************************/ NativeRect GuiRemoteController::GetBounds() { return remoteScreenConfig.bounds; } NativeRect GuiRemoteController::GetClientBounds() { return remoteScreenConfig.clientBounds; } WString GuiRemoteController::GetName() { return WString::Unmanaged(L"GacUI Virtual Remote Screen"); } bool GuiRemoteController::IsPrimary() { return true; } double GuiRemoteController::GetScalingX() { return remoteScreenConfig.scalingX; } double GuiRemoteController::GetScalingY() { return remoteScreenConfig.scalingY; } /*********************************************************************** GuiRemoteController::INativeWindowService ***********************************************************************/ const NativeWindowFrameConfig& GuiRemoteController::GetMainWindowFrameConfig() { return NativeWindowFrameConfig::Default; } const NativeWindowFrameConfig& GuiRemoteController::GetNonMainWindowFrameConfig() { return NativeWindowFrameConfig::Default; } INativeWindow* GuiRemoteController::CreateNativeWindow(INativeWindow::WindowMode windowMode) { CHECK_ERROR(!windowCreated, L"vl::presentation::GuiRemoteController::CreateNativeWindow(INativeWindow::WindowMode)#GuiHostedController is not supposed to call this function for twice."); windowCreated = true; remoteWindow.windowMode = windowMode; callbackService.InvokeNativeWindowCreated(&remoteWindow); return &remoteWindow; } void GuiRemoteController::DestroyNativeWindow(INativeWindow* window) { CHECK_ERROR(!windowDestroyed, L"vl::presentation::GuiRemoteController::CreateNativeWindow(INativeWindow::WindowMode)#GuiHostedController is not supposed to call this function for twice."); windowDestroyed = true; for (auto l : remoteWindow.listeners) l->Closed(); for (auto l : remoteWindow.listeners) l->Destroying(); callbackService.InvokeNativeWindowDestroying(&remoteWindow); for (auto l : remoteWindow.listeners) l->Destroyed(); connectionStopped = true; } INativeWindow* GuiRemoteController::GetMainWindow() { return windowCreated && !windowDestroyed ? &remoteWindow : nullptr; } INativeWindow* GuiRemoteController::GetWindow(NativePoint location) { return GetMainWindow(); } void GuiRemoteController::Run(INativeWindow* window) { CHECK_ERROR(window == &remoteWindow, L"vl::presentation::GuiRemoteController::Run(INativeWindow*)#GuiHostedController should call this function with the native window."); applicationRunning = true; window->Show(); while (RunOneCycle()); asyncService.ExecuteAsyncTasks(); applicationRunning = false; } bool GuiRemoteController::RunOneCycle() { if (!connectionStopped) { remoteProtocol->ProcessRemoteEvents(); bool disconnected = false; remoteMessages.Submit(disconnected); if (timerEnabled && !disconnected) { callbackService.InvokeGlobalTimer(); } asyncService.ExecuteAsyncTasks(); } return !connectionStopped; } /*********************************************************************** GuiRemoteController (events) ***********************************************************************/ void GuiRemoteController::OnControllerConnect() { UpdateGlobalShortcutKey(); vint idGetFontConfig = remoteMessages.RequestControllerGetFontConfig(); vint idGetScreenConfig = remoteMessages.RequestControllerGetScreenConfig(); bool disconnected = false; remoteMessages.Submit(disconnected); if (disconnected) return; remoteFontConfig = remoteMessages.RetrieveControllerGetFontConfig(idGetFontConfig); remoteScreenConfig = remoteMessages.RetrieveControllerGetScreenConfig(idGetScreenConfig); remoteWindow.OnControllerConnect(); imageService.OnControllerConnect(); resourceManager->OnControllerConnect(); } void GuiRemoteController::OnControllerDisconnect() { remoteWindow.OnControllerDisconnect(); imageService.OnControllerDisconnect(); resourceManager->OnControllerDisconnect(); } void GuiRemoteController::OnControllerRequestExit() { remoteWindow.Hide(true); } void GuiRemoteController::OnControllerForceExit() { connectionForcedToStop = true; remoteWindow.Hide(true); } void GuiRemoteController::OnControllerScreenUpdated(const remoteprotocol::ScreenConfig& arguments) { remoteScreenConfig = arguments; remoteWindow.OnControllerScreenUpdated(arguments); } /*********************************************************************** GuiRemoteController ***********************************************************************/ GuiRemoteController::GuiRemoteController(IGuiRemoteProtocol* _remoteProtocol) : remoteProtocol(_remoteProtocol) , remoteMessages(this) , remoteEvents(this) , remoteWindow(this) , imageService(this) { } GuiRemoteController::~GuiRemoteController() { } void GuiRemoteController::Initialize() { remoteProtocol->Initialize(&remoteEvents); imageService.Initialize(); } void GuiRemoteController::Finalize() { imageService.Finalize(); remoteMessages.RequestControllerConnectionStopped(); bool disconnected = false; remoteMessages.Submit(disconnected); // there is no result from this request, assuming succeeded } /*********************************************************************** GuiRemoteController (INativeController) ***********************************************************************/ INativeCallbackService* GuiRemoteController::CallbackService() { return &callbackService; } INativeResourceService* GuiRemoteController::ResourceService() { return this; } INativeAsyncService* GuiRemoteController::AsyncService() { return &asyncService; } INativeClipboardService* GuiRemoteController::ClipboardService() { CHECK_FAIL(L"Not Implemented!"); } INativeImageService* GuiRemoteController::ImageService() { return &imageService; } INativeInputService* GuiRemoteController::InputService() { return this; } INativeDialogService* GuiRemoteController::DialogService() { // Use FakeDialogServiceBase return nullptr; } WString GuiRemoteController::GetExecutablePath() { return remoteProtocol->GetExecutablePath(); } INativeScreenService* GuiRemoteController::ScreenService() { return this; } INativeWindowService* GuiRemoteController::WindowService() { return this; } } /*********************************************************************** .\PLATFORMPROVIDERS\REMOTE\GUIREMOTECONTROLLERSETUP.CPP ***********************************************************************/ using namespace vl; using namespace vl::presentation; using namespace vl::presentation::elements; /*********************************************************************** SetupRemoteNativeController ***********************************************************************/ extern void GuiApplicationMain(); int SetupRemoteNativeController(vl::presentation::IGuiRemoteProtocol* protocol) { GuiRemoteController remoteController(protocol); GuiHostedController hostedController(&remoteController); GuiRemoteGraphicsResourceManager remoteResourceManager(&remoteController, &hostedController); GuiHostedGraphicsResourceManager hostedResourceManager(&hostedController, &remoteResourceManager); SetNativeController(&hostedController); SetGuiGraphicsResourceManager(&hostedResourceManager); SetHostedApplication(hostedController.GetHostedApplication()); remoteController.Initialize(); remoteResourceManager.Initialize(); hostedController.Initialize(); GuiApplicationMain(); hostedController.Finalize(); remoteResourceManager.Finalize(); remoteController.Finalize(); SetHostedApplication(nullptr); SetGuiGraphicsResourceManager(nullptr); SetNativeController(nullptr); return 0; } /*********************************************************************** .\PLATFORMPROVIDERS\REMOTE\GUIREMOTEEVENTS.CPP ***********************************************************************/ namespace vl::presentation { /*********************************************************************** GuiRemoteMessages ***********************************************************************/ GuiRemoteMessages::GuiRemoteMessages(GuiRemoteController* _remote) : remote(_remote) { } GuiRemoteMessages::~GuiRemoteMessages() { } void GuiRemoteMessages::Submit(bool& disconnected) { remote->remoteProtocol->Submit(disconnected); } /*********************************************************************** GuiRemoteMessages (messages) ***********************************************************************/ #define MESSAGE_NOREQ_NORES(NAME, REQUEST, RESPONSE)\ void GuiRemoteMessages::Request ## NAME()\ {\ remote->remoteProtocol->Request ## NAME();\ }\ #define MESSAGE_NOREQ_RES(NAME, REQUEST, RESPONSE)\ vint GuiRemoteMessages::Request ## NAME()\ {\ vint requestId = ++id;\ remote->remoteProtocol->Request ## NAME(requestId);\ return requestId;\ }\ #define MESSAGE_REQ_NORES(NAME, REQUEST, RESPONSE)\ void GuiRemoteMessages::Request ## NAME(const REQUEST& arguments)\ {\ remote->remoteProtocol->Request ## NAME(arguments);\ }\ #define MESSAGE_REQ_RES(NAME, REQUEST, RESPONSE)\ vint GuiRemoteMessages::Request ## NAME(const REQUEST& arguments)\ {\ vint requestId = ++id;\ remote->remoteProtocol->Request ## NAME(requestId, arguments);\ return requestId;\ }\ #define MESSAGE_HANDLER(NAME, REQUEST, RESPONSE, REQTAG, RESTAG, ...) MESSAGE_ ## REQTAG ## _ ## RESTAG(NAME, REQUEST, RESPONSE) GACUI_REMOTEPROTOCOL_MESSAGES(MESSAGE_HANDLER) #undef MESSAGE_HANDLER #undef MESSAGE_REQ_RES #undef MESSAGE_REQ_NORES #undef MESSAGE_NOREQ_RES #undef MESSAGE_NOREQ_NORES #define MESSAGE_NORES(NAME, RESPONSE) #define MESSAGE_RES(NAME, RESPONSE)\ void GuiRemoteMessages::Respond ## NAME(vint id, const RESPONSE& arguments)\ {\ response ## NAME.Add(id, arguments);\ }\ RESPONSE GuiRemoteMessages::Retrieve ## NAME(vint id)\ {\ RESPONSE response = response ## NAME[id];\ response ## NAME.Remove(id);\ return response;\ }\ #define MESSAGE_HANDLER(NAME, REQUEST, RESPONSE, REQTAG, RESTAG, ...) MESSAGE_ ## RESTAG(NAME, RESPONSE) GACUI_REMOTEPROTOCOL_MESSAGES(MESSAGE_HANDLER) #undef MESSAGE_HANDLER #undef MESSAGE_RES #undef MESSAGE_NORES /*********************************************************************** GuiRemoteEvents ***********************************************************************/ GuiRemoteEvents::GuiRemoteEvents(GuiRemoteController* _remote) : remote(_remote) { } GuiRemoteEvents::~GuiRemoteEvents() { } /*********************************************************************** GuiRemoteEvents (messages) ***********************************************************************/ #define MESSAGE_NORES(NAME, RESPONSE) #define MESSAGE_RES(NAME, RESPONSE)\ void GuiRemoteEvents::Respond ## NAME(vint id, const RESPONSE& arguments)\ {\ remote->remoteMessages.Respond ## NAME(id, arguments);\ }\ #define MESSAGE_HANDLER(NAME, REQUEST, RESPONSE, REQTAG, RESTAG, ...) MESSAGE_ ## RESTAG(NAME, RESPONSE) GACUI_REMOTEPROTOCOL_MESSAGES(MESSAGE_HANDLER) #undef MESSAGE_HANDLER #undef MESSAGE_RES #undef MESSAGE_NORES /*********************************************************************** GuiRemoteEvents (events) ***********************************************************************/ void GuiRemoteEvents::OnControllerConnect() { remote->remoteMessages.RequestControllerConnectionEstablished(); remote->OnControllerConnect(); bool disconnected = false; remote->remoteMessages.Submit(disconnected); // there is no result from this request, assuming succeeded // if disconnected, OnControllerDisconnect will be called } void GuiRemoteEvents::OnControllerDisconnect() { remote->OnControllerDisconnect(); } void GuiRemoteEvents::OnControllerRequestExit() { remote->OnControllerRequestExit(); } void GuiRemoteEvents::OnControllerForceExit() { remote->OnControllerForceExit(); } void GuiRemoteEvents::OnControllerScreenUpdated(const remoteprotocol::ScreenConfig& arguments) { remote->OnControllerScreenUpdated(arguments); } void GuiRemoteEvents::OnWindowBoundsUpdated(const remoteprotocol::WindowSizingConfig& arguments) { remote->remoteWindow.OnWindowBoundsUpdated(arguments); } void GuiRemoteEvents::OnWindowActivatedUpdated(const bool& arguments) { remote->remoteWindow.OnWindowActivatedUpdated(arguments); } void GuiRemoteEvents::OnIOGlobalShortcutKey(const vint& arguments) { remote->callbackService.InvokeGlobalShortcutKeyActivated(arguments); } void GuiRemoteEvents::OnIOButtonDown(const remoteprotocol::IOMouseInfoWithButton& arguments) { switch (arguments.button) { case remoteprotocol::IOMouseButton::Left: for (auto l : remote->remoteWindow.listeners) l->LeftButtonDown(arguments.info); break; case remoteprotocol::IOMouseButton::Middle: for (auto l : remote->remoteWindow.listeners) l->MiddleButtonDown(arguments.info); break; case remoteprotocol::IOMouseButton::Right: for (auto l : remote->remoteWindow.listeners) l->RightButtonDown(arguments.info); break; default: CHECK_FAIL(L"vl::presentation::GuiRemoteEvents::OnIOButtonDown(const IOMouseInfoWithButton&)#Unrecognized button."); } } void GuiRemoteEvents::OnIOButtonDoubleClick(const remoteprotocol::IOMouseInfoWithButton& arguments) { switch (arguments.button) { case remoteprotocol::IOMouseButton::Left: for (auto l : remote->remoteWindow.listeners) l->LeftButtonDoubleClick(arguments.info); break; case remoteprotocol::IOMouseButton::Middle: for (auto l : remote->remoteWindow.listeners) l->MiddleButtonDoubleClick(arguments.info); break; case remoteprotocol::IOMouseButton::Right: for (auto l : remote->remoteWindow.listeners) l->RightButtonDoubleClick(arguments.info); break; default: CHECK_FAIL(L"vl::presentation::GuiRemoteEvents::OnIOButtonDoubleClick(const IOMouseInfoWithButton&)#Unrecognized button."); } } void GuiRemoteEvents::OnIOButtonUp(const remoteprotocol::IOMouseInfoWithButton& arguments) { switch (arguments.button) { case remoteprotocol::IOMouseButton::Left: for (auto l : remote->remoteWindow.listeners) l->LeftButtonUp(arguments.info); break; case remoteprotocol::IOMouseButton::Middle: for (auto l : remote->remoteWindow.listeners) l->MiddleButtonUp(arguments.info); break; case remoteprotocol::IOMouseButton::Right: for (auto l : remote->remoteWindow.listeners) l->RightButtonUp(arguments.info); break; default: CHECK_FAIL(L"vl::presentation::GuiRemoteEvents::OnIOButtonUp(const IOMouseInfoWithButton&)#Unrecognized button."); } } void GuiRemoteEvents::OnIOHWheel(const NativeWindowMouseInfo& arguments) { for (auto l : remote->remoteWindow.listeners) l->HorizontalWheel(arguments); } void GuiRemoteEvents::OnIOVWheel(const NativeWindowMouseInfo& arguments) { for (auto l : remote->remoteWindow.listeners) l->VerticalWheel(arguments); } void GuiRemoteEvents::OnIOMouseMoving(const NativeWindowMouseInfo& arguments) { for (auto l : remote->remoteWindow.listeners) l->MouseMoving(arguments); } void GuiRemoteEvents::OnIOMouseEntered() { for (auto l : remote->remoteWindow.listeners) l->MouseEntered(); } void GuiRemoteEvents::OnIOMouseLeaved() { for (auto l : remote->remoteWindow.listeners) l->MouseLeaved(); } void GuiRemoteEvents::OnIOKeyDown(const NativeWindowKeyInfo& arguments) { for (auto l : remote->remoteWindow.listeners) l->KeyDown(arguments); } void GuiRemoteEvents::OnIOKeyUp(const NativeWindowKeyInfo& arguments) { for (auto l : remote->remoteWindow.listeners) l->KeyUp(arguments); } void GuiRemoteEvents::OnIOChar(const NativeWindowCharInfo& arguments) { for (auto l : remote->remoteWindow.listeners) l->Char(arguments); } } /*********************************************************************** .\PLATFORMPROVIDERS\REMOTE\GUIREMOTEGRAPHICS.CPP ***********************************************************************/ namespace vl::presentation::elements { using namespace collections; using namespace compositions; /*********************************************************************** GuiRemoteGraphicsRenderTarget ***********************************************************************/ GuiRemoteGraphicsRenderTarget::HitTestResult GuiRemoteGraphicsRenderTarget::GetHitTestResultFromGenerator(reflection::DescriptableObject* generator) { if (auto composition = dynamic_cast(generator)) { auto hitTestResult = composition->GetAssociatedHitTestResult(); if (hitTestResult != INativeWindowListener::NoDecision) { if (auto graphicsHost = composition->GetRelatedGraphicsHost()) { if (auto nativeWindow = graphicsHost->GetNativeWindow()) { if (nativeWindow == GetCurrentController()->WindowService()->GetMainWindow()) { return hitTestResult; } } } } } return INativeWindowListener::NoDecision; } Nullable GuiRemoteGraphicsRenderTarget::GetCursorFromGenerator(reflection::DescriptableObject* generator) { if (auto composition = dynamic_cast(generator)) { if (auto cursor = composition->GetAssociatedCursor()) { if (auto graphicsHost = composition->GetRelatedGraphicsHost()) { if (auto nativeWindow = graphicsHost->GetNativeWindow()) { if (nativeWindow == GetCurrentController()->WindowService()->GetMainWindow()) { return cursor->GetSystemCursorType(); } } } } } return {}; } void GuiRemoteGraphicsRenderTarget::StartRenderingOnNativeWindow() { CHECK_ERROR(hitTestResults.Count() == 0, L"vl::presentation::elements::GuiRemoteGraphicsRenderTarget::StartRenderingOnNativeWindow()#Internal error: hit test result stack is not cleared."); canvasSize = remote->remoteWindow.GetClientSize(); clipperValidArea = GetClipper(); renderingBatchId++; if (destroyedRenderers.Count() > 0) { auto ids = Ptr(new List); CopyFrom(*ids.Obj(), destroyedRenderers); destroyedRenderers.Clear(); remote->remoteMessages.RequestRendererDestroyed(ids); } if (createdRenderers.Count() > 0) { auto ids = Ptr(new List); for (auto id : createdRenderers) { ids->Add({ id,renderers[id]->GetRendererType() }); } createdRenderers.Clear(); remote->remoteMessages.RequestRendererCreated(ids); } for (auto [id, renderer] : renderers) { if (renderer->IsUpdated()) { renderer->SendUpdateElementMessages(false); if (renderer->NeedUpdateMinSizeFromCache()) { if (!renderersAskingForCache.Contains(renderer)) { renderersAskingForCache.Add(renderer); } } renderer->ResetUpdated(); } } { remoteprotocol::ElementBeginRendering arguments; arguments.frameId = ++usedFrameIds; remote->remoteMessages.RequestRendererBeginRendering(arguments); } } RenderTargetFailure GuiRemoteGraphicsRenderTarget::StopRenderingOnNativeWindow() { CHECK_ERROR(hitTestResults.Count() == 0, L"vl::presentation::elements::GuiRemoteGraphicsRenderTarget::StartRenderingOnNativeWindow()#Internal error: hit test result stack is not cleared."); vint idRendering = remote->remoteMessages.RequestRendererEndRendering(); bool disconnected = false; remote->remoteMessages.Submit(disconnected); if (disconnected) return RenderTargetFailure::None; auto measuring = remote->remoteMessages.RetrieveRendererEndRendering(idRendering); bool minSizeChanged = false; if (measuring.fontHeights) { for (auto&& fontHeight : *measuring.fontHeights.Obj()) { auto key = Tuple(fontHeight.fontFamily, fontHeight.fontSize); if (!fontHeights.Keys().Contains(key)) { fontHeights.Add(key, fontHeight.height); } } // TODO: (enumerable) foreach:indexed(alterable(reversed)) for (vint i = renderersAskingForCache.Count() - 1; i >= 0; i--) { auto renderer = renderersAskingForCache[i]; auto oldMinSize = renderer->GetRenderer()->GetMinSize(); renderer->TryFetchMinSizeFromCache(); if (renderer->IsRenderedInLastBatch() && oldMinSize != renderer->GetRenderer()->GetMinSize()) { minSizeChanged = true; } if (!renderer->NeedUpdateMinSizeFromCache()) { renderersAskingForCache.RemoveAt(i); } } } if (measuring.createdImages) { for (auto&& imageMetadata : *measuring.createdImages.Obj()) { auto image = remote->imageService.GetImage(imageMetadata.id); image->UpdateFromImageMetadata(imageMetadata); } } if (measuring.minSizes) { for (auto&& minSize : *measuring.minSizes.Obj()) { vint index = renderers.Keys().IndexOf(minSize.id); if (index != -1) { auto renderer = renderers.Values()[index]; auto oldMinSize = renderer->GetRenderer()->GetMinSize(); renderer->UpdateMinSize(minSize.minSize); if (renderer->IsRenderedInLastBatch() && oldMinSize != renderer->GetRenderer()->GetMinSize()) { minSizeChanged = true; } } } } if (minSizeChanged) { hostedController->RequestRefresh(); } if (canvasSize == remote->remoteWindow.GetClientSize()) { return RenderTargetFailure::None; } else { return RenderTargetFailure::ResizeWhileRendering; } } Size GuiRemoteGraphicsRenderTarget::GetCanvasSize() { return remote->remoteWindow.Convert(canvasSize); } void GuiRemoteGraphicsRenderTarget::AfterPushedClipper(Rect clipper, Rect validArea, reflection::DescriptableObject* generator) { clipperValidArea = validArea; auto hitTestResult = GetHitTestResultFromGenerator(generator); auto cursor = GetCursorFromGenerator(generator); remoteprotocol::ElementBoundary arguments; if (hitTestResult != INativeWindowListener::NoDecision) { if (hitTestResults.Count() == 0 || hitTestResults[hitTestResults.Count() - 1] != hitTestResult) { arguments.hitTestResult = hitTestResult; } hitTestResults.Add(hitTestResult); } if (cursor) { if (cursors.Count() == 0 || cursors[cursors.Count() - 1] != cursor.Value()) { arguments.cursor = cursor.Value(); } cursors.Add(cursor.Value()); } if (arguments.hitTestResult || arguments.cursor) { // GetHitTestResultFromGenerator or GetCursorFromGenerator ensures generator must be a composition auto composition = dynamic_cast(generator); if (composition->remoteId == -1) { composition->remoteId = ++usedCompositionIds; } arguments.id = composition->remoteId; arguments.bounds = clipper; arguments.areaClippedBySelf = validArea; remote->remoteMessages.RequestRendererBeginBoundary(arguments); } } void GuiRemoteGraphicsRenderTarget::AfterPushedClipperAndBecameInvalid(Rect clipper, reflection::DescriptableObject* generator) { clipperValidArea.Reset(); } void GuiRemoteGraphicsRenderTarget::AfterPoppedClipperAndBecameValid(Rect validArea, bool clipperExists, reflection::DescriptableObject* generator) { clipperValidArea = validArea; } void GuiRemoteGraphicsRenderTarget::AfterPoppedClipper(Rect validArea, bool clipperExists, reflection::DescriptableObject* generator) { clipperValidArea = validArea; auto hitTestResult = GetHitTestResultFromGenerator(generator); auto cursor = GetCursorFromGenerator(generator); bool needEndBoundary = false; if (hitTestResult != INativeWindowListener::NoDecision) { hitTestResults.RemoveAt(hitTestResults.Count() - 1); if (hitTestResults.Count() == 0 || hitTestResults[hitTestResults.Count() - 1] != hitTestResult) { needEndBoundary = true; } } if (cursor) { cursors.RemoveAt(cursors.Count() - 1); if (cursors.Count() == 0 || cursors[cursors.Count() - 1] != cursor.Value()) { needEndBoundary = true; } } if (needEndBoundary) { remote->remoteMessages.RequestRendererEndBoundary(); } } GuiRemoteGraphicsRenderTarget::GuiRemoteGraphicsRenderTarget(GuiRemoteController* _remote, GuiHostedController* _hostedController) : remote(_remote) , hostedController(_hostedController) { } GuiRemoteGraphicsRenderTarget::~GuiRemoteGraphicsRenderTarget() { } void GuiRemoteGraphicsRenderTarget::OnControllerConnect() { fontHeights.Clear(); renderersAskingForCache.Clear(); if (renderers.Count() > 0) { { auto ids = Ptr(new List); for (auto renderer : renderers.Values()) { ids->Add({ renderer->GetID(),renderer->GetRendererType() }); renderer->NotifyMinSizeCacheInvalidated(); } createdRenderers.Clear(); remote->remoteMessages.RequestRendererCreated(ids); } for (auto renderer : renderers.Values()) { renderer->SendUpdateElementMessages(true); if (renderer->NeedUpdateMinSizeFromCache()) { renderersAskingForCache.Add(renderer); } renderer->ResetUpdated(); } } } void GuiRemoteGraphicsRenderTarget::OnControllerDisconnect() { } GuiRemoteMessages& GuiRemoteGraphicsRenderTarget::GetRemoteMessages() { return remote->remoteMessages; } vint GuiRemoteGraphicsRenderTarget::AllocateNewElementId() { return ++usedElementIds; } void GuiRemoteGraphicsRenderTarget::RegisterRenderer(elements_remoteprotocol::IGuiRemoteProtocolElementRender* renderer) { vint id = renderer->GetID(); if (!createdRenderers.Contains(id)) { renderers.Add(id, renderer); createdRenderers.Add(id); } } void GuiRemoteGraphicsRenderTarget::UnregisterRenderer(elements_remoteprotocol::IGuiRemoteProtocolElementRender* renderer) { vint id = renderer->GetID(); renderers.Remove(id); renderersAskingForCache.Remove(renderer); { vint index = createdRenderers.IndexOf(id); if (index == -1) { if (!destroyedRenderers.Contains(id)) { destroyedRenderers.Add(id); } } else { createdRenderers.RemoveAt(index); } } } Rect GuiRemoteGraphicsRenderTarget::GetClipperValidArea() { return clipperValidArea.Value(); } /*********************************************************************** GuiRemoteGraphicsResourceManager ***********************************************************************/ GuiRemoteGraphicsResourceManager::GuiRemoteGraphicsResourceManager(GuiRemoteController* _remote, GuiHostedController* _hostedController) : remote(_remote) , renderTarget(_remote, _hostedController) , hostedController(_hostedController) { remote->resourceManager = this; } GuiRemoteGraphicsResourceManager::~GuiRemoteGraphicsResourceManager() { } void GuiRemoteGraphicsResourceManager::Initialize() { elements_remoteprotocol::GuiFocusRectangleElementRenderer::Register(); elements_remoteprotocol::GuiSolidBorderElementRenderer::Register(); elements_remoteprotocol::Gui3DBorderElementRenderer::Register(); elements_remoteprotocol::Gui3DSplitterElementRenderer::Register(); elements_remoteprotocol::GuiSolidBackgroundElementRenderer::Register(); elements_remoteprotocol::GuiGradientBackgroundElementRenderer::Register(); elements_remoteprotocol::GuiInnerShadowElementRenderer::Register(); elements_remoteprotocol::GuiSolidLabelElementRenderer::Register(); elements_remoteprotocol::GuiImageFrameElementRenderer::Register(); elements_remoteprotocol::GuiPolygonElementRenderer::Register(); elements_remoteprotocol::GuiColorizedTextElementRenderer::Register(); elements::GuiDocumentElement::GuiDocumentElementRenderer::Register(); } void GuiRemoteGraphicsResourceManager::Finalize() { } void GuiRemoteGraphicsResourceManager::OnControllerConnect() { renderTarget.OnControllerConnect(); hostedController->RequestRefresh(); } void GuiRemoteGraphicsResourceManager::OnControllerDisconnect() { renderTarget.OnControllerDisconnect(); } IGuiGraphicsRenderTarget* GuiRemoteGraphicsResourceManager::GetRenderTarget(INativeWindow* window) { CHECK_ERROR(window == &remote->remoteWindow, L"vl::presentation::elements::GuiRemoteGraphicsResourceManager::GetRenderTarget(INativeWindow*)#GuiHostedController should call this function with the native window."); return &renderTarget; } void GuiRemoteGraphicsResourceManager::RecreateRenderTarget(INativeWindow* window) { } void GuiRemoteGraphicsResourceManager::ResizeRenderTarget(INativeWindow* window) { } IGuiGraphicsLayoutProvider* GuiRemoteGraphicsResourceManager::GetLayoutProvider() { CHECK_FAIL(L"Not Implemented!"); } } /*********************************************************************** .\PLATFORMPROVIDERS\REMOTE\GUIREMOTEGRAPHICS_BASICELEMENTS.CPP ***********************************************************************/ namespace vl::presentation::elements_remoteprotocol { using namespace collections; using namespace remoteprotocol; /*********************************************************************** GuiSolidBorderElementRenderer ***********************************************************************/ #define RENDERER_TEMPLATE_HEADER template #define RENDERER_CLASS_TYPE GuiRemoteProtocolElementRenderer RENDERER_TEMPLATE_HEADER void RENDERER_CLASS_TYPE::InitializeInternal() { } RENDERER_TEMPLATE_HEADER void RENDERER_CLASS_TYPE::FinalizeInternal() { if (this->renderTarget && id != -1) { this->renderTarget->UnregisterRenderer(this); id = -1; } } RENDERER_TEMPLATE_HEADER void RENDERER_CLASS_TYPE::RenderTargetChangedInternal(GuiRemoteGraphicsRenderTarget* oldRenderTarget, GuiRemoteGraphicsRenderTarget* newRenderTarget) { if (oldRenderTarget == newRenderTarget) return; if (oldRenderTarget && id != -1) { oldRenderTarget->UnregisterRenderer(this); id = -1; } if (newRenderTarget) { id = newRenderTarget->AllocateNewElementId(); newRenderTarget->RegisterRenderer(this); updated = true; renderTargetChanged = true; } } RENDERER_TEMPLATE_HEADER IGuiGraphicsRenderer* RENDERER_CLASS_TYPE::GetRenderer() { return this; } RENDERER_TEMPLATE_HEADER vint RENDERER_CLASS_TYPE::GetID() { return id; } RENDERER_TEMPLATE_HEADER remoteprotocol::RendererType RENDERER_CLASS_TYPE::GetRendererType() { return _RendererType; } RENDERER_TEMPLATE_HEADER bool RENDERER_CLASS_TYPE::IsUpdated() { return updated; } RENDERER_TEMPLATE_HEADER void RENDERER_CLASS_TYPE::ResetUpdated() { updated = false; renderTargetChanged = false; } RENDERER_TEMPLATE_HEADER bool RENDERER_CLASS_TYPE::IsRenderedInLastBatch() { return this->renderTarget && this->renderTarget->renderingBatchId == renderingBatchId; } RENDERER_TEMPLATE_HEADER bool RENDERER_CLASS_TYPE::NeedUpdateMinSizeFromCache() { return false; } RENDERER_TEMPLATE_HEADER void RENDERER_CLASS_TYPE::TryFetchMinSizeFromCache() { CHECK_FAIL(L"vl::presentation::elements_remoteprotocol::GuiRemoteProtocolElementRenderer::TryUpdateFromCache()#This function should not be called."); } RENDERER_TEMPLATE_HEADER void RENDERER_CLASS_TYPE::UpdateMinSize(Size size) { CHECK_FAIL(L"vl::presentation::elements_remoteprotocol::GuiRemoteProtocolElementRenderer::UpdateMinSize(Size)#This function should not be called."); } RENDERER_TEMPLATE_HEADER void RENDERER_CLASS_TYPE::NotifyMinSizeCacheInvalidated() { } RENDERER_TEMPLATE_HEADER void RENDERER_CLASS_TYPE::Render(Rect bounds) { remoteprotocol::ElementRendering arguments; arguments.id = id; arguments.bounds = bounds; arguments.areaClippedByParent = this->renderTarget->GetClipperValidArea(); this->renderTarget->GetRemoteMessages().RequestRendererRenderElement(arguments); renderingBatchId = this->renderTarget->renderingBatchId; } RENDERER_TEMPLATE_HEADER void RENDERER_CLASS_TYPE::OnElementStateChanged() { updated = true; } #undef RENDERER_CLASS_TYPE #undef RENDERER_TEMPLATE_HEADER /*********************************************************************** GuiSolidBorderElementRenderer ***********************************************************************/ GuiFocusRectangleElementRenderer::GuiFocusRectangleElementRenderer() { } bool GuiFocusRectangleElementRenderer::IsUpdated() { // there is no properties for this element return false; } void GuiFocusRectangleElementRenderer::ResetUpdated() { // nothing to update } void GuiFocusRectangleElementRenderer::OnElementStateChanged() { // nothing to update } void GuiFocusRectangleElementRenderer::SendUpdateElementMessages(bool fullContent) { // nothing to update } /*********************************************************************** GuiSolidBorderElementRenderer ***********************************************************************/ GuiSolidBorderElementRenderer::GuiSolidBorderElementRenderer() { } void GuiSolidBorderElementRenderer::SendUpdateElementMessages(bool fullContent) { ElementDesc_SolidBorder arguments; arguments.id = id; arguments.borderColor = element->GetColor(); arguments.shape = element->GetShape(); renderTarget->GetRemoteMessages().RequestRendererUpdateElement_SolidBorder(arguments); } /*********************************************************************** Gui3DBorderElementRenderer ***********************************************************************/ Gui3DBorderElementRenderer::Gui3DBorderElementRenderer() { } void Gui3DBorderElementRenderer::SendUpdateElementMessages(bool fullContent) { ElementDesc_SinkBorder arguments; arguments.id = id; arguments.leftTopColor = element->GetColor1(); arguments.rightBottomColor = element->GetColor2(); renderTarget->GetRemoteMessages().RequestRendererUpdateElement_SinkBorder(arguments); } /*********************************************************************** Gui3DSplitterElementRenderer ***********************************************************************/ Gui3DSplitterElementRenderer::Gui3DSplitterElementRenderer() { } void Gui3DSplitterElementRenderer::SendUpdateElementMessages(bool fullContent) { ElementDesc_SinkSplitter arguments; arguments.id = id; arguments.leftTopColor = element->GetColor1(); arguments.rightBottomColor = element->GetColor2(); arguments.direction = element->GetDirection(); renderTarget->GetRemoteMessages().RequestRendererUpdateElement_SinkSplitter(arguments); } /*********************************************************************** GuiSolidBackgroundElementRenderer ***********************************************************************/ GuiSolidBackgroundElementRenderer::GuiSolidBackgroundElementRenderer() { } void GuiSolidBackgroundElementRenderer::SendUpdateElementMessages(bool fullContent) { ElementDesc_SolidBackground arguments; arguments.id = id; arguments.backgroundColor = element->GetColor(); arguments.shape = element->GetShape(); renderTarget->GetRemoteMessages().RequestRendererUpdateElement_SolidBackground(arguments); } /*********************************************************************** GuiGradientBackgroundElementRenderer ***********************************************************************/ GuiGradientBackgroundElementRenderer::GuiGradientBackgroundElementRenderer() { } void GuiGradientBackgroundElementRenderer::SendUpdateElementMessages(bool fullContent) { ElementDesc_GradientBackground arguments; arguments.id = id; arguments.leftTopColor = element->GetColor1(); arguments.rightBottomColor = element->GetColor2(); arguments.direction = element->GetDirection(); arguments.shape = element->GetShape(); renderTarget->GetRemoteMessages().RequestRendererUpdateElement_GradientBackground(arguments); } /*********************************************************************** GuiInnerShadowElementRenderer ***********************************************************************/ GuiInnerShadowElementRenderer::GuiInnerShadowElementRenderer() { } void GuiInnerShadowElementRenderer::SendUpdateElementMessages(bool fullContent) { ElementDesc_InnerShadow arguments; arguments.id = id; arguments.shadowColor = element->GetColor(); arguments.thickness = element->GetThickness(); renderTarget->GetRemoteMessages().RequestRendererUpdateElement_InnerShadow(arguments); } /*********************************************************************** GuiSolidLabelElementRenderer ***********************************************************************/ GuiSolidLabelElementRenderer::MeasuringRequest GuiSolidLabelElementRenderer::GetMeasuringRequest() { if (element->GetEllipse()) { return ElementSolidLabelMeasuringRequest::FontHeight; } else if (element->GetWrapLine() && !element->GetWrapLineHeightCalculation()) { return {}; } else { return ElementSolidLabelMeasuringRequest::TotalSize; } } bool GuiSolidLabelElementRenderer::IsNeedFontHeight(MeasuringRequest request) { return request && request.Value() == ElementSolidLabelMeasuringRequest::FontHeight; } GuiSolidLabelElementRenderer::GuiSolidLabelElementRenderer() { minSize = { 1,1 }; } bool GuiSolidLabelElementRenderer::NeedUpdateMinSizeFromCache() { return needFontHeight; } void GuiSolidLabelElementRenderer::TryFetchMinSizeFromCache() { if (needFontHeight) { vint index = renderTarget->fontHeights.Keys().IndexOf({ lastFont.fontFamily,lastFont.size }); if (index != -1) { needFontHeight = false; vint size = renderTarget->fontHeights.Values()[index]; UpdateMinSize({ size,size }); } } } void GuiSolidLabelElementRenderer::UpdateMinSize(Size size) { minSize = size; if (minSize.x < 1)minSize.x = 1; if (minSize.y < 1)minSize.y = 1; } void GuiSolidLabelElementRenderer::NotifyMinSizeCacheInvalidated() { OnElementStateChanged(); auto request = GetMeasuringRequest(); needFontHeight = IsNeedFontHeight(request); } void GuiSolidLabelElementRenderer::SendUpdateElementMessages(bool fullContent) { ElementDesc_SolidLabel arguments; arguments.id = id; arguments.textColor = element->GetColor(); arguments.wrapLine = element->GetWrapLine(); arguments.wrapLineHeightCalculation = element->GetWrapLineHeightCalculation(); arguments.ellipse = element->GetEllipse(); arguments.multiline = element->GetMultiline(); switch (element->GetHorizontalAlignment()) { case Alignment::Left: arguments.horizontalAlignment = ElementHorizontalAlignment::Left; break; case Alignment::Right: arguments.horizontalAlignment = ElementHorizontalAlignment::Right; break; default: arguments.horizontalAlignment = ElementHorizontalAlignment::Center; } switch (element->GetVerticalAlignment()) { case Alignment::Top: arguments.verticalAlignment = ElementVerticalAlignment::Top; break; case Alignment::Bottom: arguments.verticalAlignment = ElementVerticalAlignment::Bottom; break; default: arguments.verticalAlignment = ElementVerticalAlignment::Center; } auto elementFont = element->GetFont(); auto elementText = element->GetText(); if (elementFont.fontFamily == WString::Empty) { elementFont = GetCurrentController()->ResourceService()->GetDefaultFont(); } if (renderTargetChanged || fullContent || lastFont != elementFont) { arguments.font = elementFont; } if (renderTargetChanged || fullContent || lastText != elementText) { arguments.text = elementText; } lastFont = elementFont; lastText = elementText; arguments.measuringRequest = GetMeasuringRequest(); if ((needFontHeight = IsNeedFontHeight(arguments.measuringRequest))) { TryFetchMinSizeFromCache(); if (!needFontHeight) { arguments.measuringRequest.Reset(); } } renderTarget->GetRemoteMessages().RequestRendererUpdateElement_SolidLabel(arguments); } /*********************************************************************** GuiImageFrameElementRenderer ***********************************************************************/ GuiRemoteGraphicsImage* GuiImageFrameElementRenderer::GetRemoteImage() { #define ERROR_MESSAGE_PREFIX L"vl::presentation::elements_remoteprotocol::GuiImageFrameElementRenderer::GetRemoteImage()#" if (element && element->GetImage()) { auto image = dynamic_cast(element->GetImage().Obj()); CHECK_ERROR(image, ERROR_MESSAGE_PREFIX L"Only INativeImage that created from GetCurrentController()->ImageService() is supported."); return image; } return nullptr; #undef ERROR_MESSAGE_PREFIX } void GuiImageFrameElementRenderer::UpdateMinSizeFromImage(GuiRemoteGraphicsImage* image) { needUpdateSize = true; if (!image || element->GetStretch()) { minSize = { 0,0 }; needUpdateSize = false; } else if (image->status == GuiRemoteGraphicsImage::MetadataStatus::Retrived) { if (0 <= element->GetFrameIndex() && element->GetFrameIndex() < image->GetFrameCount()) { minSize = image->GetFrame(element->GetFrameIndex())->GetSize(); } else { minSize = { 0,0 }; } needUpdateSize = false; } } GuiImageFrameElementRenderer::GuiImageFrameElementRenderer() { } bool GuiImageFrameElementRenderer::NeedUpdateMinSizeFromCache() { return needUpdateSize; } void GuiImageFrameElementRenderer::TryFetchMinSizeFromCache() { #define ERROR_MESSAGE_PREFIX L"vl::presentation::elements_remoteprotocol::GuiImageFrameElementRenderer::TryFetchMinSizeFromCache()#" auto image = GetRemoteImage(); if (!image || image->status != GuiRemoteGraphicsImage::MetadataStatus::Retrived) { return; } UpdateMinSizeFromImage(image); needUpdateSize = false; #undef ERROR_MESSAGE_PREFIX } void GuiImageFrameElementRenderer::SendUpdateElementMessages(bool fullContent) { auto image = GetRemoteImage(); if (image) { needUpdateSize = true; if (fullContent) { if (fullContent && image->status == GuiRemoteGraphicsImage::MetadataStatus::Retrived) { image->status = GuiRemoteGraphicsImage::MetadataStatus::Uninitialized; } } else { if (image->status == GuiRemoteGraphicsImage::MetadataStatus::Uninitialized) { image->EnsureMetadata(); } } if (image->status == GuiRemoteGraphicsImage::MetadataStatus::Retrived) { UpdateMinSizeFromImage(image); renderTargetChanged = false; } } remoteprotocol::ElementDesc_ImageFrame arguments; arguments.id = id; if (image) arguments.imageId = image->id; arguments.imageFrame = element->GetFrameIndex(); arguments.stretch = element->GetStretch(); arguments.enabled = element->GetEnabled(); switch (element->GetHorizontalAlignment()) { case Alignment::Left: arguments.horizontalAlignment = ElementHorizontalAlignment::Left; break; case Alignment::Right: arguments.horizontalAlignment = ElementHorizontalAlignment::Right; break; default: arguments.horizontalAlignment = ElementHorizontalAlignment::Center; } switch (element->GetVerticalAlignment()) { case Alignment::Top: arguments.verticalAlignment = ElementVerticalAlignment::Top; break; case Alignment::Bottom: arguments.verticalAlignment = ElementVerticalAlignment::Bottom; break; default: arguments.verticalAlignment = ElementVerticalAlignment::Center; } if ((renderTargetChanged || needUpdateSize) && image) { arguments.imageCreation = image->GenerateImageCreation(); } renderTarget->GetRemoteMessages().RequestRendererUpdateElement_ImageFrame(arguments); } /*********************************************************************** GuiPolygonElementRenderer ***********************************************************************/ GuiPolygonElementRenderer::GuiPolygonElementRenderer() { } void GuiPolygonElementRenderer::SendUpdateElementMessages(bool fullContent) { minSize = element->GetSize(); ElementDesc_Polygon arguments; arguments.id = id; arguments.size = element->GetSize(); arguments.borderColor = element->GetBorderColor(); arguments.backgroundColor = element->GetBackgroundColor(); arguments.points = Ptr(new List); CopyFrom(*arguments.points.Obj(), element->GetPointsArray()); renderTarget->GetRemoteMessages().RequestRendererUpdateElement_Polygon(arguments); } /*********************************************************************** GuiColorizedTextElementRenderer ***********************************************************************/ void GuiColorizedTextElementRenderer::ColorChanged() { } void GuiColorizedTextElementRenderer::FontChanged() { } void GuiColorizedTextElementRenderer::InitializeInternal() { TBase::InitializeInternal(); element->SetCallback(this); } void GuiColorizedTextElementRenderer::FinalizeInternal() { element->SetCallback(nullptr); TBase::FinalizeInternal(); } GuiColorizedTextElementRenderer::GuiColorizedTextElementRenderer() { } void GuiColorizedTextElementRenderer::OnElementStateChanged() { TBase::OnElementStateChanged(); } void GuiColorizedTextElementRenderer::SendUpdateElementMessages(bool fullContent) { // Lines // Colors // Font // PasswordChar // ViewPosition // VisuallyEnabled // Focused // CaretBegin // CaretEnd // CaretVisible // CaretColor CHECK_FAIL(L"Not Implemented!"); } } /*********************************************************************** .\PLATFORMPROVIDERS\REMOTE\GUIREMOTEGRAPHICS_IMAGESERVICE.CPP ***********************************************************************/ namespace vl::presentation { /*********************************************************************** GuiRemoteGraphicsImageFrame ***********************************************************************/ GuiRemoteGraphicsImageFrame::GuiRemoteGraphicsImageFrame(GuiRemoteGraphicsImage* _image) : image(_image) { } GuiRemoteGraphicsImageFrame::~GuiRemoteGraphicsImageFrame() { } INativeImage* GuiRemoteGraphicsImageFrame::GetImage() { return image; } Size GuiRemoteGraphicsImageFrame::GetSize() { return size; } /*********************************************************************** GuiRemoteGraphicsImage ***********************************************************************/ void GuiRemoteGraphicsImage::EnsureMetadata() { if (status == MetadataStatus::Retrived) return; auto arguments = GenerateImageCreation(); vint idImageCreated = remote->remoteMessages.RequestImageCreated(arguments); bool disconnected = false; remote->remoteMessages.Submit(disconnected); if (disconnected) return; auto imageMetadata = remote->remoteMessages.RetrieveImageCreated(idImageCreated); UpdateFromImageMetadata(imageMetadata); } GuiRemoteGraphicsImage::GuiRemoteGraphicsImage(GuiRemoteController* _remote, vint _id, Ptr _binary) : remote(_remote) , id(_id) , binary(_binary) { remote->imageService.images.Add(id, this); } GuiRemoteGraphicsImage::~GuiRemoteGraphicsImage() { if (remote) { if (status == MetadataStatus::Retrived) { remote->remoteMessages.RequestImageDestroyed(id); } remote->imageService.images.Remove(id); } } stream::IStream& GuiRemoteGraphicsImage::GetBinaryData() { return *binary.Obj(); } remoteprotocol::ImageCreation GuiRemoteGraphicsImage::GenerateImageCreation() { #define ERROR_MESSAGE_PREFIX L"vl::presentation::GuiRemoteGraphicsImage::GenerateImageCreation()#" CHECK_ERROR(status != MetadataStatus::Retrived, L"Cannot call this function when status is Retrived."); remoteprotocol::ImageCreation arguments; arguments.id = id; if (status == MetadataStatus::Uninitialized) { arguments.imageData = binary; arguments.imageDataOmitted = false; status = MetadataStatus::Requested; } else { arguments.imageDataOmitted = true; } return arguments; #undef ERROR_MESSAGE_PREFIX } void GuiRemoteGraphicsImage::UpdateFromImageMetadata(const remoteprotocol::ImageMetadata& imageMetadata) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::GuiRemoteGraphicsImage::UpdateFromImageMetadata(const remoteprotocol::ImageMetadata&)#" CHECK_ERROR(id == imageMetadata.id, L"Wrong image metadata id specified."); format = imageMetadata.format; if (imageMetadata.frames) { if (frames.Count() > 0) { CHECK_ERROR(frames.Count() == imageMetadata.frames, L"New metadata should be identical to the last one."); for (auto [imageFrameMetadata, index] : indexed(*imageMetadata.frames.Obj())) { CHECK_ERROR(frames[index]->size == imageFrameMetadata.size, L"New metadata should be identical to the last one."); } } else { for (auto imageFrameMetadata : *imageMetadata.frames.Obj()) { auto frame = Ptr(new GuiRemoteGraphicsImageFrame(this)); frame->size = imageFrameMetadata.size; frames.Add(frame); } } } else { CHECK_ERROR(frames.Count() == 0, L"New metadata should be identical to the last one."); } status = MetadataStatus::Retrived; #undef ERROR_MESSAGE_PREFIX } INativeImageService* GuiRemoteGraphicsImage::GetImageService() { return remote->ImageService(); } INativeImage::FormatType GuiRemoteGraphicsImage::GetFormat() { EnsureMetadata(); return format; } vint GuiRemoteGraphicsImage::GetFrameCount() { EnsureMetadata(); return frames.Count(); } INativeImageFrame* GuiRemoteGraphicsImage::GetFrame(vint index) { EnsureMetadata(); return frames[index].Obj(); } void GuiRemoteGraphicsImage::SaveToStream(stream::IStream& imageStream, FormatType formatType) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::GuiRemoteGraphicsImage::SaveToStream(IStream&, INativeImage::FormatType)#" CHECK_FAIL(ERROR_MESSAGE_PREFIX L"This function should not be called."); #undef ERROR_MESSAGE_PREFIX } /*********************************************************************** GuiRemoteGraphicsImageService ***********************************************************************/ Ptr GuiRemoteGraphicsImageService::CreateImage(Ptr binary) { return Ptr(new GuiRemoteGraphicsImage(remote, ++usedImageIds, binary)); } GuiRemoteGraphicsImageService::GuiRemoteGraphicsImageService(GuiRemoteController* _remote) : remote(_remote) { } GuiRemoteGraphicsImageService::~GuiRemoteGraphicsImageService() { } void GuiRemoteGraphicsImageService::ResetImageMetadata() { for (auto image : images.Values()) { image->status = GuiRemoteGraphicsImage::MetadataStatus::Uninitialized; } } void GuiRemoteGraphicsImageService::OnControllerConnect() { ResetImageMetadata(); } void GuiRemoteGraphicsImageService::OnControllerDisconnect() { } void GuiRemoteGraphicsImageService::Initialize() { } void GuiRemoteGraphicsImageService::Finalize() { // TODO: (enumerable) foreach:reversed for (vint i = images.Count() - 1; i >= 0; i--) { images.Values()[i]->remote = nullptr; } images.Clear(); } GuiRemoteGraphicsImage* GuiRemoteGraphicsImageService::GetImage(vint id) { return images[id]; } Ptr GuiRemoteGraphicsImageService::CreateImageFromFile(const WString& path) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::GuiRemoteGraphicsImageService::CreateImageFromFile(const WString&)#" CHECK_ERROR(remote, ERROR_MESSAGE_PREFIX L"This function cannot be called when GuiRemoteController is shut down."); stream::FileStream fileStream(path, stream::FileStream::ReadOnly); CHECK_ERROR(fileStream.IsAvailable(), ERROR_MESSAGE_PREFIX L"Unable to open file."); auto memoryStream = Ptr(new stream::MemoryStream((vint)fileStream.Size())); CopyStream(fileStream, *memoryStream.Obj()); return CreateImage(memoryStream); #undef ERROR_MESSAGE_PREFIX } Ptr GuiRemoteGraphicsImageService::CreateImageFromMemory(void* buffer, vint length) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::GuiRemoteGraphicsImageService::CreateImageFromMemory(void*, vint)#" CHECK_ERROR(remote, ERROR_MESSAGE_PREFIX L"This function cannot be called when GuiRemoteController is shut down."); auto memoryStream = Ptr(new stream::MemoryStream(length)); memoryStream->Write(buffer, length); return CreateImage(memoryStream); #undef ERROR_MESSAGE_PREFIX } Ptr GuiRemoteGraphicsImageService::CreateImageFromStream(stream::IStream& imageStream) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::GuiRemoteGraphicsImageService::CreateImageFromStream(IStream&)#" CHECK_ERROR(remote, ERROR_MESSAGE_PREFIX L"This function cannot be called when GuiRemoteController is shut down."); auto memoryStream = Ptr(new stream::MemoryStream(imageStream.IsLimited() ? (vint)imageStream.Size() : 65536)); imageStream.SeekFromBegin(0); CopyStream(imageStream, *memoryStream.Obj()); return CreateImage(memoryStream); #undef ERROR_MESSAGE_PREFIX } } /*********************************************************************** .\PLATFORMPROVIDERS\REMOTE\GUIREMOTEPROTOCOLSCHEMASHARED.CPP ***********************************************************************/ namespace vl::presentation::remoteprotocol { template<> Ptr ConvertCustomTypeToJson(const bool& value) { auto node = Ptr(new glr::json::JsonLiteral); node->value = value ? glr::json::JsonLiteralValue::True : glr::json::JsonLiteralValue::False; return node; } template<> Ptr ConvertCustomTypeToJson(const vint& value) { auto node = Ptr(new glr::json::JsonNumber); reflection::description::TypedValueSerializerProvider::Serialize(value, node->content.value); return node; } template<> Ptr ConvertCustomTypeToJson(const float& value) { auto node = Ptr(new glr::json::JsonNumber); reflection::description::TypedValueSerializerProvider::Serialize(value, node->content.value); return node; } template<> Ptr ConvertCustomTypeToJson(const double& value) { auto node = Ptr(new glr::json::JsonNumber); reflection::description::TypedValueSerializerProvider::Serialize(value, node->content.value); return node; } template<> Ptr ConvertCustomTypeToJson(const WString& value) { auto node = Ptr(new glr::json::JsonString); node->content.value = value; return node; } template<> Ptr ConvertCustomTypeToJson(const wchar_t& value) { return ConvertCustomTypeToJson(WString::FromChar(value)); } template<> Ptr ConvertCustomTypeToJson(const VKEY& value) { return ConvertCustomTypeToJson((vint)value); } template<> Ptr ConvertCustomTypeToJson(const Color& value) { return ConvertCustomTypeToJson(value.ToString()); } template<> Ptr ConvertCustomTypeToJson>(const Ptr& value) { if (!value) { auto node = Ptr(new glr::json::JsonLiteral); node->value = glr::json::JsonLiteralValue::Null; return node; } stream::MemoryStream base64WStringStream; { stream::UtfGeneralEncoder utf8ToWCharEncoder; stream::EncoderStream utf8ToWCharStream(base64WStringStream, utf8ToWCharEncoder); stream::Utf8Base64Encoder binaryToBase64Utf8Encoder; stream::EncoderStream binaryToBase64Utf8Stream(utf8ToWCharStream, binaryToBase64Utf8Encoder); value->SeekFromBegin(0); stream::CopyStream(*value.Obj(), binaryToBase64Utf8Stream); } { base64WStringStream.SeekFromBegin(0); stream::StreamReader reader(base64WStringStream); return ConvertCustomTypeToJson(reader.ReadToEnd()); } } template<> void ConvertJsonToCustomType(Ptr node, bool& value) { #define ERROR_MESSAGE_PREFIX L"presentation::remoteprotocol::ConvertJsonToCustomType(Ptr, bool&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"JSON node does not match the expected type."); switch (jsonNode->value) { case glr::json::JsonLiteralValue::True: value = true; break; case glr::json::JsonLiteralValue::False: value = false; break; default: CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported json literal."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType(Ptr node, vint& value) { #define ERROR_MESSAGE_PREFIX L"presentation::remoteprotocol::ConvertJsonToCustomType(Ptr, vint&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"JSON node does not match the expected type."); CHECK_ERROR(reflection::description::TypedValueSerializerProvider::Deserialize(jsonNode->content.value, value), ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType(Ptr node, float& value) { #define ERROR_MESSAGE_PREFIX L"presentation::remoteprotocol::ConvertJsonToCustomType(Ptr, float&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"JSON node does not match the expected type."); CHECK_ERROR(reflection::description::TypedValueSerializerProvider::Deserialize(jsonNode->content.value, value), ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType(Ptr node, double& value) { #define ERROR_MESSAGE_PREFIX L"presentation::remoteprotocol::ConvertJsonToCustomType(Ptr, double&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"JSON node does not match the expected type."); CHECK_ERROR(reflection::description::TypedValueSerializerProvider::Deserialize(jsonNode->content.value, value), ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType(Ptr node, WString& value) { #define ERROR_MESSAGE_PREFIX L"presentation::remoteprotocol::ConvertJsonToCustomType(Ptr, WString&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"JSON node does not match the expected type."); value = jsonNode->content.value; #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType(Ptr node, wchar_t& value) { #define ERROR_MESSAGE_PREFIX L"presentation::remoteprotocol::ConvertJsonToCustomType(Ptr, wchar_t&)#" WString strValue; ConvertJsonToCustomType(node, strValue); CHECK_ERROR(strValue.Length() == 1, ERROR_MESSAGE_PREFIX L"Char in JSON should be a string with one char."); value = strValue[0]; #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType(Ptr node, VKEY& value) { vint intValue; ConvertJsonToCustomType(node, intValue); value = (VKEY)intValue; } template<> void ConvertJsonToCustomType(Ptr node, Color& value) { WString strValue; ConvertJsonToCustomType(node, strValue); value = Color::Parse(strValue); } template<> void ConvertJsonToCustomType>(Ptr node, Ptr& value) { if (auto jsonLiteral = node.Cast()) { if (jsonLiteral->value == glr::json::JsonLiteralValue::Null) { value = {}; return; } } else { WString base64; ConvertJsonToCustomType(node, base64); value = Ptr(new stream::MemoryStream); stream::MemoryWrapperStream base64WStringStream((void*)base64.Buffer(), base64.Length() * sizeof(wchar_t)); stream::UtfGeneralDecoder wcharToUtf8Decoder; stream::DecoderStream wcharToUtf8Stream(base64WStringStream, wcharToUtf8Decoder); stream::Utf8Base64Decoder base64Utf8ToBinaryDecoder; stream::DecoderStream base64Utf8ToBinaryStream(wcharToUtf8Stream, base64Utf8ToBinaryDecoder); stream::CopyStream(base64Utf8ToBinaryStream, *value.Obj()); } } } /*********************************************************************** .\PLATFORMPROVIDERS\REMOTE\GUIREMOTEPROTOCOL_CHANNEL_ASYNC.CPP ***********************************************************************/ namespace vl::presentation::remoteprotocol::channeling { using namespace vl::collections; /*********************************************************************** GuiRemoteProtocolAsyncChannelSerializerBase ***********************************************************************/ void GuiRemoteProtocolAsyncChannelSerializerBase::QueueTask(SpinLock& lock, collections::List& tasks, TTaskProc task, EventObject* signalAfterQueue) { SPIN_LOCK(lock) { tasks.Add(task); } if (signalAfterQueue) { signalAfterQueue->Signal(); } } void GuiRemoteProtocolAsyncChannelSerializerBase::QueueTaskAndWait(SpinLock& lock, collections::List& tasks, TTaskProc task, EventObject* signalAfterQueue) { auto taskEvent = Ptr(new vl::EventObject); taskEvent->CreateAutoUnsignal(false); auto taskWithEvent = [=]() { task(); taskEvent->Signal(); }; QueueTask(lock, tasks, taskWithEvent, signalAfterQueue); taskEvent->Wait(); } void GuiRemoteProtocolAsyncChannelSerializerBase::FetchTasks(SpinLock& lock, collections::List& tasks, collections::List& results) { SPIN_LOCK(lock) { results = std::move(tasks); } } void GuiRemoteProtocolAsyncChannelSerializerBase::FetchAndExecuteTasks(SpinLock& lock, collections::List& tasks) { List results; FetchTasks(lock, tasks, results); for (auto&& task : results) { task(); } } void GuiRemoteProtocolAsyncChannelSerializerBase::FetchAndExecuteChannelTasks() { FetchAndExecuteTasks(channelThreadLock, channelThreadTasks); } void GuiRemoteProtocolAsyncChannelSerializerBase::FetchAndExecuteUITasks() { FetchAndExecuteTasks(uiThreadLock, uiThreadTasks); } GuiRemoteProtocolAsyncChannelSerializerBase::GuiRemoteProtocolAsyncChannelSerializerBase() { } GuiRemoteProtocolAsyncChannelSerializerBase::~GuiRemoteProtocolAsyncChannelSerializerBase() { } void GuiRemoteProtocolAsyncChannelSerializerBase::QueueToChannelThread(TTaskProc task, EventObject* signalAfterQueue) { QueueTask(channelThreadLock, channelThreadTasks, task, signalAfterQueue); } void GuiRemoteProtocolAsyncChannelSerializerBase::QueueToChannelThreadAndWait(TTaskProc task, EventObject* signalAfterQueue) { QueueTaskAndWait(channelThreadLock, channelThreadTasks, task, signalAfterQueue); } void GuiRemoteProtocolAsyncChannelSerializerBase::QueueToUIThread(TTaskProc task, EventObject* signalAfterQueue) { QueueTask(uiThreadLock, uiThreadTasks, task, signalAfterQueue); } void GuiRemoteProtocolAsyncChannelSerializerBase::QueueToUIThreadAndWait(TTaskProc task, EventObject* signalAfterQueue) { QueueTaskAndWait(uiThreadLock, uiThreadTasks, task, signalAfterQueue); } } /*********************************************************************** .\PLATFORMPROVIDERS\REMOTE\GUIREMOTEPROTOCOL_CHANNEL_JSON.CPP ***********************************************************************/ namespace vl::presentation::remoteprotocol::channeling { /*********************************************************************** ChannelPackageSemantic ***********************************************************************/ void JsonChannelPack(ChannelPackageSemantic semantic, vint id, const WString& name, Ptr arguments, Ptr& package) { package = Ptr(new glr::json::JsonObject); { auto value = Ptr(new glr::json::JsonString); switch (semantic) { case ChannelPackageSemantic::Message: value->content.value = WString::Unmanaged(L"Message"); break; case ChannelPackageSemantic::Request: value->content.value = WString::Unmanaged(L"Request"); break; case ChannelPackageSemantic::Response: value->content.value = WString::Unmanaged(L"Response"); break; case ChannelPackageSemantic::Event: value->content.value = WString::Unmanaged(L"Event"); break; default: value->content.value = WString::Unmanaged(L"Unknown"); } auto field = Ptr(new glr::json::JsonObjectField); field->name.value = WString::Unmanaged(L"semantic"); field->value = value; package->fields.Add(field); } if (id != -1) { auto value = Ptr(new glr::json::JsonNumber); value->content.value = itow(id); auto field = Ptr(new glr::json::JsonObjectField); field->name.value = WString::Unmanaged(L"id"); field->value = value; package->fields.Add(field); } { auto value = Ptr(new glr::json::JsonString); value->content.value = name; auto field = Ptr(new glr::json::JsonObjectField); field->name.value = WString::Unmanaged(L"name"); field->value = value; package->fields.Add(field); } if (arguments) { auto field = Ptr(new glr::json::JsonObjectField); field->name.value = WString::Unmanaged(L"arguments"); field->value = arguments; package->fields.Add(field); } } void JsonChannelUnpack(Ptr package, ChannelPackageSemantic& semantic, vint& id, WString& name, Ptr& arguments) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::channeling::JsonChannelPack(Ptr, ProtocolSemantic&, vint&, WString&, Ptr&)#" for (auto&& field : package->fields) { if (field->name.value == L"semantic") { auto value = field->value.Cast(); CHECK_ERROR(value, ERROR_MESSAGE_PREFIX L"The semantic field should be a string."); if (value->content.value == L"Message") { semantic = ChannelPackageSemantic::Message; } else if (value->content.value == L"Request") { semantic = ChannelPackageSemantic::Request; } else if (value->content.value == L"Response") { semantic = ChannelPackageSemantic::Response; } else if (value->content.value == L"Event") { semantic = ChannelPackageSemantic::Event; } } else if (field->name.value == L"id") { auto value = field->value.Cast(); CHECK_ERROR(value, ERROR_MESSAGE_PREFIX L"The id field should be a number."); id = wtoi(value->content.value); } else if (field->name.value == L"name") { auto value = field->value.Cast(); CHECK_ERROR(value, ERROR_MESSAGE_PREFIX L"The name field should be a string."); name = value->content.value; } else if (field->name.value == L"arguments") { arguments = field->value; } } #undef ERROR_MESSAGE_PREFIX } void ChannelPackageSemanticUnpack(Ptr package, ChannelPackageSemantic& semantic, vint& id, WString& name) { Ptr arguments; JsonChannelUnpack(package, semantic, id, name, arguments); } /*********************************************************************** GuiRemoteProtocolFromJsonChannel ***********************************************************************/ #define EVENT_NOREQ(NAME, REQUEST)\ void GuiRemoteProtocolFromJsonChannel::OnReceive_Event_ ## NAME (Ptr jsonArguments)\ {\ events->On ## NAME();\ }\ #define EVENT_REQ(NAME, REQUEST)\ void GuiRemoteProtocolFromJsonChannel::OnReceive_Event_ ## NAME (Ptr jsonArguments)\ {\ REQUEST arguments;\ ConvertJsonToCustomType(jsonArguments, arguments);\ events->On ## NAME(arguments);\ }\ #define EVENT_HANDLER(NAME, REQUEST, REQTAG, ...) EVENT_ ## REQTAG(NAME, REQUEST) GACUI_REMOTEPROTOCOL_EVENTS(EVENT_HANDLER) #undef EVENT_HANDLER #undef EVENT_REQ #undef EVENT_NOREQ #define MESSAGE_NORES(NAME, RESPONSE) #define MESSAGE_RES(NAME, RESPONSE)\ void GuiRemoteProtocolFromJsonChannel::OnReceive_Response_ ## NAME (vint id, Ptr jsonArguments)\ {\ RESPONSE arguments;\ ConvertJsonToCustomType(jsonArguments, arguments);\ events->Respond ## NAME(id, arguments);\ }\ #define MESSAGE_HANDLER(NAME, REQUEST, RESPONSE, REQTAG, RESTAG, ...) MESSAGE_ ## RESTAG(NAME, RESPONSE) GACUI_REMOTEPROTOCOL_MESSAGES(MESSAGE_HANDLER) #undef MESSAGE_HANDLER #undef MESSAGE_RES #undef MESSAGE_NORES void GuiRemoteProtocolFromJsonChannel::OnReceive(const Ptr& package) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::channeling::GuiRemoteProtocolFromJsonChannel::OnReceive(const Ptr&)#" auto semantic = ChannelPackageSemantic::Unknown; vint id = -1; WString name; Ptr jsonArguments; JsonChannelUnpack(package, semantic, id, name, jsonArguments); if (semantic == ChannelPackageSemantic::Event) { vint index = onReceiveEventHandlers.Keys().IndexOf(name); if (index == -1) { CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unrecognized event name"); } else { (this->*onReceiveEventHandlers.Values()[index])(jsonArguments); } } else if (semantic == ChannelPackageSemantic::Response) { vint index = onReceiveResponseHandlers.Keys().IndexOf(name); if (index == -1) { CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unrecognized response name"); } else { (this->*onReceiveResponseHandlers.Values()[index])(id, jsonArguments); } } else { CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unrecognized category name"); } #undef ERROR_MESSAGE_PREFIX } #define MESSAGE_NOREQ_NORES(NAME, REQUEST, RESPONSE)\ void GuiRemoteProtocolFromJsonChannel::Request ## NAME()\ {\ Ptr package;\ JsonChannelPack(ChannelPackageSemantic::Message, -1, WString::Unmanaged(L ## #NAME), {}, package);\ channel->Write(package);\ }\ #define MESSAGE_NOREQ_RES(NAME, REQUEST, RESPONSE)\ void GuiRemoteProtocolFromJsonChannel::Request ## NAME(vint id)\ {\ Ptr package;\ JsonChannelPack(ChannelPackageSemantic::Request, id, WString::Unmanaged(L ## #NAME), {}, package);\ channel->Write(package);\ }\ #define MESSAGE_REQ_NORES(NAME, REQUEST, RESPONSE)\ void GuiRemoteProtocolFromJsonChannel::Request ## NAME(const REQUEST& arguments)\ {\ Ptr package;\ JsonChannelPack(ChannelPackageSemantic::Message, -1, WString::Unmanaged(L ## #NAME), ConvertCustomTypeToJson(arguments), package);\ channel->Write(package);\ }\ #define MESSAGE_REQ_RES(NAME, REQUEST, RESPONSE)\ void GuiRemoteProtocolFromJsonChannel::Request ## NAME(vint id, const REQUEST& arguments)\ {\ Ptr package;\ JsonChannelPack(ChannelPackageSemantic::Request, id, WString::Unmanaged(L ## #NAME), ConvertCustomTypeToJson(arguments), package);\ channel->Write(package);\ }\ #define MESSAGE_HANDLER(NAME, REQUEST, RESPONSE, REQTAG, RESTAG, ...) MESSAGE_ ## REQTAG ## _ ## RESTAG(NAME, REQUEST, RESPONSE) GACUI_REMOTEPROTOCOL_MESSAGES(MESSAGE_HANDLER) #undef MESSAGE_HANDLER #undef MESSAGE_REQ_RES #undef MESSAGE_REQ_NORES #undef MESSAGE_NOREQ_RES #undef MESSAGE_NOREQ_NORES GuiRemoteProtocolFromJsonChannel::GuiRemoteProtocolFromJsonChannel(IJsonChannel* _channel) : channel(_channel) { #define EVENT_NOREQ(NAME, REQUEST) onReceiveEventHandlers.Add(WString::Unmanaged(L ## #NAME), &GuiRemoteProtocolFromJsonChannel::OnReceive_Event_ ## NAME); #define EVENT_REQ(NAME, REQUEST) onReceiveEventHandlers.Add(WString::Unmanaged(L ## #NAME), &GuiRemoteProtocolFromJsonChannel::OnReceive_Event_ ## NAME); #define EVENT_HANDLER(NAME, REQUEST, REQTAG, ...) EVENT_ ## REQTAG(NAME, REQUEST) GACUI_REMOTEPROTOCOL_EVENTS(EVENT_HANDLER) #undef EVENT_HANDLER #undef EVENT_REQ #undef EVENT_NOREQ #define MESSAGE_NORES(NAME, RESPONSE) #define MESSAGE_RES(NAME, RESPONSE) onReceiveResponseHandlers.Add(WString::Unmanaged(L ## #NAME), &GuiRemoteProtocolFromJsonChannel::OnReceive_Response_ ## NAME); #define MESSAGE_HANDLER(NAME, REQUEST, RESPONSE, REQTAG, RESTAG, ...) MESSAGE_ ## RESTAG(NAME, RESPONSE) GACUI_REMOTEPROTOCOL_MESSAGES(MESSAGE_HANDLER) #undef MESSAGE_HANDLER #undef MESSAGE_RES #undef MESSAGE_NORES } GuiRemoteProtocolFromJsonChannel::~GuiRemoteProtocolFromJsonChannel() { } void GuiRemoteProtocolFromJsonChannel::Initialize(IGuiRemoteProtocolEvents* _events) { events = _events; channel->Initialize(this); } WString GuiRemoteProtocolFromJsonChannel::GetExecutablePath() { return channel->GetExecutablePath(); } void GuiRemoteProtocolFromJsonChannel::Submit(bool& disconnected) { channel->Submit(disconnected); } void GuiRemoteProtocolFromJsonChannel::ProcessRemoteEvents() { channel->ProcessRemoteEvents(); } /*********************************************************************** GuiRemoteJsonChannelFromProtocol ***********************************************************************/ #define EVENT_NOREQ(NAME, REQUEST)\ void GuiRemoteJsonChannelFromProtocol::On ## NAME()\ {\ Ptr package;\ JsonChannelPack(ChannelPackageSemantic::Event, -1, WString::Unmanaged(L ## #NAME), {}, package);\ receiver->OnReceive(package);\ }\ #define EVENT_REQ(NAME, REQUEST)\ void GuiRemoteJsonChannelFromProtocol::On ## NAME(const REQUEST& arguments)\ {\ Ptr package;\ JsonChannelPack(ChannelPackageSemantic::Event, -1, WString::Unmanaged(L ## #NAME), ConvertCustomTypeToJson(arguments), package);\ receiver->OnReceive(package);\ }\ #define EVENT_HANDLER(NAME, REQUEST, REQTAG, ...) EVENT_ ## REQTAG(NAME, REQUEST) GACUI_REMOTEPROTOCOL_EVENTS(EVENT_HANDLER) #undef EVENT_HANDLER #undef EVENT_REQ #undef EVENT_NOREQ #define MESSAGE_NORES(NAME, RESPONSE) #define MESSAGE_RES(NAME, RESPONSE)\ void GuiRemoteJsonChannelFromProtocol::Respond ## NAME(vint id, const RESPONSE& arguments)\ {\ Ptr package;\ JsonChannelPack(ChannelPackageSemantic::Response, id, WString::Unmanaged(L ## #NAME), ConvertCustomTypeToJson(arguments), package);\ receiver->OnReceive(package);\ }\ #define MESSAGE_HANDLER(NAME, REQUEST, RESPONSE, REQTAG, RESTAG, ...) MESSAGE_ ## RESTAG(NAME, RESPONSE) GACUI_REMOTEPROTOCOL_MESSAGES(MESSAGE_HANDLER) #undef MESSAGE_HANDLER #undef MESSAGE_RES #undef MESSAGE_NORES #define MESSAGE_NOREQ_NORES(NAME, REQUEST, RESPONSE)\ void GuiRemoteJsonChannelFromProtocol::Write_ ## NAME(vint id, Ptr jsonArguments)\ {\ protocol->Request ## NAME();\ }\ #define MESSAGE_NOREQ_RES(NAME, REQUEST, RESPONSE)\ void GuiRemoteJsonChannelFromProtocol::Write_ ## NAME(vint id, Ptr jsonArguments)\ {\ protocol->Request ## NAME(id);\ }\ #define MESSAGE_REQ_NORES(NAME, REQUEST, RESPONSE)\ void GuiRemoteJsonChannelFromProtocol::Write_ ## NAME(vint id, Ptr jsonArguments)\ {\ REQUEST arguments;\ ConvertJsonToCustomType(jsonArguments, arguments);\ protocol->Request ## NAME(arguments);\ }\ #define MESSAGE_REQ_RES(NAME, REQUEST, RESPONSE)\ void GuiRemoteJsonChannelFromProtocol::Write_ ## NAME(vint id, Ptr jsonArguments)\ {\ REQUEST arguments;\ ConvertJsonToCustomType(jsonArguments, arguments);\ protocol->Request ## NAME(id, arguments);\ }\ #define MESSAGE_HANDLER(NAME, REQUEST, RESPONSE, REQTAG, RESTAG, ...) MESSAGE_ ## REQTAG ## _ ## RESTAG(NAME, REQUEST, RESPONSE) GACUI_REMOTEPROTOCOL_MESSAGES(MESSAGE_HANDLER) #undef MESSAGE_HANDLER #undef MESSAGE_REQ_RES #undef MESSAGE_REQ_NORES #undef MESSAGE_NOREQ_RES #undef MESSAGE_NOREQ_NORES GuiRemoteJsonChannelFromProtocol::GuiRemoteJsonChannelFromProtocol(IGuiRemoteProtocol* _protocol) : protocol(_protocol) { #define MESSAGE_NOREQ_NORES(NAME, REQUEST, RESPONSE) writeHandlers.Add(WString::Unmanaged(L ## #NAME), &GuiRemoteJsonChannelFromProtocol::Write_ ## NAME);; #define MESSAGE_NOREQ_RES(NAME, REQUEST, RESPONSE) writeHandlers.Add(WString::Unmanaged(L ## #NAME), &GuiRemoteJsonChannelFromProtocol::Write_ ## NAME);; #define MESSAGE_REQ_NORES(NAME, REQUEST, RESPONSE) writeHandlers.Add(WString::Unmanaged(L ## #NAME), &GuiRemoteJsonChannelFromProtocol::Write_ ## NAME);; #define MESSAGE_REQ_RES(NAME, REQUEST, RESPONSE) writeHandlers.Add(WString::Unmanaged(L ## #NAME), &GuiRemoteJsonChannelFromProtocol::Write_ ## NAME);; #define MESSAGE_HANDLER(NAME, REQUEST, RESPONSE, REQTAG, RESTAG, ...) MESSAGE_ ## REQTAG ## _ ## RESTAG(NAME, REQUEST, RESPONSE) GACUI_REMOTEPROTOCOL_MESSAGES(MESSAGE_HANDLER) #undef MESSAGE_HANDLER #undef MESSAGE_REQ_RES #undef MESSAGE_REQ_NORES #undef MESSAGE_NOREQ_RES #undef MESSAGE_NOREQ_NORES } GuiRemoteJsonChannelFromProtocol::~GuiRemoteJsonChannelFromProtocol() { } void GuiRemoteJsonChannelFromProtocol::Initialize(IJsonChannelReceiver* _receiver) { receiver = _receiver; protocol->Initialize(this); } IJsonChannelReceiver* GuiRemoteJsonChannelFromProtocol::GetReceiver() { return receiver; } void GuiRemoteJsonChannelFromProtocol::Write(const Ptr& package) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::channeling::GuiRemoteJsonChannelFromProtocol::Write(const Ptr&)#" auto semantic = ChannelPackageSemantic::Unknown; vint id = -1; WString name; Ptr jsonArguments; JsonChannelUnpack(package, semantic, id, name, jsonArguments); vint index = writeHandlers.Keys().IndexOf(name); if (index == -1) { CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unrecognized request name"); } else { (this->*writeHandlers.Values()[index])(id, jsonArguments); } #undef ERROR_MESSAGE_PREFIX } WString GuiRemoteJsonChannelFromProtocol::GetExecutablePath() { return protocol->GetExecutablePath(); } void GuiRemoteJsonChannelFromProtocol::Submit(bool& disconnected) { protocol->Submit(disconnected); } void GuiRemoteJsonChannelFromProtocol::ProcessRemoteEvents() { protocol->ProcessRemoteEvents(); } /*********************************************************************** JsonToStringSerializer ***********************************************************************/ void JsonToStringSerializer::Serialize(Ptr parser, const SourceType& source, DestType& dest) { glr::json::JsonFormatting formatting; formatting.spaceAfterColon = false; formatting.spaceAfterComma = false; formatting.crlf = false; formatting.compact = true; dest = glr::json::JsonToString(source, formatting); } void JsonToStringSerializer::Deserialize(Ptr parser, const DestType& source, SourceType& dest) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::channeling::GuiRemoteJsonChannelFromProtocol::Write(const Ptr&)#" auto value = glr::json::JsonParse(source, *parser.Obj()); dest = value.Cast(); CHECK_ERROR(dest, ERROR_MESSAGE_PREFIX L"JSON parssing between the channel should be JsonObject."); #undef ERROR_MESSAGE_PREFIX } } /*********************************************************************** .\PLATFORMPROVIDERS\REMOTE\GUIREMOTEPROTOCOL_DOMDIFF.CPP ***********************************************************************/ namespace vl::presentation::remoteprotocol { /*********************************************************************** GuiRemoteEventDomDiffConverter ***********************************************************************/ GuiRemoteEventDomDiffConverter::GuiRemoteEventDomDiffConverter() { } GuiRemoteEventDomDiffConverter::~GuiRemoteEventDomDiffConverter() { } void GuiRemoteEventDomDiffConverter::OnControllerConnect() { lastDom = {}; TBase::OnControllerConnect(); } /*********************************************************************** GuiRemoteProtocolDomDiffConverter ***********************************************************************/ GuiRemoteProtocolDomDiffConverter::GuiRemoteProtocolDomDiffConverter(IGuiRemoteProtocol* _protocol) : TBase(_protocol) { } GuiRemoteProtocolDomDiffConverter::~GuiRemoteProtocolDomDiffConverter() { } void GuiRemoteProtocolDomDiffConverter::RequestRendererBeginRendering(const remoteprotocol::ElementBeginRendering& arguments) { renderingDomBuilder.RequestRendererBeginRendering(); TBase::RequestRendererBeginRendering(arguments); } void GuiRemoteProtocolDomDiffConverter::RequestRendererEndRendering(vint id) { auto dom = renderingDomBuilder.RequestRendererEndRendering(); DomIndex domIndex; BuildDomIndex(dom, domIndex); if (eventCombinator.lastDom) { RenderingDom_DiffsInOrder diffs; DiffDom(eventCombinator.lastDom, eventCombinator.lastDomIndex, dom, domIndex, diffs); targetProtocol->RequestRendererRenderDomDiff(diffs); } else { targetProtocol->RequestRendererRenderDom(dom); } eventCombinator.lastDom = dom; eventCombinator.lastDomIndex = std::move(domIndex); TBase::RequestRendererEndRendering(id); } void GuiRemoteProtocolDomDiffConverter::RequestRendererBeginBoundary(const remoteprotocol::ElementBoundary& arguments) { renderingDomBuilder.RequestRendererBeginBoundary(arguments); } void GuiRemoteProtocolDomDiffConverter::RequestRendererEndBoundary() { renderingDomBuilder.RequestRendererEndBoundary(); } void GuiRemoteProtocolDomDiffConverter::RequestRendererRenderElement(const remoteprotocol::ElementRendering& arguments) { renderingDomBuilder.RequestRendererRenderElement(arguments); } } /*********************************************************************** .\PLATFORMPROVIDERS\REMOTE\GUIREMOTEPROTOCOL_FILTER.CPP ***********************************************************************/ namespace vl::presentation::remoteprotocol::repeatfiltering { /*********************************************************************** GuiRemoteEventFilter ***********************************************************************/ GuiRemoteEventFilter::GuiRemoteEventFilter() { } GuiRemoteEventFilter::~GuiRemoteEventFilter() { } void GuiRemoteEventFilter::ProcessResponses() { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::repeatfiltering::GuiRemoteProtocolFilter::ProcessResponses()#" for (auto&& response : filteredResponses) { #define MESSAGE_NORES(NAME, RESPONSE) #define MESSAGE_RES(NAME, RESPONSE)\ case FilteredResponseNames::NAME:\ targetEvents->Respond ## NAME(response.id, response.arguments.Get());\ break;\ #define MESSAGE_HANDLER(NAME, REQUEST, RESPONSE, REQTAG, RESTAG, ...) MESSAGE_ ## RESTAG(NAME, RESPONSE) switch (response.name) { GACUI_REMOTEPROTOCOL_MESSAGES(MESSAGE_HANDLER) default: CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unrecognized response."); } #undef MESSAGE_HANDLER #undef MESSAGE_RES #undef MESSAGE_NORES } CHECK_ERROR(responseIds.Count() == 0, ERROR_MESSAGE_PREFIX L"Messages sending to IGuiRemoteProtocol should be all responded."); filteredResponses.Clear(); #undef ERROR_MESSAGE_PREFIX } void GuiRemoteEventFilter::ProcessEvents() { #define EVENT_NODROP(NAME) #define EVENT_DROPREP(NAME) lastDropRepeatEvent ## NAME = -1; #define EVENT_DROPCON(NAME) lastDropConsecutiveEvent ## NAME = -1; #define EVENT_HANDLER(NAME, REQUEST, REQTAG, DROPTAG, ...) EVENT_ ## DROPTAG(NAME) GACUI_REMOTEPROTOCOL_EVENTS(EVENT_HANDLER) #undef EVENT_HANDLER #undef EVENT_DROPCON #undef EVENT_DROPREP #undef EVENT_NODROP collections::List events(std::move(filteredEvents)); for (auto&& event : events) { if (event.dropped) { continue; } #define EVENT_NOREQ(NAME, REQUEST)\ case FilteredEventNames::NAME:\ targetEvents->On ## NAME();\ break;\ #define EVENT_REQ(NAME, REQUEST)\ case FilteredEventNames::NAME:\ targetEvents->On ## NAME(event.arguments.Get());\ break;\ #define EVENT_HANDLER(NAME, REQUEST, REQTAG, ...) EVENT_ ## REQTAG(NAME, REQUEST) switch (event.name) { GACUI_REMOTEPROTOCOL_EVENTS(EVENT_HANDLER) default: CHECK_FAIL(L"vl::presentation::remoteprotocol::GuiRemoteEventFilter::ProcessEvents()#Unrecognized event."); } #undef EVENT_HANDLER #undef EVENT_REQ #undef EVENT_NOREQ } } // responses #define MESSAGE_NORES(NAME, RESPONSE) #define MESSAGE_RES(NAME, RESPONSE)\ void GuiRemoteEventFilter::Respond ## NAME(vint id, const RESPONSE& arguments)\ {\ CHECK_ERROR(\ responseIds[id] == FilteredResponseNames::NAME,\ L"vl::presentation::remoteprotocol::GuiRemoteEventFilter::"\ L"Respond" L ## #NAME L"()#"\ L"Messages sending to IGuiRemoteProtocol should be responded by calling the correct function.");\ responseIds.Remove(id);\ FilteredResponse response;\ response.id = id;\ response.name = FilteredResponseNames::NAME;\ response.arguments = arguments;\ filteredResponses.Add(response);\ }\ #define MESSAGE_HANDLER(NAME, REQUEST, RESPONSE, REQTAG, RESTAG, ...) MESSAGE_ ## RESTAG(NAME, RESPONSE) GACUI_REMOTEPROTOCOL_MESSAGES(MESSAGE_HANDLER) #undef MESSAGE_HANDLER #undef MESSAGE_RES #undef MESSAGE_NORES // events #define EVENT_NODROP(NAME) #define EVENT_DROPREP(NAME)\ if (lastDropRepeatEvent ## NAME != -1)\ {\ filteredEvents[lastDropRepeatEvent ## NAME].dropped = true;\ }\ lastDropRepeatEvent ## NAME = filteredEvents.Count() - 1\ #define EVENT_DROPCON(NAME)\ if (lastDropConsecutiveEvent ## NAME != -1 && lastDropConsecutiveEvent ## NAME == filteredEvents.Count() - 1)\ {\ filteredEvents[lastDropConsecutiveEvent ## NAME].dropped = true;\ }\ lastDropConsecutiveEvent ## NAME = filteredEvents.Count() - 1\ #define EVENT_NOREQ(NAME, REQUEST, DROPTAG)\ void GuiRemoteEventFilter::On ## NAME()\ {\ if (submitting)\ {\ EVENT_ ## DROPTAG(NAME);\ FilteredEvent event;\ event.name = FilteredEventNames::NAME;\ filteredEvents.Add(event);\ }\ else\ {\ targetEvents->On ## NAME();\ }\ }\ #define EVENT_REQ(NAME, REQUEST, DROPTAG)\ void GuiRemoteEventFilter::On ## NAME(const REQUEST& arguments)\ {\ if (submitting)\ {\ EVENT_ ## DROPTAG(NAME);\ FilteredEvent event;\ event.name = FilteredEventNames::NAME;\ event.arguments = arguments;\ filteredEvents.Add(event);\ }\ else\ {\ targetEvents->On ## NAME(arguments);\ }\ }\ #define EVENT_HANDLER(NAME, REQUEST, REQTAG, DROPTAG, ...) EVENT_ ## REQTAG(NAME, REQUEST, DROPTAG) GACUI_REMOTEPROTOCOL_EVENTS(EVENT_HANDLER) #undef EVENT_HANDLER #undef EVENT_REQ #undef EVENT_NOREQ #undef EVENT_DROPCON #undef EVENT_DROPREP #undef EVENT_NOREP /*********************************************************************** GuiRemoteProtocolFilter ***********************************************************************/ void GuiRemoteProtocolFilter::ProcessRequests() { #define MESSAGE_NODROP(NAME) #define MESSAGE_DROPREP(NAME) lastDropRepeatRequest ## NAME = -1; #define MESSAGE_HANDLER(NAME, REQUEST, RESPONSE, REQTAG, RESTAG, DROPTAG) MESSAGE_ ## DROPTAG(NAME) GACUI_REMOTEPROTOCOL_MESSAGES(MESSAGE_HANDLER) #undef MESSAGE_HANDLER #undef MESSAGE_DROPREP #undef MESSAGE_NODROP for (auto&& request : filteredRequests) { CHECK_ERROR(\ !request.dropped || request.id == -1,\ L"vl::presentation::remoteprotocol::GuiRemoteProtocolFilter::ProcessRequests()#"\ L"Messages with id cannot be dropped.");\ if (request.dropped) { continue; } #define MESSAGE_NOREQ_NORES(NAME, REQUEST, RESPONSE)\ case FilteredRequestNames::NAME:\ targetProtocol->Request ## NAME();\ break;\ #define MESSAGE_NOREQ_RES(NAME, REQUEST, RESPONSE)\ case FilteredRequestNames::NAME:\ targetProtocol->Request ## NAME(request.id);\ break;\ #define MESSAGE_REQ_NORES(NAME, REQUEST, RESPONSE)\ case FilteredRequestNames::NAME:\ targetProtocol->Request ## NAME(request.arguments.Get());\ break;\ #define MESSAGE_REQ_RES(NAME, REQUEST, RESPONSE)\ case FilteredRequestNames::NAME:\ targetProtocol->Request ## NAME(request.id, request.arguments.Get());\ break;\ #define MESSAGE_HANDLER(NAME, REQUEST, RESPONSE, REQTAG, RESTAG, ...) MESSAGE_ ## REQTAG ## _ ## RESTAG(NAME, REQUEST, RESPONSE) switch (request.name) { GACUI_REMOTEPROTOCOL_MESSAGES(MESSAGE_HANDLER) default: CHECK_FAIL(L"vl::presentation::remoteprotocol::GuiRemoteProtocolFilter::ProcessRequests()#Unrecognized request."); } #undef MESSAGE_HANDLER #undef MESSAGE_REQ_RES #undef MESSAGE_REQ_NORES #undef MESSAGE_NOREQ_RES #undef MESSAGE_NOREQ_NORES } filteredRequests.Clear(); } GuiRemoteProtocolFilter::GuiRemoteProtocolFilter(IGuiRemoteProtocol* _protocol) : GuiRemoteProtocolCombinator(_protocol) { } GuiRemoteProtocolFilter::~GuiRemoteProtocolFilter() { } // messages #define MESSAGE_NODROP(NAME) #define MESSAGE_DROPREP(NAME)\ if (lastDropRepeatRequest ## NAME != -1)\ {\ filteredRequests[lastDropRepeatRequest ## NAME].dropped = true;\ }\ lastDropRepeatRequest ## NAME = filteredRequests.Count()\ #define MESSAGE_NOREQ_NORES(NAME, REQUEST, RESPONSE, DROPTAG)\ void GuiRemoteProtocolFilter::Request ## NAME()\ {\ MESSAGE_ ## DROPTAG(NAME);\ FilteredRequest request;\ request.name = FilteredRequestNames::NAME;\ filteredRequests.Add(request);\ }\ #define MESSAGE_NOREQ_RES(NAME, REQUEST, RESPONSE, DROPTAG)\ void GuiRemoteProtocolFilter::Request ## NAME(vint id)\ {\ MESSAGE_ ## DROPTAG(NAME);\ CHECK_ERROR(\ lastRequestId < id,\ L"vl::presentation::remoteprotocol::GuiRemoteProtocolFilter::"\ L"Request" L ## #NAME L"()#"\ L"Id of a message sending to IGuiRemoteProtocol should be increasing.");\ lastRequestId = id;\ FilteredRequest request;\ request.id = id;\ request.name = FilteredRequestNames::NAME;\ filteredRequests.Add(request);\ eventCombinator.responseIds.Add(id, FilteredResponseNames::NAME);\ }\ #define MESSAGE_REQ_NORES(NAME, REQUEST, RESPONSE, DROPTAG)\ void GuiRemoteProtocolFilter::Request ## NAME(const REQUEST& arguments)\ {\ MESSAGE_ ## DROPTAG(NAME);\ FilteredRequest request;\ request.name = FilteredRequestNames::NAME;\ request.arguments = arguments;\ filteredRequests.Add(request);\ }\ #define MESSAGE_REQ_RES(NAME, REQUEST, RESPONSE, DROPTAG)\ void GuiRemoteProtocolFilter::Request ## NAME(vint id, const REQUEST& arguments)\ {\ MESSAGE_ ## DROPTAG(NAME);\ CHECK_ERROR(\ lastRequestId < id,\ L"vl::presentation::remoteprotocol::GuiRemoteProtocolFilter::"\ L"Request" L ## #NAME L"()#"\ L"Id of a message sending to IGuiRemoteProtocol should be increasing.");\ lastRequestId = id;\ FilteredRequest request;\ request.id = id;\ request.name = FilteredRequestNames::NAME;\ request.arguments = arguments;\ filteredRequests.Add(request);\ eventCombinator.responseIds.Add(id, FilteredResponseNames::NAME);\ }\ #define MESSAGE_HANDLER(NAME, REQUEST, RESPONSE, REQTAG, RESTAG, DROPTAG, ...) MESSAGE_ ## REQTAG ## _ ## RESTAG(NAME, REQUEST, RESPONSE, DROPTAG) GACUI_REMOTEPROTOCOL_MESSAGES(MESSAGE_HANDLER) #undef MESSAGE_HANDLER #undef MESSAGE_REQ_RES #undef MESSAGE_REQ_NORES #undef MESSAGE_NOREQ_RES #undef MESSAGE_NOREQ_NORES #undef MESSAGE_DROPREP #undef MESSAGE_NODROP // protocol void GuiRemoteProtocolFilter::Initialize(IGuiRemoteProtocolEvents* _events) { if (auto verifierProtocol = dynamic_cast(targetProtocol)) { verifierProtocol->targetProtocol->Initialize(&eventCombinator); eventCombinator.targetEvents = &verifierProtocol->eventCombinator; verifierProtocol->eventCombinator.targetEvents = _events; } else { GuiRemoteProtocolCombinator::Initialize(_events); } } void GuiRemoteProtocolFilter::Submit(bool& disconnected) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::repeatfiltering::GuiRemoteProtocolFilter::Submit()#" CHECK_ERROR(!eventCombinator.submitting, ERROR_MESSAGE_PREFIX L"This function is not allowed to be called recursively."); eventCombinator.submitting = true; ProcessRequests(); GuiRemoteProtocolCombinator::Submit(disconnected); if (disconnected) { eventCombinator.responseIds.Clear(); } else { eventCombinator.ProcessResponses(); } eventCombinator.submitting = false; eventCombinator.ProcessEvents(); #undef ERROR_MESSAGE_PREFIX } } /*********************************************************************** .\PLATFORMPROVIDERS\REMOTE\GUIREMOTEPROTOCOL_FILTERVERIFIER.CPP ***********************************************************************/ namespace vl::presentation::remoteprotocol::repeatfiltering { /*********************************************************************** GuiRemoteEventFilterVerifier ***********************************************************************/ GuiRemoteEventFilterVerifier::GuiRemoteEventFilterVerifier() { } GuiRemoteEventFilterVerifier::~GuiRemoteEventFilterVerifier() { } void GuiRemoteEventFilterVerifier::ClearDropRepeatMasks() { #define EVENT_NODROP(NAME) #define EVENT_DROPREP(NAME) lastDropRepeatEvent ## NAME = false; #define EVENT_DROPCON(NAME) #define EVENT_HANDLER(NAME, REQUEST, REQTAG, DROPTAG, ...) EVENT_ ## DROPTAG(NAME) GACUI_REMOTEPROTOCOL_EVENTS(EVENT_HANDLER) #undef EVENT_HANDLER #undef EVENT_DROPCON #undef EVENT_DROPREP #undef EVENT_NODROP } void GuiRemoteEventFilterVerifier::ClearDropConsecutiveMasks() { #define EVENT_NODROP(NAME) #define EVENT_DROPREP(NAME) #define EVENT_DROPCON(NAME) lastDropConsecutiveEvent ## NAME = false; #define EVENT_HANDLER(NAME, REQUEST, REQTAG, DROPTAG, ...) EVENT_ ## DROPTAG(NAME) GACUI_REMOTEPROTOCOL_EVENTS(EVENT_HANDLER) #undef EVENT_HANDLER #undef EVENT_DROPCON #undef EVENT_DROPREP #undef EVENT_NODROP } // responses #define MESSAGE_NORES(NAME, RESPONSE) #define MESSAGE_RES(NAME, RESPONSE)\ void GuiRemoteEventFilterVerifier::Respond ## NAME(vint id, const RESPONSE& arguments)\ {\ targetEvents->Respond ## NAME(id, arguments);\ }\ #define MESSAGE_HANDLER(NAME, REQUEST, RESPONSE, REQTAG, RESTAG, ...) MESSAGE_ ## RESTAG(NAME, RESPONSE) GACUI_REMOTEPROTOCOL_MESSAGES(MESSAGE_HANDLER) #undef MESSAGE_HANDLER #undef MESSAGE_RES #undef MESSAGE_NORES // events #define EVENT_NODROP(NAME) #define EVENT_DROPREP(NAME)\ CHECK_ERROR(!lastDropRepeatEvent ## NAME, L"vl::presentation::remoteprotocol::GuiRemoteEventFilterVerifier::On" L ## #NAME L"(...)#[@DropRepeat] event repeated.");\ lastDropRepeatEvent ## NAME = true;\ #define EVENT_DROPCON(NAME)\ CHECK_ERROR(!lastDropConsecutiveEvent ## NAME, L"vl::presentation::remoteprotocol::GuiRemoteEventFilterVerifier::On" L ## #NAME L"(...)#[@DropConsecutive] event repeated.");\ ClearDropConsecutiveMasks();\ lastDropConsecutiveEvent ## NAME = true;\ #define EVENT_NOREQ(NAME, REQUEST, DROPTAG)\ void GuiRemoteEventFilterVerifier::On ## NAME()\ {\ if (submitting)\ {\ EVENT_ ## DROPTAG(NAME);\ targetEvents->On ## NAME();\ }\ else\ {\ targetEvents->On ## NAME();\ }\ }\ #define EVENT_REQ(NAME, REQUEST, DROPTAG)\ void GuiRemoteEventFilterVerifier::On ## NAME(const REQUEST& arguments)\ {\ if (submitting)\ {\ EVENT_ ## DROPTAG(NAME);\ targetEvents->On ## NAME(arguments);\ }\ else\ {\ targetEvents->On ## NAME(arguments);\ }\ }\ #define EVENT_HANDLER(NAME, REQUEST, REQTAG, DROPTAG, ...) EVENT_ ## REQTAG(NAME, REQUEST, DROPTAG) GACUI_REMOTEPROTOCOL_EVENTS(EVENT_HANDLER) #undef EVENT_HANDLER #undef EVENT_REQ #undef EVENT_NOREQ #undef EVENT_DROPCON #undef EVENT_DROPREP #undef EVENT_NOREP /*********************************************************************** GuiRemoteProtocolFilterVerifier ***********************************************************************/ void GuiRemoteProtocolFilterVerifier::ClearDropRepeatMasks() { #define MESSAGE_NODROP(NAME) #define MESSAGE_DROPREP(NAME) lastDropRepeatRequest ## NAME = false; #define MESSAGE_HANDLER(NAME, REQUEST, RESPONSE, REQTAG, RESTAG, DROPTAG) MESSAGE_ ## DROPTAG(NAME) GACUI_REMOTEPROTOCOL_MESSAGES(MESSAGE_HANDLER) #undef MESSAGE_HANDLER #undef MESSAGE_DROPREP #undef MESSAGE_NODROP } GuiRemoteProtocolFilterVerifier::GuiRemoteProtocolFilterVerifier(IGuiRemoteProtocol* _protocol) : GuiRemoteProtocolCombinator(_protocol) { } GuiRemoteProtocolFilterVerifier::~GuiRemoteProtocolFilterVerifier() { } // messages #define MESSAGE_NODROP(NAME) #define MESSAGE_DROPREP(NAME)\ CHECK_ERROR(!lastDropRepeatRequest ## NAME, L"vl::presentation::remoteprotocol::GuiRemoteProtocolFilterVerifier::Request" L ## #NAME L"(...)#[@DropRepeat] message repeated.");\ lastDropRepeatRequest ## NAME = true;\ #define MESSAGE_NOREQ_NORES(NAME, REQUEST, RESPONSE, DROPTAG)\ void GuiRemoteProtocolFilterVerifier::Request ## NAME()\ {\ MESSAGE_ ## DROPTAG(NAME);\ targetProtocol->Request ## NAME();\ }\ #define MESSAGE_NOREQ_RES(NAME, REQUEST, RESPONSE, DROPTAG)\ void GuiRemoteProtocolFilterVerifier::Request ## NAME(vint id)\ {\ MESSAGE_ ## DROPTAG(NAME);\ targetProtocol->Request ## NAME(id);\ }\ #define MESSAGE_REQ_NORES(NAME, REQUEST, RESPONSE, DROPTAG)\ void GuiRemoteProtocolFilterVerifier::Request ## NAME(const REQUEST& arguments)\ {\ MESSAGE_ ## DROPTAG(NAME);\ targetProtocol->Request ## NAME(arguments);\ }\ #define MESSAGE_REQ_RES(NAME, REQUEST, RESPONSE, DROPTAG)\ void GuiRemoteProtocolFilterVerifier::Request ## NAME(vint id, const REQUEST& arguments)\ {\ MESSAGE_ ## DROPTAG(NAME);\ targetProtocol->Request ## NAME(id, arguments);\ }\ #define MESSAGE_HANDLER(NAME, REQUEST, RESPONSE, REQTAG, RESTAG, DROPTAG, ...) MESSAGE_ ## REQTAG ## _ ## RESTAG(NAME, REQUEST, RESPONSE, DROPTAG) GACUI_REMOTEPROTOCOL_MESSAGES(MESSAGE_HANDLER) #undef MESSAGE_HANDLER #undef MESSAGE_REQ_RES #undef MESSAGE_REQ_NORES #undef MESSAGE_NOREQ_RES #undef MESSAGE_NOREQ_NORES #undef MESSAGE_DROPREP #undef MESSAGE_NODROP // protocol void GuiRemoteProtocolFilterVerifier::Submit(bool& disconnected) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::repeatfiltering::GuiRemoteProtocolFilterVerifier::Submit()#" CHECK_ERROR(!eventCombinator.submitting, ERROR_MESSAGE_PREFIX L"This function is not allowed to be called recursively."); eventCombinator.submitting = true; GuiRemoteProtocolCombinator::Submit(disconnected); ClearDropRepeatMasks(); eventCombinator.ClearDropRepeatMasks(); eventCombinator.ClearDropConsecutiveMasks(); eventCombinator.submitting = false; #undef ERROR_MESSAGE_PREFIX } } /*********************************************************************** .\PLATFORMPROVIDERS\REMOTE\GUIREMOTEWINDOW.CPP ***********************************************************************/ namespace vl::presentation { /*********************************************************************** GuiRemoteWindow ***********************************************************************/ #define SET_REMOTE_WINDOW_STYLE(STYLE, VALUE)\ if (style ## STYLE != VALUE)\ {\ style ## STYLE = VALUE;\ remoteMessages.RequestWindowNotifySet ## STYLE(VALUE);\ }\ #define SET_REMOTE_WINDOW_STYLE_INVALIDATE(STYLE, VALUE)\ if (style ## STYLE != VALUE)\ {\ style ## STYLE = VALUE;\ sizingConfigInvalidated = true;\ remoteMessages.RequestWindowNotifySet ## STYLE(VALUE);\ }\ void GuiRemoteWindow::RequestGetBounds() { vint idGetBounds = remoteMessages.RequestWindowGetBounds(); bool disconnected = false; remoteMessages.Submit(disconnected); if (disconnected) return; sizingConfigInvalidated = false; OnWindowBoundsUpdated(remoteMessages.RetrieveWindowGetBounds(idGetBounds)); } void GuiRemoteWindow::Opened() { if (!statusVisible) { statusVisible = true; for (auto l : listeners)l->Opened(); } } void GuiRemoteWindow::SetActivated(bool activated) { if (statusActivated != activated) { statusActivated = activated; if (statusActivated) { for (auto l : listeners)l->GotFocus(); for (auto l : listeners)l->RenderingAsActivated(); } else { for (auto l : listeners)l->LostFocus(); for (auto l : listeners)l->RenderingAsDeactivated(); } } } void GuiRemoteWindow::ShowWithSizeState(bool activate, INativeWindow::WindowSizeState sizeState) { if (!statusVisible || remoteWindowSizingConfig.sizeState != sizeState) { remoteprotocol::WindowShowing windowShowing; windowShowing.activate = activate; windowShowing.sizeState = sizeState; remoteMessages.RequestWindowNotifyShow(windowShowing); bool disconnected = false; remoteMessages.Submit(disconnected); // there is no result from this request, assuming succeeded remoteWindowSizingConfig.sizeState = sizeState; Opened(); SetActivated(activate); } else if (!statusActivated && activate) { SetActivate(); } } /*********************************************************************** GuiRemoteWindow (events) ***********************************************************************/ void GuiRemoteWindow::OnControllerConnect() { if (disconnected) { disconnected = false; } sizingConfigInvalidated = true; remoteMessages.RequestWindowNotifySetBounds(remoteWindowSizingConfig.bounds); RequestGetBounds(); // TODO: // This is a workaround to call GuiWindow::UpdateCustomFramePadding // Refactor to make it more elegant. for (auto l : listeners) l->DpiChanged(true); for (auto l : listeners) l->DpiChanged(false); if (remote->applicationRunning) { remoteMessages.RequestWindowNotifySetTitle(styleTitle); remoteMessages.RequestWindowNotifySetEnabled(styleEnabled); remoteMessages.RequestWindowNotifySetTopMost(styleTopMost); remoteMessages.RequestWindowNotifySetShowInTaskBar(styleShowInTaskBar); remoteMessages.RequestWindowNotifySetCustomFrameMode(styleCustomFrameMode); remoteMessages.RequestWindowNotifySetMaximizedBox(styleMaximizedBox); remoteMessages.RequestWindowNotifySetMinimizedBox(styleMinimizedBox); remoteMessages.RequestWindowNotifySetBorder(styleBorder); remoteMessages.RequestWindowNotifySetSizeBox(styleSizeBox); remoteMessages.RequestWindowNotifySetIconVisible(styleIconVisible); remoteMessages.RequestWindowNotifySetTitleBar(styleTitleBar); if (statusCapturing) { remoteMessages.RequestIORequireCapture(); } else { remoteMessages.RequestIOReleaseCapture(); } bool disconnected = false; remoteMessages.Submit(disconnected); // there is no result from this request, assuming succeeded } } void GuiRemoteWindow::OnControllerDisconnect() { disconnected = true; } void GuiRemoteWindow::OnControllerScreenUpdated(const remoteprotocol::ScreenConfig& arguments) { if (scalingX != arguments.scalingX || scalingY != arguments.scalingY) { scalingX = arguments.scalingX; scalingY = arguments.scalingY; for (auto l : listeners) l->DpiChanged(true); for (auto l : listeners) l->DpiChanged(false); } } void GuiRemoteWindow::OnWindowBoundsUpdated(const remoteprotocol::WindowSizingConfig& arguments) { bool callMoved = false; if (remoteWindowSizingConfig.bounds != arguments.bounds || remoteWindowSizingConfig.clientBounds != arguments.clientBounds) { callMoved = true; } remoteWindowSizingConfig = arguments; if(callMoved) { for (auto l : listeners) l->Moved(); } } void GuiRemoteWindow::OnWindowActivatedUpdated(bool activated) { SetActivated(activated); } /*********************************************************************** GuiRemoteWindow ***********************************************************************/ GuiRemoteWindow::GuiRemoteWindow(GuiRemoteController* _remote) : remote(_remote) , remoteMessages(_remote->remoteMessages) , remoteEvents(_remote->remoteEvents) { remoteWindowSizingConfig.sizeState = INativeWindow::Restored; } GuiRemoteWindow::~GuiRemoteWindow() { } /*********************************************************************** GuiRemoteWindow (INativeWindow) ***********************************************************************/ bool GuiRemoteWindow::IsActivelyRefreshing() { return true; } NativeSize GuiRemoteWindow::GetRenderingOffset() { return { 0,0 }; } Point GuiRemoteWindow::Convert(NativePoint value) { return Point((vint)(value.x.value / scalingX), (vint)(value.y.value / scalingY)); } NativePoint GuiRemoteWindow::Convert(Point value) { return NativePoint((vint)(value.x * scalingX), (vint)(value.y * scalingY)); } Size GuiRemoteWindow::Convert(NativeSize value) { return Size((vint)(value.x.value / scalingX), (vint)(value.y.value / scalingY)); } NativeSize GuiRemoteWindow::Convert(Size value) { return NativeSize((vint)(value.x * scalingX), (vint)(value.y * scalingY)); } Margin GuiRemoteWindow::Convert(NativeMargin value) { return Margin( (vint)(value.left.value / scalingX), (vint)(value.top.value / scalingY), (vint)(value.right.value / scalingX), (vint)(value.bottom.value / scalingY) ); } NativeMargin GuiRemoteWindow::Convert(Margin value) { return NativeMargin( (vint)(value.left * scalingX), (vint)(value.top * scalingY), (vint)(value.right * scalingX), (vint)(value.bottom * scalingY) ); } NativeRect GuiRemoteWindow::GetBounds() { if (sizingConfigInvalidated) RequestGetBounds(); return remoteWindowSizingConfig.bounds; } void GuiRemoteWindow::SetBounds(const NativeRect& bounds) { if (remoteWindowSizingConfig.bounds != bounds) { auto x1 = remoteWindowSizingConfig.clientBounds.x1 - remoteWindowSizingConfig.bounds.x1; auto y1 = remoteWindowSizingConfig.clientBounds.y1 - remoteWindowSizingConfig.bounds.y1; auto x2 = remoteWindowSizingConfig.clientBounds.x2 - remoteWindowSizingConfig.bounds.x2; auto y2 = remoteWindowSizingConfig.clientBounds.y2 - remoteWindowSizingConfig.bounds.y2; remoteWindowSizingConfig.bounds = bounds; remoteWindowSizingConfig.clientBounds = { x1 + remoteWindowSizingConfig.bounds.x1, y1 + remoteWindowSizingConfig.bounds.y1, x2 + remoteWindowSizingConfig.bounds.x2, y2 + remoteWindowSizingConfig.bounds.y2 }; remoteMessages.RequestWindowNotifySetBounds(bounds); sizingConfigInvalidated = true; } } NativeSize GuiRemoteWindow::GetClientSize() { if (sizingConfigInvalidated) RequestGetBounds(); return remoteWindowSizingConfig.clientBounds.GetSize(); } void GuiRemoteWindow::SetClientSize(NativeSize size) { if (remoteWindowSizingConfig.clientBounds.GetSize() != size) { auto x1 = remoteWindowSizingConfig.bounds.x1 - remoteWindowSizingConfig.clientBounds.x1; auto y1 = remoteWindowSizingConfig.bounds.y1 - remoteWindowSizingConfig.clientBounds.y1; auto x2 = remoteWindowSizingConfig.bounds.x2 - remoteWindowSizingConfig.clientBounds.x2; auto y2 = remoteWindowSizingConfig.bounds.y2 - remoteWindowSizingConfig.clientBounds.y2; remoteWindowSizingConfig.clientBounds = { remoteWindowSizingConfig.clientBounds.LeftTop(),size }; remoteWindowSizingConfig.bounds = { x1 + remoteWindowSizingConfig.clientBounds.x1, y1 + remoteWindowSizingConfig.clientBounds.y1, x2 + remoteWindowSizingConfig.clientBounds.x2, y2 + remoteWindowSizingConfig.clientBounds.y2 }; remoteMessages.RequestWindowNotifySetClientSize(size); sizingConfigInvalidated = true; } } NativeRect GuiRemoteWindow::GetClientBoundsInScreen() { auto bounds = remoteWindowSizingConfig.clientBounds; bounds.x1.value += remoteWindowSizingConfig.bounds.x1.value; bounds.y1.value += remoteWindowSizingConfig.bounds.y1.value; bounds.x2.value += remoteWindowSizingConfig.bounds.x1.value; bounds.y2.value += remoteWindowSizingConfig.bounds.y1.value; return bounds; } WString GuiRemoteWindow::GetTitle() { return styleTitle; } void GuiRemoteWindow::SetTitle(const WString& title) { SET_REMOTE_WINDOW_STYLE(Title, title); } INativeCursor* GuiRemoteWindow::GetWindowCursor() { if (!styleCursor) { styleCursor = remote->ResourceService()->GetDefaultSystemCursor(); } return styleCursor; } void GuiRemoteWindow::SetWindowCursor(INativeCursor* cursor) { styleCursor = cursor; } NativePoint GuiRemoteWindow::GetCaretPoint() { return styleCaret; } void GuiRemoteWindow::SetCaretPoint(NativePoint point) { styleCaret = point; } INativeWindow* GuiRemoteWindow::GetParent() { return nullptr; } void GuiRemoteWindow::SetParent(INativeWindow* parent) { CHECK_FAIL(L"vl::presentation::GuiRemoteWindow::SetParent(INativeWindow*)#GuiHostedController is not supposed to call this."); } INativeWindow::WindowMode GuiRemoteWindow::GetWindowMode() { return windowMode; } void GuiRemoteWindow::EnableCustomFrameMode() { SET_REMOTE_WINDOW_STYLE_INVALIDATE(CustomFrameMode, true); } void GuiRemoteWindow::DisableCustomFrameMode() { SET_REMOTE_WINDOW_STYLE_INVALIDATE(CustomFrameMode, false); } bool GuiRemoteWindow::IsCustomFrameModeEnabled() { return styleCustomFrameMode; } NativeMargin GuiRemoteWindow::GetCustomFramePadding() { return remoteWindowSizingConfig.customFramePadding; } Ptr GuiRemoteWindow::GetIcon() { return styleIcon; } void GuiRemoteWindow::SetIcon(Ptr icon) { styleIcon = icon; } INativeWindow::WindowSizeState GuiRemoteWindow::GetSizeState() { return remoteWindowSizingConfig.sizeState; } void GuiRemoteWindow::Show() { ShowWithSizeState(true, remoteWindowSizingConfig.sizeState); } void GuiRemoteWindow::ShowDeactivated() { ShowWithSizeState(false, remoteWindowSizingConfig.sizeState); } void GuiRemoteWindow::ShowRestored() { ShowWithSizeState(true, INativeWindow::Restored); } void GuiRemoteWindow::ShowMaximized() { ShowWithSizeState(true, INativeWindow::Maximized); } void GuiRemoteWindow::ShowMinimized() { ShowWithSizeState(true, INativeWindow::Minimized); } void GuiRemoteWindow::Hide(bool closeWindow) { if (!remote->connectionForcedToStop) { bool cancel = false; for (auto l : listeners) { l->BeforeClosing(cancel); if (cancel) return; } for (auto l : listeners) l->AfterClosing(); } remote->AsyncService()->InvokeInMainThread(this, [this]() { remote->DestroyNativeWindow(this); }); } bool GuiRemoteWindow::IsVisible() { return statusVisible; } void GuiRemoteWindow::Enable() { if (styleEnabled != true) { styleEnabled = true; remoteMessages.RequestWindowNotifySetEnabled(true); for (auto l : listeners) l->Enabled(); } } void GuiRemoteWindow::Disable() { if (styleEnabled != false) { styleEnabled = false; remoteMessages.RequestWindowNotifySetEnabled(false); for (auto l : listeners) l->Disabled(); } } bool GuiRemoteWindow::IsEnabled() { return styleEnabled; } void GuiRemoteWindow::SetActivate() { if (statusActivated != true) { SetActivated(true); remoteMessages.RequestWindowNotifyActivate(); } } bool GuiRemoteWindow::IsActivated() { return statusActivated; } bool GuiRemoteWindow::IsRenderingAsActivated() { return statusActivated; } void GuiRemoteWindow::ShowInTaskBar() { SET_REMOTE_WINDOW_STYLE(ShowInTaskBar, true); } void GuiRemoteWindow::HideInTaskBar() { SET_REMOTE_WINDOW_STYLE(ShowInTaskBar, false); } bool GuiRemoteWindow::IsAppearedInTaskBar() { return styleShowInTaskBar; } void GuiRemoteWindow::EnableActivate() { CHECK_FAIL(L"vl::presentation::GuiRemoteWindow::EnableActivate()#GuiHostedController is not supposed to call this."); } void GuiRemoteWindow::DisableActivate() { CHECK_FAIL(L"vl::presentation::GuiRemoteWindow::EnableActivate()#GuiHostedController is not supposed to call this."); } bool GuiRemoteWindow::IsEnabledActivate() { return true; } bool GuiRemoteWindow::RequireCapture() { if (!statusCapturing) { statusCapturing = true; remoteMessages.RequestIORequireCapture(); bool disconnected = false; remoteMessages.Submit(disconnected); // there is no result from this request, assuming succeeded } return true; } bool GuiRemoteWindow::ReleaseCapture() { if (statusCapturing) { statusCapturing = false; remoteMessages.RequestIOReleaseCapture(); bool disconnected = false; remoteMessages.Submit(disconnected); // there is no result from this request, assuming succeeded } return true; } bool GuiRemoteWindow::IsCapturing() { return statusCapturing; } bool GuiRemoteWindow::GetMaximizedBox() { return styleMaximizedBox; } void GuiRemoteWindow::SetMaximizedBox(bool visible) { SET_REMOTE_WINDOW_STYLE_INVALIDATE(MaximizedBox, visible); } bool GuiRemoteWindow::GetMinimizedBox() { return styleMinimizedBox; } void GuiRemoteWindow::SetMinimizedBox(bool visible) { SET_REMOTE_WINDOW_STYLE_INVALIDATE(MinimizedBox, visible); } bool GuiRemoteWindow::GetBorder() { return styleBorder; } void GuiRemoteWindow::SetBorder(bool visible) { SET_REMOTE_WINDOW_STYLE_INVALIDATE(Border, visible); } bool GuiRemoteWindow::GetSizeBox() { return styleSizeBox; } void GuiRemoteWindow::SetSizeBox(bool visible) { SET_REMOTE_WINDOW_STYLE_INVALIDATE(SizeBox, visible); } bool GuiRemoteWindow::GetIconVisible() { return styleIconVisible; } void GuiRemoteWindow::SetIconVisible(bool visible) { SET_REMOTE_WINDOW_STYLE_INVALIDATE(IconVisible, visible); } bool GuiRemoteWindow::GetTitleBar() { return styleTitleBar; } void GuiRemoteWindow::SetTitleBar(bool visible) { SET_REMOTE_WINDOW_STYLE_INVALIDATE(TitleBar, visible); } bool GuiRemoteWindow::GetTopMost() { return styleTopMost; } void GuiRemoteWindow::SetTopMost(bool topmost) { SET_REMOTE_WINDOW_STYLE(TopMost, topmost); } void GuiRemoteWindow::SupressAlt() { } bool GuiRemoteWindow::InstallListener(INativeWindowListener* listener) { if (listeners.Contains(listener)) { return false; } else { listeners.Add(listener); return true; } } bool GuiRemoteWindow::UninstallListener(INativeWindowListener* listener) { if (listeners.Contains(listener)) { listeners.Remove(listener); return true; } else { return false; } } void GuiRemoteWindow::RedrawContent() { } #undef SET_REMOTE_WINDOW_STYLE_INVALIDATE #undef SET_REMOTE_WINDOW_STYLE } /*********************************************************************** .\PLATFORMPROVIDERS\REMOTE\PROTOCOL\FRAMEOPERATIONS\GUIREMOTEPROTOCOLSCHEMA_BUILDFRAME.CPP ***********************************************************************/ namespace vl::presentation::remoteprotocol { vint RenderingDomBuilder::GetCurrentBoundary() { if (domBoundaries.Count() > 0) { return domBoundaries[domBoundaries.Count() - 1]; } else { return 0; } } vint RenderingDomBuilder::Push(RenderingResultRef ref) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::RenderingDomBuilder::Push(RenderingResultRef)#" CHECK_ERROR(ref, ERROR_MESSAGE_PREFIX L"Cannot push a null dom object."); vint index = domStack.Add(ref); if (!domCurrent->children) domCurrent->children = Ptr(new RenderingResultRefList); domCurrent->children->Add(ref); domCurrent = ref; return index; #undef ERROR_MESSAGE_PREFIX } void RenderingDomBuilder::PopTo(vint index) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::RenderingDomBuilder::PopTo(vint)#" if (index == domStack.Count() - 1) return; CHECK_ERROR(0 <= index && index < domStack.Count(), ERROR_MESSAGE_PREFIX L"Cannot pop to an invalid position."); CHECK_ERROR(index >= GetCurrentBoundary(), ERROR_MESSAGE_PREFIX L"Cannot pop across a boundary."); while (domStack.Count() - 1 > index) { domStack.RemoveAt(domStack.Count() - 1); } domCurrent = domStack[index]; #undef ERROR_MESSAGE_PREFIX } void RenderingDomBuilder::Pop() { PopTo(domStack.Count() - 2); } void RenderingDomBuilder::PopBoundary() { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::RenderingDomBuilder::PopBoundary()#" CHECK_ERROR(domBoundaries.Count() > 0, ERROR_MESSAGE_PREFIX L"Cannot pop a boundary when none is in the stack."); auto boundaryIndex = domBoundaries.Count() - 1; auto boundary = domBoundaries[boundaryIndex]; domBoundaries.RemoveAt(boundaryIndex); PopTo(boundary - 1); #undef ERROR_MESSAGE_PREFIX } template void RenderingDomBuilder::PrepareParentFromCommand(Rect commandBounds, Rect commandValidArea, vint newDomId, TCallback&& calculateValidAreaFromDom) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::RenderingDomBuilder::PrepareParentFromCommand(Rect, Rect, vint, auto&&)#" vint min = GetCurrentBoundary(); bool found = false; if (commandValidArea.Contains(commandBounds)) { // if the command is not clipped for (vint i = domStack.Count() - 1; i >= min; i--) { if (domStack[i]->content.validArea.Contains(commandBounds) || i == 0) { // find the deepest node that could contain the command PopTo(i); found = true; break; } } } else { // otherwise, a parent node causing such clipping should be found or created for (vint i = domStack.Count() - 1; i >= min; i--) { auto domValidArea = calculateValidAreaFromDom(domStack[i]); if (domValidArea == commandValidArea) { // if there is a node who clips command's bound to its valid area // that is the parent node of the command PopTo(i); found = true; break; } else if (domValidArea.Contains(commandValidArea) || i == 0) { // otherwise find a deepest node who could visually contain the command // create a virtual node to satisfy the clipper PopTo(i); auto parent = Ptr(new RenderingDom); parent->id = newDomId; parent->content.bounds = commandValidArea; parent->content.validArea = commandValidArea; Push(parent); found = true; break; } } } // if the new boundary could not fit in the current boundary // there must be something wrong CHECK_ERROR(found, ERROR_MESSAGE_PREFIX L"Incorrect valid area of dom."); #undef ERROR_MESSAGE_PREFIX } void RenderingDomBuilder::RequestRendererBeginRendering() { domStack.Clear(); domBoundaries.Clear(); domRoot = Ptr(new RenderingDom); domRoot->id = -1; domCurrent = domRoot; domStack.Add(domRoot); } void RenderingDomBuilder::RequestRendererBeginBoundary(const remoteprotocol::ElementBoundary& arguments) { // a new boundary should be a new node covering existing nodes // the valid area of boundary is clipped by its bounds // so the valid area to compare from its potential parent dom needs to clipped by its bounds PrepareParentFromCommand( arguments.bounds, arguments.areaClippedBySelf, (arguments.id << 2) + 3, [&](auto&& dom) { return dom->content.validArea.Intersect(arguments.bounds); } ); auto dom = Ptr(new RenderingDom); dom->id = (arguments.id << 2) + 2; dom->content.hitTestResult = arguments.hitTestResult; dom->content.cursor = arguments.cursor; dom->content.bounds = arguments.bounds; dom->content.validArea = arguments.areaClippedBySelf; domBoundaries.Add(Push(dom)); } void RenderingDomBuilder::RequestRendererEndBoundary() { PopBoundary(); } void RenderingDomBuilder::RequestRendererRenderElement(const remoteprotocol::ElementRendering& arguments) { // a new element should be a new node covering existing nodes // the valid area of boundary is clipped by its parent // so the valid area to compare from its potential parent dom is its valid area PrepareParentFromCommand( arguments.bounds, arguments.areaClippedByParent, (arguments.id << 2) + 1, [&](auto&& dom) { return dom->content.validArea; } ); auto dom = Ptr(new RenderingDom); dom->id = (arguments.id << 2) + 0; dom->content.element = arguments.id; dom->content.bounds = arguments.bounds; dom->content.validArea = arguments.bounds.Intersect(arguments.areaClippedByParent); Push(dom); } Ptr RenderingDomBuilder::RequestRendererEndRendering() { return domRoot; } } /*********************************************************************** .\PLATFORMPROVIDERS\REMOTE\PROTOCOL\FRAMEOPERATIONS\GUIREMOTEPROTOCOLSCHEMA_COPYDOM.CPP ***********************************************************************/ namespace vl::presentation::remoteprotocol { using namespace collections; Ptr CopyDom(Ptr root) { auto newRoot = Ptr(new RenderingDom); newRoot->id = root->id; newRoot->content = root->content; if (root->children) { newRoot->children = Ptr(new List>); for (auto child : *root->children.Obj()) { newRoot->children->Add(CopyDom(child)); } } return newRoot; } } /*********************************************************************** .\PLATFORMPROVIDERS\REMOTE\PROTOCOL\FRAMEOPERATIONS\GUIREMOTEPROTOCOLSCHEMA_DIFFFRAME.CPP ***********************************************************************/ namespace vl::presentation::remoteprotocol { using namespace collections; vint CountDomIndex(Ptr root) { vint counter = 1; if (root->children) { for (auto child : *root->children.Obj()) { counter += CountDomIndex(child); } } return counter; } void BuildDomIndexInternal(Ptr dom, vint parentId, DomIndex& index, vint& writing) { index[writing++] = { dom->id,parentId,dom }; if (dom->children) { for (auto child : *dom->children.Obj()) { BuildDomIndexInternal(child, dom->id, index, writing); } } } void SortDomIndex(DomIndex& index) { if (index.Count() > 0) { SortLambda(&index[0], index.Count(), [](const DomIndexItem& a, const DomIndexItem& b) { return a.id <=> b.id; }); } } void BuildDomIndex(Ptr root, DomIndex& index) { vint count = CountDomIndex(root); vint writing = 0; index.Resize(count); if (count > 0) { BuildDomIndexInternal(root, -1, index, writing); SortDomIndex(index); } } void UpdateDomInplace(Ptr root, DomIndex& index, const RenderingDom_DiffsInOrder& diffs) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::UpdateDomInplace(Ptr, DomIndex&, const RenderingDom_DiffsInOrder&)#" CHECK_ERROR(root && root->id == -1, ERROR_MESSAGE_PREFIX L"Roots of a DOM must have ID -1."); vint createdCount = 0; // creating { vint readingFrom = 0; vint readingTo = 0; auto markCreated = [&]() { auto&& to = diffs.diffsInOrder->Get(readingTo++); CHECK_ERROR(to.diffType == RenderingDom_DiffType::Created, ERROR_MESSAGE_PREFIX L"Diff of unexisting node must have diffType == Created."); createdCount++; }; while (diffs.diffsInOrder && readingTo < diffs.diffsInOrder->Count()) { if (readingFrom < index.Count()) { auto&& from = index[readingFrom]; auto&& to = diffs.diffsInOrder->Get(readingTo); if (from.id < to.id) { // Nothing happened to this DOM node readingFrom++; } else if (from.id > to.id) { markCreated(); } else { // Modified will be delayed and processed together with Created readingFrom++; readingTo++; CHECK_ERROR(to.diffType != RenderingDom_DiffType::Created, ERROR_MESSAGE_PREFIX L"Diff of existing node must have diffType != Created."); } } else { markCreated(); } } } { vint writing = index.Count(); index.Resize(index.Count() + createdCount); if (diffs.diffsInOrder) { for (auto&& to : *diffs.diffsInOrder.Obj()) { if (to.diffType == RenderingDom_DiffType::Created) { // parentId will be filled later auto dom = Ptr(new RenderingDom); dom->id = to.id; index[writing++] = { to.id,-1,dom }; } } } SortDomIndex(index); } // modifying { vint readingFrom = 0; vint readingTo = 0; while (readingFrom < index.Count() && (diffs.diffsInOrder && readingTo < diffs.diffsInOrder->Count())) { bool hasFrom = readingFrom < index.Count(); bool hasTo = diffs.diffsInOrder && readingTo < diffs.diffsInOrder->Count(); auto&& from = index[readingFrom]; auto&& to = diffs.diffsInOrder->Get(readingTo); if (from.id < to.id) { readingFrom++; } else if (from.id > to.id) { readingTo++; } else { readingFrom++; readingTo++; if (to.diffType != RenderingDom_DiffType::Deleted) { if (to.content) { from.dom->content = to.content.Value(); } if (to.children) { if (to.children->Count() == 0) { from.dom->children = nullptr; } else { from.dom->children = Ptr(new List>); for (vint childId : *to.children.Obj()) { vint indexToInsert = 0; vint indexOfChild = BinarySearchLambda( &index[0], index.Count(), childId, indexToInsert, [](const DomIndexItem& item, vint id) { return item.id <=> id; } ); CHECK_ERROR(indexOfChild != -1, ERROR_MESSAGE_PREFIX L"Unknown DOM id in diff."); index[indexOfChild].parentId = from.id; from.dom->children->Add(index[indexOfChild].dom); } } } } } } } // deleting { vint readingFrom = 0; vint readingTo = 0; List deleteIndices; while (diffs.diffsInOrder && readingTo < diffs.diffsInOrder->Count()) { if (readingFrom < index.Count()) { auto&& from = index[readingFrom]; auto&& to = diffs.diffsInOrder->Get(readingTo); if (from.id < to.id) { readingFrom++; } else if (from.id > to.id) { readingTo++; } else { if (to.diffType == RenderingDom_DiffType::Deleted) { deleteIndices.Add(readingFrom); } readingFrom++; readingTo++; } } else { CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Nodes to be deleted must should appear in the index before modification"); } } vint reading = 0; vint writing = 0; vint testing = 0; while (reading < index.Count()) { if (testing < deleteIndices.Count() && deleteIndices[testing] == reading) { // A node to delete is found, mark and skip testing++; reading++; } else { if (reading != writing) { // Compact index by removing deleted entries index[writing] = index[reading]; } reading++; writing++; } } index.Resize(index.Count() - deleteIndices.Count()); } #undef ERROR_MESSAGE_PREFIX } void DiffDom(Ptr domFrom, DomIndex& indexFrom, Ptr domTo, DomIndex& indexTo, RenderingDom_DiffsInOrder& diffs) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::DiffDom(Ptr, DomIndex&, Ptr, DomIndex&, RenderingDom_DiffsInOrder&)#" CHECK_ERROR(domFrom && domTo && domFrom->id == domTo->id, ERROR_MESSAGE_PREFIX L"Roots of two DOMs tree must have the same ID."); diffs.diffsInOrder = Ptr(new List); vint readingFrom = 0; vint readingTo = 0; auto pushDeleted = [&]() { auto&& dom = indexFrom[readingFrom++].dom; RenderingDom_Diff diff; diff.id = dom->id; diff.diffType = RenderingDom_DiffType::Deleted; diffs.diffsInOrder->Add(diff); }; auto pushCreated = [&]() { auto&& dom = indexTo[readingTo++].dom; RenderingDom_Diff diff; diff.id = dom->id; diff.diffType = RenderingDom_DiffType::Created; diff.content = dom->content; if (dom->children && dom->children->Count() > 0) { diff.children = Ptr(new List); CopyFrom( *diff.children.Obj(), From(*dom->children.Obj()) .Select([](Ptr child) { return child->id; }) ); } diffs.diffsInOrder->Add(diff); }; auto pushModified = [&]() { auto&& domFrom = indexFrom[readingFrom++].dom; auto&& domTo = indexTo[readingTo++].dom; RenderingDom_Diff diff; diff.id = domFrom->id; diff.diffType = RenderingDom_DiffType::Modified; if ( domFrom->content.hitTestResult != domTo->content.hitTestResult || domFrom->content.cursor != domTo->content.cursor || domFrom->content.element != domTo->content.element || domFrom->content.bounds != domTo->content.bounds || domFrom->content.validArea != domTo->content.validArea ) { diff.content = domTo->content; } bool fromHasChild = domFrom->children && domFrom->children->Count() > 0; bool toHasChild = domTo->children && domTo->children->Count() > 0; bool childDifferent = false; if (fromHasChild != toHasChild) { childDifferent = true; } else if (fromHasChild && toHasChild) { auto fromIds = From(*domFrom->children.Obj()) .Select([](Ptr child) { return child->id; }); auto toIds = From(*domTo->children.Obj()) .Select([](Ptr child) { return child->id; }); childDifferent = CompareEnumerable(fromIds, toIds) != 0; } if (childDifferent) { diff.children = Ptr(new List); if (toHasChild) { CopyFrom( *diff.children.Obj(), From(*domTo->children.Obj()) .Select([](Ptr child) { return child->id; }) ); } } if (diff.content || diff.children) { diffs.diffsInOrder->Add(diff); } }; while (true) { if (readingFrom < indexFrom.Count() && readingTo < indexTo.Count()) { if (indexFrom[readingFrom].id < indexTo[readingTo].id) { pushDeleted(); } else if (indexFrom[readingFrom].id > indexTo[readingTo].id) { pushCreated(); } else { pushModified(); } } else if (readingFrom < indexFrom.Count()) { pushDeleted(); } else if (readingTo < indexTo.Count()) { pushCreated(); } else { break; } } #undef ERROR_MESSAGE_PREFIX } } /*********************************************************************** .\PLATFORMPROVIDERS\REMOTE\PROTOCOL\GENERATED\GUIREMOTEPROTOCOLSCHEMA.CPP ***********************************************************************/ /*********************************************************************** This file is generated by : Vczh GacUI Remote Protocol Generator Licensed under https ://github.com/vczh-libraries/License ***********************************************************************/ namespace vl::presentation::remoteprotocol { template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::INativeWindowListener::HitTestResult>(const ::vl::presentation::INativeWindowListener::HitTestResult & value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertCustomTypeToJson<::vl::presentation::INativeWindowListener::HitTestResult>(const ::vl::presentation::INativeWindowListener::HitTestResult&)#" auto node = Ptr(new glr::json::JsonString); switch (value) { case ::vl::presentation::INativeWindowListener::BorderNoSizing: node->content.value = WString::Unmanaged(L"BorderNoSizing"); break; case ::vl::presentation::INativeWindowListener::BorderLeft: node->content.value = WString::Unmanaged(L"BorderLeft"); break; case ::vl::presentation::INativeWindowListener::BorderRight: node->content.value = WString::Unmanaged(L"BorderRight"); break; case ::vl::presentation::INativeWindowListener::BorderTop: node->content.value = WString::Unmanaged(L"BorderTop"); break; case ::vl::presentation::INativeWindowListener::BorderBottom: node->content.value = WString::Unmanaged(L"BorderBottom"); break; case ::vl::presentation::INativeWindowListener::BorderLeftTop: node->content.value = WString::Unmanaged(L"BorderLeftTop"); break; case ::vl::presentation::INativeWindowListener::BorderRightTop: node->content.value = WString::Unmanaged(L"BorderRightTop"); break; case ::vl::presentation::INativeWindowListener::BorderLeftBottom: node->content.value = WString::Unmanaged(L"BorderLeftBottom"); break; case ::vl::presentation::INativeWindowListener::BorderRightBottom: node->content.value = WString::Unmanaged(L"BorderRightBottom"); break; case ::vl::presentation::INativeWindowListener::Title: node->content.value = WString::Unmanaged(L"Title"); break; case ::vl::presentation::INativeWindowListener::ButtonMinimum: node->content.value = WString::Unmanaged(L"ButtonMinimum"); break; case ::vl::presentation::INativeWindowListener::ButtonMaximum: node->content.value = WString::Unmanaged(L"ButtonMaximum"); break; case ::vl::presentation::INativeWindowListener::ButtonClose: node->content.value = WString::Unmanaged(L"ButtonClose"); break; case ::vl::presentation::INativeWindowListener::Client: node->content.value = WString::Unmanaged(L"Client"); break; case ::vl::presentation::INativeWindowListener::Icon: node->content.value = WString::Unmanaged(L"Icon"); break; case ::vl::presentation::INativeWindowListener::NoDecision: node->content.value = WString::Unmanaged(L"NoDecision"); break; default: CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported enum value."); } return node; #undef ERROR_MESSAGE_PREFIX } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::INativeCursor::SystemCursorType>(const ::vl::presentation::INativeCursor::SystemCursorType & value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertCustomTypeToJson<::vl::presentation::INativeCursor::SystemCursorType>(const ::vl::presentation::INativeCursor::SystemCursorType&)#" auto node = Ptr(new glr::json::JsonString); switch (value) { case ::vl::presentation::INativeCursor::SmallWaiting: node->content.value = WString::Unmanaged(L"SmallWaiting"); break; case ::vl::presentation::INativeCursor::LargeWaiting: node->content.value = WString::Unmanaged(L"LargeWaiting"); break; case ::vl::presentation::INativeCursor::Arrow: node->content.value = WString::Unmanaged(L"Arrow"); break; case ::vl::presentation::INativeCursor::Cross: node->content.value = WString::Unmanaged(L"Cross"); break; case ::vl::presentation::INativeCursor::Hand: node->content.value = WString::Unmanaged(L"Hand"); break; case ::vl::presentation::INativeCursor::Help: node->content.value = WString::Unmanaged(L"Help"); break; case ::vl::presentation::INativeCursor::IBeam: node->content.value = WString::Unmanaged(L"IBeam"); break; case ::vl::presentation::INativeCursor::SizeAll: node->content.value = WString::Unmanaged(L"SizeAll"); break; case ::vl::presentation::INativeCursor::SizeNESW: node->content.value = WString::Unmanaged(L"SizeNESW"); break; case ::vl::presentation::INativeCursor::SizeNS: node->content.value = WString::Unmanaged(L"SizeNS"); break; case ::vl::presentation::INativeCursor::SizeNWSE: node->content.value = WString::Unmanaged(L"SizeNWSE"); break; case ::vl::presentation::INativeCursor::SizeWE: node->content.value = WString::Unmanaged(L"SizeWE"); break; default: CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported enum value."); } return node; #undef ERROR_MESSAGE_PREFIX } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::INativeWindow::WindowSizeState>(const ::vl::presentation::INativeWindow::WindowSizeState & value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertCustomTypeToJson<::vl::presentation::INativeWindow::WindowSizeState>(const ::vl::presentation::INativeWindow::WindowSizeState&)#" auto node = Ptr(new glr::json::JsonString); switch (value) { case ::vl::presentation::INativeWindow::WindowSizeState::Minimized: node->content.value = WString::Unmanaged(L"Minimized"); break; case ::vl::presentation::INativeWindow::WindowSizeState::Restored: node->content.value = WString::Unmanaged(L"Restored"); break; case ::vl::presentation::INativeWindow::WindowSizeState::Maximized: node->content.value = WString::Unmanaged(L"Maximized"); break; default: CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported enum value."); } return node; #undef ERROR_MESSAGE_PREFIX } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::IOMouseButton>(const ::vl::presentation::remoteprotocol::IOMouseButton & value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::IOMouseButton>(const ::vl::presentation::remoteprotocol::IOMouseButton&)#" auto node = Ptr(new glr::json::JsonString); switch (value) { case ::vl::presentation::remoteprotocol::IOMouseButton::Left: node->content.value = WString::Unmanaged(L"Left"); break; case ::vl::presentation::remoteprotocol::IOMouseButton::Middle: node->content.value = WString::Unmanaged(L"Middle"); break; case ::vl::presentation::remoteprotocol::IOMouseButton::Right: node->content.value = WString::Unmanaged(L"Right"); break; default: CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported enum value."); } return node; #undef ERROR_MESSAGE_PREFIX } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::elements::ElementShapeType>(const ::vl::presentation::elements::ElementShapeType & value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertCustomTypeToJson<::vl::presentation::elements::ElementShapeType>(const ::vl::presentation::elements::ElementShapeType&)#" auto node = Ptr(new glr::json::JsonString); switch (value) { case ::vl::presentation::elements::ElementShapeType::Rectangle: node->content.value = WString::Unmanaged(L"Rectangle"); break; case ::vl::presentation::elements::ElementShapeType::Ellipse: node->content.value = WString::Unmanaged(L"Ellipse"); break; case ::vl::presentation::elements::ElementShapeType::RoundRect: node->content.value = WString::Unmanaged(L"RoundRect"); break; default: CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported enum value."); } return node; #undef ERROR_MESSAGE_PREFIX } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::elements::GuiGradientBackgroundElement::Direction>(const ::vl::presentation::elements::GuiGradientBackgroundElement::Direction & value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertCustomTypeToJson<::vl::presentation::elements::GuiGradientBackgroundElement::Direction>(const ::vl::presentation::elements::GuiGradientBackgroundElement::Direction&)#" auto node = Ptr(new glr::json::JsonString); switch (value) { case ::vl::presentation::elements::GuiGradientBackgroundElement::Horizontal: node->content.value = WString::Unmanaged(L"Horizontal"); break; case ::vl::presentation::elements::GuiGradientBackgroundElement::Vertical: node->content.value = WString::Unmanaged(L"Vertical"); break; case ::vl::presentation::elements::GuiGradientBackgroundElement::Slash: node->content.value = WString::Unmanaged(L"Slash"); break; case ::vl::presentation::elements::GuiGradientBackgroundElement::Backslash: node->content.value = WString::Unmanaged(L"Backslash"); break; default: CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported enum value."); } return node; #undef ERROR_MESSAGE_PREFIX } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::elements::Gui3DSplitterElement::Direction>(const ::vl::presentation::elements::Gui3DSplitterElement::Direction & value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertCustomTypeToJson<::vl::presentation::elements::Gui3DSplitterElement::Direction>(const ::vl::presentation::elements::Gui3DSplitterElement::Direction&)#" auto node = Ptr(new glr::json::JsonString); switch (value) { case ::vl::presentation::elements::Gui3DSplitterElement::Horizontal: node->content.value = WString::Unmanaged(L"Horizontal"); break; case ::vl::presentation::elements::Gui3DSplitterElement::Vertical: node->content.value = WString::Unmanaged(L"Vertical"); break; default: CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported enum value."); } return node; #undef ERROR_MESSAGE_PREFIX } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::ElementHorizontalAlignment>(const ::vl::presentation::remoteprotocol::ElementHorizontalAlignment & value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::ElementHorizontalAlignment>(const ::vl::presentation::remoteprotocol::ElementHorizontalAlignment&)#" auto node = Ptr(new glr::json::JsonString); switch (value) { case ::vl::presentation::remoteprotocol::ElementHorizontalAlignment::Left: node->content.value = WString::Unmanaged(L"Left"); break; case ::vl::presentation::remoteprotocol::ElementHorizontalAlignment::Right: node->content.value = WString::Unmanaged(L"Right"); break; case ::vl::presentation::remoteprotocol::ElementHorizontalAlignment::Center: node->content.value = WString::Unmanaged(L"Center"); break; default: CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported enum value."); } return node; #undef ERROR_MESSAGE_PREFIX } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::ElementVerticalAlignment>(const ::vl::presentation::remoteprotocol::ElementVerticalAlignment & value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::ElementVerticalAlignment>(const ::vl::presentation::remoteprotocol::ElementVerticalAlignment&)#" auto node = Ptr(new glr::json::JsonString); switch (value) { case ::vl::presentation::remoteprotocol::ElementVerticalAlignment::Top: node->content.value = WString::Unmanaged(L"Top"); break; case ::vl::presentation::remoteprotocol::ElementVerticalAlignment::Bottom: node->content.value = WString::Unmanaged(L"Bottom"); break; case ::vl::presentation::remoteprotocol::ElementVerticalAlignment::Center: node->content.value = WString::Unmanaged(L"Center"); break; default: CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported enum value."); } return node; #undef ERROR_MESSAGE_PREFIX } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::ElementSolidLabelMeasuringRequest>(const ::vl::presentation::remoteprotocol::ElementSolidLabelMeasuringRequest & value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::ElementSolidLabelMeasuringRequest>(const ::vl::presentation::remoteprotocol::ElementSolidLabelMeasuringRequest&)#" auto node = Ptr(new glr::json::JsonString); switch (value) { case ::vl::presentation::remoteprotocol::ElementSolidLabelMeasuringRequest::FontHeight: node->content.value = WString::Unmanaged(L"FontHeight"); break; case ::vl::presentation::remoteprotocol::ElementSolidLabelMeasuringRequest::TotalSize: node->content.value = WString::Unmanaged(L"TotalSize"); break; default: CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported enum value."); } return node; #undef ERROR_MESSAGE_PREFIX } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::INativeImage::FormatType>(const ::vl::presentation::INativeImage::FormatType & value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertCustomTypeToJson<::vl::presentation::INativeImage::FormatType>(const ::vl::presentation::INativeImage::FormatType&)#" auto node = Ptr(new glr::json::JsonString); switch (value) { case ::vl::presentation::INativeImage::Bmp: node->content.value = WString::Unmanaged(L"Bmp"); break; case ::vl::presentation::INativeImage::Gif: node->content.value = WString::Unmanaged(L"Gif"); break; case ::vl::presentation::INativeImage::Icon: node->content.value = WString::Unmanaged(L"Icon"); break; case ::vl::presentation::INativeImage::Jpeg: node->content.value = WString::Unmanaged(L"Jpeg"); break; case ::vl::presentation::INativeImage::Png: node->content.value = WString::Unmanaged(L"Png"); break; case ::vl::presentation::INativeImage::Tiff: node->content.value = WString::Unmanaged(L"Tiff"); break; case ::vl::presentation::INativeImage::Wmp: node->content.value = WString::Unmanaged(L"Wmp"); break; case ::vl::presentation::INativeImage::Unknown: node->content.value = WString::Unmanaged(L"Unknown"); break; default: CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported enum value."); } return node; #undef ERROR_MESSAGE_PREFIX } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::RendererType>(const ::vl::presentation::remoteprotocol::RendererType & value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::RendererType>(const ::vl::presentation::remoteprotocol::RendererType&)#" auto node = Ptr(new glr::json::JsonString); switch (value) { case ::vl::presentation::remoteprotocol::RendererType::FocusRectangle: node->content.value = WString::Unmanaged(L"FocusRectangle"); break; case ::vl::presentation::remoteprotocol::RendererType::SolidBorder: node->content.value = WString::Unmanaged(L"SolidBorder"); break; case ::vl::presentation::remoteprotocol::RendererType::SinkBorder: node->content.value = WString::Unmanaged(L"SinkBorder"); break; case ::vl::presentation::remoteprotocol::RendererType::SinkSplitter: node->content.value = WString::Unmanaged(L"SinkSplitter"); break; case ::vl::presentation::remoteprotocol::RendererType::SolidBackground: node->content.value = WString::Unmanaged(L"SolidBackground"); break; case ::vl::presentation::remoteprotocol::RendererType::GradientBackground: node->content.value = WString::Unmanaged(L"GradientBackground"); break; case ::vl::presentation::remoteprotocol::RendererType::InnerShadow: node->content.value = WString::Unmanaged(L"InnerShadow"); break; case ::vl::presentation::remoteprotocol::RendererType::SolidLabel: node->content.value = WString::Unmanaged(L"SolidLabel"); break; case ::vl::presentation::remoteprotocol::RendererType::Polygon: node->content.value = WString::Unmanaged(L"Polygon"); break; case ::vl::presentation::remoteprotocol::RendererType::ImageFrame: node->content.value = WString::Unmanaged(L"ImageFrame"); break; case ::vl::presentation::remoteprotocol::RendererType::UnsupportedColorizedText: node->content.value = WString::Unmanaged(L"UnsupportedColorizedText"); break; case ::vl::presentation::remoteprotocol::RendererType::UnsupportedDocument: node->content.value = WString::Unmanaged(L"UnsupportedDocument"); break; default: CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported enum value."); } return node; #undef ERROR_MESSAGE_PREFIX } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::RenderingDom_DiffType>(const ::vl::presentation::remoteprotocol::RenderingDom_DiffType & value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::RenderingDom_DiffType>(const ::vl::presentation::remoteprotocol::RenderingDom_DiffType&)#" auto node = Ptr(new glr::json::JsonString); switch (value) { case ::vl::presentation::remoteprotocol::RenderingDom_DiffType::Deleted: node->content.value = WString::Unmanaged(L"Deleted"); break; case ::vl::presentation::remoteprotocol::RenderingDom_DiffType::Created: node->content.value = WString::Unmanaged(L"Created"); break; case ::vl::presentation::remoteprotocol::RenderingDom_DiffType::Modified: node->content.value = WString::Unmanaged(L"Modified"); break; default: CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported enum value."); } return node; #undef ERROR_MESSAGE_PREFIX } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::NativeCoordinate>(const ::vl::presentation::NativeCoordinate & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"value", value.value); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::NativePoint>(const ::vl::presentation::NativePoint & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"x", value.x); ConvertCustomTypeToJsonField(node, L"y", value.y); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::NativeSize>(const ::vl::presentation::NativeSize & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"x", value.x); ConvertCustomTypeToJsonField(node, L"y", value.y); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::NativeRect>(const ::vl::presentation::NativeRect & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"x1", value.x1); ConvertCustomTypeToJsonField(node, L"y1", value.y1); ConvertCustomTypeToJsonField(node, L"x2", value.x2); ConvertCustomTypeToJsonField(node, L"y2", value.y2); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::NativeMargin>(const ::vl::presentation::NativeMargin & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"left", value.left); ConvertCustomTypeToJsonField(node, L"top", value.top); ConvertCustomTypeToJsonField(node, L"right", value.right); ConvertCustomTypeToJsonField(node, L"bottom", value.bottom); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::Point>(const ::vl::presentation::Point & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"x", value.x); ConvertCustomTypeToJsonField(node, L"y", value.y); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::Size>(const ::vl::presentation::Size & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"x", value.x); ConvertCustomTypeToJsonField(node, L"y", value.y); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::Rect>(const ::vl::presentation::Rect & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"x1", value.x1); ConvertCustomTypeToJsonField(node, L"y1", value.y1); ConvertCustomTypeToJsonField(node, L"x2", value.x2); ConvertCustomTypeToJsonField(node, L"y2", value.y2); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::FontProperties>(const ::vl::presentation::FontProperties & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"fontFamily", value.fontFamily); ConvertCustomTypeToJsonField(node, L"size", value.size); ConvertCustomTypeToJsonField(node, L"bold", value.bold); ConvertCustomTypeToJsonField(node, L"italic", value.italic); ConvertCustomTypeToJsonField(node, L"underline", value.underline); ConvertCustomTypeToJsonField(node, L"strikeline", value.strikeline); ConvertCustomTypeToJsonField(node, L"antialias", value.antialias); ConvertCustomTypeToJsonField(node, L"verticalAntialias", value.verticalAntialias); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::FontConfig>(const ::vl::presentation::remoteprotocol::FontConfig & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"defaultFont", value.defaultFont); ConvertCustomTypeToJsonField(node, L"supportedFonts", value.supportedFonts); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::ScreenConfig>(const ::vl::presentation::remoteprotocol::ScreenConfig & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"bounds", value.bounds); ConvertCustomTypeToJsonField(node, L"clientBounds", value.clientBounds); ConvertCustomTypeToJsonField(node, L"scalingX", value.scalingX); ConvertCustomTypeToJsonField(node, L"scalingY", value.scalingY); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::WindowSizingConfig>(const ::vl::presentation::remoteprotocol::WindowSizingConfig & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"bounds", value.bounds); ConvertCustomTypeToJsonField(node, L"clientBounds", value.clientBounds); ConvertCustomTypeToJsonField(node, L"sizeState", value.sizeState); ConvertCustomTypeToJsonField(node, L"customFramePadding", value.customFramePadding); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::WindowShowing>(const ::vl::presentation::remoteprotocol::WindowShowing & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"activate", value.activate); ConvertCustomTypeToJsonField(node, L"sizeState", value.sizeState); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::NativeWindowMouseInfo>(const ::vl::presentation::NativeWindowMouseInfo & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"ctrl", value.ctrl); ConvertCustomTypeToJsonField(node, L"shift", value.shift); ConvertCustomTypeToJsonField(node, L"left", value.left); ConvertCustomTypeToJsonField(node, L"middle", value.middle); ConvertCustomTypeToJsonField(node, L"right", value.right); ConvertCustomTypeToJsonField(node, L"x", value.x); ConvertCustomTypeToJsonField(node, L"y", value.y); ConvertCustomTypeToJsonField(node, L"wheel", value.wheel); ConvertCustomTypeToJsonField(node, L"nonClient", value.nonClient); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::IOMouseInfoWithButton>(const ::vl::presentation::remoteprotocol::IOMouseInfoWithButton & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"button", value.button); ConvertCustomTypeToJsonField(node, L"info", value.info); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::NativeWindowKeyInfo>(const ::vl::presentation::NativeWindowKeyInfo & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"code", value.code); ConvertCustomTypeToJsonField(node, L"ctrl", value.ctrl); ConvertCustomTypeToJsonField(node, L"shift", value.shift); ConvertCustomTypeToJsonField(node, L"alt", value.alt); ConvertCustomTypeToJsonField(node, L"capslock", value.capslock); ConvertCustomTypeToJsonField(node, L"autoRepeatKeyDown", value.autoRepeatKeyDown); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::NativeWindowCharInfo>(const ::vl::presentation::NativeWindowCharInfo & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"code", value.code); ConvertCustomTypeToJsonField(node, L"ctrl", value.ctrl); ConvertCustomTypeToJsonField(node, L"shift", value.shift); ConvertCustomTypeToJsonField(node, L"alt", value.alt); ConvertCustomTypeToJsonField(node, L"capslock", value.capslock); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::GlobalShortcutKey>(const ::vl::presentation::remoteprotocol::GlobalShortcutKey & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"id", value.id); ConvertCustomTypeToJsonField(node, L"ctrl", value.ctrl); ConvertCustomTypeToJsonField(node, L"shift", value.shift); ConvertCustomTypeToJsonField(node, L"alt", value.alt); ConvertCustomTypeToJsonField(node, L"code", value.code); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::elements::ElementShape>(const ::vl::presentation::elements::ElementShape & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"shapeType", value.shapeType); ConvertCustomTypeToJsonField(node, L"radiusX", value.radiusX); ConvertCustomTypeToJsonField(node, L"radiusY", value.radiusY); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::ElementDesc_SolidBorder>(const ::vl::presentation::remoteprotocol::ElementDesc_SolidBorder & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"id", value.id); ConvertCustomTypeToJsonField(node, L"borderColor", value.borderColor); ConvertCustomTypeToJsonField(node, L"shape", value.shape); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::ElementDesc_SinkBorder>(const ::vl::presentation::remoteprotocol::ElementDesc_SinkBorder & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"id", value.id); ConvertCustomTypeToJsonField(node, L"leftTopColor", value.leftTopColor); ConvertCustomTypeToJsonField(node, L"rightBottomColor", value.rightBottomColor); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::ElementDesc_SinkSplitter>(const ::vl::presentation::remoteprotocol::ElementDesc_SinkSplitter & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"id", value.id); ConvertCustomTypeToJsonField(node, L"leftTopColor", value.leftTopColor); ConvertCustomTypeToJsonField(node, L"rightBottomColor", value.rightBottomColor); ConvertCustomTypeToJsonField(node, L"direction", value.direction); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::ElementDesc_SolidBackground>(const ::vl::presentation::remoteprotocol::ElementDesc_SolidBackground & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"id", value.id); ConvertCustomTypeToJsonField(node, L"backgroundColor", value.backgroundColor); ConvertCustomTypeToJsonField(node, L"shape", value.shape); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::ElementDesc_GradientBackground>(const ::vl::presentation::remoteprotocol::ElementDesc_GradientBackground & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"id", value.id); ConvertCustomTypeToJsonField(node, L"leftTopColor", value.leftTopColor); ConvertCustomTypeToJsonField(node, L"rightBottomColor", value.rightBottomColor); ConvertCustomTypeToJsonField(node, L"direction", value.direction); ConvertCustomTypeToJsonField(node, L"shape", value.shape); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::ElementDesc_InnerShadow>(const ::vl::presentation::remoteprotocol::ElementDesc_InnerShadow & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"id", value.id); ConvertCustomTypeToJsonField(node, L"shadowColor", value.shadowColor); ConvertCustomTypeToJsonField(node, L"thickness", value.thickness); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::ElementDesc_Polygon>(const ::vl::presentation::remoteprotocol::ElementDesc_Polygon & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"id", value.id); ConvertCustomTypeToJsonField(node, L"size", value.size); ConvertCustomTypeToJsonField(node, L"borderColor", value.borderColor); ConvertCustomTypeToJsonField(node, L"backgroundColor", value.backgroundColor); ConvertCustomTypeToJsonField(node, L"points", value.points); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::ElementDesc_SolidLabel>(const ::vl::presentation::remoteprotocol::ElementDesc_SolidLabel & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"id", value.id); ConvertCustomTypeToJsonField(node, L"textColor", value.textColor); ConvertCustomTypeToJsonField(node, L"horizontalAlignment", value.horizontalAlignment); ConvertCustomTypeToJsonField(node, L"verticalAlignment", value.verticalAlignment); ConvertCustomTypeToJsonField(node, L"wrapLine", value.wrapLine); ConvertCustomTypeToJsonField(node, L"wrapLineHeightCalculation", value.wrapLineHeightCalculation); ConvertCustomTypeToJsonField(node, L"ellipse", value.ellipse); ConvertCustomTypeToJsonField(node, L"multiline", value.multiline); ConvertCustomTypeToJsonField(node, L"font", value.font); ConvertCustomTypeToJsonField(node, L"text", value.text); ConvertCustomTypeToJsonField(node, L"measuringRequest", value.measuringRequest); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::ImageCreation>(const ::vl::presentation::remoteprotocol::ImageCreation & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"id", value.id); ConvertCustomTypeToJsonField(node, L"imageData", value.imageData); ConvertCustomTypeToJsonField(node, L"imageDataOmitted", value.imageDataOmitted); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::ImageFrameMetadata>(const ::vl::presentation::remoteprotocol::ImageFrameMetadata & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"size", value.size); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::ImageMetadata>(const ::vl::presentation::remoteprotocol::ImageMetadata & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"id", value.id); ConvertCustomTypeToJsonField(node, L"format", value.format); ConvertCustomTypeToJsonField(node, L"frames", value.frames); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::ElementDesc_ImageFrame>(const ::vl::presentation::remoteprotocol::ElementDesc_ImageFrame & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"id", value.id); ConvertCustomTypeToJsonField(node, L"imageId", value.imageId); ConvertCustomTypeToJsonField(node, L"imageFrame", value.imageFrame); ConvertCustomTypeToJsonField(node, L"horizontalAlignment", value.horizontalAlignment); ConvertCustomTypeToJsonField(node, L"verticalAlignment", value.verticalAlignment); ConvertCustomTypeToJsonField(node, L"stretch", value.stretch); ConvertCustomTypeToJsonField(node, L"enabled", value.enabled); ConvertCustomTypeToJsonField(node, L"imageCreation", value.imageCreation); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::RendererCreation>(const ::vl::presentation::remoteprotocol::RendererCreation & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"id", value.id); ConvertCustomTypeToJsonField(node, L"type", value.type); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::ElementBeginRendering>(const ::vl::presentation::remoteprotocol::ElementBeginRendering & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"frameId", value.frameId); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::ElementRendering>(const ::vl::presentation::remoteprotocol::ElementRendering & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"id", value.id); ConvertCustomTypeToJsonField(node, L"bounds", value.bounds); ConvertCustomTypeToJsonField(node, L"areaClippedByParent", value.areaClippedByParent); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::ElementBoundary>(const ::vl::presentation::remoteprotocol::ElementBoundary & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"id", value.id); ConvertCustomTypeToJsonField(node, L"hitTestResult", value.hitTestResult); ConvertCustomTypeToJsonField(node, L"cursor", value.cursor); ConvertCustomTypeToJsonField(node, L"bounds", value.bounds); ConvertCustomTypeToJsonField(node, L"areaClippedBySelf", value.areaClippedBySelf); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::ElementMeasuring_FontHeight>(const ::vl::presentation::remoteprotocol::ElementMeasuring_FontHeight & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"fontFamily", value.fontFamily); ConvertCustomTypeToJsonField(node, L"fontSize", value.fontSize); ConvertCustomTypeToJsonField(node, L"height", value.height); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::ElementMeasuring_ElementMinSize>(const ::vl::presentation::remoteprotocol::ElementMeasuring_ElementMinSize & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"id", value.id); ConvertCustomTypeToJsonField(node, L"minSize", value.minSize); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::ElementMeasurings>(const ::vl::presentation::remoteprotocol::ElementMeasurings & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"fontHeights", value.fontHeights); ConvertCustomTypeToJsonField(node, L"minSizes", value.minSizes); ConvertCustomTypeToJsonField(node, L"createdImages", value.createdImages); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::RenderingDomContent>(const ::vl::presentation::remoteprotocol::RenderingDomContent & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"hitTestResult", value.hitTestResult); ConvertCustomTypeToJsonField(node, L"cursor", value.cursor); ConvertCustomTypeToJsonField(node, L"element", value.element); ConvertCustomTypeToJsonField(node, L"bounds", value.bounds); ConvertCustomTypeToJsonField(node, L"validArea", value.validArea); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::RenderingDom>(const ::vl::presentation::remoteprotocol::RenderingDom & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"id", value.id); ConvertCustomTypeToJsonField(node, L"content", value.content); ConvertCustomTypeToJsonField(node, L"children", value.children); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::RenderingDom_Diff>(const ::vl::presentation::remoteprotocol::RenderingDom_Diff & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"id", value.id); ConvertCustomTypeToJsonField(node, L"diffType", value.diffType); ConvertCustomTypeToJsonField(node, L"content", value.content); ConvertCustomTypeToJsonField(node, L"children", value.children); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::RenderingDom_DiffsInOrder>(const ::vl::presentation::remoteprotocol::RenderingDom_DiffsInOrder & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"diffsInOrder", value.diffsInOrder); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::UnitTest_RenderingFrame>(const ::vl::presentation::remoteprotocol::UnitTest_RenderingFrame & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"frameId", value.frameId); ConvertCustomTypeToJsonField(node, L"frameName", value.frameName); ConvertCustomTypeToJsonField(node, L"windowSize", value.windowSize); ConvertCustomTypeToJsonField(node, L"elements", value.elements); ConvertCustomTypeToJsonField(node, L"root", value.root); return node; } template<> vl::Ptr ConvertCustomTypeToJson<::vl::presentation::remoteprotocol::UnitTest_RenderingTrace>(const ::vl::presentation::remoteprotocol::UnitTest_RenderingTrace & value) { auto node = Ptr(new glr::json::JsonObject); ConvertCustomTypeToJsonField(node, L"createdElements", value.createdElements); ConvertCustomTypeToJsonField(node, L"imageCreations", value.imageCreations); ConvertCustomTypeToJsonField(node, L"imageMetadatas", value.imageMetadatas); ConvertCustomTypeToJsonField(node, L"frames", value.frames); return node; } template<> void ConvertJsonToCustomType<::vl::presentation::INativeWindowListener::HitTestResult>(vl::Ptr node, ::vl::presentation::INativeWindowListener::HitTestResult& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::INativeWindowListener::HitTestResult>(Ptr, ::vl::presentation::INativeWindowListener::HitTestResult&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); if (jsonNode->content.value == L"BorderNoSizing") value = ::vl::presentation::INativeWindowListener::BorderNoSizing; else if (jsonNode->content.value == L"BorderLeft") value = ::vl::presentation::INativeWindowListener::BorderLeft; else if (jsonNode->content.value == L"BorderRight") value = ::vl::presentation::INativeWindowListener::BorderRight; else if (jsonNode->content.value == L"BorderTop") value = ::vl::presentation::INativeWindowListener::BorderTop; else if (jsonNode->content.value == L"BorderBottom") value = ::vl::presentation::INativeWindowListener::BorderBottom; else if (jsonNode->content.value == L"BorderLeftTop") value = ::vl::presentation::INativeWindowListener::BorderLeftTop; else if (jsonNode->content.value == L"BorderRightTop") value = ::vl::presentation::INativeWindowListener::BorderRightTop; else if (jsonNode->content.value == L"BorderLeftBottom") value = ::vl::presentation::INativeWindowListener::BorderLeftBottom; else if (jsonNode->content.value == L"BorderRightBottom") value = ::vl::presentation::INativeWindowListener::BorderRightBottom; else if (jsonNode->content.value == L"Title") value = ::vl::presentation::INativeWindowListener::Title; else if (jsonNode->content.value == L"ButtonMinimum") value = ::vl::presentation::INativeWindowListener::ButtonMinimum; else if (jsonNode->content.value == L"ButtonMaximum") value = ::vl::presentation::INativeWindowListener::ButtonMaximum; else if (jsonNode->content.value == L"ButtonClose") value = ::vl::presentation::INativeWindowListener::ButtonClose; else if (jsonNode->content.value == L"Client") value = ::vl::presentation::INativeWindowListener::Client; else if (jsonNode->content.value == L"Icon") value = ::vl::presentation::INativeWindowListener::Icon; else if (jsonNode->content.value == L"NoDecision") value = ::vl::presentation::INativeWindowListener::NoDecision; else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported enum value."); #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::INativeCursor::SystemCursorType>(vl::Ptr node, ::vl::presentation::INativeCursor::SystemCursorType& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::INativeCursor::SystemCursorType>(Ptr, ::vl::presentation::INativeCursor::SystemCursorType&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); if (jsonNode->content.value == L"SmallWaiting") value = ::vl::presentation::INativeCursor::SmallWaiting; else if (jsonNode->content.value == L"LargeWaiting") value = ::vl::presentation::INativeCursor::LargeWaiting; else if (jsonNode->content.value == L"Arrow") value = ::vl::presentation::INativeCursor::Arrow; else if (jsonNode->content.value == L"Cross") value = ::vl::presentation::INativeCursor::Cross; else if (jsonNode->content.value == L"Hand") value = ::vl::presentation::INativeCursor::Hand; else if (jsonNode->content.value == L"Help") value = ::vl::presentation::INativeCursor::Help; else if (jsonNode->content.value == L"IBeam") value = ::vl::presentation::INativeCursor::IBeam; else if (jsonNode->content.value == L"SizeAll") value = ::vl::presentation::INativeCursor::SizeAll; else if (jsonNode->content.value == L"SizeNESW") value = ::vl::presentation::INativeCursor::SizeNESW; else if (jsonNode->content.value == L"SizeNS") value = ::vl::presentation::INativeCursor::SizeNS; else if (jsonNode->content.value == L"SizeNWSE") value = ::vl::presentation::INativeCursor::SizeNWSE; else if (jsonNode->content.value == L"SizeWE") value = ::vl::presentation::INativeCursor::SizeWE; else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported enum value."); #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::INativeWindow::WindowSizeState>(vl::Ptr node, ::vl::presentation::INativeWindow::WindowSizeState& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::INativeWindow::WindowSizeState>(Ptr, ::vl::presentation::INativeWindow::WindowSizeState&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); if (jsonNode->content.value == L"Minimized") value = ::vl::presentation::INativeWindow::WindowSizeState::Minimized; else if (jsonNode->content.value == L"Restored") value = ::vl::presentation::INativeWindow::WindowSizeState::Restored; else if (jsonNode->content.value == L"Maximized") value = ::vl::presentation::INativeWindow::WindowSizeState::Maximized; else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported enum value."); #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::IOMouseButton>(vl::Ptr node, ::vl::presentation::remoteprotocol::IOMouseButton& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::IOMouseButton>(Ptr, ::vl::presentation::remoteprotocol::IOMouseButton&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); if (jsonNode->content.value == L"Left") value = ::vl::presentation::remoteprotocol::IOMouseButton::Left; else if (jsonNode->content.value == L"Middle") value = ::vl::presentation::remoteprotocol::IOMouseButton::Middle; else if (jsonNode->content.value == L"Right") value = ::vl::presentation::remoteprotocol::IOMouseButton::Right; else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported enum value."); #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::elements::ElementShapeType>(vl::Ptr node, ::vl::presentation::elements::ElementShapeType& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::elements::ElementShapeType>(Ptr, ::vl::presentation::elements::ElementShapeType&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); if (jsonNode->content.value == L"Rectangle") value = ::vl::presentation::elements::ElementShapeType::Rectangle; else if (jsonNode->content.value == L"Ellipse") value = ::vl::presentation::elements::ElementShapeType::Ellipse; else if (jsonNode->content.value == L"RoundRect") value = ::vl::presentation::elements::ElementShapeType::RoundRect; else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported enum value."); #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::elements::GuiGradientBackgroundElement::Direction>(vl::Ptr node, ::vl::presentation::elements::GuiGradientBackgroundElement::Direction& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::elements::GuiGradientBackgroundElement::Direction>(Ptr, ::vl::presentation::elements::GuiGradientBackgroundElement::Direction&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); if (jsonNode->content.value == L"Horizontal") value = ::vl::presentation::elements::GuiGradientBackgroundElement::Horizontal; else if (jsonNode->content.value == L"Vertical") value = ::vl::presentation::elements::GuiGradientBackgroundElement::Vertical; else if (jsonNode->content.value == L"Slash") value = ::vl::presentation::elements::GuiGradientBackgroundElement::Slash; else if (jsonNode->content.value == L"Backslash") value = ::vl::presentation::elements::GuiGradientBackgroundElement::Backslash; else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported enum value."); #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::elements::Gui3DSplitterElement::Direction>(vl::Ptr node, ::vl::presentation::elements::Gui3DSplitterElement::Direction& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::elements::Gui3DSplitterElement::Direction>(Ptr, ::vl::presentation::elements::Gui3DSplitterElement::Direction&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); if (jsonNode->content.value == L"Horizontal") value = ::vl::presentation::elements::Gui3DSplitterElement::Horizontal; else if (jsonNode->content.value == L"Vertical") value = ::vl::presentation::elements::Gui3DSplitterElement::Vertical; else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported enum value."); #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementHorizontalAlignment>(vl::Ptr node, ::vl::presentation::remoteprotocol::ElementHorizontalAlignment& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementHorizontalAlignment>(Ptr, ::vl::presentation::remoteprotocol::ElementHorizontalAlignment&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); if (jsonNode->content.value == L"Left") value = ::vl::presentation::remoteprotocol::ElementHorizontalAlignment::Left; else if (jsonNode->content.value == L"Right") value = ::vl::presentation::remoteprotocol::ElementHorizontalAlignment::Right; else if (jsonNode->content.value == L"Center") value = ::vl::presentation::remoteprotocol::ElementHorizontalAlignment::Center; else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported enum value."); #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementVerticalAlignment>(vl::Ptr node, ::vl::presentation::remoteprotocol::ElementVerticalAlignment& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementVerticalAlignment>(Ptr, ::vl::presentation::remoteprotocol::ElementVerticalAlignment&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); if (jsonNode->content.value == L"Top") value = ::vl::presentation::remoteprotocol::ElementVerticalAlignment::Top; else if (jsonNode->content.value == L"Bottom") value = ::vl::presentation::remoteprotocol::ElementVerticalAlignment::Bottom; else if (jsonNode->content.value == L"Center") value = ::vl::presentation::remoteprotocol::ElementVerticalAlignment::Center; else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported enum value."); #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementSolidLabelMeasuringRequest>(vl::Ptr node, ::vl::presentation::remoteprotocol::ElementSolidLabelMeasuringRequest& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementSolidLabelMeasuringRequest>(Ptr, ::vl::presentation::remoteprotocol::ElementSolidLabelMeasuringRequest&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); if (jsonNode->content.value == L"FontHeight") value = ::vl::presentation::remoteprotocol::ElementSolidLabelMeasuringRequest::FontHeight; else if (jsonNode->content.value == L"TotalSize") value = ::vl::presentation::remoteprotocol::ElementSolidLabelMeasuringRequest::TotalSize; else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported enum value."); #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::INativeImage::FormatType>(vl::Ptr node, ::vl::presentation::INativeImage::FormatType& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::INativeImage::FormatType>(Ptr, ::vl::presentation::INativeImage::FormatType&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); if (jsonNode->content.value == L"Bmp") value = ::vl::presentation::INativeImage::Bmp; else if (jsonNode->content.value == L"Gif") value = ::vl::presentation::INativeImage::Gif; else if (jsonNode->content.value == L"Icon") value = ::vl::presentation::INativeImage::Icon; else if (jsonNode->content.value == L"Jpeg") value = ::vl::presentation::INativeImage::Jpeg; else if (jsonNode->content.value == L"Png") value = ::vl::presentation::INativeImage::Png; else if (jsonNode->content.value == L"Tiff") value = ::vl::presentation::INativeImage::Tiff; else if (jsonNode->content.value == L"Wmp") value = ::vl::presentation::INativeImage::Wmp; else if (jsonNode->content.value == L"Unknown") value = ::vl::presentation::INativeImage::Unknown; else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported enum value."); #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::RendererType>(vl::Ptr node, ::vl::presentation::remoteprotocol::RendererType& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::RendererType>(Ptr, ::vl::presentation::remoteprotocol::RendererType&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); if (jsonNode->content.value == L"FocusRectangle") value = ::vl::presentation::remoteprotocol::RendererType::FocusRectangle; else if (jsonNode->content.value == L"SolidBorder") value = ::vl::presentation::remoteprotocol::RendererType::SolidBorder; else if (jsonNode->content.value == L"SinkBorder") value = ::vl::presentation::remoteprotocol::RendererType::SinkBorder; else if (jsonNode->content.value == L"SinkSplitter") value = ::vl::presentation::remoteprotocol::RendererType::SinkSplitter; else if (jsonNode->content.value == L"SolidBackground") value = ::vl::presentation::remoteprotocol::RendererType::SolidBackground; else if (jsonNode->content.value == L"GradientBackground") value = ::vl::presentation::remoteprotocol::RendererType::GradientBackground; else if (jsonNode->content.value == L"InnerShadow") value = ::vl::presentation::remoteprotocol::RendererType::InnerShadow; else if (jsonNode->content.value == L"SolidLabel") value = ::vl::presentation::remoteprotocol::RendererType::SolidLabel; else if (jsonNode->content.value == L"Polygon") value = ::vl::presentation::remoteprotocol::RendererType::Polygon; else if (jsonNode->content.value == L"ImageFrame") value = ::vl::presentation::remoteprotocol::RendererType::ImageFrame; else if (jsonNode->content.value == L"UnsupportedColorizedText") value = ::vl::presentation::remoteprotocol::RendererType::UnsupportedColorizedText; else if (jsonNode->content.value == L"UnsupportedDocument") value = ::vl::presentation::remoteprotocol::RendererType::UnsupportedDocument; else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported enum value."); #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::RenderingDom_DiffType>(vl::Ptr node, ::vl::presentation::remoteprotocol::RenderingDom_DiffType& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::RenderingDom_DiffType>(Ptr, ::vl::presentation::remoteprotocol::RenderingDom_DiffType&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); if (jsonNode->content.value == L"Deleted") value = ::vl::presentation::remoteprotocol::RenderingDom_DiffType::Deleted; else if (jsonNode->content.value == L"Created") value = ::vl::presentation::remoteprotocol::RenderingDom_DiffType::Created; else if (jsonNode->content.value == L"Modified") value = ::vl::presentation::remoteprotocol::RenderingDom_DiffType::Modified; else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported enum value."); #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::NativeCoordinate>(vl::Ptr node, ::vl::presentation::NativeCoordinate& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::NativeCoordinate>(Ptr, ::vl::presentation::NativeCoordinate&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"value") ConvertJsonToCustomType(field->value, value.value); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::NativePoint>(vl::Ptr node, ::vl::presentation::NativePoint& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::NativePoint>(Ptr, ::vl::presentation::NativePoint&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"x") ConvertJsonToCustomType(field->value, value.x); else if (field->name.value == L"y") ConvertJsonToCustomType(field->value, value.y); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::NativeSize>(vl::Ptr node, ::vl::presentation::NativeSize& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::NativeSize>(Ptr, ::vl::presentation::NativeSize&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"x") ConvertJsonToCustomType(field->value, value.x); else if (field->name.value == L"y") ConvertJsonToCustomType(field->value, value.y); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::NativeRect>(vl::Ptr node, ::vl::presentation::NativeRect& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::NativeRect>(Ptr, ::vl::presentation::NativeRect&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"x1") ConvertJsonToCustomType(field->value, value.x1); else if (field->name.value == L"y1") ConvertJsonToCustomType(field->value, value.y1); else if (field->name.value == L"x2") ConvertJsonToCustomType(field->value, value.x2); else if (field->name.value == L"y2") ConvertJsonToCustomType(field->value, value.y2); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::NativeMargin>(vl::Ptr node, ::vl::presentation::NativeMargin& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::NativeMargin>(Ptr, ::vl::presentation::NativeMargin&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"left") ConvertJsonToCustomType(field->value, value.left); else if (field->name.value == L"top") ConvertJsonToCustomType(field->value, value.top); else if (field->name.value == L"right") ConvertJsonToCustomType(field->value, value.right); else if (field->name.value == L"bottom") ConvertJsonToCustomType(field->value, value.bottom); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::Point>(vl::Ptr node, ::vl::presentation::Point& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::Point>(Ptr, ::vl::presentation::Point&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"x") ConvertJsonToCustomType(field->value, value.x); else if (field->name.value == L"y") ConvertJsonToCustomType(field->value, value.y); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::Size>(vl::Ptr node, ::vl::presentation::Size& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::Size>(Ptr, ::vl::presentation::Size&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"x") ConvertJsonToCustomType(field->value, value.x); else if (field->name.value == L"y") ConvertJsonToCustomType(field->value, value.y); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::Rect>(vl::Ptr node, ::vl::presentation::Rect& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::Rect>(Ptr, ::vl::presentation::Rect&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"x1") ConvertJsonToCustomType(field->value, value.x1); else if (field->name.value == L"y1") ConvertJsonToCustomType(field->value, value.y1); else if (field->name.value == L"x2") ConvertJsonToCustomType(field->value, value.x2); else if (field->name.value == L"y2") ConvertJsonToCustomType(field->value, value.y2); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::FontProperties>(vl::Ptr node, ::vl::presentation::FontProperties& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::FontProperties>(Ptr, ::vl::presentation::FontProperties&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"fontFamily") ConvertJsonToCustomType(field->value, value.fontFamily); else if (field->name.value == L"size") ConvertJsonToCustomType(field->value, value.size); else if (field->name.value == L"bold") ConvertJsonToCustomType(field->value, value.bold); else if (field->name.value == L"italic") ConvertJsonToCustomType(field->value, value.italic); else if (field->name.value == L"underline") ConvertJsonToCustomType(field->value, value.underline); else if (field->name.value == L"strikeline") ConvertJsonToCustomType(field->value, value.strikeline); else if (field->name.value == L"antialias") ConvertJsonToCustomType(field->value, value.antialias); else if (field->name.value == L"verticalAntialias") ConvertJsonToCustomType(field->value, value.verticalAntialias); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::FontConfig>(vl::Ptr node, ::vl::presentation::remoteprotocol::FontConfig& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::FontConfig>(Ptr, ::vl::presentation::remoteprotocol::FontConfig&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"defaultFont") ConvertJsonToCustomType(field->value, value.defaultFont); else if (field->name.value == L"supportedFonts") ConvertJsonToCustomType(field->value, value.supportedFonts); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ScreenConfig>(vl::Ptr node, ::vl::presentation::remoteprotocol::ScreenConfig& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ScreenConfig>(Ptr, ::vl::presentation::remoteprotocol::ScreenConfig&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"bounds") ConvertJsonToCustomType(field->value, value.bounds); else if (field->name.value == L"clientBounds") ConvertJsonToCustomType(field->value, value.clientBounds); else if (field->name.value == L"scalingX") ConvertJsonToCustomType(field->value, value.scalingX); else if (field->name.value == L"scalingY") ConvertJsonToCustomType(field->value, value.scalingY); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::WindowSizingConfig>(vl::Ptr node, ::vl::presentation::remoteprotocol::WindowSizingConfig& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::WindowSizingConfig>(Ptr, ::vl::presentation::remoteprotocol::WindowSizingConfig&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"bounds") ConvertJsonToCustomType(field->value, value.bounds); else if (field->name.value == L"clientBounds") ConvertJsonToCustomType(field->value, value.clientBounds); else if (field->name.value == L"sizeState") ConvertJsonToCustomType(field->value, value.sizeState); else if (field->name.value == L"customFramePadding") ConvertJsonToCustomType(field->value, value.customFramePadding); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::WindowShowing>(vl::Ptr node, ::vl::presentation::remoteprotocol::WindowShowing& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::WindowShowing>(Ptr, ::vl::presentation::remoteprotocol::WindowShowing&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"activate") ConvertJsonToCustomType(field->value, value.activate); else if (field->name.value == L"sizeState") ConvertJsonToCustomType(field->value, value.sizeState); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::NativeWindowMouseInfo>(vl::Ptr node, ::vl::presentation::NativeWindowMouseInfo& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::NativeWindowMouseInfo>(Ptr, ::vl::presentation::NativeWindowMouseInfo&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"ctrl") ConvertJsonToCustomType(field->value, value.ctrl); else if (field->name.value == L"shift") ConvertJsonToCustomType(field->value, value.shift); else if (field->name.value == L"left") ConvertJsonToCustomType(field->value, value.left); else if (field->name.value == L"middle") ConvertJsonToCustomType(field->value, value.middle); else if (field->name.value == L"right") ConvertJsonToCustomType(field->value, value.right); else if (field->name.value == L"x") ConvertJsonToCustomType(field->value, value.x); else if (field->name.value == L"y") ConvertJsonToCustomType(field->value, value.y); else if (field->name.value == L"wheel") ConvertJsonToCustomType(field->value, value.wheel); else if (field->name.value == L"nonClient") ConvertJsonToCustomType(field->value, value.nonClient); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::IOMouseInfoWithButton>(vl::Ptr node, ::vl::presentation::remoteprotocol::IOMouseInfoWithButton& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::IOMouseInfoWithButton>(Ptr, ::vl::presentation::remoteprotocol::IOMouseInfoWithButton&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"button") ConvertJsonToCustomType(field->value, value.button); else if (field->name.value == L"info") ConvertJsonToCustomType(field->value, value.info); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::NativeWindowKeyInfo>(vl::Ptr node, ::vl::presentation::NativeWindowKeyInfo& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::NativeWindowKeyInfo>(Ptr, ::vl::presentation::NativeWindowKeyInfo&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"code") ConvertJsonToCustomType(field->value, value.code); else if (field->name.value == L"ctrl") ConvertJsonToCustomType(field->value, value.ctrl); else if (field->name.value == L"shift") ConvertJsonToCustomType(field->value, value.shift); else if (field->name.value == L"alt") ConvertJsonToCustomType(field->value, value.alt); else if (field->name.value == L"capslock") ConvertJsonToCustomType(field->value, value.capslock); else if (field->name.value == L"autoRepeatKeyDown") ConvertJsonToCustomType(field->value, value.autoRepeatKeyDown); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::NativeWindowCharInfo>(vl::Ptr node, ::vl::presentation::NativeWindowCharInfo& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::NativeWindowCharInfo>(Ptr, ::vl::presentation::NativeWindowCharInfo&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"code") ConvertJsonToCustomType(field->value, value.code); else if (field->name.value == L"ctrl") ConvertJsonToCustomType(field->value, value.ctrl); else if (field->name.value == L"shift") ConvertJsonToCustomType(field->value, value.shift); else if (field->name.value == L"alt") ConvertJsonToCustomType(field->value, value.alt); else if (field->name.value == L"capslock") ConvertJsonToCustomType(field->value, value.capslock); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::GlobalShortcutKey>(vl::Ptr node, ::vl::presentation::remoteprotocol::GlobalShortcutKey& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::GlobalShortcutKey>(Ptr, ::vl::presentation::remoteprotocol::GlobalShortcutKey&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"id") ConvertJsonToCustomType(field->value, value.id); else if (field->name.value == L"ctrl") ConvertJsonToCustomType(field->value, value.ctrl); else if (field->name.value == L"shift") ConvertJsonToCustomType(field->value, value.shift); else if (field->name.value == L"alt") ConvertJsonToCustomType(field->value, value.alt); else if (field->name.value == L"code") ConvertJsonToCustomType(field->value, value.code); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::elements::ElementShape>(vl::Ptr node, ::vl::presentation::elements::ElementShape& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::elements::ElementShape>(Ptr, ::vl::presentation::elements::ElementShape&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"shapeType") ConvertJsonToCustomType(field->value, value.shapeType); else if (field->name.value == L"radiusX") ConvertJsonToCustomType(field->value, value.radiusX); else if (field->name.value == L"radiusY") ConvertJsonToCustomType(field->value, value.radiusY); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementDesc_SolidBorder>(vl::Ptr node, ::vl::presentation::remoteprotocol::ElementDesc_SolidBorder& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementDesc_SolidBorder>(Ptr, ::vl::presentation::remoteprotocol::ElementDesc_SolidBorder&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"id") ConvertJsonToCustomType(field->value, value.id); else if (field->name.value == L"borderColor") ConvertJsonToCustomType(field->value, value.borderColor); else if (field->name.value == L"shape") ConvertJsonToCustomType(field->value, value.shape); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementDesc_SinkBorder>(vl::Ptr node, ::vl::presentation::remoteprotocol::ElementDesc_SinkBorder& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementDesc_SinkBorder>(Ptr, ::vl::presentation::remoteprotocol::ElementDesc_SinkBorder&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"id") ConvertJsonToCustomType(field->value, value.id); else if (field->name.value == L"leftTopColor") ConvertJsonToCustomType(field->value, value.leftTopColor); else if (field->name.value == L"rightBottomColor") ConvertJsonToCustomType(field->value, value.rightBottomColor); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementDesc_SinkSplitter>(vl::Ptr node, ::vl::presentation::remoteprotocol::ElementDesc_SinkSplitter& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementDesc_SinkSplitter>(Ptr, ::vl::presentation::remoteprotocol::ElementDesc_SinkSplitter&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"id") ConvertJsonToCustomType(field->value, value.id); else if (field->name.value == L"leftTopColor") ConvertJsonToCustomType(field->value, value.leftTopColor); else if (field->name.value == L"rightBottomColor") ConvertJsonToCustomType(field->value, value.rightBottomColor); else if (field->name.value == L"direction") ConvertJsonToCustomType(field->value, value.direction); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementDesc_SolidBackground>(vl::Ptr node, ::vl::presentation::remoteprotocol::ElementDesc_SolidBackground& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementDesc_SolidBackground>(Ptr, ::vl::presentation::remoteprotocol::ElementDesc_SolidBackground&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"id") ConvertJsonToCustomType(field->value, value.id); else if (field->name.value == L"backgroundColor") ConvertJsonToCustomType(field->value, value.backgroundColor); else if (field->name.value == L"shape") ConvertJsonToCustomType(field->value, value.shape); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementDesc_GradientBackground>(vl::Ptr node, ::vl::presentation::remoteprotocol::ElementDesc_GradientBackground& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementDesc_GradientBackground>(Ptr, ::vl::presentation::remoteprotocol::ElementDesc_GradientBackground&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"id") ConvertJsonToCustomType(field->value, value.id); else if (field->name.value == L"leftTopColor") ConvertJsonToCustomType(field->value, value.leftTopColor); else if (field->name.value == L"rightBottomColor") ConvertJsonToCustomType(field->value, value.rightBottomColor); else if (field->name.value == L"direction") ConvertJsonToCustomType(field->value, value.direction); else if (field->name.value == L"shape") ConvertJsonToCustomType(field->value, value.shape); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementDesc_InnerShadow>(vl::Ptr node, ::vl::presentation::remoteprotocol::ElementDesc_InnerShadow& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementDesc_InnerShadow>(Ptr, ::vl::presentation::remoteprotocol::ElementDesc_InnerShadow&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"id") ConvertJsonToCustomType(field->value, value.id); else if (field->name.value == L"shadowColor") ConvertJsonToCustomType(field->value, value.shadowColor); else if (field->name.value == L"thickness") ConvertJsonToCustomType(field->value, value.thickness); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementDesc_Polygon>(vl::Ptr node, ::vl::presentation::remoteprotocol::ElementDesc_Polygon& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementDesc_Polygon>(Ptr, ::vl::presentation::remoteprotocol::ElementDesc_Polygon&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"id") ConvertJsonToCustomType(field->value, value.id); else if (field->name.value == L"size") ConvertJsonToCustomType(field->value, value.size); else if (field->name.value == L"borderColor") ConvertJsonToCustomType(field->value, value.borderColor); else if (field->name.value == L"backgroundColor") ConvertJsonToCustomType(field->value, value.backgroundColor); else if (field->name.value == L"points") ConvertJsonToCustomType(field->value, value.points); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementDesc_SolidLabel>(vl::Ptr node, ::vl::presentation::remoteprotocol::ElementDesc_SolidLabel& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementDesc_SolidLabel>(Ptr, ::vl::presentation::remoteprotocol::ElementDesc_SolidLabel&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"id") ConvertJsonToCustomType(field->value, value.id); else if (field->name.value == L"textColor") ConvertJsonToCustomType(field->value, value.textColor); else if (field->name.value == L"horizontalAlignment") ConvertJsonToCustomType(field->value, value.horizontalAlignment); else if (field->name.value == L"verticalAlignment") ConvertJsonToCustomType(field->value, value.verticalAlignment); else if (field->name.value == L"wrapLine") ConvertJsonToCustomType(field->value, value.wrapLine); else if (field->name.value == L"wrapLineHeightCalculation") ConvertJsonToCustomType(field->value, value.wrapLineHeightCalculation); else if (field->name.value == L"ellipse") ConvertJsonToCustomType(field->value, value.ellipse); else if (field->name.value == L"multiline") ConvertJsonToCustomType(field->value, value.multiline); else if (field->name.value == L"font") ConvertJsonToCustomType(field->value, value.font); else if (field->name.value == L"text") ConvertJsonToCustomType(field->value, value.text); else if (field->name.value == L"measuringRequest") ConvertJsonToCustomType(field->value, value.measuringRequest); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ImageCreation>(vl::Ptr node, ::vl::presentation::remoteprotocol::ImageCreation& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ImageCreation>(Ptr, ::vl::presentation::remoteprotocol::ImageCreation&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"id") ConvertJsonToCustomType(field->value, value.id); else if (field->name.value == L"imageData") ConvertJsonToCustomType(field->value, value.imageData); else if (field->name.value == L"imageDataOmitted") ConvertJsonToCustomType(field->value, value.imageDataOmitted); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ImageFrameMetadata>(vl::Ptr node, ::vl::presentation::remoteprotocol::ImageFrameMetadata& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ImageFrameMetadata>(Ptr, ::vl::presentation::remoteprotocol::ImageFrameMetadata&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"size") ConvertJsonToCustomType(field->value, value.size); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ImageMetadata>(vl::Ptr node, ::vl::presentation::remoteprotocol::ImageMetadata& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ImageMetadata>(Ptr, ::vl::presentation::remoteprotocol::ImageMetadata&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"id") ConvertJsonToCustomType(field->value, value.id); else if (field->name.value == L"format") ConvertJsonToCustomType(field->value, value.format); else if (field->name.value == L"frames") ConvertJsonToCustomType(field->value, value.frames); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementDesc_ImageFrame>(vl::Ptr node, ::vl::presentation::remoteprotocol::ElementDesc_ImageFrame& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementDesc_ImageFrame>(Ptr, ::vl::presentation::remoteprotocol::ElementDesc_ImageFrame&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"id") ConvertJsonToCustomType(field->value, value.id); else if (field->name.value == L"imageId") ConvertJsonToCustomType(field->value, value.imageId); else if (field->name.value == L"imageFrame") ConvertJsonToCustomType(field->value, value.imageFrame); else if (field->name.value == L"horizontalAlignment") ConvertJsonToCustomType(field->value, value.horizontalAlignment); else if (field->name.value == L"verticalAlignment") ConvertJsonToCustomType(field->value, value.verticalAlignment); else if (field->name.value == L"stretch") ConvertJsonToCustomType(field->value, value.stretch); else if (field->name.value == L"enabled") ConvertJsonToCustomType(field->value, value.enabled); else if (field->name.value == L"imageCreation") ConvertJsonToCustomType(field->value, value.imageCreation); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::RendererCreation>(vl::Ptr node, ::vl::presentation::remoteprotocol::RendererCreation& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::RendererCreation>(Ptr, ::vl::presentation::remoteprotocol::RendererCreation&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"id") ConvertJsonToCustomType(field->value, value.id); else if (field->name.value == L"type") ConvertJsonToCustomType(field->value, value.type); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementBeginRendering>(vl::Ptr node, ::vl::presentation::remoteprotocol::ElementBeginRendering& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementBeginRendering>(Ptr, ::vl::presentation::remoteprotocol::ElementBeginRendering&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"frameId") ConvertJsonToCustomType(field->value, value.frameId); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementRendering>(vl::Ptr node, ::vl::presentation::remoteprotocol::ElementRendering& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementRendering>(Ptr, ::vl::presentation::remoteprotocol::ElementRendering&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"id") ConvertJsonToCustomType(field->value, value.id); else if (field->name.value == L"bounds") ConvertJsonToCustomType(field->value, value.bounds); else if (field->name.value == L"areaClippedByParent") ConvertJsonToCustomType(field->value, value.areaClippedByParent); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementBoundary>(vl::Ptr node, ::vl::presentation::remoteprotocol::ElementBoundary& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementBoundary>(Ptr, ::vl::presentation::remoteprotocol::ElementBoundary&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"id") ConvertJsonToCustomType(field->value, value.id); else if (field->name.value == L"hitTestResult") ConvertJsonToCustomType(field->value, value.hitTestResult); else if (field->name.value == L"cursor") ConvertJsonToCustomType(field->value, value.cursor); else if (field->name.value == L"bounds") ConvertJsonToCustomType(field->value, value.bounds); else if (field->name.value == L"areaClippedBySelf") ConvertJsonToCustomType(field->value, value.areaClippedBySelf); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementMeasuring_FontHeight>(vl::Ptr node, ::vl::presentation::remoteprotocol::ElementMeasuring_FontHeight& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementMeasuring_FontHeight>(Ptr, ::vl::presentation::remoteprotocol::ElementMeasuring_FontHeight&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"fontFamily") ConvertJsonToCustomType(field->value, value.fontFamily); else if (field->name.value == L"fontSize") ConvertJsonToCustomType(field->value, value.fontSize); else if (field->name.value == L"height") ConvertJsonToCustomType(field->value, value.height); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementMeasuring_ElementMinSize>(vl::Ptr node, ::vl::presentation::remoteprotocol::ElementMeasuring_ElementMinSize& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementMeasuring_ElementMinSize>(Ptr, ::vl::presentation::remoteprotocol::ElementMeasuring_ElementMinSize&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"id") ConvertJsonToCustomType(field->value, value.id); else if (field->name.value == L"minSize") ConvertJsonToCustomType(field->value, value.minSize); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementMeasurings>(vl::Ptr node, ::vl::presentation::remoteprotocol::ElementMeasurings& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::ElementMeasurings>(Ptr, ::vl::presentation::remoteprotocol::ElementMeasurings&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"fontHeights") ConvertJsonToCustomType(field->value, value.fontHeights); else if (field->name.value == L"minSizes") ConvertJsonToCustomType(field->value, value.minSizes); else if (field->name.value == L"createdImages") ConvertJsonToCustomType(field->value, value.createdImages); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::RenderingDomContent>(vl::Ptr node, ::vl::presentation::remoteprotocol::RenderingDomContent& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::RenderingDomContent>(Ptr, ::vl::presentation::remoteprotocol::RenderingDomContent&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"hitTestResult") ConvertJsonToCustomType(field->value, value.hitTestResult); else if (field->name.value == L"cursor") ConvertJsonToCustomType(field->value, value.cursor); else if (field->name.value == L"element") ConvertJsonToCustomType(field->value, value.element); else if (field->name.value == L"bounds") ConvertJsonToCustomType(field->value, value.bounds); else if (field->name.value == L"validArea") ConvertJsonToCustomType(field->value, value.validArea); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::RenderingDom>(vl::Ptr node, ::vl::presentation::remoteprotocol::RenderingDom& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::RenderingDom>(Ptr, ::vl::presentation::remoteprotocol::RenderingDom&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"id") ConvertJsonToCustomType(field->value, value.id); else if (field->name.value == L"content") ConvertJsonToCustomType(field->value, value.content); else if (field->name.value == L"children") ConvertJsonToCustomType(field->value, value.children); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::RenderingDom_Diff>(vl::Ptr node, ::vl::presentation::remoteprotocol::RenderingDom_Diff& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::RenderingDom_Diff>(Ptr, ::vl::presentation::remoteprotocol::RenderingDom_Diff&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"id") ConvertJsonToCustomType(field->value, value.id); else if (field->name.value == L"diffType") ConvertJsonToCustomType(field->value, value.diffType); else if (field->name.value == L"content") ConvertJsonToCustomType(field->value, value.content); else if (field->name.value == L"children") ConvertJsonToCustomType(field->value, value.children); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::RenderingDom_DiffsInOrder>(vl::Ptr node, ::vl::presentation::remoteprotocol::RenderingDom_DiffsInOrder& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::RenderingDom_DiffsInOrder>(Ptr, ::vl::presentation::remoteprotocol::RenderingDom_DiffsInOrder&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"diffsInOrder") ConvertJsonToCustomType(field->value, value.diffsInOrder); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::UnitTest_RenderingFrame>(vl::Ptr node, ::vl::presentation::remoteprotocol::UnitTest_RenderingFrame& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::UnitTest_RenderingFrame>(Ptr, ::vl::presentation::remoteprotocol::UnitTest_RenderingFrame&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"frameId") ConvertJsonToCustomType(field->value, value.frameId); else if (field->name.value == L"frameName") ConvertJsonToCustomType(field->value, value.frameName); else if (field->name.value == L"windowSize") ConvertJsonToCustomType(field->value, value.windowSize); else if (field->name.value == L"elements") ConvertJsonToCustomType(field->value, value.elements); else if (field->name.value == L"root") ConvertJsonToCustomType(field->value, value.root); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } template<> void ConvertJsonToCustomType<::vl::presentation::remoteprotocol::UnitTest_RenderingTrace>(vl::Ptr node, ::vl::presentation::remoteprotocol::UnitTest_RenderingTrace& value) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::ConvertJsonToCustomType<::vl::presentation::remoteprotocol::UnitTest_RenderingTrace>(Ptr, ::vl::presentation::remoteprotocol::UnitTest_RenderingTrace&)#" auto jsonNode = node.Cast(); CHECK_ERROR(jsonNode, ERROR_MESSAGE_PREFIX L"Json node does not match the expected type."); for (auto field : jsonNode->fields) { if (field->name.value == L"createdElements") ConvertJsonToCustomType(field->value, value.createdElements); else if (field->name.value == L"imageCreations") ConvertJsonToCustomType(field->value, value.imageCreations); else if (field->name.value == L"imageMetadatas") ConvertJsonToCustomType(field->value, value.imageMetadatas); else if (field->name.value == L"frames") ConvertJsonToCustomType(field->value, value.frames); else CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unsupported struct member."); } #undef ERROR_MESSAGE_PREFIX } } /*********************************************************************** .\PLATFORMPROVIDERS\REMOTERENDERER\GUIREMOTERENDERERSINGLE.CPP ***********************************************************************/ namespace vl::presentation::remote_renderer { using namespace elements; using namespace remoteprotocol; remoteprotocol::ScreenConfig GuiRemoteRendererSingle::GetScreenConfig(INativeScreen* screen) { ScreenConfig response; response.bounds = screen->GetBounds(); response.clientBounds = screen->GetClientBounds(); response.scalingX = screen->GetScalingX(); response.scalingY = screen->GetScalingY(); return response; } remoteprotocol::WindowSizingConfig GuiRemoteRendererSingle::GetWindowSizingConfig() { WindowSizingConfig response; response.bounds = window->GetBounds(); response.clientBounds = window->GetClientBoundsInScreen(); response.sizeState = window->GetSizeState(); response.customFramePadding = window->GetCustomFramePadding(); return response; } void GuiRemoteRendererSingle::UpdateConfigsIfNecessary() { if (screen) { auto currentScreen = GetCurrentController()->ScreenService()->GetScreen(window); if (screen != currentScreen) { screen = currentScreen; events->OnControllerScreenUpdated(GetScreenConfig(screen)); } auto newWindowSizingConfig = GetWindowSizingConfig(); if ( newWindowSizingConfig.bounds != windowSizingConfig.bounds || newWindowSizingConfig.clientBounds != windowSizingConfig.clientBounds) { windowSizingConfig = newWindowSizingConfig; if (!updatingBounds) { events->OnWindowBoundsUpdated(windowSizingConfig); } } else if ( newWindowSizingConfig.sizeState != windowSizingConfig.sizeState || newWindowSizingConfig.customFramePadding != windowSizingConfig.customFramePadding) { windowSizingConfig = newWindowSizingConfig; events->OnWindowBoundsUpdated(windowSizingConfig); } } } void GuiRemoteRendererSingle::NativeWindowDestroying(INativeWindow* _window) { if (window == _window) { window->UninstallListener(this); window = nullptr; } } void GuiRemoteRendererSingle::Opened() { events->OnControllerConnect(); } void GuiRemoteRendererSingle::BeforeClosing(bool& cancel) { if (!disconnectingFromCore) { cancel = true; events->OnControllerRequestExit(); } } void GuiRemoteRendererSingle::AfterClosing() { renderingDom = nullptr; availableElements.Clear(); availableImages.Clear(); } void GuiRemoteRendererSingle::Closed() { } void GuiRemoteRendererSingle::Moved() { UpdateConfigsIfNecessary(); } void GuiRemoteRendererSingle::DpiChanged(bool preparing) { if (preparing) { UpdateRenderTarget(nullptr); } else { GetGuiGraphicsResourceManager()->RecreateRenderTarget(window); UpdateRenderTarget(GetGuiGraphicsResourceManager()->GetRenderTarget(window)); UpdateConfigsIfNecessary(); } } void GuiRemoteRendererSingle::RenderingAsActivated() { if (disconnectingFromCore) return; events->OnWindowActivatedUpdated(true); } void GuiRemoteRendererSingle::RenderingAsDeactivated() { if (disconnectingFromCore) return; events->OnWindowActivatedUpdated(false); } GuiRemoteRendererSingle::GuiRemoteRendererSingle() { } GuiRemoteRendererSingle::~GuiRemoteRendererSingle() { } void GuiRemoteRendererSingle::RegisterMainWindow(INativeWindow* _window) { window = _window; window->InstallListener(this); GetCurrentController()->CallbackService()->InstallListener(this); } void GuiRemoteRendererSingle::UnregisterMainWindow() { UnregisterGlobalShortcutKeys(); GetCurrentController()->CallbackService()->UninstallListener(this); } void GuiRemoteRendererSingle::ForceExitByFatelError() { if (window) { disconnectingFromCore = true; window->Hide(true); } } WString GuiRemoteRendererSingle::GetExecutablePath() { CHECK_FAIL(L"This function should not be called!"); } void GuiRemoteRendererSingle::Initialize(IGuiRemoteProtocolEvents* _events) { events = _events; } void GuiRemoteRendererSingle::Submit(bool& disconnected) { CHECK_FAIL(L"This function should not be called!"); } void GuiRemoteRendererSingle::ProcessRemoteEvents() { CHECK_FAIL(L"This function should not be called!"); } } /*********************************************************************** .\PLATFORMPROVIDERS\REMOTERENDERER\GUIREMOTERENDERERSINGLE_CONTROLLER.CPP ***********************************************************************/ namespace vl::presentation::remote_renderer { using namespace collections; using namespace remoteprotocol; void GuiRemoteRendererSingle::RequestControllerGetFontConfig(vint id) { FontConfig response; auto rs = GetCurrentController()->ResourceService(); response.defaultFont = rs->GetDefaultFont(); response.supportedFonts = Ptr(new List); rs->EnumerateFonts(*response.supportedFonts.Obj()); events->RespondControllerGetFontConfig(id, response); } void GuiRemoteRendererSingle::RequestControllerGetScreenConfig(vint id) { auto primary = screen ? screen : GetCurrentController()->ScreenService()->GetScreen((vint)0); events->RespondControllerGetScreenConfig(id, GetScreenConfig(primary)); } void GuiRemoteRendererSingle::RequestControllerConnectionEstablished() { } void GuiRemoteRendererSingle::RequestControllerConnectionStopped() { if (window) { disconnectingFromCore = true; window->ReleaseCapture(); window->Hide(true); } } } /*********************************************************************** .\PLATFORMPROVIDERS\REMOTERENDERER\GUIREMOTERENDERERSINGLE_IO.CPP ***********************************************************************/ namespace vl::presentation::remote_renderer { using namespace remoteprotocol; /*********************************************************************** * Rendering (Commands) ***********************************************************************/ void GuiRemoteRendererSingle::UnregisterGlobalShortcutKeys() { auto inputService = GetCurrentController()->InputService(); for (vint id : globalShortcuts.Keys()) { inputService->UnregisterGlobalShortcutKey(id); } globalShortcuts.Clear(); } void GuiRemoteRendererSingle::GlobalShortcutKeyActivated(vint id) { vint index = globalShortcuts.Keys().IndexOf(id); if (index != -1) { events->OnIOGlobalShortcutKey(globalShortcuts.Values()[index].id); } } void GuiRemoteRendererSingle::RequestIOUpdateGlobalShortcutKey(const Ptr>& arguments) { UnregisterGlobalShortcutKeys(); if (arguments) { auto inputService = GetCurrentController()->InputService(); for (auto&& shortcut : *arguments.Obj()) { vint id = inputService->RegisterGlobalShortcutKey(shortcut.ctrl, shortcut.shift, shortcut.alt, shortcut.code); if (id != -1) { globalShortcuts.Add(id, shortcut); } } } } void GuiRemoteRendererSingle::RequestIORequireCapture() { window->RequireCapture(); } void GuiRemoteRendererSingle::RequestIOReleaseCapture() { window->ReleaseCapture(); } void GuiRemoteRendererSingle::RequestIOIsKeyPressing(vint id, const VKEY& arguments) { CHECK_FAIL(L"Not Implemented"); } void GuiRemoteRendererSingle::RequestIOIsKeyToggled(vint id, const VKEY& arguments) { CHECK_FAIL(L"Not Implemented"); } /*********************************************************************** * Rendering (INativeWindow) ***********************************************************************/ void GuiRemoteRendererSingle::LeftButtonDown(const NativeWindowMouseInfo& info) { IOMouseInfoWithButton arguments; arguments.button = IOMouseButton::Left; arguments.info = info; events->OnIOButtonDown(arguments); } void GuiRemoteRendererSingle::LeftButtonUp(const NativeWindowMouseInfo& info) { IOMouseInfoWithButton arguments; arguments.button = IOMouseButton::Left; arguments.info = info; events->OnIOButtonUp(arguments); } void GuiRemoteRendererSingle::LeftButtonDoubleClick(const NativeWindowMouseInfo& info) { IOMouseInfoWithButton arguments; arguments.button = IOMouseButton::Left; arguments.info = info; events->OnIOButtonDoubleClick(arguments); } void GuiRemoteRendererSingle::RightButtonDown(const NativeWindowMouseInfo& info) { IOMouseInfoWithButton arguments; arguments.button = IOMouseButton::Right; arguments.info = info; events->OnIOButtonDown(arguments); } void GuiRemoteRendererSingle::RightButtonUp(const NativeWindowMouseInfo& info) { IOMouseInfoWithButton arguments; arguments.button = IOMouseButton::Right; arguments.info = info; events->OnIOButtonUp(arguments); } void GuiRemoteRendererSingle::RightButtonDoubleClick(const NativeWindowMouseInfo& info) { IOMouseInfoWithButton arguments; arguments.button = IOMouseButton::Right; arguments.info = info; events->OnIOButtonDoubleClick(arguments); } void GuiRemoteRendererSingle::MiddleButtonDown(const NativeWindowMouseInfo& info) { IOMouseInfoWithButton arguments; arguments.button = IOMouseButton::Middle; arguments.info = info; events->OnIOButtonDown(arguments); } void GuiRemoteRendererSingle::MiddleButtonUp(const NativeWindowMouseInfo& info) { IOMouseInfoWithButton arguments; arguments.button = IOMouseButton::Middle; arguments.info = info; events->OnIOButtonUp(arguments); } void GuiRemoteRendererSingle::MiddleButtonDoubleClick(const NativeWindowMouseInfo& info) { IOMouseInfoWithButton arguments; arguments.button = IOMouseButton::Middle; arguments.info = info; events->OnIOButtonDoubleClick(arguments); } void GuiRemoteRendererSingle::HorizontalWheel(const NativeWindowMouseInfo& info) { events->OnIOHWheel(info); } void GuiRemoteRendererSingle::VerticalWheel(const NativeWindowMouseInfo& info) { events->OnIOVWheel(info); } void GuiRemoteRendererSingle::MouseMoving(const NativeWindowMouseInfo& info) { if (renderingDom) { INativeWindowListener::HitTestResult hitTestResult = INativeWindowListener::NoDecision; INativeCursor* cursor = nullptr; HitTest(renderingDom, window->Convert(NativePoint{ info.x,info.y }), hitTestResult, cursor); window->SetWindowCursor(cursor); } events->OnIOMouseMoving(info); } void GuiRemoteRendererSingle::MouseEntered() { events->OnIOMouseEntered(); } void GuiRemoteRendererSingle::MouseLeaved() { events->OnIOMouseLeaved(); } void GuiRemoteRendererSingle::KeyDown(const NativeWindowKeyInfo& info) { events->OnIOKeyDown(info); } void GuiRemoteRendererSingle::KeyUp(const NativeWindowKeyInfo& info) { if (!info.ctrl && !info.shift && info.code == VKEY::KEY_MENU) { window->SupressAlt(); } events->OnIOKeyUp(info); } void GuiRemoteRendererSingle::Char(const NativeWindowCharInfo& info) { events->OnIOChar(info); } } /*********************************************************************** .\PLATFORMPROVIDERS\REMOTERENDERER\GUIREMOTERENDERERSINGLE_MAINWINDOW.CPP ***********************************************************************/ namespace vl::presentation::remote_renderer { using namespace remoteprotocol; void GuiRemoteRendererSingle::RequestWindowGetBounds(vint id) { events->RespondWindowGetBounds(id, GetWindowSizingConfig()); } void GuiRemoteRendererSingle::RequestWindowNotifySetBounds(const NativeRect& arguments) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remote_renderer::GuiRemoteRendererSingle::RequestWindowNotifySetBounds(const NativeRect&)#" CHECK_ERROR(!updatingBounds, ERROR_MESSAGE_PREFIX L"This function cannot be called recursively."); updatingBounds = true; if (!screen) { auto primary = GetCurrentController()->ScreenService()->GetScreen((vint)0); NativeRect screenBounds = primary->GetBounds(); NativeRect windowBounds = arguments; windowBounds.x1 = (screenBounds.Width() - windowBounds.Width()) / 2; windowBounds.y1 = (screenBounds.Height() - windowBounds.Height()) / 2; window->SetBounds(windowBounds); screen = primary; windowSizingConfig = GetWindowSizingConfig(); } else { window->SetBounds(arguments); } updatingBounds = false; #undef ERROR_MESSAGE_PREFIX } void GuiRemoteRendererSingle::RequestWindowNotifySetTitle(const WString& arguments) { window->SetTitle(arguments); } void GuiRemoteRendererSingle::RequestWindowNotifySetEnabled(const bool& arguments) { if (arguments) { window->Enable(); } else { window->Disable(); } } void GuiRemoteRendererSingle::RequestWindowNotifySetTopMost(const bool& arguments) { window->SetTopMost(arguments); } void GuiRemoteRendererSingle::RequestWindowNotifySetShowInTaskBar(const bool& arguments) { if (arguments) { window->ShowInTaskBar(); } else { window->HideInTaskBar(); } } void GuiRemoteRendererSingle::RequestWindowNotifySetClientSize(const NativeSize& arguments) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remote_renderer::GuiRemoteRendererSingle::RequestWindowNotifySetClientSize(const NativeSize&)#" CHECK_ERROR(screen, ERROR_MESSAGE_PREFIX L"This function cannot be called before RequestWindowNotifySetBounds."); window->SetClientSize(arguments); #undef ERROR_MESSAGE_PREFIX } void GuiRemoteRendererSingle::RequestWindowNotifySetCustomFrameMode(const bool& arguments) { if (window->IsCustomFrameModeEnabled() != arguments) { if (arguments) { window->EnableCustomFrameMode(); } else { window->DisableCustomFrameMode(); } UpdateConfigsIfNecessary(); } } void GuiRemoteRendererSingle::RequestWindowNotifySetMaximizedBox(const bool& arguments) { window->SetMaximizedBox(arguments); UpdateConfigsIfNecessary(); } void GuiRemoteRendererSingle::RequestWindowNotifySetMinimizedBox(const bool& arguments) { window->SetMinimizedBox(arguments); UpdateConfigsIfNecessary(); } void GuiRemoteRendererSingle::RequestWindowNotifySetBorder(const bool& arguments) { window->SetBorder(arguments); UpdateConfigsIfNecessary(); } void GuiRemoteRendererSingle::RequestWindowNotifySetSizeBox(const bool& arguments) { window->SetSizeBox(arguments); UpdateConfigsIfNecessary(); } void GuiRemoteRendererSingle::RequestWindowNotifySetIconVisible(const bool& arguments) { window->SetIconVisible(arguments); UpdateConfigsIfNecessary(); } void GuiRemoteRendererSingle::RequestWindowNotifySetTitleBar(const bool& arguments) { window->SetTitleBar(arguments); UpdateConfigsIfNecessary(); } void GuiRemoteRendererSingle::RequestWindowNotifyActivate() { window->SetActivate(); } void GuiRemoteRendererSingle::RequestWindowNotifyShow(const remoteprotocol::WindowShowing& arguments) { if (arguments.sizeState != window->GetSizeState()) { if (arguments.activate) { window->SetActivate(); } switch (arguments.sizeState) { case INativeWindow::Minimized: window->ShowMinimized(); break; case INativeWindow::Restored: window->ShowRestored(); break; case INativeWindow::Maximized: window->ShowMaximized(); break; } } } } /*********************************************************************** .\PLATFORMPROVIDERS\REMOTERENDERER\GUIREMOTERENDERERSINGLE_RENDERING.CPP ***********************************************************************/ namespace vl::presentation::remote_renderer { using namespace collections; using namespace elements; using namespace remoteprotocol; Alignment GuiRemoteRendererSingle::GetAlignment(remoteprotocol::ElementHorizontalAlignment alignment) { switch (alignment) { case remoteprotocol::ElementHorizontalAlignment::Left: return Alignment::Left; case remoteprotocol::ElementHorizontalAlignment::Right: return Alignment::Right; default: return Alignment::Center; } } Alignment GuiRemoteRendererSingle::GetAlignment(remoteprotocol::ElementVerticalAlignment alignment) { switch (alignment) { case remoteprotocol::ElementVerticalAlignment::Top: return Alignment::Top; case remoteprotocol::ElementVerticalAlignment::Bottom: return Alignment::Bottom; default: return Alignment::Center; } } /*********************************************************************** * Rendering ***********************************************************************/ void GuiRemoteRendererSingle::RequestRendererCreated(const Ptr>& arguments) { if (arguments) { for (auto&& rc : *arguments.Obj()) { Ptr element; switch (rc.type) { case RendererType::FocusRectangle: element = Ptr(GuiFocusRectangleElement::Create()); break; case RendererType::SolidBorder: element = Ptr(GuiSolidBorderElement::Create()); break; case RendererType::SinkBorder: element = Ptr(Gui3DBorderElement::Create()); break; case RendererType::SinkSplitter: element = Ptr(Gui3DSplitterElement::Create()); break; case RendererType::SolidBackground: element = Ptr(GuiSolidBackgroundElement::Create()); break; case RendererType::GradientBackground: element = Ptr(GuiGradientBackgroundElement::Create()); break; case RendererType::InnerShadow: element = Ptr(GuiInnerShadowElement::Create()); break; case RendererType::SolidLabel: element = Ptr(GuiSolidLabelElement::Create()); break; case RendererType::Polygon: element = Ptr(GuiPolygonElement::Create()); break; case RendererType::ImageFrame: element = Ptr(GuiImageFrameElement::Create()); break; default:; } element->GetRenderer()->SetRenderTarget(GetGuiGraphicsResourceManager()->GetRenderTarget(window)); if (availableElements.Keys().Contains(rc.id)) { availableElements.Set(rc.id, element); } else { availableElements.Add(rc.id, element); } } } } void GuiRemoteRendererSingle::RequestRendererDestroyed(const Ptr>& arguments) { if (arguments) { for (auto id : *arguments.Obj()) { availableElements.Remove(id); solidLabelMeasurings.Remove(id); } } } void GuiRemoteRendererSingle::RequestRendererBeginRendering(const remoteprotocol::ElementBeginRendering& arguments) { } void GuiRemoteRendererSingle::RequestRendererEndRendering(vint id) { events->RespondRendererEndRendering(id, elementMeasurings); elementMeasurings = {}; 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 (Dom) ***********************************************************************/ void GuiRemoteRendererSingle::CheckDom() { needRefresh = true; } void GuiRemoteRendererSingle::RequestRendererRenderDom(const Ptr& arguments) { renderingDom = arguments; if (renderingDom) { BuildDomIndex(renderingDom, renderingDomIndex); } CheckDom(); } void GuiRemoteRendererSingle::RequestRendererRenderDomDiff(const remoteprotocol::RenderingDom_DiffsInOrder& arguments) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remote_renderer::GuiRemoteRendererSingle::RequestRendererRenderDomDiff(const RenderingDom_DiffsInOrder&)#" CHECK_ERROR(renderingDom, ERROR_MESSAGE_PREFIX L"This function must be called after RequestRendererRenderDom."); UpdateDomInplace(renderingDom, renderingDomIndex, arguments); CheckDom(); #undef ERROR_MESSAGE_PREFIX } /*********************************************************************** * Rendering (Commands) ***********************************************************************/ void GuiRemoteRendererSingle::RequestRendererBeginBoundary(const remoteprotocol::ElementBoundary& arguments) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remote_renderer::GuiRemoteRendererSingle::RequestRendererBeginBoundary(const ElementBoundary&)#" CHECK_FAIL(ERROR_MESSAGE_PREFIX L"The current implementation require dom-diff enabled in core side."); #undef ERROR_MESSAGE_PREFIX } void GuiRemoteRendererSingle::RequestRendererEndBoundary() { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remote_renderer::GuiRemoteRendererSingle::RequestRendererEndBoundary()#" CHECK_FAIL(ERROR_MESSAGE_PREFIX L"The current implementation require dom-diff enabled in core side."); #undef ERROR_MESSAGE_PREFIX } void GuiRemoteRendererSingle::RequestRendererRenderElement(const remoteprotocol::ElementRendering& arguments) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::remote_renderer::GuiRemoteRendererSingle::RequestRendererRenderElement(const ElementRendering&)#" CHECK_FAIL(ERROR_MESSAGE_PREFIX L"The current implementation require dom-diff enabled in core side."); #undef ERROR_MESSAGE_PREFIX } /*********************************************************************** * Rendering (INativeWindow) ***********************************************************************/ void GuiRemoteRendererSingle::UpdateRenderTarget(elements::IGuiGraphicsRenderTarget* rt) { for (auto element : availableElements.Values()) { element->GetRenderer()->SetRenderTarget(rt); } } void GuiRemoteRendererSingle::Render(Ptr dom, elements::IGuiGraphicsRenderTarget* rt) { if (dom->content.element) { vint index = availableElements.Keys().IndexOf(dom->content.element.Value()); if (index != -1) { auto element = availableElements.Values()[index]; rt->PushClipper(dom->content.validArea, nullptr); element->GetRenderer()->Render(dom->content.bounds); rt->PopClipper(nullptr); if (auto solidLabel = element.Cast()) { index = solidLabelMeasurings.Keys().IndexOf(dom->content.element.Value()); if (index != -1) { auto& measuring = const_cast(solidLabelMeasurings.Values()[index]); auto minSize = element->GetRenderer()->GetMinSize(); bool measuringChanged = false; if (!measuring.minSize) { measuringChanged = true; } else switch (measuring.request) { case ElementSolidLabelMeasuringRequest::FontHeight: if (measuring.minSize.Value().y != minSize.y) { measuringChanged = true; } break; case ElementSolidLabelMeasuringRequest::TotalSize: if (measuring.minSize.Value() != minSize) { measuringChanged = true; } break; } measuring.minSize = minSize; if (measuringChanged) { StoreLabelMeasuring(dom->content.element.Value(), measuring.request, solidLabel, minSize); } } } } } if (dom->children) { for (auto child : *dom->children.Obj()) { if (child->content.validArea.Width() > 0 && child->content.validArea.Height()> 0) { Render(child, rt); } } } } void GuiRemoteRendererSingle::HitTestInternal(Ptr dom, Point location, Nullable& hitTestResult, Nullable& cursorType) { if (dom->children) { for (auto child : *dom->children.Obj()) { if (child->content.validArea.Contains(location)) { HitTestInternal(child, location, hitTestResult, cursorType); if (!hitTestResult && child->content.hitTestResult) { hitTestResult = child->content.hitTestResult; } if (!cursorType && child->content.cursor) { cursorType = child->content.cursor; } } } } } void GuiRemoteRendererSingle::HitTest(Ptr dom, Point location, INativeWindowListener::HitTestResult& hitTestResult, INativeCursor*& cursor) { Nullable hitTestResultNullable; Nullable cursorTypeNullable; HitTestInternal(dom, location, hitTestResultNullable, cursorTypeNullable); hitTestResult = hitTestResultNullable ? hitTestResultNullable.Value() : INativeWindowListener::NoDecision; cursor = cursorTypeNullable ? GetCurrentController()->ResourceService()->GetSystemCursor(cursorTypeNullable.Value()) : GetCurrentController()->ResourceService()->GetDefaultSystemCursor(); } void GuiRemoteRendererSingle::GlobalTimer() { if (!needRefresh) return; needRefresh = false; if (!window) return; if (!renderingDom) return; supressPaint = true; auto rt = GetGuiGraphicsResourceManager()->GetRenderTarget(window); rt->StartRendering(); Render(renderingDom, rt); auto result = rt->StopRendering(); window->RedrawContent(); supressPaint = false; switch (result) { case RenderTargetFailure::ResizeWhileRendering: GetGuiGraphicsResourceManager()->ResizeRenderTarget(window); needRefresh = true; break; case RenderTargetFailure::LostDevice: UpdateRenderTarget(nullptr); GetGuiGraphicsResourceManager()->RecreateRenderTarget(window); UpdateRenderTarget(GetGuiGraphicsResourceManager()->GetRenderTarget(window)); needRefresh = true; break; default:; } } void GuiRemoteRendererSingle::Paint() { if (!supressPaint) { needRefresh = true; } } INativeWindowListener::HitTestResult GuiRemoteRendererSingle::HitTest(NativePoint location) { INativeWindowListener::HitTestResult hitTestResult = INativeWindowListener::NoDecision; INativeCursor* cursor = nullptr; if (renderingDom) { HitTest(renderingDom, window->Convert(location), hitTestResult, cursor); } return hitTestResult; } } /*********************************************************************** .\RESOURCES\GUIDOCUMENT.CPP ***********************************************************************/ namespace vl { namespace presentation { using namespace collections; using namespace glr::xml; using namespace regex; using namespace stream; /*********************************************************************** DocumentFontSize ***********************************************************************/ DocumentFontSize DocumentFontSize::Parse(const WString& value) { if (value.Length() > 0 && value[value.Length() - 1] == L'x') { return DocumentFontSize(wtof(value.Left(value.Length() - 1)), true); } else { return DocumentFontSize(wtof(value), false); } } WString DocumentFontSize::ToString()const { return ftow(size) + (relative ? L"x" : L""); } /*********************************************************************** DocumentImageRun ***********************************************************************/ const wchar_t* DocumentImageRun::RepresentationText=L"[Image]"; const wchar_t* DocumentEmbeddedObjectRun::RepresentationText=L"[EmbeddedObject]"; /*********************************************************************** ExtractTextVisitor ***********************************************************************/ namespace document_operation_visitors { class ExtractTextVisitor : public Object, public DocumentRun::IVisitor { public: stream::TextWriter& writer; bool skipNonTextContent; ExtractTextVisitor(stream::TextWriter& _writer, bool _skipNonTextContent) :writer(_writer) ,skipNonTextContent(_skipNonTextContent) { } void VisitContainer(DocumentContainerRun* run) { for (auto subRun : run->runs) { subRun->Accept(this); } } void VisitContent(DocumentContentRun* run) { writer.WriteString(run->GetRepresentationText()); } void Visit(DocumentTextRun* run)override { VisitContent(run); } void Visit(DocumentStylePropertiesRun* run)override { VisitContainer(run); } void Visit(DocumentStyleApplicationRun* run)override { VisitContainer(run); } void Visit(DocumentHyperlinkRun* run)override { VisitContainer(run); } void Visit(DocumentImageRun* run)override { if(!skipNonTextContent) { VisitContent(run); } } void Visit(DocumentEmbeddedObjectRun* run)override { if(!skipNonTextContent) { VisitContent(run); } } void Visit(DocumentParagraphRun* run)override { VisitContainer(run); } }; } using namespace document_operation_visitors; /*********************************************************************** DocumentParagraphRun ***********************************************************************/ WString DocumentParagraphRun::GetText(bool skipNonTextContent) { return GenerateToStream([&](StreamWriter& writer) { GetText(writer, skipNonTextContent); }); } void DocumentParagraphRun::GetText(stream::TextWriter& writer, bool skipNonTextContent) { ExtractTextVisitor visitor(writer, skipNonTextContent); Accept(&visitor); } /*********************************************************************** DocumentModel ***********************************************************************/ const wchar_t* DocumentModel::DefaultStyleName = L"#Default"; const wchar_t* DocumentModel::SelectionStyleName = L"#Selection"; const wchar_t* DocumentModel::ContextStyleName = L"#Context"; const wchar_t* DocumentModel::NormalLinkStyleName = L"#NormalLink"; const wchar_t* DocumentModel::ActiveLinkStyleName = L"#ActiveLink"; DocumentModel::DocumentModel() { { FontProperties font=GetCurrentController()->ResourceService()->GetDefaultFont(); auto sp=Ptr(new DocumentStyleProperties); sp->face=font.fontFamily; sp->size=DocumentFontSize((double)font.size, false); sp->color=Color(); sp->backgroundColor=Color(0, 0, 0, 0); sp->bold=font.bold; sp->italic=font.italic; sp->underline=font.underline; sp->strikeline=font.strikeline; sp->antialias=font.antialias; sp->verticalAntialias=font.verticalAntialias; auto style = Ptr(new DocumentStyle); style->styles=sp; styles.Add(L"#Default", style); } { auto sp = Ptr(new DocumentStyleProperties); sp->color=Color(255, 255, 255); sp->backgroundColor=Color(51, 153, 255); auto style = Ptr(new DocumentStyle); style->styles=sp; styles.Add(L"#Selection", style); } { auto sp = Ptr(new DocumentStyleProperties); auto style = Ptr(new DocumentStyle); style->styles=sp; styles.Add(L"#Context", style); } { auto sp = Ptr(new DocumentStyleProperties); sp->color=Color(0, 0, 255); sp->underline=true; auto style = Ptr(new DocumentStyle); style->parentStyleName=L"#Context"; style->styles=sp; styles.Add(L"#NormalLink", style); } { auto sp = Ptr(new DocumentStyleProperties); sp->color=Color(255, 128, 0); sp->underline=true; auto style = Ptr(new DocumentStyle); style->parentStyleName=L"#Context"; style->styles=sp; styles.Add(L"#ActiveLink", style); } } void DocumentModel::MergeStyle(Ptr style, Ptr parent) { if(!style->face && parent->face) style->face =parent->face; if(!style->size && parent->size) style->size =parent->size; if(!style->color && parent->color) style->color =parent->color; if(!style->backgroundColor && parent->backgroundColor) style->backgroundColor =parent->backgroundColor; if(!style->bold && parent->bold) style->bold =parent->bold; if(!style->italic && parent->italic) style->italic =parent->italic; if(!style->underline && parent->underline) style->underline =parent->underline; if(!style->strikeline && parent->strikeline) style->strikeline =parent->strikeline; if(!style->antialias && parent->antialias) style->antialias =parent->antialias; if(!style->verticalAntialias && parent->verticalAntialias) style->verticalAntialias =parent->verticalAntialias; } void DocumentModel::MergeBaselineStyle(Ptr style, const WString& styleName) { auto indexDst = styles.Keys().IndexOf(styleName); auto sp = Ptr(new DocumentStyleProperties); MergeStyle(sp, style); if (indexDst != -1) { MergeStyle(sp, styles.Values()[indexDst]->styles); } if (indexDst == -1) { auto style = Ptr(new DocumentStyle); style->styles = sp; styles.Add(styleName, style); } else { styles.Values()[indexDst]->styles = sp; } for (auto style : styles.Values()) { style->resolvedStyles = nullptr; } } void DocumentModel::MergeBaselineStyle(Ptr baselineDocument, const WString& styleName) { auto indexSrc = baselineDocument->styles.Keys().IndexOf(styleName + L"-Override"); if (indexSrc == -1) { return; } auto csp = baselineDocument->styles.Values()[indexSrc]->styles; MergeBaselineStyle(csp, styleName); } void DocumentModel::MergeBaselineStyles(Ptr baselineDocument) { MergeBaselineStyle(baselineDocument, DefaultStyleName); MergeBaselineStyle(baselineDocument, SelectionStyleName); MergeBaselineStyle(baselineDocument, ContextStyleName); MergeBaselineStyle(baselineDocument, NormalLinkStyleName); MergeBaselineStyle(baselineDocument, ActiveLinkStyleName); } void DocumentModel::MergeDefaultFont(const FontProperties& defaultFont) { auto style = Ptr(new DocumentStyleProperties); style->face =defaultFont.fontFamily; style->size =DocumentFontSize((double)defaultFont.size, false); style->bold =defaultFont.bold; style->italic =defaultFont.italic; style->underline =defaultFont.underline; style->strikeline =defaultFont.strikeline; style->antialias =defaultFont.antialias; style->verticalAntialias =defaultFont.verticalAntialias; MergeBaselineStyle(style, DefaultStyleName); } DocumentModel::ResolvedStyle DocumentModel::GetStyle(Ptr sp, const ResolvedStyle& context) { FontProperties font; font.fontFamily =sp->face ?sp->face.Value() :context.style.fontFamily; font.bold =sp->bold ?sp->bold.Value() :context.style.bold; font.italic =sp->italic ?sp->italic.Value() :context.style.italic; font.underline =sp->underline ?sp->underline.Value() :context.style.underline; font.strikeline =sp->strikeline ?sp->strikeline.Value() :context.style.strikeline; font.antialias =sp->antialias ?sp->antialias.Value() :context.style.antialias; font.verticalAntialias =sp->verticalAntialias ?sp->verticalAntialias.Value() :context.style.verticalAntialias; Color color =sp->color ?sp->color.Value() :context.color; Color backgroundColor =sp->backgroundColor ?sp->backgroundColor.Value() :context.backgroundColor; if (sp->size) { font.size = (vint)(sp->size.Value().relative ? context.style.size * sp->size.Value().size : sp->size.Value().size); } else { font.size = context.style.size; } return ResolvedStyle(font, color, backgroundColor); } DocumentModel::ResolvedStyle DocumentModel::GetStyle(const WString& styleName, const ResolvedStyle& context) { Ptr selectedStyle; { vint index=styles.Keys().IndexOf(styleName); if(index!=-1) { selectedStyle=styles.Values()[index]; } else { selectedStyle=styles[L"#Default"]; } } if(!selectedStyle->resolvedStyles) { auto sp = Ptr(new DocumentStyleProperties); selectedStyle->resolvedStyles = sp; Ptr currentStyle; WString currentName = styleName; while(true) { vint index = styles.Keys().IndexOf(currentName); if (index == -1) break; currentStyle = styles.Values().Get(index); currentName = currentStyle->parentStyleName; MergeStyle(sp, currentStyle->styles); } } Ptr sp=selectedStyle->resolvedStyles; return GetStyle(sp, context); } WString DocumentModel::GetText(bool skipNonTextContent) { return GenerateToStream([&](StreamWriter& writer) { GetText(writer, skipNonTextContent); }); } void DocumentModel::GetText(stream::TextWriter& writer, bool skipNonTextContent) { // TODO: (enumerable) Linq:Aggregate for(vint i=0;i paragraph=paragraphs[i]; paragraph->GetText(writer, skipNonTextContent); if(iruns) { childRun->Accept(this); } } void Visit(DocumentTextRun* run)override { } void Visit(DocumentStylePropertiesRun* run)override { VisitContainer(run); } void Visit(DocumentStyleApplicationRun* run)override { VisitContainer(run); } void Visit(DocumentHyperlinkRun* run)override { VisitContainer(run); } void Visit(DocumentImageRun* run)override { } void Visit(DocumentEmbeddedObjectRun* run)override { } void Visit(DocumentParagraphRun* run)override { VisitContainer(run); } }; class ModifyDocumentForClipboardVisitor : public TraverseDocumentVisitor { public: ModifyDocumentForClipboardVisitor() { } void VisitContainer(DocumentContainerRun* run)override { // TODO: (enumerable) foreach:indexed(alterable(reversed)) for (vint i = run->runs.Count() - 1; i >= 0; i--) { auto childRun = run->runs[i]; if (childRun.Cast()) { run->runs.RemoveAt(i); } } TraverseDocumentVisitor::VisitContainer(run); } }; class CollectImageRunsVisitor : public TraverseDocumentVisitor { public: List> imageRuns; CollectImageRunsVisitor() { } void Visit(DocumentImageRun* run)override { run->source = L"res://Image_" + itow(imageRuns.Count()); imageRuns.Add(Ptr(run)); } }; } using namespace document_clipboard_visitors; void ModifyDocumentForClipboard(Ptr model) { ModifyDocumentForClipboardVisitor visitor; for (auto paragraph : model->paragraphs) { paragraph->Accept(&visitor); } } Ptr LoadDocumentFromClipboardStream(stream::IStream& clipboardStream) { auto tempResource = Ptr(new GuiResource); auto tempResourceItem = Ptr(new GuiResourceItem); tempResource->AddItem(L"Document", tempResourceItem); auto tempResolver = Ptr(new GuiResourcePathResolver(tempResource, L"")); internal::ContextFreeReader reader(clipboardStream); { WString title; vint32_t version = 0; reader << title << version; if (title != L"WCF_Document" || version < 1) { return nullptr; } } WString xmlText; reader << xmlText; List errors; auto parser = GetParserManager()->GetParser(L"XML"); auto xml = parser->Parse({}, xmlText, errors); if (errors.Count() > 0) return nullptr; { vint32_t count = 0; reader << count; for (vint i = 0; i < count; i++) { MemoryStream memoryStream; reader << (IStream&)memoryStream; if (auto image = GetCurrentController()->ImageService()->CreateImageFromStream(memoryStream)) { auto imageItem = Ptr(new GuiResourceItem); imageItem->SetContent(L"Image", Ptr(new GuiImageData(image, 0))); tempResource->AddItem(L"Image_" + itow(i), imageItem); } } } auto document = DocumentModel::LoadFromXml(tempResourceItem, xml, tempResolver, errors); return document; } void SaveDocumentToClipboardStream(Ptr model, stream::IStream& clipboardStream) { CollectImageRunsVisitor visitor; for (auto paragraph : model->paragraphs) { paragraph->Accept(&visitor); } internal::ContextFreeWriter writer(clipboardStream); { WString title = L"WCF_Document"; vint32_t version = 1; writer << title << version; } { auto xmlText = GenerateToStream([&](StreamWriter& streamWriter) { auto xml = model->SaveToXml(); XmlPrint(xml, streamWriter); }); writer << xmlText; } { vint32_t count = (vint32_t)visitor.imageRuns.Count(); writer << count; for (auto imageRun : visitor.imageRuns) { MemoryStream memoryStream; if (imageRun->image) { auto format = imageRun->image->GetFormat(); if (format == INativeImage::Gif) { format = INativeImage::Png; } imageRun->image->SaveToStream(memoryStream, format); } writer << (stream::IStream&)memoryStream; } } } } } /*********************************************************************** .\RESOURCES\GUIDOCUMENTCLIPBOARD_HTMLFORMAT.CPP ***********************************************************************/ namespace vl { namespace presentation { using namespace collections; using namespace stream; namespace document_clipboard_visitors { class GenerateHtmlVisitor : public Object, public DocumentRun::IVisitor { typedef DocumentModel::ResolvedStyle ResolvedStyle; public: List styles; DocumentModel* model; StreamWriter& writer; GenerateHtmlVisitor(DocumentModel* _model, StreamWriter& _writer) :model(_model) , writer(_writer) { ResolvedStyle style; style.color = Color(0, 0, 0, 0); style.backgroundColor = Color(0, 0, 0, 0); style = model->GetStyle(DocumentModel::DefaultStyleName, style); styles.Add(style); } void VisitContainer(DocumentContainerRun* run) { for (auto subRun : run->runs) { subRun->Accept(this); } } WString ColorToString(Color c) { auto result = c.ToString(); if (result.Length() == 9) result = result.Left(7); return result; } void Visit(DocumentTextRun* run)override { WString text = run->GetRepresentationText(); if (text.Length() > 0) { ResolvedStyle style = styles[styles.Count() - 1]; writer.WriteString(L""); for (vint i = 0; i < text.Length(); i++) { switch (wchar_t c = text[i]) { case L'&': writer.WriteString(L"&"); break; case L'<': writer.WriteString(L"<"); break; case L'>': writer.WriteString(L">"); break; case L'\r': break; case L'\n': writer.WriteString(L"
"); break; case L' ': writer.WriteString(L" "); break; case L'\t': writer.WriteString(L"
\t
"); break; default: writer.WriteChar(c); break; } } writer.WriteString(L"
"); } } 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 { writer.WriteString(L"reference.Length(); i++) { switch (wchar_t c = run->reference[i]) { case L'&': writer.WriteString(L"&"); break; case L'<': writer.WriteString(L"<"); break; case L'>': writer.WriteString(L">"); break; case L'"': writer.WriteString(L"""); break; case L'\'': writer.WriteString(L"'"); break; default: writer.WriteChar(c); break; } } writer.WriteString(L"\">"); ResolvedStyle style = styles[styles.Count() - 1]; style = model->GetStyle((run->normalStyleName == L"" ? DocumentModel::NormalLinkStyleName : run->normalStyleName), style); styles.Add(style); VisitContainer(run); styles.RemoveAt(styles.Count() - 1); writer.WriteString(L""); } void Visit(DocumentImageRun* run)override { if (run->image) { writer.WriteString(L"size.x) + L"\" height=\"" + itow(run->size.y) + L"\" src=\"data:image/"); auto format = run->image->GetFormat(); if (format == INativeImage::Gif) { format = INativeImage::Png; } switch (format) { case INativeImage::Bmp: writer.WriteString(L"bmp;base64,"); break; case INativeImage::Gif: writer.WriteString(L"gif;base64,"); break; case INativeImage::Icon: writer.WriteString(L"icon;base64,"); break; case INativeImage::Jpeg: writer.WriteString(L"jpeg;base64,"); break; case INativeImage::Png: writer.WriteString(L"png;base64,"); break; case INativeImage::Tiff: writer.WriteString(L"tiff;base64,"); break; case INativeImage::Wmp: writer.WriteString(L"wmp;base64,"); break; default: writer.WriteString(L"unsupported;base64,\"/>"); return; } MemoryStream memoryStream; run->image->SaveToStream(memoryStream, format); memoryStream.SeekFromBegin(0); while (true) { vuint8_t bytes[3] = { 0,0,0 }; vint read = memoryStream.Read(&bytes, sizeof(bytes)); if (read == 0) break; vuint8_t b1 = bytes[0] / (1 << 2); vuint8_t b2 = ((bytes[0] % (1 << 2)) << 4) + bytes[1] / (1 << 4); vuint8_t b3 = ((bytes[1] % (1 << 4)) << 2) + bytes[2] / (1 << 6); vuint8_t b4 = bytes[2] % (1 << 6); const wchar_t* BASE64 = L"ABCDEFG" L"HIJKLMN" L"OPQRST" L"UVWXYZ" L"abcdefg" L"hijklmn" L"opqrst" L"uvwxyz" L"0123456789" L"+/"; #define BASE64_CHAR(b) BASE64[b] switch (read) { case 1: writer.WriteChar(BASE64_CHAR(b1)); writer.WriteChar(BASE64_CHAR(b2)); writer.WriteChar(L'='); writer.WriteChar(L'='); break; case 2: writer.WriteChar(BASE64_CHAR(b1)); writer.WriteChar(BASE64_CHAR(b2)); writer.WriteChar(BASE64_CHAR(b3)); writer.WriteChar(L'='); break; case 3: writer.WriteChar(BASE64_CHAR(b1)); writer.WriteChar(BASE64_CHAR(b2)); writer.WriteChar(BASE64_CHAR(b3)); writer.WriteChar(BASE64_CHAR(b4)); break; } #undef BASE64_CHAR } writer.WriteString(L"\"/>"); } } void Visit(DocumentEmbeddedObjectRun* run)override { } void Visit(DocumentParagraphRun* run)override { VisitContainer(run); } }; } using namespace document_clipboard_visitors; #define HTML_LINE(LINE) LINE "\r\n" void SaveDocumentToHtmlUtf8(Ptr model, AString& header, AString& content, AString& footer) { header = HTML_LINE("") HTML_LINE("") HTML_LINE("
") HTML_LINE("GacUI Document 1.0") HTML_LINE("") HTML_LINE("
") HTML_LINE("") ; MemoryStream memoryStream; { Utf8Encoder encoder; EncoderStream encoderStream(memoryStream, encoder); StreamWriter writer(encoderStream); GenerateHtmlVisitor visitor(model.Obj(), writer); // TODO: (enumerable) foreach for (auto paragraph : model->paragraphs) { writer.WriteString(L"

alignment) { switch (paragraph->alignment.Value()) { case Alignment::Left: writer.WriteString(L"left;"); break; case Alignment::Center: writer.WriteString(L"center;"); break; case Alignment::Right: writer.WriteString(L"right;"); break; } } else { writer.WriteString(L"left;"); } writer.WriteString(L"\">"); paragraph->Accept(&visitor); writer.WriteString(L"

\r\n"); } } char zero = 0; memoryStream.Write(&zero, sizeof(zero)); content = (const char*)memoryStream.GetInternalBuffer(); footer = HTML_LINE("") HTML_LINE("") ; } void SaveDocumentToHtmlClipboardStream(Ptr model, stream::IStream& clipboardStream) { AString header, content, footer; SaveDocumentToHtmlUtf8(model, header, content, footer); char clipboardHeader[] = HTML_LINE("StartHTML:-1") HTML_LINE("EndHTML:-1") HTML_LINE("StartFragment:0000000000") HTML_LINE("EndFragment:0000000000") ; char commentStart[] = ""; char commentEnd[] = ""; vint offsetStart = sizeof(clipboardHeader) - 1 + header.Length() + sizeof(commentStart) - 1; vint offsetEnd = offsetStart + content.Length(); AString offsetStartString = itoa(offsetStart); AString offsetEndString = itoa(offsetEnd); memcpy(strstr(clipboardHeader, "EndFragment:") - offsetStartString.Length() - 2, offsetStartString.Buffer(), offsetStartString.Length()); memcpy(clipboardHeader + sizeof(clipboardHeader) - 1 - offsetEndString.Length() - 2, offsetEndString.Buffer(), offsetEndString.Length()); clipboardStream.Write(clipboardHeader, sizeof(clipboardHeader) - 1); if (header.Length() > 0) clipboardStream.Write((void*)header.Buffer(), header.Length()); clipboardStream.Write(commentStart, sizeof(commentStart) - 1); if (content.Length() > 0) clipboardStream.Write((void*)content.Buffer(), content.Length()); clipboardStream.Write(commentEnd, sizeof(commentEnd) - 1); if (footer.Length() > 0) clipboardStream.Write((void*)footer.Buffer(), footer.Length()); } #undef HTML_LINE } } /*********************************************************************** .\RESOURCES\GUIDOCUMENTCLIPBOARD_RICHTEXTFORMAT.CPP ***********************************************************************/ namespace vl { namespace presentation { using namespace collections; using namespace stream; namespace document_clipboard_visitors { class GenerateRtfVisitor : public Object, public DocumentRun::IVisitor { typedef DocumentModel::ResolvedStyle ResolvedStyle; public: List styles; DocumentModel* model; StreamWriter& writer; List& fontTable; List& colorTable; Dictionary fontIndex; Dictionary colorIndex; vint GetFont(const WString& fontName) { vint index = fontIndex.Keys().IndexOf(fontName); if (index == -1) { index = fontTable.Add(fontName); fontIndex.Add(fontName, index); return index; } return fontIndex.Values()[index]; } vint GetColor(Color color) { if (color.a == 0) return 0; vint index = colorIndex.Keys().IndexOf(color); if (index == -1) { index = colorTable.Add(color) + 1; colorIndex.Add(color, index); return index; } return colorIndex.Values()[index]; } GenerateRtfVisitor(DocumentModel* _model, List& _fontTable, List& _colorTable, StreamWriter& _writer) :model(_model) , writer(_writer) , fontTable(_fontTable) , colorTable(_colorTable) { ResolvedStyle style; style.color = Color(0, 0, 0, 0); style.backgroundColor = Color(0, 0, 0, 0); style = model->GetStyle(DocumentModel::DefaultStyleName, style); styles.Add(style); } void VisitContainer(DocumentContainerRun* run) { for (auto subRun : run->runs) { subRun->Accept(this); } } void Visit(DocumentTextRun* run)override { WString text = run->GetRepresentationText(); if (text.Length() > 0) { ResolvedStyle style = styles[styles.Count() - 1]; writer.WriteString(L"{\\f" + itow(GetFont(style.style.fontFamily))); writer.WriteString(L"\\fs" + itow((vint)(style.style.size * 1.5))); writer.WriteString(L"\\cf" + itow(GetColor(style.color))); writer.WriteString(L"\\cb" + itow(GetColor(style.backgroundColor))); writer.WriteString(L"\\chshdng" + itow(GetColor(style.backgroundColor))); writer.WriteString(L"\\chcbpat" + itow(GetColor(style.backgroundColor))); if (style.style.bold) writer.WriteString(L"\\b"); if (style.style.italic) writer.WriteString(L"\\i"); if (style.style.underline) writer.WriteString(L"\\ul"); if (style.style.strikeline) writer.WriteString(L"\\strike"); for (vint i = 0; i < text.Length(); i++) { writer.WriteString(L"\\u" + itow(text[i])); } writer.WriteString(L"}"); } } 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->normalStyleName == L"" ? DocumentModel::NormalLinkStyleName : run->normalStyleName), style); styles.Add(style); VisitContainer(run); styles.RemoveAt(styles.Count() - 1); } void Visit(DocumentImageRun* run)override { if (run->image) { writer.WriteString(L"{\\pict\\pngblip"); writer.WriteString(L"\\picw" + itow(run->size.x) + L"\\pich" + itow(run->size.y)); writer.WriteString(L"\\picwgoal" + itow(run->size.x * 15) + L"\\pichgoal" + itow(run->size.y * 15) + L" "); MemoryStream memoryStream; run->image->SaveToStream(memoryStream, INativeImage::Png); vint count = (vint)memoryStream.Size(); vuint8_t* buffer = (vuint8_t*)memoryStream.GetInternalBuffer(); for (vint i = 0; i < count; i++) { writer.WriteChar(L"0123456789abcdef"[buffer[i] / 16]); writer.WriteChar(L"0123456789abcdef"[buffer[i] % 16]); } writer.WriteString(L"}"); } } void Visit(DocumentEmbeddedObjectRun* run)override { } void Visit(DocumentParagraphRun* run)override { VisitContainer(run); } }; } using namespace document_clipboard_visitors; void SaveDocumentToRtf(Ptr model, AString& rtf) { List fontTable; List colorTable; MemoryStream bodyStream; { StreamWriter writer(bodyStream); GenerateRtfVisitor visitor(model.Obj(), fontTable, colorTable, writer); for (auto paragraph : model->paragraphs) { if (paragraph->alignment) { switch (paragraph->alignment.Value()) { case Alignment::Left: writer.WriteString(L"\\ql{"); break; case Alignment::Center: writer.WriteString(L"\\qc{"); break; case Alignment::Right: writer.WriteString(L"\\qr{"); break; } } else { writer.WriteString(L"\\ql{"); } paragraph->Accept(&visitor); writer.WriteString(L"}\\par"); } } MemoryStream rtfStream; { Utf8Encoder encoder; EncoderStream encoderStream(rtfStream, encoder); StreamWriter writer(encoderStream); writer.WriteString(L"{\\rtf1\\ansi\\deff0{\\fonttbl"); for (auto [fontName, index] : indexed(fontTable)) { writer.WriteString(L"{\\f"); writer.WriteString(itow(index)); writer.WriteString(L" "); writer.WriteString(fontName); writer.WriteString(L";}"); } writer.WriteString(L"}{\\colortbl"); for (auto [color, index] : indexed(colorTable)) { writer.WriteString(L";\\red"); writer.WriteString(itow(color.r)); writer.WriteString(L"\\green"); writer.WriteString(itow(color.g)); writer.WriteString(L"\\blue"); writer.WriteString(itow(color.b)); } writer.WriteString(L";}{\\*\\generator GacUI Document 1.0}\\uc0"); { bodyStream.SeekFromBegin(0); StreamReader reader(bodyStream); writer.WriteString(reader.ReadToEnd()); } writer.WriteString(L"}"); } char zero = 0; rtfStream.Write(&zero, sizeof(zero)); rtf = (const char*)rtfStream.GetInternalBuffer(); } void SaveDocumentToRtfStream(Ptr model, stream::IStream& rtfStream) { AString rtf; SaveDocumentToRtf(model, rtf); rtfStream.Write((void*)rtf.Buffer(), rtf.Length()); } } } /*********************************************************************** .\RESOURCES\GUIDOCUMENTEDITOR_ADDCONTAINER.CPP ***********************************************************************/ namespace vl { namespace presentation { using namespace collections; using namespace document_editor; /*********************************************************************** Insert container runs on top of all text ranges that intersect with the specified range AddStyleVisitor : Apply a style on the specified range AddHyperlinkVisitor : Apply a hyperlink on the specified range AddStyleNameVisitor : Apply a style name on the specified range ***********************************************************************/ namespace document_operation_visitors { class AddContainerVisitor : public Object, public DocumentRun::IVisitor { public: RunRangeMap& runRanges; vint start; vint end; bool insertStyle; virtual Ptr CreateContainer() = 0; AddContainerVisitor(RunRangeMap& _runRanges, vint _start, vint _end) :runRanges(_runRanges) , start(_start) , end(_end) , insertStyle(false) { } void VisitContainer(DocumentContainerRun* run) { // TODO: (enumerable) foreach:indexed(alterable(reversed)) for (vint i = run->runs.Count() - 1; i >= 0; i--) { Ptr subRun = run->runs[i]; RunRange range = runRanges[subRun.Obj()]; if (range.startAccept(this); if (insertStyle) { Ptr containerRun = CreateContainer(); run->runs.RemoveAt(i); containerRun->runs.Add(subRun); run->runs.Insert(i, containerRun); } } } insertStyle = false; } void Visit(DocumentTextRun* run)override { insertStyle = true; } void Visit(DocumentStylePropertiesRun* run)override { VisitContainer(run); } void Visit(DocumentStyleApplicationRun* run)override { VisitContainer(run); } void Visit(DocumentHyperlinkRun* run)override { VisitContainer(run); } void Visit(DocumentImageRun* run)override { insertStyle = false; } void Visit(DocumentEmbeddedObjectRun* run)override { insertStyle = false; } void Visit(DocumentParagraphRun* run)override { VisitContainer(run); } }; class AddStyleVisitor : public AddContainerVisitor { public: Ptr style; Ptr CreateContainer()override { auto containerRun = Ptr(new DocumentStylePropertiesRun); containerRun->style = CopyStyle(style); return containerRun; } AddStyleVisitor(RunRangeMap& _runRanges, vint _start, vint _end, Ptr _style) :AddContainerVisitor(_runRanges, _start, _end) , style(_style) { } }; class AddHyperlinkVisitor : public AddContainerVisitor { public: WString reference; WString normalStyleName; WString activeStyleName; Ptr CreateContainer()override { auto containerRun = Ptr(new DocumentHyperlinkRun); containerRun->reference = reference; containerRun->normalStyleName = normalStyleName; containerRun->activeStyleName = activeStyleName; containerRun->styleName = normalStyleName; return containerRun; } AddHyperlinkVisitor(RunRangeMap& _runRanges, vint _start, vint _end, const WString& _reference, const WString& _normalStyleName, const WString& _activeStyleName) :AddContainerVisitor(_runRanges, _start, _end) , reference(_reference) , normalStyleName(_normalStyleName) , activeStyleName(_activeStyleName) { } }; class AddStyleNameVisitor : public AddContainerVisitor { public: WString styleName; Ptr CreateContainer()override { auto containerRun = Ptr(new DocumentStyleApplicationRun); containerRun->styleName = styleName; return containerRun; } AddStyleNameVisitor(RunRangeMap& _runRanges, vint _start, vint _end, const WString& _styleName) :AddContainerVisitor(_runRanges, _start, _end) , styleName(_styleName) { } }; } using namespace document_operation_visitors; namespace document_editor { void AddStyle(DocumentParagraphRun* run, RunRangeMap& runRanges, vint start, vint end, Ptr style) { AddStyleVisitor visitor(runRanges, start, end, style); run->Accept(&visitor); } void AddHyperlink(DocumentParagraphRun* run, RunRangeMap& runRanges, vint start, vint end, const WString& reference, const WString& normalStyleName, const WString& activeStyleName) { AddHyperlinkVisitor visitor(runRanges, start, end, reference, normalStyleName, activeStyleName); run->Accept(&visitor); } void AddStyleName(DocumentParagraphRun* run, RunRangeMap& runRanges, vint start, vint end, const WString& styleName) { AddStyleNameVisitor visitor(runRanges, start, end, styleName); run->Accept(&visitor); } } } } /*********************************************************************** .\RESOURCES\GUIDOCUMENTEDITOR_CLEARUNNECESSARYRUN.CPP ***********************************************************************/ namespace vl { namespace presentation { using namespace collections; using namespace document_editor; /*********************************************************************** Clear all runs that have an empty length ***********************************************************************/ namespace document_operation_visitors { class ClearRunVisitor : public Object, public DocumentRun::IVisitor { public: vint start; ClearRunVisitor() :start(0) { } void VisitContainer(DocumentContainerRun* run) { // TODO: (enumerable) foreach:indexed(alterable(reversed)) for (vint i = run->runs.Count() - 1; i >= 0; i--) { vint oldStart = start; run->runs[i]->Accept(this); if (oldStart == start) { run->runs.RemoveAt(i); } } } void VisitContent(DocumentContentRun* run) { start += run->GetRepresentationText().Length(); } void Visit(DocumentTextRun* run)override { VisitContent(run); } void Visit(DocumentStylePropertiesRun* run)override { VisitContainer(run); } void Visit(DocumentStyleApplicationRun* run)override { VisitContainer(run); } void Visit(DocumentHyperlinkRun* run)override { VisitContainer(run); } void Visit(DocumentImageRun* run)override { VisitContent(run); } void Visit(DocumentEmbeddedObjectRun* run)override { VisitContent(run); } void Visit(DocumentParagraphRun* run)override { VisitContainer(run); } }; } using namespace document_operation_visitors; /*********************************************************************** Ensure DocumentStylePropertiesRun doesn't have a child which is another DocumentStylePropertiesRun Remove DocumentStylePropertiesRun's property if it set a value but doesn't change the output Remove DocumentStylePropertiesRun if it is empty or contains no text run ***********************************************************************/ namespace document_operation_visitors { class CompressStyleRunVisitor : public Object, public DocumentRun::IVisitor { public: DocumentModel* model; List resolvedStyles; List> replacedRuns; CompressStyleRunVisitor(DocumentModel* _model) :model(_model) { DocumentModel::ResolvedStyle resolvedStyle; resolvedStyle = model->GetStyle(DocumentModel::DefaultStyleName, resolvedStyle); resolvedStyles.Add(resolvedStyle); } const DocumentModel::ResolvedStyle& GetCurrentResolvedStyle() { return resolvedStyles[resolvedStyles.Count() - 1]; } void VisitContainer(DocumentContainerRun* run) { // TODO: (enumerable) foreach:indexed(alterable) for (vint i = 0; i < run->runs.Count(); i++) { Ptr subRun = run->runs[i]; replacedRuns.Clear(); subRun->Accept(this); if (replacedRuns.Count() > 0) { run->runs.RemoveAt(i); // TODO: (enumerable) foreach for (vint j = 0; j < replacedRuns.Count(); j++) { run->runs.Insert(i + j, replacedRuns[j]); } i--; } } } void Visit(DocumentTextRun* run)override { } bool OnlyImageOrObject(DocumentContainerRun* run) { bool onlyImageOrObject = true; for (auto subRun : run->runs) { if (!subRun.Cast() && !subRun.Cast()) { onlyImageOrObject = false; break; } } return onlyImageOrObject; } void Visit(DocumentStylePropertiesRun* run)override { if (OnlyImageOrObject(run)) { CopyFrom(replacedRuns, run->runs); return; } const DocumentModel::ResolvedStyle& currentResolvedStyle = GetCurrentResolvedStyle(); DocumentModel::ResolvedStyle resolvedStyle = model->GetStyle(run->style, currentResolvedStyle); if (currentResolvedStyle.style.fontFamily == resolvedStyle.style.fontFamily) run->style->face = Nullable(); if (currentResolvedStyle.style.size == resolvedStyle.style.size) run->style->size = Nullable(); if (currentResolvedStyle.color == resolvedStyle.color) run->style->color = Nullable(); if (currentResolvedStyle.backgroundColor == resolvedStyle.backgroundColor) run->style->backgroundColor = Nullable(); if (currentResolvedStyle.style.bold == resolvedStyle.style.bold) run->style->bold = Nullable(); if (currentResolvedStyle.style.italic == resolvedStyle.style.italic) run->style->italic = Nullable(); if (currentResolvedStyle.style.underline == resolvedStyle.style.underline) run->style->underline = Nullable(); if (currentResolvedStyle.style.strikeline == resolvedStyle.style.strikeline) run->style->strikeline = Nullable(); if (currentResolvedStyle.style.antialias == resolvedStyle.style.antialias) run->style->antialias = Nullable(); if (currentResolvedStyle.style.verticalAntialias == resolvedStyle.style.verticalAntialias) run->style->verticalAntialias = Nullable(); if (run->style->face) goto CONTINUE_PROCESSING; if (run->style->size) goto CONTINUE_PROCESSING; if (run->style->color) goto CONTINUE_PROCESSING; if (run->style->backgroundColor) goto CONTINUE_PROCESSING; if (run->style->bold) goto CONTINUE_PROCESSING; if (run->style->italic) goto CONTINUE_PROCESSING; if (run->style->underline) goto CONTINUE_PROCESSING; if (run->style->strikeline) goto CONTINUE_PROCESSING; if (run->style->antialias) goto CONTINUE_PROCESSING; if (run->style->verticalAntialias) goto CONTINUE_PROCESSING; CopyFrom(replacedRuns, run->runs); return; CONTINUE_PROCESSING: if (From(run->runs).Cast().First(nullptr) != nullptr) { for (auto subRun : run->runs) { if (auto styleRun = subRun.Cast()) { DocumentModel::MergeStyle(styleRun->style, run->style); replacedRuns.Add(styleRun); } else { auto parentRun = CopyRun(run).Cast(); parentRun->runs.Add(subRun); replacedRuns.Add(parentRun); } } return; } resolvedStyles.Add(resolvedStyle); VisitContainer(run); resolvedStyles.RemoveAt(resolvedStyles.Count() - 1); } void Visit(DocumentStyleApplicationRun* run)override { if (OnlyImageOrObject(run)) { CopyFrom(replacedRuns, run->runs); return; } const DocumentModel::ResolvedStyle& currentResolvedStyle = GetCurrentResolvedStyle(); DocumentModel::ResolvedStyle resolvedStyle = model->GetStyle(run->styleName, currentResolvedStyle); resolvedStyles.Add(resolvedStyle); VisitContainer(run); resolvedStyles.RemoveAt(resolvedStyles.Count() - 1); } void Visit(DocumentHyperlinkRun* run)override { const DocumentModel::ResolvedStyle& currentResolvedStyle = GetCurrentResolvedStyle(); DocumentModel::ResolvedStyle resolvedStyle = model->GetStyle(run->styleName, currentResolvedStyle); resolvedStyles.Add(resolvedStyle); VisitContainer(run); resolvedStyles.RemoveAt(resolvedStyles.Count() - 1); } void Visit(DocumentImageRun* run)override { } void Visit(DocumentEmbeddedObjectRun* run)override { } void Visit(DocumentParagraphRun* run)override { VisitContainer(run); } }; } using namespace document_operation_visitors; /*********************************************************************** Merge sibling runs if they are exactly the same ***********************************************************************/ namespace document_operation_visitors { class MergeSiblingRunVisitor : public Object, public DocumentRun::IVisitor { public: Ptr nextRun; Ptr replacedRun; void Visit(DocumentTextRun* run)override { if (auto sibilingRun = nextRun.Cast()) { run->text += sibilingRun->text; replacedRun = Ptr(run); } } void Visit(DocumentStylePropertiesRun* run)override { if (auto sibilingRun = nextRun.Cast()) { if (run->style->face != sibilingRun->style->face) return; if (run->style->size != sibilingRun->style->size) return; if (run->style->color != sibilingRun->style->color) return; if (run->style->backgroundColor != sibilingRun->style->backgroundColor) return; if (run->style->bold != sibilingRun->style->bold) return; if (run->style->italic != sibilingRun->style->italic) return; if (run->style->underline != sibilingRun->style->underline) return; if (run->style->strikeline != sibilingRun->style->strikeline) return; if (run->style->antialias != sibilingRun->style->antialias) return; if (run->style->verticalAntialias != sibilingRun->style->verticalAntialias) return; CopyFrom(run->runs, sibilingRun->runs, true); replacedRun = Ptr(run); } } void Visit(DocumentStyleApplicationRun* run)override { if (auto sibilingRun = nextRun.Cast()) { if (run->styleName == sibilingRun->styleName) { CopyFrom(run->runs, sibilingRun->runs, true); replacedRun = Ptr(run); } } } void Visit(DocumentHyperlinkRun* run)override { if (auto sibilingRun = nextRun.Cast()) { if (run->styleName == sibilingRun->styleName && run->normalStyleName == sibilingRun->normalStyleName && run->activeStyleName == sibilingRun->activeStyleName && run->reference == sibilingRun->reference) { CopyFrom(run->runs, sibilingRun->runs, true); replacedRun = Ptr(run); } } } void Visit(DocumentImageRun* run)override { } void Visit(DocumentEmbeddedObjectRun* run)override { } void Visit(DocumentParagraphRun* run)override { } }; class MergeSiblingRunRecursivelyVisitor : public Object, public DocumentRun::IVisitor { public: Ptr replacedRun; Ptr nextRun; void VisitContainer(DocumentContainerRun* run) { // TODO: (enumerable) foreach:indexed(alterable(reversed)) for (vint i = 0; i < run->runs.Count() - 1; i++) { auto currentRun = run->runs[i]; auto nextRun = run->runs[i + 1]; MergeSiblingRunVisitor visitor; visitor.nextRun = nextRun; currentRun->Accept(&visitor); if (visitor.replacedRun) { run->runs.RemoveAt(i + 1); run->runs[i] = visitor.replacedRun; i--; } } // TODO: (enumerable) foreach for (vint i = 0; i < run->runs.Count() - 1; i++) { run->runs[i]->Accept(this); } } void Visit(DocumentTextRun* run)override { } void Visit(DocumentStylePropertiesRun* run)override { VisitContainer(run); } void Visit(DocumentStyleApplicationRun* run)override { VisitContainer(run); } void Visit(DocumentHyperlinkRun* run)override { VisitContainer(run); } void Visit(DocumentImageRun* run)override { } void Visit(DocumentEmbeddedObjectRun* run)override { } void Visit(DocumentParagraphRun* run)override { VisitContainer(run); } }; } using namespace document_operation_visitors; namespace document_editor { void ClearUnnecessaryRun(DocumentParagraphRun* run, DocumentModel* model) { { ClearRunVisitor visitor; run->Accept(&visitor); } { CompressStyleRunVisitor visitor(model); run->Accept(&visitor); } { MergeSiblingRunRecursivelyVisitor visitor; run->Accept(&visitor); } } } } } /*********************************************************************** .\RESOURCES\GUIDOCUMENTEDITOR_CLONERUN.CPP ***********************************************************************/ namespace vl { namespace presentation { using namespace collections; using namespace document_editor; /*********************************************************************** Clone the current run without its children If clonedRun field is assigned then it will be added to the cloned container run ***********************************************************************/ namespace document_operation_visitors { class CloneRunVisitor : public Object, public DocumentRun::IVisitor { public: Ptr clonedRun; CloneRunVisitor(Ptr subRun) :clonedRun(subRun) { } void VisitContainer(Ptr cloned) { if (clonedRun) { cloned->runs.Add(clonedRun); } clonedRun = cloned; } void Visit(DocumentTextRun* run)override { auto cloned = Ptr(new DocumentTextRun); cloned->text = run->text; clonedRun = cloned; } void Visit(DocumentStylePropertiesRun* run)override { auto cloned = Ptr(new DocumentStylePropertiesRun); cloned->style = CopyStyle(run->style); VisitContainer(cloned); } void Visit(DocumentStyleApplicationRun* run)override { auto cloned = Ptr(new DocumentStyleApplicationRun); cloned->styleName = run->styleName; VisitContainer(cloned); } void Visit(DocumentHyperlinkRun* run)override { auto cloned = Ptr(new DocumentHyperlinkRun); cloned->styleName = run->styleName; cloned->normalStyleName = run->normalStyleName; cloned->activeStyleName = run->activeStyleName; cloned->reference = run->reference; VisitContainer(cloned); } void Visit(DocumentImageRun* run)override { auto cloned = Ptr(new DocumentImageRun); cloned->size = run->size; cloned->baseline = run->baseline; cloned->image = run->image; cloned->frameIndex = run->frameIndex; cloned->source = run->source; clonedRun = cloned; } void Visit(DocumentEmbeddedObjectRun* run)override { auto cloned = Ptr(new DocumentEmbeddedObjectRun); cloned->name = run->name; clonedRun = cloned; } void Visit(DocumentParagraphRun* run)override { auto cloned = Ptr(new DocumentParagraphRun); cloned->alignment = run->alignment; VisitContainer(cloned); } }; } using namespace document_operation_visitors; /*********************************************************************** Clone the current run with its children ***********************************************************************/ namespace document_operation_visitors { class CloneRunRecursivelyVisitor : public Object, public DocumentRun::IVisitor { public: Ptr clonedRun; RunRangeMap& runRanges; vint start; vint end; bool deepCopy; CloneRunRecursivelyVisitor(RunRangeMap& _runRanges, vint _start, vint _end, bool _deepCopy) :runRanges(_runRanges) , start(_start) , end(_end) , deepCopy(_deepCopy) { } void VisitContainer(DocumentContainerRun* run) { clonedRun = 0; RunRange range = runRanges[run]; if (range.start <= end && start <= range.end) { if (start <= range.start && range.end <= end && !deepCopy) { clonedRun = Ptr(run); } else { Ptr containerRun = CopyRun(run).Cast(); for (auto subRun : run->runs) { subRun->Accept(this); if (clonedRun) { containerRun->runs.Add(clonedRun); } } clonedRun = containerRun; } } } void Visit(DocumentTextRun* run)override { clonedRun = 0; RunRange range = runRanges[run]; if (range.startrange.start ? start : range.start; vint copyEnd = endtext = run->text.Sub(copyStart - range.start, copyEnd - copyStart); } clonedRun = textRun; } } } void Visit(DocumentStylePropertiesRun* run)override { VisitContainer(run); } void Visit(DocumentStyleApplicationRun* run)override { VisitContainer(run); } void Visit(DocumentHyperlinkRun* run)override { VisitContainer(run); } void Visit(DocumentImageRun* run)override { clonedRun = 0; RunRange range = runRanges[run]; if (range.start CopyStyle(Ptr style) { if (!style) return nullptr; auto newStyle = Ptr(new DocumentStyleProperties); newStyle->face = style->face; newStyle->size = style->size; newStyle->color = style->color; newStyle->backgroundColor = style->backgroundColor; newStyle->bold = style->bold; newStyle->italic = style->italic; newStyle->underline = style->underline; newStyle->strikeline = style->strikeline; newStyle->antialias = style->antialias; newStyle->verticalAntialias = style->verticalAntialias; return newStyle; } Ptr CopyRun(DocumentRun* run) { CloneRunVisitor visitor(0); run->Accept(&visitor); return visitor.clonedRun; } Ptr CopyStyledText(List& styleRuns, const WString& text) { auto textRun = Ptr(new DocumentTextRun); textRun->text = text; CloneRunVisitor visitor(textRun); // TODO: (enumerable) foreach:reversed for (vint i = styleRuns.Count() - 1; i >= 0; i--) { styleRuns[i]->Accept(&visitor); } return visitor.clonedRun; } Ptr CopyRunRecursively(DocumentParagraphRun* run, RunRangeMap& runRanges, vint start, vint end, bool deepCopy) { CloneRunRecursivelyVisitor visitor(runRanges, start, end, deepCopy); run->Accept(&visitor); return visitor.clonedRun; } } } } /*********************************************************************** .\RESOURCES\GUIDOCUMENTEDITOR_COLLECTSTYLE.CPP ***********************************************************************/ namespace vl { namespace presentation { using namespace collections; /*********************************************************************** Search all used style names ***********************************************************************/ namespace document_operation_visitors { class CollectStyleNameVisitor : public Object, public DocumentRun::IVisitor { public: List& styleNames; CollectStyleNameVisitor(List& _styleNames) :styleNames(_styleNames) { } void VisitContainer(DocumentContainerRun* run) { for (auto subRun : run->runs) { subRun->Accept(this); } } void Visit(DocumentTextRun* run)override { } void Visit(DocumentStylePropertiesRun* run)override { VisitContainer(run); } void Visit(DocumentStyleApplicationRun* run)override { if (!styleNames.Contains(run->styleName)) { styleNames.Add(run->styleName); } VisitContainer(run); } void Visit(DocumentHyperlinkRun* run)override { if (!styleNames.Contains(run->normalStyleName)) { styleNames.Add(run->normalStyleName); } if (!styleNames.Contains(run->activeStyleName)) { styleNames.Add(run->activeStyleName); } VisitContainer(run); } void Visit(DocumentImageRun* run)override { } void Visit(DocumentEmbeddedObjectRun* run)override { } void Visit(DocumentParagraphRun* run)override { VisitContainer(run); } }; } using namespace document_operation_visitors; namespace document_editor { void CollectStyleName(DocumentParagraphRun* run, List& styleNames) { CollectStyleNameVisitor visitor(styleNames); run->Accept(&visitor); } } } } /*********************************************************************** .\RESOURCES\GUIDOCUMENTEDITOR_CUTRUN.CPP ***********************************************************************/ namespace vl { namespace presentation { using namespace collections; using namespace document_editor; /*********************************************************************** Cut all runs into pieces so that a run either completely outside or inside the specified range If a run decides that itself should be cut, then leftRun and rightRun contains new run that will be inserted before and after it ***********************************************************************/ namespace document_operation_visitors { class CutRunVisitor : public Object, public DocumentRun::IVisitor { public: RunRangeMap& runRanges; vint position; Ptr leftRun; Ptr rightRun; CutRunVisitor(RunRangeMap& _runRanges, vint _position) :runRanges(_runRanges) , position(_position) { } void VisitContainer(DocumentContainerRun* run) { vint leftCount = 0; Ptr selectedRun; for (auto subRun : run->runs) { RunRange range = runRanges[subRun.Obj()]; if (range.startAccept(this); if (leftRun && rightRun) { run->runs.RemoveAt(leftCount - 1); run->runs.Insert(leftCount - 1, leftRun); run->runs.Insert(leftCount, rightRun); } } Ptr leftContainer = CopyRun(run).Cast(); Ptr rightContainer = CopyRun(run).Cast(); // TODO: (enumerable) foreach for (vint i = 0; iruns.Count(); i++) { (iruns.Add(run->runs[i]); } leftRun = leftContainer; rightRun = rightContainer; } void Visit(DocumentTextRun* run)override { RunRange range = runRanges[run]; auto leftText = Ptr(new DocumentTextRun); leftText->text = run->text.Sub(0, position - range.start); auto rightText = Ptr(new DocumentTextRun); rightText->text = run->text.Sub(position - range.start, range.end - position); leftRun = leftText; rightRun = rightText; } void Visit(DocumentStylePropertiesRun* run)override { VisitContainer(run); } void Visit(DocumentStyleApplicationRun* run)override { VisitContainer(run); } void Visit(DocumentHyperlinkRun* run)override { VisitContainer(run); } void Visit(DocumentImageRun* run)override { leftRun = 0; rightRun = 0; } void Visit(DocumentEmbeddedObjectRun* run)override { leftRun = 0; rightRun = 0; } void Visit(DocumentParagraphRun* run)override { VisitContainer(run); } }; } using namespace document_operation_visitors; namespace document_editor { void CutRun(DocumentParagraphRun* run, RunRangeMap& runRanges, vint position, Ptr& leftRun, Ptr& rightRun) { CutRunVisitor visitor(runRanges, position); run->Accept(&visitor); leftRun = visitor.leftRun; rightRun = visitor.rightRun; } } } } /*********************************************************************** .\RESOURCES\GUIDOCUMENTEDITOR_GETRUNRANGE.CPP ***********************************************************************/ namespace vl { namespace presentation { using namespace collections; /*********************************************************************** Calculate range informations for each run object ***********************************************************************/ namespace document_operation_visitors { class GetRunRangeVisitor : public Object, public DocumentRun::IVisitor { public: RunRangeMap& runRanges; vint start; GetRunRangeVisitor(RunRangeMap& _runRanges) :runRanges(_runRanges) , start(0) { } void VisitContainer(DocumentContainerRun* run) { RunRange range; range.start = start; for (auto subRun : run->runs) { subRun->Accept(this); } range.end = start; runRanges.Add(run, range); } void VisitContent(DocumentContentRun* run) { RunRange range; range.start = start; start += run->GetRepresentationText().Length(); range.end = start; runRanges.Add(run, range); } void Visit(DocumentTextRun* run)override { VisitContent(run); } void Visit(DocumentStylePropertiesRun* run)override { VisitContainer(run); } void Visit(DocumentStyleApplicationRun* run)override { VisitContainer(run); } void Visit(DocumentHyperlinkRun* run)override { VisitContainer(run); } void Visit(DocumentImageRun* run)override { VisitContent(run); } void Visit(DocumentEmbeddedObjectRun* run)override { VisitContent(run); } void Visit(DocumentParagraphRun* run)override { VisitContainer(run); } }; } using namespace document_operation_visitors; namespace document_editor { void GetRunRange(DocumentParagraphRun* run, RunRangeMap& runRanges) { GetRunRangeVisitor visitor(runRanges); run->Accept(&visitor); } } } } /*********************************************************************** .\RESOURCES\GUIDOCUMENTEDITOR_LOCALEHYPERLINK.CPP ***********************************************************************/ namespace vl { namespace presentation { using namespace collections; /*********************************************************************** Get the hyperlink run that contains the specified position ***********************************************************************/ namespace document_operation_visitors { class LocateHyperlinkVisitor : public Object, public DocumentRun::IVisitor { public: Ptr package; RunRangeMap& runRanges; vint start; vint end; LocateHyperlinkVisitor(RunRangeMap& _runRanges, Ptr _package, vint _start, vint _end) :runRanges(_runRanges) , package(_package) , start(_start) , end(_end) { } void VisitContainer(DocumentContainerRun* run) { Ptr selectedRun; for (auto subRun : run->runs) { RunRange range = runRanges[subRun.Obj()]; if (range.start <= start && end <= range.end) { subRun->Accept(this); break; } } } void Visit(DocumentTextRun* run)override { } void Visit(DocumentStylePropertiesRun* run)override { VisitContainer(run); } void Visit(DocumentStyleApplicationRun* run)override { VisitContainer(run); } void Visit(DocumentHyperlinkRun* run)override { package->hyperlinks.Add(Ptr(run)); } void Visit(DocumentImageRun* run)override { } void Visit(DocumentEmbeddedObjectRun* run)override { } void Visit(DocumentParagraphRun* run)override { VisitContainer(run); } }; } using namespace document_operation_visitors; namespace document_editor { Ptr LocateHyperlink(DocumentParagraphRun* run, RunRangeMap& runRanges, vint row, vint start, vint end) { auto package = Ptr(new DocumentHyperlinkRun::Package); package->row = row; { LocateHyperlinkVisitor visitor(runRanges, package, start, end); run->Accept(&visitor); } Ptr startRun, endRun; for (auto run : package->hyperlinks) { auto range = runRanges[run.Obj()]; if (package->start == -1 || range.start < package->start) { package->start = range.start; startRun = run; } if (package->end == -1 || range.end > package->end) { package->end = range.end; endRun = run; } } while (startRun) { vint pos = runRanges[startRun.Obj()].start; if (pos == 0) break; auto newPackage = Ptr(new DocumentHyperlinkRun::Package); LocateHyperlinkVisitor visitor(runRanges, newPackage, pos - 1, pos); run->Accept(&visitor); if (newPackage->hyperlinks.Count() == 0) break; auto newRun = newPackage->hyperlinks[0]; if (startRun->reference != newRun->reference) break; auto range = runRanges[newRun.Obj()]; package->hyperlinks.Add(newRun); package->start = range.start; startRun = newRun; } vint length = runRanges[run].end; while (endRun) { vint pos = runRanges[endRun.Obj()].end; if (pos == length) break; auto newPackage = Ptr(new DocumentHyperlinkRun::Package); LocateHyperlinkVisitor visitor(runRanges, newPackage, pos, pos + 1); run->Accept(&visitor); if (newPackage->hyperlinks.Count() == 0) break; auto newRun = newPackage->hyperlinks[0]; if (endRun->reference != newRun->reference) break; auto range = runRanges[newRun.Obj()]; package->hyperlinks.Add(newRun); package->end = range.end; endRun = newRun; } return package; } } } } /*********************************************************************** .\RESOURCES\GUIDOCUMENTEDITOR_LOCALESTYLE.CPP ***********************************************************************/ namespace vl { namespace presentation { using namespace collections; /*********************************************************************** Get all container runs that contain the specified position from top to bottom ***********************************************************************/ namespace document_operation_visitors { class LocateStyleVisitor : public Object, public DocumentRun::IVisitor { public: List& locatedRuns; RunRangeMap& runRanges; vint position; bool frontSide; LocateStyleVisitor(List& _locatedRuns, RunRangeMap& _runRanges, vint _position, bool _frontSide) :locatedRuns(_locatedRuns) , runRanges(_runRanges) , position(_position) , frontSide(_frontSide) { } void VisitContainer(DocumentContainerRun* run) { locatedRuns.Add(run); Ptr selectedRun; for (auto subRun : run->runs) { RunRange range = runRanges[subRun.Obj()]; if (position == range.start) { if (!frontSide) { selectedRun = subRun; break; } } else if (position == range.end) { if (frontSide) { selectedRun = subRun; break; } } else if (range.startAccept(this); } } void Visit(DocumentTextRun* run)override { } void Visit(DocumentStylePropertiesRun* run)override { VisitContainer(run); } void Visit(DocumentStyleApplicationRun* run)override { VisitContainer(run); } void Visit(DocumentHyperlinkRun* run)override { VisitContainer(run); } void Visit(DocumentImageRun* run)override { } void Visit(DocumentEmbeddedObjectRun* run)override { } void Visit(DocumentParagraphRun* run)override { VisitContainer(run); } }; } using namespace document_operation_visitors; namespace document_editor { void LocateStyle(DocumentParagraphRun* run, RunRangeMap& runRanges, vint position, bool frontSide, List& locatedRuns) { LocateStyleVisitor visitor(locatedRuns, runRanges, position, frontSide); run->Accept(&visitor); } } } } /*********************************************************************** .\RESOURCES\GUIDOCUMENTEDITOR_REMOVECONTAINER.CPP ***********************************************************************/ namespace vl { namespace presentation { using namespace collections; /*********************************************************************** Remove some containers that intersect with the specified range If a run decides that itself should be removed, then replacedRuns contains all runs to replace itself RemoveHyperlinkVisitor : Remove all hyperlinks that intersect with the specified range RemoveStyleNameVisitor : Remove all style names that intersect with the specified range ClearStyleVisitor : Remove all styles that intersect with the specified range ***********************************************************************/ namespace document_operation_visitors { class RemoveContainerVisitor : public Object, public DocumentRun::IVisitor { public: RunRangeMap& runRanges; vint start; vint end; List> replacedRuns; RemoveContainerVisitor(RunRangeMap& _runRanges, vint _start, vint _end) :runRanges(_runRanges) , start(_start) , end(_end) { } void VisitContainer(DocumentContainerRun* run) { // TODO: (enumerable) foreach:indexed(alterable(reversed)) for (vint i = run->runs.Count() - 1; i >= 0; i--) { Ptr subRun = run->runs[i]; RunRange range = runRanges[subRun.Obj()]; if (range.startAccept(this); if (replacedRuns.Count() != 1 || replacedRuns[0] != subRun) { run->runs.RemoveAt(i); // TODO: (enumerable) foreach for (vint j = 0; jruns.Insert(i + j, replacedRuns[j]); } i += replacedRuns.Count(); } } } replacedRuns.Clear(); replacedRuns.Add(Ptr(run)); } void VisitContent(DocumentContentRun* run) { replacedRuns.Add(Ptr(run)); } void RemoveContainer(DocumentContainerRun* run) { CopyFrom(replacedRuns, run->runs); } void Visit(DocumentTextRun* run)override { VisitContent(run); } void Visit(DocumentStylePropertiesRun* run)override { VisitContainer(run); } void Visit(DocumentStyleApplicationRun* run)override { VisitContainer(run); } void Visit(DocumentHyperlinkRun* run)override { VisitContainer(run); } void Visit(DocumentImageRun* run)override { VisitContent(run); } void Visit(DocumentEmbeddedObjectRun* run)override { VisitContent(run); } void Visit(DocumentParagraphRun* run)override { VisitContainer(run); } }; class RemoveHyperlinkVisitor : public RemoveContainerVisitor { public: RemoveHyperlinkVisitor(RunRangeMap& _runRanges, vint _start, vint _end) :RemoveContainerVisitor(_runRanges, _start, _end) { } void Visit(DocumentHyperlinkRun* run)override { RemoveContainer(run); } }; class RemoveStyleNameVisitor : public RemoveContainerVisitor { public: RemoveStyleNameVisitor(RunRangeMap& _runRanges, vint _start, vint _end) :RemoveContainerVisitor(_runRanges, _start, _end) { } void Visit(DocumentStyleApplicationRun* run)override { RemoveContainer(run); } }; class ClearStyleVisitor : public RemoveContainerVisitor { public: ClearStyleVisitor(RunRangeMap& _runRanges, vint _start, vint _end) :RemoveContainerVisitor(_runRanges, _start, _end) { } void Visit(DocumentStylePropertiesRun* run)override { RemoveContainer(run); } void Visit(DocumentStyleApplicationRun* run)override { RemoveContainer(run); } }; } using namespace document_operation_visitors; namespace document_editor { void RemoveHyperlink(DocumentParagraphRun* run, RunRangeMap& runRanges, vint start, vint end) { RemoveHyperlinkVisitor visitor(runRanges, start, end); run->Accept(&visitor); } void RemoveStyleName(DocumentParagraphRun* run, RunRangeMap& runRanges, vint start, vint end) { RemoveStyleNameVisitor visitor(runRanges, start, end); run->Accept(&visitor); } void ClearStyle(DocumentParagraphRun* run, RunRangeMap& runRanges, vint start, vint end) { ClearStyleVisitor visitor(runRanges, start, end); run->Accept(&visitor); } } } } /*********************************************************************** .\RESOURCES\GUIDOCUMENTEDITOR_REMOVERUN.CPP ***********************************************************************/ namespace vl { namespace presentation { using namespace collections; /*********************************************************************** Remove text run contents with the specified range, or other content runs that intersect with the range If a run decides that itself should be removed, then replacedRuns contains all runs to replace itself ***********************************************************************/ namespace document_operation_visitors { class RemoveRunVisitor : public Object, public DocumentRun::IVisitor { public: RunRangeMap& runRanges; vint start; vint end; List> replacedRuns; RemoveRunVisitor(RunRangeMap& _runRanges, vint _start, vint _end) :runRanges(_runRanges) , start(_start) , end(_end) { } void VisitContainer(DocumentContainerRun* run) { if (start == end) return; // TODO: (enumerable) foreach:indexed(alterable(reversed)) for (vint i = run->runs.Count() - 1; i >= 0; i--) { Ptr subRun = run->runs[i]; RunRange range = runRanges[subRun.Obj()]; vint maxStart = range.start > start ? range.start : start; vint minEnd = range.end < end ? range.end : end; if (maxStart < minEnd) { subRun->Accept(this); if (replacedRuns.Count() == 0 || subRun != replacedRuns[0]) { run->runs.RemoveAt(i); // TODO: (enumerable) foreach for (vint j = 0; jruns.Insert(i + j, replacedRuns[j]); } } } } replacedRuns.Clear(); replacedRuns.Add(Ptr(run)); } void Visit(DocumentTextRun* run)override { replacedRuns.Clear(); RunRange range = runRanges[run]; if (start <= range.start) { if (endtext = run->text.Sub(end - range.start, range.end - end); replacedRuns.Add(Ptr(run)); } } else { if (endtext = run->text.Sub(0, start - range.start); secondRun->text = run->text.Sub(end - range.start, range.end - end); replacedRuns.Add(firstRun); replacedRuns.Add(secondRun); } else { run->text = run->text.Sub(0, start - range.start); replacedRuns.Add(Ptr(run)); } } } void Visit(DocumentStylePropertiesRun* run)override { VisitContainer(run); } void Visit(DocumentStyleApplicationRun* run)override { VisitContainer(run); } void Visit(DocumentHyperlinkRun* run)override { VisitContainer(run); } void Visit(DocumentImageRun* run)override { replacedRuns.Clear(); } void Visit(DocumentEmbeddedObjectRun* run)override { replacedRuns.Clear(); } void Visit(DocumentParagraphRun* run)override { VisitContainer(run); } }; } using namespace document_operation_visitors; namespace document_editor { void RemoveRun(DocumentParagraphRun* run, RunRangeMap& runRanges, vint start, vint end) { RemoveRunVisitor visitor(runRanges, start, end); run->Accept(&visitor); } } } } /*********************************************************************** .\RESOURCES\GUIDOCUMENTEDITOR_REPLACESTYLENAME.CPP ***********************************************************************/ namespace vl { namespace presentation { using namespace collections; /*********************************************************************** Replace a style name with another one ***********************************************************************/ namespace document_operation_visitors { class ReplaceStyleNameVisitor : public Object, public DocumentRun::IVisitor { public: WString oldStyleName; WString newStyleName; ReplaceStyleNameVisitor(const WString& _oldStyleName, const WString& _newStyleName) :oldStyleName(_oldStyleName) , newStyleName(_newStyleName) { } void VisitContainer(DocumentContainerRun* run) { for (auto subRun : run->runs) { subRun->Accept(this); } } void Visit(DocumentTextRun* run)override { } void Visit(DocumentStylePropertiesRun* run)override { VisitContainer(run); } void Visit(DocumentStyleApplicationRun* run)override { if (run->styleName == oldStyleName) run->styleName = newStyleName; VisitContainer(run); } void Visit(DocumentHyperlinkRun* run)override { if (run->styleName == oldStyleName) run->styleName = newStyleName; if (run->normalStyleName == oldStyleName) run->normalStyleName = newStyleName; if (run->activeStyleName == oldStyleName) run->activeStyleName = newStyleName; VisitContainer(run); } void Visit(DocumentImageRun* run)override { } void Visit(DocumentEmbeddedObjectRun* run)override { } void Visit(DocumentParagraphRun* run)override { VisitContainer(run); } }; } using namespace document_operation_visitors; namespace document_editor { void ReplaceStyleName(DocumentParagraphRun* run, const WString& oldStyleName, const WString& newStyleName) { ReplaceStyleNameVisitor visitor(oldStyleName, newStyleName); run->Accept(&visitor); } } } } /*********************************************************************** .\RESOURCES\GUIDOCUMENTEDITOR_SUMMERIZESTYLE.CPP ***********************************************************************/ namespace vl { namespace presentation { using namespace collections; namespace document_operation_visitors { /*********************************************************************** Calculate if all text in the specified range has some common styles ***********************************************************************/ class SummarizeStyleVisitor : public Object, public DocumentRun::IVisitor { public: RunRangeMap& runRanges; DocumentModel* model; vint start; vint end; Ptr style; List resolvedStyles; SummarizeStyleVisitor(RunRangeMap& _runRanges, DocumentModel* _model, vint _start, vint _end) :runRanges(_runRanges) , model(_model) , start(_start) , end(_end) { DocumentModel::ResolvedStyle resolvedStyle; resolvedStyle = model->GetStyle(DocumentModel::DefaultStyleName, resolvedStyle); resolvedStyles.Add(resolvedStyle); } const DocumentModel::ResolvedStyle& GetCurrentResolvedStyle() { return resolvedStyles[resolvedStyles.Count() - 1]; } // --------------------------------------------------------- template void SetStyleItem(Nullable DocumentStyleProperties::* dstField, T FontProperties::* srcField) { const DocumentModel::ResolvedStyle& src = GetCurrentResolvedStyle(); if (style.Obj()->*dstField && (style.Obj()->*dstField).Value() != src.style.*srcField) { style.Obj()->*dstField = Nullable(); } } template void SetStyleItem(Nullable DocumentStyleProperties::* dstField, T DocumentModel::ResolvedStyle::* srcField) { const DocumentModel::ResolvedStyle& src = GetCurrentResolvedStyle(); if (style.Obj()->*dstField && (style.Obj()->*dstField).Value() != src.*srcField) { style.Obj()->*dstField = Nullable(); } } void SetStyleItem(Nullable DocumentStyleProperties::* dstField, vint FontProperties::* srcField) { const DocumentModel::ResolvedStyle& src = GetCurrentResolvedStyle(); if (style.Obj()->*dstField) { auto dfs = (style.Obj()->*dstField).Value(); if (dfs.relative || dfs.size != src.style.*srcField) { style.Obj()->*dstField = Nullable(); } } } // --------------------------------------------------------- template void OverrideStyleItem(Nullable DocumentStyleProperties::* dstField, T FontProperties::* srcField) { const DocumentModel::ResolvedStyle& src = GetCurrentResolvedStyle(); style.Obj()->*dstField = src.style.*srcField; } template void OverrideStyleItem(Nullable DocumentStyleProperties::* dstField, T DocumentModel::ResolvedStyle::* srcField) { const DocumentModel::ResolvedStyle& src = GetCurrentResolvedStyle(); style.Obj()->*dstField = src.*srcField; } void OverrideStyleItem(Nullable DocumentStyleProperties::* dstField, vint FontProperties::* srcField) { const DocumentModel::ResolvedStyle& src = GetCurrentResolvedStyle(); style.Obj()->*dstField = DocumentFontSize((double)(src.style.*srcField), false); } // --------------------------------------------------------- void VisitContainer(DocumentContainerRun* run) { // TODO: (enumerable) foreach:reversed for (vint i = run->runs.Count() - 1; i >= 0; i--) { Ptr subRun = run->runs[i]; RunRange range = runRanges[subRun.Obj()]; if (range.startAccept(this); } } } void Visit(DocumentTextRun* run)override { const DocumentModel::ResolvedStyle& currentResolvedStyle = GetCurrentResolvedStyle(); if (style) { SetStyleItem(&DocumentStyleProperties::face, &FontProperties::fontFamily); SetStyleItem(&DocumentStyleProperties::size, &FontProperties::size); SetStyleItem(&DocumentStyleProperties::color, &DocumentModel::ResolvedStyle::color); SetStyleItem(&DocumentStyleProperties::backgroundColor, &DocumentModel::ResolvedStyle::backgroundColor); SetStyleItem(&DocumentStyleProperties::bold, &FontProperties::bold); SetStyleItem(&DocumentStyleProperties::italic, &FontProperties::italic); SetStyleItem(&DocumentStyleProperties::underline, &FontProperties::underline); SetStyleItem(&DocumentStyleProperties::strikeline, &FontProperties::strikeline); SetStyleItem(&DocumentStyleProperties::antialias, &FontProperties::antialias); SetStyleItem(&DocumentStyleProperties::verticalAntialias, &FontProperties::verticalAntialias); } else { style = Ptr(new DocumentStyleProperties); OverrideStyleItem(&DocumentStyleProperties::face, &FontProperties::fontFamily); OverrideStyleItem(&DocumentStyleProperties::size, &FontProperties::size); OverrideStyleItem(&DocumentStyleProperties::color, &DocumentModel::ResolvedStyle::color); OverrideStyleItem(&DocumentStyleProperties::backgroundColor, &DocumentModel::ResolvedStyle::backgroundColor); OverrideStyleItem(&DocumentStyleProperties::bold, &FontProperties::bold); OverrideStyleItem(&DocumentStyleProperties::italic, &FontProperties::italic); OverrideStyleItem(&DocumentStyleProperties::underline, &FontProperties::underline); OverrideStyleItem(&DocumentStyleProperties::strikeline, &FontProperties::strikeline); OverrideStyleItem(&DocumentStyleProperties::antialias, &FontProperties::antialias); OverrideStyleItem(&DocumentStyleProperties::verticalAntialias, &FontProperties::verticalAntialias); } } void Visit(DocumentStylePropertiesRun* run)override { const DocumentModel::ResolvedStyle& currentResolvedStyle = GetCurrentResolvedStyle(); DocumentModel::ResolvedStyle resolvedStyle = model->GetStyle(run->style, currentResolvedStyle); resolvedStyles.Add(resolvedStyle); VisitContainer(run); resolvedStyles.RemoveAt(resolvedStyles.Count() - 1); } void Visit(DocumentStyleApplicationRun* run)override { const DocumentModel::ResolvedStyle& currentResolvedStyle = GetCurrentResolvedStyle(); DocumentModel::ResolvedStyle resolvedStyle = model->GetStyle(run->styleName, currentResolvedStyle); resolvedStyles.Add(resolvedStyle); VisitContainer(run); resolvedStyles.RemoveAt(resolvedStyles.Count() - 1); } void Visit(DocumentHyperlinkRun* run)override { const DocumentModel::ResolvedStyle& currentResolvedStyle = GetCurrentResolvedStyle(); DocumentModel::ResolvedStyle resolvedStyle = model->GetStyle(run->styleName, currentResolvedStyle); resolvedStyles.Add(resolvedStyle); VisitContainer(run); resolvedStyles.RemoveAt(resolvedStyles.Count() - 1); } void Visit(DocumentImageRun* run)override { } void Visit(DocumentEmbeddedObjectRun* run)override { } void Visit(DocumentParagraphRun* run)override { VisitContainer(run); } }; /*********************************************************************** Calculate if all text in the specified range has a common style name ***********************************************************************/ class SummarizeStyleNameVisitor : public Object, public DocumentRun::IVisitor { public: RunRangeMap& runRanges; DocumentModel* model; vint start; vint end; Nullable currentStyleName; Nullable styleName; bool assignedStyleName = false; SummarizeStyleNameVisitor(RunRangeMap& _runRanges, DocumentModel* _model, vint _start, vint _end) :runRanges(_runRanges) , model(_model) , start(_start) , end(_end) { } void VisitContentRun(DocumentContentRun* run) { if (!assignedStyleName) { styleName = currentStyleName; assignedStyleName = true; } else if (styleName && (!currentStyleName || styleName.Value() != currentStyleName.Value())) { styleName = Nullable(); } } void VisitContainer(DocumentContainerRun* run) { // TODO: (enumerable) foreach:reversed for (vint i = run->runs.Count() - 1; i >= 0; i--) { Ptr subRun = run->runs[i]; RunRange range = runRanges[subRun.Obj()]; if (range.startAccept(this); } } } void Visit(DocumentTextRun* run)override { VisitContentRun(run); } void Visit(DocumentStylePropertiesRun* run)override { VisitContainer(run); } void Visit(DocumentStyleApplicationRun* run)override { auto oldStyleName = currentStyleName; currentStyleName = run->styleName; VisitContainer(run); currentStyleName = oldStyleName; } void Visit(DocumentHyperlinkRun* run)override { VisitContainer(run); } void Visit(DocumentImageRun* run)override { VisitContentRun(run); } void Visit(DocumentEmbeddedObjectRun* run)override { VisitContentRun(run); } void Visit(DocumentParagraphRun* run)override { VisitContainer(run); } }; } using namespace document_operation_visitors; namespace document_editor { Ptr SummarizeStyle(DocumentParagraphRun* run, RunRangeMap& runRanges, DocumentModel* model, vint start, vint end) { SummarizeStyleVisitor visitor(runRanges, model, start, end); run->Accept(&visitor); return visitor.style; } Nullable SummarizeStyleName(DocumentParagraphRun* run, RunRangeMap& runRanges, DocumentModel* model, vint start, vint end) { SummarizeStyleNameVisitor visitor(runRanges, model, start, end); run->Accept(&visitor); return visitor.styleName; } template void AggregateStyleItem(Ptr& dst, Ptr src, Nullable DocumentStyleProperties::* field) { if (dst.Obj()->*field && (!(src.Obj()->*field) || (dst.Obj()->*field).Value() != (src.Obj()->*field).Value())) { dst.Obj()->*field = Nullable(); } } void AggregateStyle(Ptr& dst, Ptr src) { AggregateStyleItem(dst, src, &DocumentStyleProperties::face); AggregateStyleItem(dst, src, &DocumentStyleProperties::size); AggregateStyleItem(dst, src, &DocumentStyleProperties::color); AggregateStyleItem(dst, src, &DocumentStyleProperties::backgroundColor); AggregateStyleItem(dst, src, &DocumentStyleProperties::bold); AggregateStyleItem(dst, src, &DocumentStyleProperties::italic); AggregateStyleItem(dst, src, &DocumentStyleProperties::underline); AggregateStyleItem(dst, src, &DocumentStyleProperties::strikeline); AggregateStyleItem(dst, src, &DocumentStyleProperties::antialias); AggregateStyleItem(dst, src, &DocumentStyleProperties::verticalAntialias); } } } } /*********************************************************************** .\RESOURCES\GUIDOCUMENT_EDIT.CPP ***********************************************************************/ namespace vl { namespace presentation { using namespace collections; using namespace document_editor; /*********************************************************************** DocumentModel::EditRangeOperations ***********************************************************************/ bool DocumentModel::CheckEditRange(TextPos begin, TextPos end, RunRangeMap& relatedRanges) { // check caret range if(begin>end) return false; if(begin.row<0 || begin.row>=paragraphs.Count()) return false; if(end.row<0 || end.row>=paragraphs.Count()) return false; // determine run ranges GetRunRange(paragraphs[begin.row].Obj(), relatedRanges); if(begin.row!=end.row) { GetRunRange(paragraphs[end.row].Obj(), relatedRanges); } // check caret range RunRange beginRange=relatedRanges[paragraphs[begin.row].Obj()]; RunRange endRange=relatedRanges[paragraphs[end.row].Obj()]; if(begin.column<0 || begin.column>beginRange.end) return false; if(end.column<0 || end.column>endRange.end) return false; return true; } Ptr DocumentModel::CopyDocument(TextPos begin, TextPos end, bool deepCopy) { // check caret range RunRangeMap runRanges; if(!CheckEditRange(begin, end, runRanges)) return nullptr; // get ranges for(vint i=begin.row+1;iparagraphs.Add(CopyRunRecursively(paragraphs[begin.row].Obj(), runRanges, begin.column, end.column, deepCopy).Cast()); } else { for(vint i=begin.row;i<=end.row;i++) { Ptr paragraph=paragraphs[i]; RunRange range=runRanges[paragraph.Obj()]; if(i==begin.row) { newDocument->paragraphs.Add(CopyRunRecursively(paragraph.Obj(), runRanges, begin.column, range.end, deepCopy).Cast()); } else if(i==end.row) { newDocument->paragraphs.Add(CopyRunRecursively(paragraph.Obj(), runRanges, range.start, end.column, deepCopy).Cast()); } else if(deepCopy) { newDocument->paragraphs.Add(CopyRunRecursively(paragraph.Obj(), runRanges, range.start, range.end, deepCopy).Cast()); } else { newDocument->paragraphs.Add(paragraph); } } } // copy styles List styleNames; for (auto paragraph : newDocument->paragraphs) { CollectStyleName(paragraph.Obj(), styleNames); } // TODO: (enumerable) foreach:alterable for(vint i=0;istyles.Keys().Contains(styleName)) { Ptr style=styles[styleName]; if(deepCopy) { auto newStyle = Ptr(new DocumentStyle); newStyle->parentStyleName=style->parentStyleName; newStyle->styles=CopyStyle(style->styles); newStyle->resolvedStyles=CopyStyle(style->resolvedStyles); newDocument->styles.Add(styleName, newStyle); } else { newDocument->styles.Add(styleName, style); } if(!styleNames.Contains(style->parentStyleName)) { styleNames.Add(style->parentStyleName); } } } return newDocument; } Ptr DocumentModel::CopyDocument() { // determine run ranges RunRangeMap runRanges; vint lastParagraphIndex = paragraphs.Count() - 1; GetRunRange(paragraphs[lastParagraphIndex].Obj(), runRanges); TextPos begin(0, 0); TextPos end(lastParagraphIndex, runRanges[paragraphs[lastParagraphIndex].Obj()].end); return CopyDocument(begin, end, true); } bool DocumentModel::CutParagraph(TextPos position) { if(position.row<0 || position.row>=paragraphs.Count()) return false; Ptr paragraph=paragraphs[position.row]; RunRangeMap runRanges; Ptr leftRun, rightRun; GetRunRange(paragraph.Obj(), runRanges); CutRun(paragraph.Obj(), runRanges, position.column, leftRun, rightRun); CopyFrom(paragraph->runs, leftRun.Cast()->runs); CopyFrom(paragraph->runs, rightRun.Cast()->runs, true); return true; } bool DocumentModel::CutEditRange(TextPos begin, TextPos end) { // check caret range if(begin>end) return false; if(begin.row<0 || begin.row>=paragraphs.Count()) return false; if(end.row<0 || end.row>=paragraphs.Count()) return false; // cut paragraphs CutParagraph(begin); if(begin!=end) { CutParagraph(end); } return true; } bool DocumentModel::EditContainer(TextPos begin, TextPos end, const Func& editor) { if(begin==end) return false; // cut paragraphs if(!CutEditRange(begin, end)) return false; // check caret range RunRangeMap runRanges; if(!CheckEditRange(begin, end, runRanges)) return false; // edit container if(begin.row==end.row) { editor(paragraphs[begin.row].Obj(), runRanges, begin.column, end.column); } else { for(vint i=begin.row;i<=end.row;i++) { Ptr paragraph=paragraphs[i]; if(begin.row replaceToModel, bool copy) { // check caret range RunRangeMap runRanges; if(!CheckEditRange(begin, end, runRanges)) return -1; auto model = replaceToModel; if (copy) { model = replaceToModel->CopyDocument(); } // calculate new names for the model's styles to prevent conflicting List oldNames, newNames; CopyFrom(oldNames, model->styles.Keys()); CopyFrom(newNames, model->styles.Keys()); // TODO: (enumerable) foreach:indexed(allow-set) for(vint i=0;istyles.Keys().Contains(newName)) { newNames[i]=newName; break; } } } } // rename model's styles typedef Pair NamePair; for (auto name : From(oldNames).Pairwise(newNames)) { model->RenameStyle(name.key, name.value); } for (auto name : newNames) { if((name.Length()==0 || name[0]!=L'#') && !styles.Keys().Contains(name)) { styles.Add(name, model->styles[name]); } } // edit runs Array> runs; CopyFrom(runs, model->paragraphs); return EditRunNoCopy(begin, end, runs); } vint DocumentModel::EditRunNoCopy(TextPos begin, TextPos end, const collections::Array>& runs) { // check caret range RunRangeMap runRanges; if(!CheckEditRange(begin, end, runRanges)) return -1; // remove unnecessary paragraphs if(begin.row!=end.row) { for(vint i=end.row-1;i>begin.row;i--) { paragraphs.RemoveAt(i); } end.row=begin.row+1; } // remove unnecessary runs and ensure begin.row!=end.row if(begin.row==end.row) { RemoveRun(paragraphs[begin.row].Obj(), runRanges, begin.column, end.column); Ptr leftRun, rightRun; runRanges.Clear(); GetRunRange(paragraphs[begin.row].Obj(), runRanges); CutRun(paragraphs[begin.row].Obj(), runRanges, begin.column, leftRun, rightRun); paragraphs.RemoveAt(begin.row); paragraphs.Insert(begin.row, leftRun.Cast()); paragraphs.Insert(begin.row+1, rightRun.Cast()); end.row=begin.row+1; } else { RemoveRun(paragraphs[begin.row].Obj(), runRanges, begin.column, runRanges[paragraphs[begin.row].Obj()].end); RemoveRun(paragraphs[end.row].Obj(), runRanges, 0, end.column); } // insert new paragraphs Ptr beginParagraph=paragraphs[begin.row]; Ptr endParagraph=paragraphs[end.row]; if(runs.Count()==0) { CopyFrom(beginParagraph->runs, endParagraph->runs, true); paragraphs.RemoveAt(end.row); } else if(runs.Count()==1) { CopyFrom(beginParagraph->runs, runs[0]->runs, true); CopyFrom(beginParagraph->runs, endParagraph->runs, true); paragraphs.RemoveAt(end.row); } else { Ptr newBeginRuns=runs[0]; CopyFrom(beginParagraph->runs, newBeginRuns->runs, true); Ptr newEndRuns=runs[runs.Count()-1]; if (newEndRuns->alignment) { endParagraph->alignment = newEndRuns->alignment; } // TODO: (enumerable) foreach for(vint i=0;iruns.Count();i++) { endParagraph->runs.Insert(i, newEndRuns->runs[i]); } // TODO: (enumerable) foreach:indexed for(vint i=1;i& text) { // check caret range RunRangeMap runRanges; if(!CheckEditRange(begin, end, runRanges)) return -1; // calcuate the position to get the text style TextPos stylePosition; if(frontSide) { stylePosition=begin; if(stylePosition.column==0) { frontSide=false; } } else { stylePosition=end; if(stylePosition.column==runRanges[paragraphs[end.row].Obj()].end) { frontSide=true; } } // copy runs that contains the target style for new text List styleRuns; LocateStyle(paragraphs[stylePosition.row].Obj(), runRanges, stylePosition.column, frontSide, styleRuns); // create paragraphs Array> runs(text.Count()); // TODO: (enumerable) foreach:indexed for(vint i=0;i paragraph=CopyStyledText(styleRuns, text[i]); runs[i]=paragraph.Cast(); } // replace the paragraphs return EditRunNoCopy(begin, end, runs); } /*********************************************************************** DocumentModel::EditStyle ***********************************************************************/ bool DocumentModel::EditStyle(TextPos begin, TextPos end, Ptr style) { return EditContainer(begin, end, [=](DocumentParagraphRun* paragraph, RunRangeMap& runRanges, vint start, vint end) { AddStyle(paragraph, runRanges, start, end, style); }); } /*********************************************************************** DocumentModel::EditImage ***********************************************************************/ Ptr DocumentModel::EditImage(TextPos begin, TextPos end, Ptr image) { auto imageRun = Ptr(new DocumentImageRun); imageRun->size=image->GetImage()->GetFrame(image->GetFrameIndex())->GetSize(); imageRun->baseline=imageRun->size.y; imageRun->image=image->GetImage(); imageRun->frameIndex=image->GetFrameIndex(); auto paragraph = Ptr(new DocumentParagraphRun); paragraph->runs.Add(imageRun); Array> runs(1); runs[0]=paragraph; if(EditRunNoCopy(begin, end, runs)) { return imageRun; } else { return 0; } } /*********************************************************************** DocumentModel::EditHyperlink ***********************************************************************/ bool DocumentModel::EditHyperlink(vint paragraphIndex, vint begin, vint end, const WString& reference, const WString& normalStyleName, const WString& activeStyleName) { auto package = GetHyperlink(paragraphIndex, begin, end); if (package->hyperlinks.Count() > 0) { for (auto run : package->hyperlinks) { run->reference = reference; run->normalStyleName = normalStyleName; run->activeStyleName = activeStyleName; run->styleName = normalStyleName; } return true; } else if (RemoveHyperlink(paragraphIndex, begin, end)) { CutEditRange(TextPos(paragraphIndex, begin), TextPos(paragraphIndex, end)); RunRangeMap runRanges; Ptr paragraph = paragraphs[paragraphIndex]; GetRunRange(paragraph.Obj(), runRanges); AddHyperlink(paragraph.Obj(), runRanges, begin, end, reference, normalStyleName, activeStyleName); ClearUnnecessaryRun(paragraph.Obj(), this); return true; } return false; } bool DocumentModel::RemoveHyperlink(vint paragraphIndex, vint begin, vint end) { RunRangeMap runRanges; if (!CheckEditRange(TextPos(paragraphIndex, begin), TextPos(paragraphIndex, end), runRanges)) return 0; auto paragraph = paragraphs[paragraphIndex]; auto package = LocateHyperlink(paragraph.Obj(), runRanges, paragraphIndex, begin, end); document_editor::RemoveHyperlink(paragraph.Obj(), runRanges, package->start, package->end); ClearUnnecessaryRun(paragraph.Obj(), this); return true; } Ptr DocumentModel::GetHyperlink(vint paragraphIndex, vint begin, vint end) { RunRangeMap runRanges; if (!CheckEditRange(TextPos(paragraphIndex, begin), TextPos(paragraphIndex, end), runRanges)) return 0; auto paragraph = paragraphs[paragraphIndex]; return LocateHyperlink(paragraph.Obj(), runRanges, paragraphIndex, begin, end); } /*********************************************************************** DocumentModel::EditStyleName ***********************************************************************/ bool DocumentModel::EditStyleName(TextPos begin, TextPos end, const WString& styleName) { return EditContainer(begin, end, [=](DocumentParagraphRun* paragraph, RunRangeMap& runRanges, vint start, vint end) { AddStyleName(paragraph, runRanges, start, end, styleName); }); } bool DocumentModel::RemoveStyleName(TextPos begin, TextPos end) { return EditContainer(begin, end, [=](DocumentParagraphRun* paragraph, RunRangeMap& runRanges, vint start, vint end) { document_editor::RemoveStyleName(paragraph, runRanges, start, end); }); } bool DocumentModel::RenameStyle(const WString& oldStyleName, const WString& newStyleName) { vint index=styles.Keys().IndexOf(oldStyleName); if(index==-1) return false; if(styles.Keys().Contains(newStyleName)) return false; Ptr style=styles.Values()[index]; styles.Remove(oldStyleName); styles.Add(newStyleName, style); for (auto subStyle : styles.Values()) { if(subStyle->parentStyleName==oldStyleName) { subStyle->parentStyleName=newStyleName; } } for (auto paragraph : paragraphs) { ReplaceStyleName(paragraph.Obj(), oldStyleName, newStyleName); } return true; } /*********************************************************************** DocumentModel::ClearStyle ***********************************************************************/ bool DocumentModel::ClearStyle(TextPos begin, TextPos end) { return EditContainer(begin, end, [=](DocumentParagraphRun* paragraph, RunRangeMap& runRanges, vint start, vint end) { document_editor::ClearStyle(paragraph, runRanges, start, end); }); } /*********************************************************************** DocumentModel::ClearStyle ***********************************************************************/ Ptr DocumentModel::SummarizeStyle(TextPos begin, TextPos end) { Ptr style; RunRangeMap runRanges; if (begin == end) goto END_OF_SUMMERIZING; // check caret range if (!CheckEditRange(begin, end, runRanges)) return nullptr; // Summarize container if (begin.row == end.row) { style = document_editor::SummarizeStyle(paragraphs[begin.row].Obj(), runRanges, this, begin.column, end.column); } else { for (vint i = begin.row; i <= end.row; i++) { Ptr paragraph = paragraphs[i]; if (begin.row < i && i < end.row) { GetRunRange(paragraph.Obj(), runRanges); } RunRange range = runRanges[paragraph.Obj()]; Ptr paragraphStyle; if (i == begin.row) { paragraphStyle = document_editor::SummarizeStyle(paragraph.Obj(), runRanges, this, begin.column, range.end); } else if (i == end.row) { paragraphStyle = document_editor::SummarizeStyle(paragraph.Obj(), runRanges, this, range.start, end.column); } else { paragraphStyle = document_editor::SummarizeStyle(paragraph.Obj(), runRanges, this, range.start, range.end); } if (!style) { style = paragraphStyle; } else if (paragraphStyle) { AggregateStyle(style, paragraphStyle); } } } END_OF_SUMMERIZING: if (!style) { style = Ptr(new DocumentStyleProperties); } return style; } Nullable DocumentModel::SummarizeStyleName(TextPos begin, TextPos end) { if (begin == end) return {}; // check caret range RunRangeMap runRanges; if (!CheckEditRange(begin, end, runRanges)) return {}; // Summarize container Nullable styleName; if (begin.row == end.row) { styleName = document_editor::SummarizeStyleName(paragraphs[begin.row].Obj(), runRanges, this, begin.column, end.column); } else { for (vint i = begin.row; i <= end.row; i++) { Ptr paragraph = paragraphs[i]; if (begin.row < i && i < end.row) { GetRunRange(paragraph.Obj(), runRanges); } RunRange range = runRanges[paragraph.Obj()]; Nullable newStyleName; if (i == begin.row) { newStyleName = document_editor::SummarizeStyleName(paragraph.Obj(), runRanges, this, begin.column, range.end); } else if (i == end.row) { newStyleName = document_editor::SummarizeStyleName(paragraph.Obj(), runRanges, this, range.start, end.column); } else { newStyleName = document_editor::SummarizeStyleName(paragraph.Obj(), runRanges, this, range.start, end.column); } if (i == begin.row) { styleName = newStyleName; } else if (!styleName || !newStyleName || styleName.Value() != newStyleName.Value()) { styleName = Nullable(); } } } return styleName; } Nullable DocumentModel::SummarizeParagraphAlignment(TextPos begin, TextPos end) { bool left = false; bool center = false; bool right = false; RunRangeMap runRanges; if (!CheckEditRange(begin, end, runRanges)) return {}; for (vint i = begin.row; i <= end.row; i++) { auto paragraph = paragraphs[i]; if (paragraph->alignment) { switch (paragraph->alignment.Value()) { case Alignment::Left: left = true; break; case Alignment::Center: center = true; break; case Alignment::Right: right = true; break; } } else { left = true; } } if (left && !center && !right) return Alignment::Left; if (!left && center && !right) return Alignment::Center; if (!left && !center && right) return Alignment::Right; return {}; } } } /*********************************************************************** .\RESOURCES\GUIDOCUMENT_LOAD.CPP ***********************************************************************/ namespace vl { namespace presentation { using namespace collections; using namespace glr::xml; using namespace regex; /*********************************************************************** document_operation_visitors::DeserializeNodeVisitor ***********************************************************************/ namespace document_operation_visitors { class DeserializeNodeVisitor : public XmlNode::IVisitor { public: Ptr model; Ptr container; vint paragraphIndex; Ptr resource; Ptr resolver; Regex regexAttributeApply; GuiResourceError::List& errors; DeserializeNodeVisitor(Ptr _model, Ptr _paragraph, vint _paragraphIndex, Ptr _resource, Ptr _resolver, GuiResourceError::List& _errors) :model(_model) , container(_paragraph) , paragraphIndex(_paragraphIndex) , resource(_resource) , resolver(_resolver) , regexAttributeApply(L"/{@([^{}]+)/}") , errors(_errors) { } void PrintText(const WString& text) { auto run = Ptr(new DocumentTextRun); run->text = text; container->runs.Add(run); } void Visit(XmlText* node)override { PrintText(node->content.value); } void Visit(XmlCData* node)override { PrintText(node->content.value); } void Visit(XmlComment* node)override { } void Visit(XmlElement* node)override { Ptr createdContainer; bool useTemplateInfo = false; XmlElement* subNodeContainer = node; if (node->name.value == L"br") { PrintText(L"\r\n"); } else if (node->name.value == L"sp") { PrintText(L" "); } else if (node->name.value == L"tab") { PrintText(L"\t"); } else if (node->name.value == L"img") { auto run = Ptr(new DocumentImageRun); run->baseline = -1; if (Ptr source = XmlGetAttribute(node, L"source")) { run->source = source->value.value; WString protocol, path; if (IsResourceUrl(run->source, protocol, path)) { Ptr imageData = resolver->ResolveResource(protocol, path).Cast(); if (imageData) { run->image = imageData->GetImage(); } if (run->image && run->image->GetFrameCount() > 0) { run->size = run->image->GetFrame(0)->GetSize(); run->frameIndex = 0; } } for (auto att : node->attributes) { if (att->name.value == L"width") { run->size.x = wtoi(att->value.value); } else if (att->name.value == L"height") { run->size.y = wtoi(att->value.value); } else if (att->name.value == L"baseline") { run->baseline = wtoi(att->value.value); } else if (att->name.value == L"frameIndex") { run->frameIndex = wtoi(att->value.value); } else if (att->name.value != L"source") { errors.Add(GuiResourceError({ {resource},att->name.codeRange.start }, L"Unknown attribute in : \"" + att->name.value + L"\".")); } } container->runs.Add(run); } else { errors.Add(GuiResourceError({ {resource},node->codeRange.start }, L"Attribute \"source\" is missing in .")); } } else if (node->name.value == L"object") { auto run = Ptr(new DocumentEmbeddedObjectRun); run->baseline = -1; if (auto name = XmlGetAttribute(node, L"name")) { run->name = name->value.value; container->runs.Add(run); } else { errors.Add(GuiResourceError({ {resource},node->codeRange.start }, L"The \"name\" attribute in is missing.")); } } else if (node->name.value == L"font") { auto run = Ptr(new DocumentStylePropertiesRun); auto sp = Ptr(new DocumentStyleProperties); run->style = sp; for (auto att : node->attributes) { if (att->name.value == L"face") { sp->face = att->value.value; } else if (att->name.value == L"size") { sp->size = DocumentFontSize::Parse(att->value.value); } else if (att->name.value == L"color") { sp->color = Color::Parse(att->value.value); } else if (att->name.value == L"bkcolor") { sp->backgroundColor = Color::Parse(att->value.value); } else { errors.Add(GuiResourceError({ {resource},att->name.codeRange.start }, L"Unknown attribute in : \"" + att->name.value + L"\".")); } } container->runs.Add(run); createdContainer = run; } else if (node->name.value == L"b" || node->name.value == L"b-") { auto run = Ptr(new DocumentStylePropertiesRun); run->style = Ptr(new DocumentStyleProperties); run->style->bold = node->name.value == L"b"; container->runs.Add(run); createdContainer = run; } else if (node->name.value == L"i" || node->name.value == L"i-") { auto run = Ptr(new DocumentStylePropertiesRun); run->style = Ptr(new DocumentStyleProperties); run->style->italic = node->name.value == L"i"; container->runs.Add(run); createdContainer = run; } else if (node->name.value == L"u" || node->name.value == L"u-") { auto run = Ptr(new DocumentStylePropertiesRun); run->style = Ptr(new DocumentStyleProperties); run->style->underline = node->name.value == L"u"; container->runs.Add(run); createdContainer = run; } else if (node->name.value == L"s" || node->name.value == L"s-") { auto run = Ptr(new DocumentStylePropertiesRun); run->style = Ptr(new DocumentStyleProperties); run->style->strikeline = node->name.value == L"s"; container->runs.Add(run); createdContainer = run; } else if (node->name.value == L"ha") { auto run = Ptr(new DocumentStylePropertiesRun); run->style = Ptr(new DocumentStyleProperties); run->style->antialias = true; run->style->verticalAntialias = false; container->runs.Add(run); createdContainer = run; } else if (node->name.value == L"va") { auto run = Ptr(new DocumentStylePropertiesRun); run->style = Ptr(new DocumentStyleProperties); run->style->antialias = true; run->style->verticalAntialias = true; container->runs.Add(run); createdContainer = run; } else if (node->name.value == L"na") { auto run = Ptr(new DocumentStylePropertiesRun); run->style = Ptr(new DocumentStyleProperties); run->style->antialias = false; run->style->verticalAntialias = false; container->runs.Add(run); createdContainer = run; } else if (node->name.value == L"div") { if (Ptr att = XmlGetAttribute(node, L"style")) { WString styleName = att->value.value; auto run = Ptr(new DocumentStyleApplicationRun); run->styleName = styleName; container->runs.Add(run); createdContainer = run; } else { createdContainer = container; } } else if (node->name.value == L"a") { auto run = Ptr(new DocumentHyperlinkRun); run->normalStyleName = L"#NormalLink"; run->activeStyleName = L"#ActiveLink"; if (Ptr att = XmlGetAttribute(node, L"normal")) { run->normalStyleName = att->value.value; } if (Ptr att = XmlGetAttribute(node, L"active")) { run->activeStyleName = att->value.value; } if (Ptr att = XmlGetAttribute(node, L"href")) { run->reference = att->value.value; } run->styleName = run->normalStyleName; container->runs.Add(run); createdContainer = run; } else if (node->name.value == L"p") { for (auto sub : node->subNodes) { sub->Accept(this); } } else { if (node->name.value != L"nop") { errors.Add(GuiResourceError({ {resource},node->codeRange.start }, L"Unknown element in

: \"" + node->name.value + L"\".")); } for (auto sub : node->subNodes) { sub->Accept(this); } } if (createdContainer) { Ptr oldContainer = container; container = createdContainer; for (auto subNode : subNodeContainer->subNodes) { subNode->Accept(this); } container = oldContainer; } } void Visit(XmlInstruction* node)override { } void Visit(XmlDocument* node)override { } }; Ptr ParseDocumentStyle(Ptr resource, Ptr styleElement, GuiResourceError::List& errors) { auto style = Ptr(new DocumentStyle); if(Ptr parent=XmlGetAttribute(styleElement, L"parent")) { style->parentStyleName=parent->value.value; } auto sp = Ptr(new DocumentStyleProperties); style->styles=sp; for (auto att : XmlGetElements(styleElement)) { if(att->name.value==L"face") { sp->face=XmlGetValue(att); } else if(att->name.value==L"size") { sp->size=DocumentFontSize::Parse(XmlGetValue(att)); } else if(att->name.value==L"color") { sp->color=Color::Parse(XmlGetValue(att)); } else if(att->name.value==L"bkcolor") { sp->backgroundColor=Color::Parse(XmlGetValue(att)); } else if(att->name.value==L"b") { sp->bold=XmlGetValue(att)==L"true"; } else if(att->name.value==L"i") { sp->italic=XmlGetValue(att)==L"true"; } else if(att->name.value==L"u") { sp->underline=XmlGetValue(att)==L"true"; } else if(att->name.value==L"s") { sp->strikeline=XmlGetValue(att)==L"true"; } else if(att->name.value==L"antialias") { WString value=XmlGetValue(att); if(value==L"horizontal" || value==L"default") { sp->antialias=true; sp->verticalAntialias=false; } else if(value==L"no") { sp->antialias=false; sp->verticalAntialias=false; } else if(value==L"vertical") { sp->antialias=true; sp->verticalAntialias=true; } } else { errors.Add(GuiResourceError({ {resource},att->codeRange.start }, L"Unknown element in