Repository: RetroAchievements/RAIntegration (cpp)

Issue:
Memory View flickers on drawing/clearing local code note text
Memory view often flickers when selecting a local code note, or deselecting a local code note. Does not flicker when selecting/deselecting published code notes. Does not flicker when selecting another local code note while a local code note is selected. Started noticing this after switching to Windows 11.
Exhibited when selecting/deselecting local code notes in memory viewer, memory bookmarks, and code notes window. Seems to occur more frequently when selecting in the memory viewer/bookmarks window, and less frequently when selecting in the code notes window. 
Occurs with or without the styling, easier to see with the dark background. 

https://user-images.githubusercontent.com/12983268/216205015-167cdcdf-1612-4045-9389-98ea2aea8fe8.mp4



Pull request description:
suppress WM_ERASEBKGND message for memory viewer
I believe this fixes #972, though I could not reliably reproduce it. I only saw a brief flicker once every 30-40 seconds while moving on and off an address with a note. The problem appears to be that the window is clearing the area before repainting the memory viewer. 

When going to/from a local note, the publish and revert button states are updated. Because of a workaround for running within the SDL framework, this forces the entire Memory Inspector dialog to repaint, including the Memory View. In this situation, the WM_ERASEBKGND message is sent, clearing the area "behind" the memory viewer. This happens almost instantaneously before drawing the control, so I don't know why you're seeing so much flicker (thanks for the video). Since the control is fully pre-rendered offscreen, the background doesn't need to be reset before painting, I've suppressed the WM_ERASEBKGND message, and no longer see any flicker locally. Hopefully it works for you too.



Patch:
diff --git a/RAInterface b/RAInterface
index d1443922..f3a14127 160000
--- a/RAInterface
+++ b/RAInterface
@@ -1,1 +1,1 @@
-Subproject commit d1443922a4d5400aadf608a6a34fc988bd7d2a8b
+Subproject commit f3a14127b87c398fc19553bdfe2536e300f4cc48
diff --git a/rcheevos b/rcheevos
index a5dc2b2e..286f2305 160000
--- a/rcheevos
+++ b/rcheevos
@@ -1,1 +1,1 @@
-Subproject commit a5dc2b2e6fbaad0f586161e8cf9d05de0546fa80
+Subproject commit 286f230586004615ac0eb001b3ff352623c94f04
diff --git a/src/data/context/GameContext.cpp b/src/data/context/GameContext.cpp
index 3f5af4ee..7bee3813 100644
--- a/src/data/context/GameContext.cpp
+++ b/src/data/context/GameContext.cpp
@@ -125,6 +125,11 @@ void GameContext::LoadGame(unsigned int nGameId, Mode nMode)
             // GB memory map is just a subset of GBC, and GBC runs GB games, so allow loading a
             // GB game in GBC context.
         }
+        else if (nServerConsoleId == ConsoleID::DS && pConsoleContext.Id() == ConsoleID::DSi)
+        {
+            // DS memory map is just a subset of DSi, and DSi runs DS games, so allow loading a
+            // DS game in DSi context.
+        }
         else if (ra::etoi(nServerConsoleId) == RC_CONSOLE_EVENTS)
         {
             // Hashes for Events are generated by modifying an existing ROM in a very specific manner
diff --git a/src/ui/viewmodels/TriggerViewModel.cpp b/src/ui/viewmodels/TriggerViewModel.cpp
index b19394ef..e8d00c38 100644
--- a/src/ui/viewmodels/TriggerViewModel.cpp
+++ b/src/ui/viewmodels/TriggerViewModel.cpp
@@ -381,26 +381,38 @@ void TriggerViewModel::NewCondition()
     vmCondition.SetSourceSize(nSize);
     vmCondition.SetSourceValue(nAddress);
 
-    // assume the user wants to compare to the current value of the watched memory address
-    vmCondition.SetOperator(TriggerOperatorType::Equals);
-
-    const auto& pEmulatorContext = ra::services::ServiceLocator::Get<ra::data::context::EmulatorContext>();
-    const auto nValue = pEmulatorContext.ReadMemory(nAddress, nSize);
+    if (m_bIsValue && m_vConditions.Count() == 1)
+    {
+        // first condition added to a value should be Measured and not have a operator/target.
+        vmCondition.SetType(TriggerConditionType::Measured);
 
-    vmCondition.SetTargetSize(nSize);
-    switch (nSize)
+        // if the operator is None when changing to a non-modifying type, the operator is
+        // automatically changed to Equals. change it back.
+        vmCondition.SetOperator(TriggerOperatorType::None);
+    }
+    else
     {
-        case MemSize::Float:
-        case MemSize::MBF32:
-        case MemSize::MBF32LE:
-            vmCondition.SetTargetType(TriggerOperandType::Float);
-            vmCondition.SetTargetValue(ra::data::U32ToFloat(nValue, nSize));
-            break;
-
-        default:
-            vmCondition.SetTargetType(TriggerOperandType::Value);
-            vmCondition.SetTargetValue(nValue);
-            break;
+        // assume the user wants to compare to the current value of the watched memory address
+        vmCondition.SetOperator(TriggerOperatorType::Equals);
+
+        const auto& pEmulatorContext = ra::services::ServiceLocator::Get<ra::data::context::EmulatorContext>();
+        const auto nValue = pEmulatorContext.ReadMemory(nAddress, nSize);
+
+        vmCondition.SetTargetSize(nSize);
+        switch (nSize)
+        {
+            case MemSize::Float:
+            case MemSize::MBF32:
+            case MemSize::MBF32LE:
+                vmCondition.SetTargetType(TriggerOperandType::Float);
+                vmCondition.SetTargetValue(ra::data::U32ToFloat(nValue, nSize));
+                break;
+
+            default:
+                vmCondition.SetTargetType(TriggerOperandType::Value);
+                vmCondition.SetTargetValue(nValue);
+                break;
+        }
     }
 
     vmCondition.SetSelected(true);
diff --git a/src/ui/win32/bindings/MemoryViewerControlBinding.cpp b/src/ui/win32/bindings/MemoryViewerControlBinding.cpp
index a1c1ec06..485db86f 100644
--- a/src/ui/win32/bindings/MemoryViewerControlBinding.cpp
+++ b/src/ui/win32/bindings/MemoryViewerControlBinding.cpp
@@ -27,6 +27,10 @@ static LRESULT CALLBACK s_MemoryViewerControlWndProc(HWND hControl, UINT uMsg, W
             pControl->RenderMemViewer();
             return 0;
 
+        case WM_ERASEBKGND:
+            // we'll repaint the entire control area in WM_PAINT, so don't need to clear first.
+            return TRUE;
+
         case WM_MOUSEWHEEL:
             if (GET_WHEEL_DELTA_WPARAM(wParam) > 0)
                 pControl->ScrollUp();


Test patch:
diff --git a/tests/data/context/GameContext_Tests.cpp b/tests/data/context/GameContext_Tests.cpp
index fbd98765..a417b7d9 100644
--- a/tests/data/context/GameContext_Tests.cpp
+++ b/tests/data/context/GameContext_Tests.cpp
@@ -317,6 +317,43 @@ public:
         Assert::IsFalse(bDialogShown);
     }
 
+    TEST_METHOD(TestLoadGameTitleConsoleMismatchDSvDSi)
+    {
+        GameContextHarness game;
+        game.mockServer.HandleRequest<ra::api::FetchGameData>([](const ra::api::FetchGameData::Request& request, ra::api::FetchGameData::Response& response)
+        {
+            Assert::AreEqual(1U, request.GameId);
+
+            response.Title = L"Game";
+            response.ConsoleId = ra::etoi(ConsoleID::DS);
+            response.ImageIcon = "9743";
+            return true;
+        });
+
+        game.mockConsoleContext.SetId(ConsoleID::DSi);
+        game.mockConsoleContext.SetName(L"Nintendo DSi");
+
+        bool bDialogShown = false;
+        game.mockDesktop.ExpectWindow<ra::ui::viewmodels::MessageBoxViewModel>([&bDialogShown](ra::ui::viewmodels::MessageBoxViewModel&)
+        {
+            bDialogShown = true;
+            return ra::ui::DialogResult::OK;
+        });
+
+        game.LoadGame(1U);
+
+        Assert::IsTrue(game.mockAudioSystem.WasAudioFilePlayed(std::wstring(L"Overlay\\info.wav")));
+
+        const auto* pPopup = game.mockOverlayManager.GetMessage(1);
+        Expects(pPopup != nullptr);
+        Assert::IsNotNull(pPopup);
+        Assert::AreEqual(std::wstring(L"Loaded Game"), pPopup->GetTitle());
+        Assert::AreEqual(std::wstring(L"0 achievements, 0 points"), pPopup->GetDescription());
+        Assert::AreEqual(std::string("9743"), pPopup->GetImage().Name());
+
+        Assert::IsFalse(bDialogShown);
+    }
+
     TEST_METHOD(TestLoadGameTitleConsoleMismatchNESvEvent)
     {
         GameContextHarness game;
diff --git a/tests/ui/viewmodels/TriggerViewModel_Tests.cpp b/tests/ui/viewmodels/TriggerViewModel_Tests.cpp
index a5dfe33b..d5cb8b4f 100644
--- a/tests/ui/viewmodels/TriggerViewModel_Tests.cpp
+++ b/tests/ui/viewmodels/TriggerViewModel_Tests.cpp
@@ -949,6 +949,38 @@ public:
             vmTrigger.Conditions().GetItemAt(2)->GetTooltip(TriggerConditionViewModel::SourceValueProperty));
     }
 
+    TEST_METHOD(TestNewConditionValue)
+    {
+        std::array<uint8_t, 10> pMemory = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+        TriggerViewModelHarness vmTrigger;
+        vmTrigger.SetIsValue(true);
+        vmTrigger.AddGroup();
+        Assert::AreEqual({ 0U }, vmTrigger.Conditions().Count());
+
+        vmTrigger.InitializeMemory(&pMemory.at(0), pMemory.size());
+        vmTrigger.mockWindowManager.MemoryInspector.Viewer().SetAddress(8);
+        vmTrigger.mockWindowManager.MemoryInspector.Viewer().SetSize(MemSize::EightBit);
+        vmTrigger.NewCondition();
+
+        // first condition added to a value should be Measured without an operator
+        Assert::AreEqual({ 1U }, vmTrigger.Conditions().Count());
+        Assert::IsTrue(vmTrigger.Conditions().GetItemAt(0)->IsSelected());
+        Assert::AreEqual(std::string("M:0xH0008"), vmTrigger.Serialize());
+
+        // second condition should be added normally (as a comparison)
+        vmTrigger.mockWindowManager.MemoryInspector.Viewer().SetAddress(4);
+        vmTrigger.mockWindowManager.MemoryInspector.Viewer().SetSize(MemSize::SixteenBit);
+        vmTrigger.NewCondition();
+
+        Assert::AreEqual({ 2U }, vmTrigger.Conditions().Count());
+
+        // new condition should be selected, any previously selected items should be deselected
+        Assert::IsFalse(vmTrigger.Conditions().GetItemAt(0)->IsSelected());
+        Assert::IsTrue(vmTrigger.Conditions().GetItemAt(1)->IsSelected());
+
+        Assert::AreEqual(std::string("M:0xH0008_0x 0004=1284"), vmTrigger.Serialize());
+    }
+
     TEST_METHOD(TestAddGroupNoAlts)
     {
         TriggerViewModelHarness vmTrigger;