From 6aaa4453247677dee03a4dd561ffc631225ce93b Mon Sep 17 00:00:00 2001
From: Bjoern Esswein <692-bessw@users.noreply.gl.kwarc.info>
Date: Wed, 17 Jul 2024 18:11:01 +0200
Subject: [PATCH] WIP: refactor ScrollDetails into model (ActiveScroll.cs) and
 view (ScrollDetails.cs)

---
 .../Prefabs/UI/Facts/Factscreen.prefab        |   2 +-
 Assets/Resources/Prefabs/UI/FrameITUI.prefab  |   8 +-
 .../Prefabs/UI/Ingame/FrameITUI_mobile.prefab |   2 +-
 .../Prefabs/UI/Ingame/HidingCanvas.prefab     |   6 +-
 .../InteractionEngine/CommunicationEvents.cs  |  11 +-
 .../FactWrapper/RenderedScrollFact.cs         |  22 +-
 .../Scripts/InteractionEngine/ShinyThings.cs  |   6 +-
 Assets/Scripts/InventoryStuff/ActiveScroll.cs | 535 ++++++++++++++++++
 .../InventoryStuff/ActiveScroll.cs.meta       |  11 +
 .../Scripts/InventoryStuff/DisplayScrolls.cs  |   5 +-
 .../InventoryStuff/ScrollClickedScript.cs     |   3 +-
 .../Scripts/InventoryStuff/ScrollDetails.cs   | 446 ---------------
 .../UI/FactExplorer/OpenFactExplorer.cs       |   2 +-
 Assets/Scripts/UI/InGame/PopupBehavior.cs     |  30 +-
 Assets/Scripts/UI/InGame/ScrollDetails.cs     | 111 ++++
 .../InGame}/ScrollDetails.cs.meta             |   0
 Assets/Scripts/UI/InGame/ScrollView.cs        |   5 +
 Assets/Scripts/UI/InGame/ScrollView.cs.meta   |  11 +
 Assets/Scripts/UI/InGame/WebViewController.cs |   2 +-
 19 files changed, 734 insertions(+), 484 deletions(-)
 create mode 100644 Assets/Scripts/InventoryStuff/ActiveScroll.cs
 create mode 100644 Assets/Scripts/InventoryStuff/ActiveScroll.cs.meta
 delete mode 100644 Assets/Scripts/InventoryStuff/ScrollDetails.cs
 create mode 100644 Assets/Scripts/UI/InGame/ScrollDetails.cs
 rename Assets/Scripts/{InventoryStuff => UI/InGame}/ScrollDetails.cs.meta (100%)
 create mode 100644 Assets/Scripts/UI/InGame/ScrollView.cs
 create mode 100644 Assets/Scripts/UI/InGame/ScrollView.cs.meta

diff --git a/Assets/Resources/Prefabs/UI/Facts/Factscreen.prefab b/Assets/Resources/Prefabs/UI/Facts/Factscreen.prefab
index 8addc4b7..9dff07b6 100644
--- a/Assets/Resources/Prefabs/UI/Facts/Factscreen.prefab
+++ b/Assets/Resources/Prefabs/UI/Facts/Factscreen.prefab
@@ -507,7 +507,7 @@ RectTransform:
   m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
   m_AnchorMin: {x: 0, y: 1}
   m_AnchorMax: {x: 1, y: 1}
-  m_AnchoredPosition: {x: 0, y: -180}
+  m_AnchoredPosition: {x: 0, y: 0.000030517578}
   m_SizeDelta: {x: 0, y: 0}
   m_Pivot: {x: 0.5, y: 1}
 --- !u!114 &8823539307371861913
diff --git a/Assets/Resources/Prefabs/UI/FrameITUI.prefab b/Assets/Resources/Prefabs/UI/FrameITUI.prefab
index 65acbc15..0a25a451 100644
--- a/Assets/Resources/Prefabs/UI/FrameITUI.prefab
+++ b/Assets/Resources/Prefabs/UI/FrameITUI.prefab
@@ -406,12 +406,12 @@ PrefabInstance:
     - target: {fileID: 8553388048532215990, guid: 884ac57de337c364391b247761071fb1,
         type: 3}
       propertyPath: m_AnchorMax.y
-      value: 0
+      value: 1
       objectReference: {fileID: 0}
     - target: {fileID: 8553388048532215990, guid: 884ac57de337c364391b247761071fb1,
         type: 3}
       propertyPath: m_AnchorMin.y
-      value: 0
+      value: 1
       objectReference: {fileID: 0}
     - target: {fileID: 8553388048532215990, guid: 884ac57de337c364391b247761071fb1,
         type: 3}
@@ -556,7 +556,7 @@ PrefabInstance:
     - target: {fileID: 6500467619489830996, guid: 292834880e6f0e54186b873acc62d3f2,
         type: 3}
       propertyPath: m_AnchoredPosition.y
-      value: 443880
+      value: 506520
       objectReference: {fileID: 0}
     - target: {fileID: 7849991042685492731, guid: 292834880e6f0e54186b873acc62d3f2,
         type: 3}
@@ -606,7 +606,7 @@ PrefabInstance:
     - target: {fileID: 7989559431199338490, guid: 292834880e6f0e54186b873acc62d3f2,
         type: 3}
       propertyPath: m_AnchoredPosition.y
-      value: -24660
+      value: -28140
       objectReference: {fileID: 0}
     - target: {fileID: 8004702056544321748, guid: 292834880e6f0e54186b873acc62d3f2,
         type: 3}
diff --git a/Assets/Resources/Prefabs/UI/Ingame/FrameITUI_mobile.prefab b/Assets/Resources/Prefabs/UI/Ingame/FrameITUI_mobile.prefab
index bce53f30..4f103c2a 100644
--- a/Assets/Resources/Prefabs/UI/Ingame/FrameITUI_mobile.prefab
+++ b/Assets/Resources/Prefabs/UI/Ingame/FrameITUI_mobile.prefab
@@ -4952,7 +4952,7 @@ PrefabInstance:
     - target: {fileID: 7989559431199338490, guid: 292834880e6f0e54186b873acc62d3f2,
         type: 3}
       propertyPath: m_AnchoredPosition.y
-      value: -1700
+      value: -2000
       objectReference: {fileID: 0}
     - target: {fileID: 8004702056544321748, guid: 292834880e6f0e54186b873acc62d3f2,
         type: 3}
diff --git a/Assets/Resources/Prefabs/UI/Ingame/HidingCanvas.prefab b/Assets/Resources/Prefabs/UI/Ingame/HidingCanvas.prefab
index ec56a279..e1fc2e4b 100644
--- a/Assets/Resources/Prefabs/UI/Ingame/HidingCanvas.prefab
+++ b/Assets/Resources/Prefabs/UI/Ingame/HidingCanvas.prefab
@@ -2464,9 +2464,6 @@ MonoBehaviour:
   parameterDisplayPrefab: {fileID: 8358525157842135574, guid: 0651df442e07acf439dd439c86c20e93,
     type: 3}
   mmtAnswerPopUp: {fileID: 6618856106128302243}
-  currentMmtAnswer: 
-  DynamicScrollDescriptionsActive: 1
-  AutomaticHintGenerationActive: 1
 --- !u!114 &8004702057798297409
 MonoBehaviour:
   m_ObjectHideFlags: 0
@@ -3036,7 +3033,6 @@ MonoBehaviour:
   ScrollButtons: []
   ScrollPrefab: {fileID: 3173330253721512196, guid: a6a9a3ebdb022e546a21d9f9ff148261,
     type: 3}
-  DetailScreen: {fileID: 8004702057798297436}
   scrollscreenContent: {fileID: 6500467619489830996}
 --- !u!114 &3490402746730127523
 MonoBehaviour:
@@ -3711,7 +3707,7 @@ PrefabInstance:
     - target: {fileID: 4838871000058222821, guid: 49deb83b881477047bfac0ee629a7ae9,
         type: 3}
       propertyPath: m_AnchoredPosition.y
-      value: -0.000045776367
+      value: -0.000030517578
       objectReference: {fileID: 0}
     m_RemovedComponents: []
   m_SourcePrefab: {fileID: 100100000, guid: 49deb83b881477047bfac0ee629a7ae9, type: 3}
diff --git a/Assets/Scripts/InteractionEngine/CommunicationEvents.cs b/Assets/Scripts/InteractionEngine/CommunicationEvents.cs
index bca75d87..f9b0f0f3 100644
--- a/Assets/Scripts/InteractionEngine/CommunicationEvents.cs
+++ b/Assets/Scripts/InteractionEngine/CommunicationEvents.cs
@@ -13,20 +13,15 @@ public static class CommunicationEvents
     public static UnityEvent<Fact> AddFactEvent = new();
     public static UnityEvent<Fact> RemoveFactEvent = new();
 
-    public static UnityEvent<REST_JSON_API.ScrollApplicationCheckingError[]> ScrollApplicationCheckingErrorEvent = new();
-    public static UnityEvent PushoutFactFailEvent = new();
-
     public static UnityEvent gameSucceededEvent = new();
     public static UnityEvent gameNotSucceededEvent = new();
-    public static UnityEvent NewAssignmentEvent = new();
     public static UnityEvent StartT0Event = new();
-
-    public static UnityEvent<string> ScrollFactHintEvent = new();
+    /** <summary>Inform ui that it should animate the given fact with the given material.</summary> */
     public static UnityEvent<string, FactWrapper.FactMaterials> AnimateExistingFactEvent = new();
+    /** <summary>Start firework and invoke AnimateExistingFactEvent.</summary> */
     public static UnityEvent<Fact, FactWrapper.FactMaterials> AnimateExistingAsSolutionEvent = new();
+    /** <summary>Show a missing fact for a short time as hint for the user.</summary> */
     public static UnityEvent<Fact> AnimateNonExistingFactEvent = new();
-    public static UnityEvent<List<string>> HintAvailableEvent = new();
-
 
 
     //------------------------------------------------------------------------------------
diff --git a/Assets/Scripts/InteractionEngine/FactHandling/FactWrapper/RenderedScrollFact.cs b/Assets/Scripts/InteractionEngine/FactHandling/FactWrapper/RenderedScrollFact.cs
index 6a362b1c..a52dff62 100644
--- a/Assets/Scripts/InteractionEngine/FactHandling/FactWrapper/RenderedScrollFact.cs
+++ b/Assets/Scripts/InteractionEngine/FactHandling/FactWrapper/RenderedScrollFact.cs
@@ -1,5 +1,6 @@
 using REST_JSON_API;
 using System.Collections.Generic;
+using System.Linq;
 using TMPro;
 using UnityEngine;
 using UnityEngine.EventSystems;
@@ -66,7 +67,6 @@ protected override void FactUpdated()
         if (VerboseURI)
             Debug.Log(nameof(RenderedScrollFact) + " recieved Fact: " + URI);
 
-        NewAssignmentEvent.Invoke();
         ResetPayload();
         base.FactUpdated();
     }
@@ -97,13 +97,15 @@ protected override void _DeleteFactEvent(Fact fact)
 
     protected override void _OnEnable()
     {
-        HintAvailableEvent.AddListener(OnHintAvailable);
+        ActiveScroll.HintAvailableEvent.AddListener(OnHintAvailable);
+        ActiveScroll.OnScrollDynamicInfoUpdated.AddListener(OnScrollUpdated);
         ResetPayload();
     }
 
     protected override void _OnDisable()
     {
-        HintAvailableEvent.RemoveListener(OnHintAvailable);
+        ActiveScroll.HintAvailableEvent.RemoveListener(OnHintAvailable);
+        ActiveScroll.OnScrollDynamicInfoUpdated.RemoveListener(OnScrollUpdated);
     }
 
     public void Populate(Scroll scroll, string scroll_fact_uri)
@@ -120,12 +122,22 @@ private void SetLabel(string label)
         LabelMesh.text = label ?? Scroll?.requiredFacts[ID].label ?? "Err";
     }
 
+    private void OnScrollUpdated(Scroll rendered) {
+        // this if has been copied during refactoring, I don't know if we need it^^
+        if(ActiveScroll.Instance.DynamicScrollDescriptionsActive &&
+            rendered.requiredFacts.Any(rf => rf.@ref.uri == ScrollFactURI))
+        {
+            // Update ScrollParameter label (side effect of setting the property "Scroll")
+            Scroll = rendered;
+        }
+    }
+
     public void OnClickHintButton()
     {
-        ScrollFactHintEvent.Invoke(URI);
+        ActiveScroll.Instance.ButtonClicked(new HintScrollButton(URI));
     }
 
-    public void OnHintAvailable(List<string> uris)
+    private void OnHintAvailable(IReadOnlyList<string> uris)
     {
         HintButton.SetActive(uris.Contains(ScrollFactURI));
     }
diff --git a/Assets/Scripts/InteractionEngine/ShinyThings.cs b/Assets/Scripts/InteractionEngine/ShinyThings.cs
index 37993ee7..cb0ff215 100644
--- a/Assets/Scripts/InteractionEngine/ShinyThings.cs
+++ b/Assets/Scripts/InteractionEngine/ShinyThings.cs
@@ -28,13 +28,13 @@ public GameObject
 
     private void OnEnable()
     {
-        CommunicationEvents.PushoutFactFailEvent.AddListener(LetItRain);
+        ActiveScroll.OnScrollApplicationError.AddListener(LetItRain);
         CommunicationEvents.AnimateExistingAsSolutionEvent.AddListener(HighlightWithFireworks);
     }
 
     private void OnDisable()
     {
-        CommunicationEvents.PushoutFactFailEvent.RemoveListener(LetItRain);
+        ActiveScroll.OnScrollApplicationError.RemoveListener(LetItRain);
         CommunicationEvents.AnimateExistingAsSolutionEvent.RemoveListener(HighlightWithFireworks);
     }
 
@@ -117,7 +117,7 @@ IEnumerator _BlossomAndDie()
         }
     }
 
-    public void LetItRain()
+    public void LetItRain(REST_JSON_API.ScrollApplicationCheckingError[] _)
     {
         // check if couroutine is waiting or finished 
         if (!rain_wait.MoveNext() || !rain.MoveNext())
diff --git a/Assets/Scripts/InventoryStuff/ActiveScroll.cs b/Assets/Scripts/InventoryStuff/ActiveScroll.cs
new file mode 100644
index 00000000..67fc90f7
--- /dev/null
+++ b/Assets/Scripts/InventoryStuff/ActiveScroll.cs
@@ -0,0 +1,535 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using Newtonsoft.Json;
+using REST_JSON_API;
+using UnityEngine;
+using UnityEngine.Events;
+using UnityEngine.Networking;
+
+#nullable enable annotations
+
+/// <summary>
+/// This class is a singleton that keeps track of the currently active scroll and comunicates with the FrameIT Server.
+/// It represents the model of the Model-View-Controler pattern.
+/// </summary>
+public class ActiveScroll : MonoBehaviour
+{
+    #region static part
+
+    /// <summary>
+    /// Static property to access the singleton instance of ActiveScroll
+    /// </summary>
+    public static ActiveScroll Instance
+    {
+        get => _Instance;
+        set
+        {
+            if (_Instance != value)
+                Destroy(_Instance);
+
+            _Instance = value;
+        }
+    }
+    private static ActiveScroll _Instance;
+
+    /** <summary>Event that is invoked when a scroll button is clicked, e.g. magic button. You can use it to handle custom buttons.</summary> */
+    public static UnityEvent<ScrollButton> OnButtonClick = new();
+    /** <summary>Notifies about availible hints.</summary> */
+    public static UnityEvent<IReadOnlyList<string>> HintAvailableEvent = new();
+    /** <summary>Event that is invoked when a fact has been assigned to a slot and the ui needs to update.</summary> */
+    public static UnityEvent<string,SlotAssignment> OnFactAssignmentUpdated = new();
+    /** <summary>Event that is invoked when the MMT server returned an error.</summary> */
+    public static UnityEvent<string> OnMMTServerComunicationError = new();
+    /** <summary>Event that is invoked when the scroll dynamic info returned errors in the current scroll assignment.</summary> */
+    public static UnityEvent<ScrollApplicationCheckingError[]> OnScrollDynamicInfoError = new();
+    /** <summary>Event that is invoked when the scroll application returned errors in the current assignment.</summary> */
+    public static UnityEvent<ScrollApplicationCheckingError[]> OnScrollApplicationError = new();
+    /** <summary>Event that is invoked when the OnMMTServerError or OnScrollApplicationFailed error state has been resolved and e.g. the error popup can be cloesed.</summary> */
+    public static UnityEvent OnCancelErrorState = new();
+    /** <summary>Event that is invoked when the scroll has been solved.</summary> */
+    public static UnityEvent<IReadOnlyList<Fact>> OnScrollSolved = new();
+    /** <summary>Event that is invoked when an other scroll has been selected.</summary> */
+    public static UnityEvent<ActiveScroll> OnScrollChanged = new();
+    /** <summary>Event that is invoked when the dynamic info of the scroll has been updated e.g. because a fact has been assigned.</summary> */
+    public static UnityEvent<Scroll> OnScrollDynamicInfoUpdated = new();
+
+    void Awake()
+    {
+        Instance = this;
+    }
+    private void OnDestroy()
+    {
+        _Instance = null;
+    }
+
+
+    /// <summary>
+    /// Creates a new instance and sets the scroll
+    /// </summary>
+    public static void SetScroll(Scroll scroll_to_set)
+    {
+        Instance = new ActiveScroll
+        {
+            Scroll = scroll_to_set
+        };
+        // notify the observers that the scroll has changed
+        OnScrollChanged.Invoke(Instance);
+    }
+
+    #endregion static part
+
+
+    public bool DynamicScrollDescriptionsActive = true;
+    public bool AutomaticHintGenerationActive = true;
+
+    private readonly IReadOnlyList<string> NoDynamicScroll = new List<string>()
+    {
+        // Insert ScrollURIS that are poorly optimized
+    };
+
+
+    /// <summary>
+    /// Variable used to block that only one SendView is running at a time.
+    /// </summary>
+    private bool SendingViewDone = true;
+    /// <summary>
+    /// this is true while a fact assignment is enqueued
+    /// </summary>
+    private bool DynamicScrollInQue = false;
+    /// <summary>
+    /// this is true while a scroll application via magic button is enqueued
+    /// </summary>
+    private bool MagicInQue = false;
+    private string currentMmtAnswer;
+
+    /** <summary>List of available hints for the current partial assignment.</summary> */
+    private List<Fact> LatestRenderedHints;
+    /** <summary>List of available auto comletions for the current partial assignment. Used to generate hints.</summary> */
+    private List<ScrollAssignment> LatestBackwartsCompletions;
+
+    private Scroll _Scroll;
+    /// <summary>
+    /// The currently active scroll
+    /// </summary>
+    public Scroll Scroll
+    {
+        get => _Scroll;
+        private set {
+            _Scroll = value;
+            
+            // initialize the assignments dictionary with the references to the required fact slots as keys
+            // and an empty SlottAssignment
+            _Assignments = value.requiredFacts
+                .ToDictionary(fact => fact.@ref.uri, _ => new SlotAssignment());
+            
+            // apply fact assignments that are pre defined by the stage (defaults or constrains), if there are any
+            if (StageStatic.stage.solution.ScrollOverwrites
+                    .TryGetValue(value.ScrollReference, out (string, int, bool)[] population)
+            && population.Length > 0)
+            {
+                foreach ((string Id, int index, bool show) in population)
+                {
+                    var key = value.requiredFacts[index].@ref.uri;
+                    _Assignments[key].fact = FactRecorder.AllFacts[Id];
+                    _Assignments[key].IsVisible = show;
+                }
+            }
+        }
+    }
+    /// <summary>
+    /// The currently active scroll with rendered description and backward_completions (hints)
+    /// </summary>
+    public Scroll RenderedScroll { get; private set; }
+
+    public class SlotAssignment
+    {
+        public Fact? fact = null;
+        public bool IsVisible = true;
+        public bool IsSet => fact is not null;
+    }
+
+    /// <summary>
+    /// Maps the required facts (keys) to the assigned facts (values)
+    /// </summary>
+    private Dictionary<string, SlotAssignment> _Assignments = new();
+
+    /// <summary>
+    /// Maps the required facts (keys) to the assigned facts (values).
+    /// </summary>
+    public IReadOnlyDictionary<string, SlotAssignment> Assignments => _Assignments;
+
+    /// <summary>
+    /// The ScrollView should call this method when a button is clicked
+    /// </summary>
+    public void ButtonClicked(ScrollButton button)
+    {
+        switch (button)
+        {
+            // Magic button to apply the scroll
+            case MagicScrollButton:
+                if ((Scroll is not null) && ! MagicInQue) StartCoroutine(SendScrollApplication());
+                break;
+            case HintScrollButton:
+                HintButtonPressed((HintScrollButton)button);
+                break;
+            // add cases for future additional buttons here
+        }
+        // notify observers that a button was clicked
+        OnButtonClick.Invoke(button);
+    }
+
+    /// <summary>
+    /// Assigns the given fact to the given slotUri and calls the OnFactAssignmentUpdated Event
+    /// </summary>
+    /// <param name="slotUri"></param>
+    /// <param name="fact"></param>
+    /// <exception cref="ArgumentException"></exception>
+    public void AssignFact(string slotUri, Fact fact)
+    {
+        if (Assignments.ContainsKey(slotUri))
+        {
+            var slot = _Assignments[slotUri];
+            slot.fact = fact;
+            OnFactAssignmentUpdated.Invoke(slotUri, slot);
+            SendCurrentFactAssignments();
+        } else {
+            throw new ArgumentException($"Can't assign fact, the given slot doesn't exist on the current scroll: '{slotUri}'");
+        }
+    }
+
+    /// <summary>
+    /// If the fact is not already assigned, assign it to the next empty slot, otherwise unassign it.
+    /// And calls the OnFactAssignmentUpdated Event.
+    /// </summary>
+    /// <param name="fact"></param>
+    /// <returns></returns>
+    public string? AssignOrRemoveFactToNextEmptySlot(Fact fact)
+    {
+        // unassign the fact if it is already assigned and return
+        try {
+            var assignedSlot = _Assignments.First(pair => pair.Value.fact == fact).Key;
+            AssignFact(assignedSlot, null);
+            return null;
+        } catch (Exception) {
+            // the fact has not been assigned yet => continue
+        }
+
+        // find the first empty slot and assign the fact to it
+        try {
+            KeyValuePair<string, SlotAssignment> emptySlot = _Assignments.First(pair => pair.Value.IsSet);
+            AssignFact(emptySlot.Key, fact);
+            return emptySlot.Key;
+        } catch (Exception) {
+            return null;
+        }
+    }
+
+
+    #region MagicButton
+    private IEnumerator SendScrollApplication()
+    {
+        // there is already a MagicButton coroutine waiting => exit
+        if (MagicInQue) yield break;
+
+        MagicInQue = true;
+        // wait if SendView is not yet done or dynamic scroll is in the queue
+        while (!SendingViewDone || DynamicScrollInQue)
+            yield return null;
+        MagicInQue = false;
+
+        yield return SendView("/scroll/apply");
+
+        if (currentMmtAnswer == null)
+        {
+            // OnMMTServerComunicationError is already invoked in SendView
+            Debug.LogError("Magic FAILED");
+            yield break;
+        }
+        else
+        {
+            if (CommunicationEvents.VerboseURI)
+                Debug.Log("Magic answers:\n" + currentMmtAnswer);
+
+            System.DateTime deserializeTime = System.DateTime.UtcNow;
+            ScrollApplicationInfo scrollApplication = JsonConvert.DeserializeObject<ScrollApplicationInfo>(currentMmtAnswer);
+            Debug.LogFormat($"Answerd deserialized in : {(System.DateTime.UtcNow - deserializeTime).TotalMilliseconds}ms");
+
+            if (scrollApplication.acquiredFacts.Count == 0
+                || scrollApplication.errors.Length > 0)
+            {
+                OnScrollApplicationError.Invoke(scrollApplication.errors);
+            }
+            else
+            {
+                OnCancelErrorState.Invoke();
+            }
+
+            yield return __GenerateUnityFactsForAcquiredMMTFacts(scrollApplication.acquiredFacts);
+        }
+    }
+
+    private IEnumerator __GenerateUnityFactsForAcquiredMMTFacts(List<MMTFact> pushoutFacts)
+    {
+        List<Fact> new_facts = new();
+        Dictionary<string, string> old_to_new = new();
+        System.DateTime parseTime = System.DateTime.UtcNow;
+
+        bool samestep = false;
+        for (int i = 0; i < pushoutFacts.Count; i++)
+        {
+            List<Fact> new_list = Fact.MMTFactory(pushoutFacts[i].MapURIs(old_to_new));
+
+            if (new_list.Count == 0)
+            {
+                Debug.LogWarning("Parsing on pushout-fact returned empty List -> One of the dependent facts does not exist or parsing failed");
+                continue;
+            }
+
+            foreach (Fact new_fact in new_list)
+            {
+                Fact added = FactAdder.AddFactIfNotFound(new_fact, out bool exists, samestep, null, ActiveScroll.Instance.Scroll.label);
+                if (!exists)
+                {
+                    new_facts.Add(added);
+                    CommunicationEvents.AnimateExistingFactEvent.Invoke(added.Id, FactWrapper.FactMaterials.Solution);
+                    samestep = true;
+                }
+                else
+                {
+                    // AnimateExistingFactEvent.Invoke(_new, FactWrapper.FactMaterials.Hint); // Automaticly done in FactRecorder
+                    old_to_new.Add(new_fact.Id, added.Id);
+                }
+            }
+
+            //yield return null;
+        }
+
+        Debug.Log($"Facts parsed within {(System.DateTime.UtcNow - parseTime).TotalMilliseconds}ms");
+        OnScrollSolved.Invoke(new_facts);
+        yield break;
+    }
+
+    #endregion MagicButton
+
+    #region HintButton
+    
+    private void HintButtonPressed(HintScrollButton hintButton)
+    {
+        if (FactRecorder.AllFacts.ContainsKey(hintButton.SlotUri))
+        {
+            CommunicationEvents.AnimateExistingFactEvent.Invoke(
+                    hintButton.SlotUri,
+                    FactWrapper.FactMaterials.Hint
+                );
+        }
+
+        Fact hintFact = LatestRenderedHints.Find(x => x.Id == hintButton.SlotUri); // "Dictionary"
+
+        ScrollAssignment suitableCompletion =
+            LatestBackwartsCompletions.Find((ScrollAssignment x) => x.fact.uri == hintButton.SlotUri); // "Dictionary"
+
+        if (suitableCompletion != null && suitableCompletion.assignment is OMS assignment)
+        {
+            if (FactRecorder.AllFacts.ContainsKey(assignment.uri))
+            {
+                CommunicationEvents.AnimateExistingFactEvent.Invoke(
+                    assignment.uri,
+                    FactWrapper.FactMaterials.Hint
+                );
+            }
+        }
+        else if (hintFact != null)
+        {
+            if (FactRecorder.FindEquivalent(StageStatic.stage.factState.MyFactSpace, hintFact, out string found_key, out Fact _, out bool _, false))
+                // existing fact -> Animate that
+                CommunicationEvents.AnimateExistingFactEvent.Invoke(
+                    found_key,
+                    FactWrapper.FactMaterials.Hint
+                );
+
+            else
+            {   // Generate new FactRepresentation and animate it
+                CommunicationEvents.AnimateNonExistingFactEvent.Invoke(hintFact);
+                CommunicationEvents.AnimateExistingFactEvent.Invoke(
+                    hintButton.SlotUri,
+                    FactWrapper.FactMaterials.Hint
+                );
+            }
+        }
+    }
+    #endregion HintButton
+
+    #region FactAssignment
+
+    public void SendCurrentFactAssignments()
+    {
+        if (Scroll?.ScrollReference == null
+         || NoDynamicScroll.Contains(Scroll.ScrollReference))
+            return;
+
+        if (AutomaticHintGenerationActive || DynamicScrollDescriptionsActive)
+            StartCoroutine(_SendAssignments());
+    }
+
+    private IEnumerator _SendAssignments()
+    {
+        if (DynamicScrollInQue)
+            yield break; // only need next in que to finish
+
+        DynamicScrollInQue = true;
+        while (!SendingViewDone)
+            yield return null; // if we dont wait => server will crash
+        DynamicScrollInQue = false;
+
+        yield return SendView("/scroll/dynamic");
+
+        if (currentMmtAnswer == null)
+        {
+            // OnMMTServerComunicationError is already invoked in SendView
+            Debug.LogError("Dynamic Scroll FAILED");
+            yield break;
+        }
+        else
+        {
+            ScrollDynamicInfo scrollDynamicInfo;
+            try
+            {
+                scrollDynamicInfo = JsonConvert.DeserializeObject<ScrollDynamicInfo>(currentMmtAnswer);
+                //scrollDynamicInfo = IJSONsavable<ScrollDynamicInfo>.postprocess(scrollDynamicInfo); // DON'T! will remove hints
+            }
+            catch (JsonSerializationException ex)
+            {
+                Debug.LogException(ex);
+                Debug.LogError("Could not Deserialize MMT aswer for /scroll/dynamic\n" + currentMmtAnswer);
+                OnMMTServerComunicationError.Invoke("MMT Server communication error: could not deserialize message.");
+                yield break;
+            }
+
+            ScrollApplicationCheckingError[] errors = scrollDynamicInfo.errors
+                .Where(err => err.kind != "nonTotal") // expected
+                .ToArray();
+
+            if (errors.Length > 0)
+                OnScrollDynamicInfoError.Invoke(errors);
+
+            _ProcessScrollDynamicInfo(scrollDynamicInfo);
+        }
+    }
+
+    private void _ProcessScrollDynamicInfo(ScrollDynamicInfo scrollDynamicInfo)
+    {
+        //TODO: more hints available in scrollDynamicInfo.rendered.requiredFacts
+
+        LatestBackwartsCompletions = scrollDynamicInfo.backward_completions.Count > 0
+            ? scrollDynamicInfo.backward_completions[0]
+            : new List<ScrollAssignment>();
+
+        List<string> hintUris = LatestBackwartsCompletions
+            .Select(completion => completion.fact.uri)
+            .ToList();
+
+        //Update Scroll, process data for later hints and update Uri-List for which hints are available
+        _processRenderedScroll(scrollDynamicInfo.rendered, hintUris);
+
+        if (AutomaticHintGenerationActive)
+            //Show that Hint is available for ScrollParameter
+            HintAvailableEvent.Invoke(hintUris);
+
+        return;
+
+        void _processRenderedScroll(Scroll rendered, List<string> hintUris)
+        {
+            OnScrollDynamicInfoUpdated.Invoke(rendered);
+
+            LatestRenderedHints = new();
+            foreach (var requiredFact in rendered.requiredFacts)
+            {
+                //If ScrollFact is not assigned -> enable hint button
+                if (Assignments.TryGetValue(requiredFact.@ref.uri, out SlotAssignment assignedFact) && ! assignedFact.IsSet)
+                {
+                    List<Fact> HintFactList = Fact.MMTFactory(requiredFact);
+
+                    foreach (Fact HintFact in HintFactList)
+                    {
+                        hintUris.Add(HintFact.Id); // == rendered.requiredFacts[i].@ref.uri
+                        LatestRenderedHints.Add(HintFact);
+                    }
+                }
+            }
+
+            return;
+        }
+    }
+
+
+    #endregion FactAssignment
+
+    /// <summary>
+    /// Only one instance of the SendView coroutine should run at a time.
+    /// </summary>
+    private IEnumerator SendView(string endpoint)
+    {
+        SendingViewDone = false;
+
+        //while (ParameterDisplays == null) // Wait for server
+        //    yield return null;
+
+        string body = prepareScrollAssignments();
+
+        using UnityWebRequest www = UnityWebRequest.Put(CommunicationEvents.ServerAdress + endpoint, body);
+        www.method = UnityWebRequest.kHttpVerbPOST;
+        www.SetRequestHeader("Content-Type", "application/json");
+
+        System.DateTime sendTime = System.DateTime.UtcNow;
+        yield return www.SendWebRequest();
+        //if (VerboseURI)
+        Debug.LogFormat("Server answerd in : {0}ms"
+            , (System.DateTime.UtcNow - sendTime).TotalMilliseconds);
+
+        if (www.result == UnityWebRequest.Result.ConnectionError
+         || www.result == UnityWebRequest.Result.ProtocolError)
+        {
+            Debug.LogWarning(www.error);
+            currentMmtAnswer = null;
+            OnMMTServerComunicationError.Invoke($"Server communication error: {www.error}");
+            SendingViewDone = true;
+            yield break;
+        }
+        else
+        {
+            while (!www.downloadHandler.isDone)
+                yield return null;
+
+            currentMmtAnswer = www.downloadHandler.text
+                .Replace("\"float\":null", "\"float\":0.0"); // cannot convert null to value type
+        }
+
+        SendingViewDone = true;
+        yield break;
+
+        /// <summary>Maps the assigned slots to ScrollAssignment objects.
+        /// Returns json serialized ScrollApplication containing the list of ScrollAssignments.</summary>
+        string prepareScrollAssignments()
+        {
+            List<ScrollAssignment> assignmentList = Assignments.Where(assignment => assignment.Value is not null)
+                .Select(assignment => new ScrollAssignment(assignment.Key, assignment.Value.fact.ScalaFact.@ref))
+                .ToList();
+
+            return JsonConvert.SerializeObject(new ScrollApplication(Scroll.ScrollReference, assignmentList));
+        }
+    }
+}
+
+/// <summary>
+/// Parrent class for all scroll buttons, inherit from this class to implement custom buttons.
+/// </summary>
+public abstract class ScrollButton {}
+
+public class MagicScrollButton : ScrollButton {}
+public class HintScrollButton : ScrollButton
+{
+    public readonly string SlotUri;
+    public HintScrollButton(string slotUri) => SlotUri = slotUri;
+}
\ No newline at end of file
diff --git a/Assets/Scripts/InventoryStuff/ActiveScroll.cs.meta b/Assets/Scripts/InventoryStuff/ActiveScroll.cs.meta
new file mode 100644
index 00000000..9098ea19
--- /dev/null
+++ b/Assets/Scripts/InventoryStuff/ActiveScroll.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ae310511ad607a64e891bcb46517277b
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/Scripts/InventoryStuff/DisplayScrolls.cs b/Assets/Scripts/InventoryStuff/DisplayScrolls.cs
index a9ec4ddc..2ce0a98a 100644
--- a/Assets/Scripts/InventoryStuff/DisplayScrolls.cs
+++ b/Assets/Scripts/InventoryStuff/DisplayScrolls.cs
@@ -10,7 +10,6 @@ public class DisplayScrolls : MonoBehaviour
     static public List<REST_JSON_API.Scroll> AllowedScrolls;
     public GameObject[] ScrollButtons;
     public GameObject ScrollPrefab;
-    public GameObject DetailScreen;
 
     public Transform scrollscreenContent;
 
@@ -35,13 +34,11 @@ void BuildScrollGUI()
         {
             var obj = Instantiate(ScrollPrefab, scrollscreenContent);
             obj.GetComponent<ScrollClickedScript>().scroll = AllowedScrolls[i];
-            obj.GetComponent<ScrollClickedScript>().DetailScreen = this.DetailScreen;
             obj.transform.GetChild(0).gameObject.GetComponent<TextMeshProUGUI>().text = AllowedScrolls[i].label;
             ScrollButtons[i] = obj;
         }
 
         REST_JSON_API.Scroll preferredStartScroll = AllowedScrolls.Find(x => x.label.Equals(preferredStartScrollName));
-        if (preferredStartScroll != null)
-            ScrollDetails.Instance?.SetScroll(preferredStartScroll);
+        if (preferredStartScroll != null) ActiveScroll.SetScroll(preferredStartScroll);
     }
 }
diff --git a/Assets/Scripts/InventoryStuff/ScrollClickedScript.cs b/Assets/Scripts/InventoryStuff/ScrollClickedScript.cs
index 50ee8f5c..45c8f5b2 100644
--- a/Assets/Scripts/InventoryStuff/ScrollClickedScript.cs
+++ b/Assets/Scripts/InventoryStuff/ScrollClickedScript.cs
@@ -5,10 +5,9 @@
 public class ScrollClickedScript : MonoBehaviour, IPointerDownHandler
 {
     public Scroll scroll;
-    public GameObject DetailScreen;
 
     public void OnPointerDown(PointerEventData eventData)
     {
-        this.DetailScreen.GetComponent<ScrollDetails>().SetScroll(this.scroll);
+        ActiveScroll.SetScroll(this.scroll);
     }
 }
diff --git a/Assets/Scripts/InventoryStuff/ScrollDetails.cs b/Assets/Scripts/InventoryStuff/ScrollDetails.cs
deleted file mode 100644
index 63850233..00000000
--- a/Assets/Scripts/InventoryStuff/ScrollDetails.cs
+++ /dev/null
@@ -1,446 +0,0 @@
-using Newtonsoft.Json;
-using REST_JSON_API;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-using TMPro;
-using UnityEngine;
-using UnityEngine.Networking;
-using static CommunicationEvents;
-
-public class ScrollDetails : MonoBehaviour
-{
-    public static ScrollDetails Instance
-    {
-        get => _Instance;
-        set
-        {
-            if (_Instance != value)
-                Destroy(_Instance);
-
-            _Instance = value;
-        }
-    }
-    private static ScrollDetails _Instance;
-
-    public WorldCursor cursor;
-    public GameObject parameterDisplayPrefab;
-    public static Scroll ActiveScroll { get; private set; }
-    public GameObject mmtAnswerPopUp;
-    private PopupBehavior Popup;
-
-    public static List<RenderedScrollFact> ParameterDisplays { get; private set; }
-    private static List<ScrollAssignment> LatestBackwartsCompletions;
-    private static List<Fact> LatestRenderedHints;
-
-    public string currentMmtAnswer;
-
-    public bool DynamicScrollDescriptionsActive = true;
-    public bool AutomaticHintGenerationActive = true;
-
-    private bool SendingViewDone = true;
-    private bool DynamicScrollInQue = false;
-    private bool MagicInQue = false;
-
-    private readonly IReadOnlyList<string> NoDynamicScroll = new List<string>()
-    {
-        // Insert ScrollURIS that are poorly optimized
-    };
-
-    void Awake()
-    {
-        Instance = this;
-
-        if (cursor == null)
-            cursor = FindObjectOfType<WorldCursor>();
-
-        Popup = mmtAnswerPopUp.GetComponent<PopupBehavior>();
-        Popup.gameObject.SetActive(true); // force Awake
-    }
-
-    private void OnEnable()
-    {
-        ScrollFactHintEvent.AddListener(animateHint);
-        NewAssignmentEvent.AddListener(NewAssignmentTrigger);
-    }
-
-    private void OnDisable()
-    {
-        ScrollFactHintEvent.RemoveListener(animateHint);
-        NewAssignmentEvent.RemoveListener(NewAssignmentTrigger);
-    }
-
-    private void OnDestroy()
-    {
-        _Instance = null;
-    }
-
-    public void SetScroll(Scroll scroll_to_set)
-    {
-        ActiveScroll = scroll_to_set;
-
-        Transform originalScroll = gameObject.transform.GetChild(1);
-        Transform originalScrollView = originalScroll.GetChild(1);
-        Transform originalViewport = originalScrollView.GetChild(0);
-
-        //Clear all current ScrollFacts
-        originalViewport.GetChild(0).gameObject.DestroyAllChildren();
-
-        originalScroll.GetChild(0).GetComponent<TextMeshProUGUI>().text = ActiveScroll.description;
-
-        //ParameterDisplays.ForEach(gameObj => Destroy(gameObj));
-        ParameterDisplays = new();
-        for (int i = 0; i < ActiveScroll.requiredFacts.Count; i++)
-        {
-            GameObject originalObj =
-                Instantiate(parameterDisplayPrefab, originalViewport.GetChild(0));
-
-            RenderedScrollFact originalRSF =
-                originalObj.GetComponentInChildren<RenderedScrollFact>();
-
-            ParameterDisplays.Add(originalRSF);
-
-            originalRSF.Populate(ActiveScroll, ActiveScroll.requiredFacts[i].@ref.uri);
-        }
-
-        if (StageStatic.stage.solution.ScrollOverwrites
-                .TryGetValue(ActiveScroll.ScrollReference, out (string, int, bool)[] population)
-         && population.Length > 0)
-        {
-            DynamicScrollInQue = true; // block update on population
-
-            foreach ((string Id, int index, bool show) in population)
-            {
-                ParameterDisplays[index].URI = Id;
-                ParameterDisplays[index].transform.parent.gameObject.SetActive(show);
-            }
-
-            DynamicScrollInQue = false; // unblock
-        }
-
-        NewAssignmentEvent.Invoke(); // init display
-    }
-
-    public bool SetNextEmptyTo(FactObjectUI activator)
-    {
-        RenderedScrollFact check = ParameterDisplays
-            .Find(RSF => RSF != null
-                      && RSF.Payload != null
-                      && RSF.Payload.Equals(activator));
-
-        if (check != null)
-        {
-            check.URI = null;
-            return false;
-        }
-
-        RenderedScrollFact empty = ParameterDisplays
-            .Find(RSF => !RSF.IsSet);
-
-        if (empty == null)
-            return false;
-
-        empty.SetByFactObject(activator);
-        return true;
-    }
-
-    public void MagicButtonTrigger()
-    {
-        if (ActiveScroll == null)
-            return;
-
-        StartCoroutine(_MagicButton());
-
-        IEnumerator _MagicButton()
-        {
-            if (MagicInQue)
-                yield break;  // only need next in que to finish
-
-            MagicInQue = true;
-            while (!SendingViewDone || DynamicScrollInQue)
-                yield return null; // Wait for last assignment
-            MagicInQue = false;
-
-            yield return SendView("/scroll/apply");
-
-            if (currentMmtAnswer == null)
-            {
-                Debug.LogError("Magic FAILED");
-                ScrollApplicationCheckingErrorEvent.Invoke(null);
-            }
-            else
-            {
-                if (VerboseURI)
-                    Debug.Log("Magic answers:\n" + currentMmtAnswer);
-
-                System.DateTime serializeTime = System.DateTime.UtcNow;
-                ScrollApplicationInfo pushout = JsonConvert.DeserializeObject<ScrollApplicationInfo>(currentMmtAnswer);
-                Debug.LogFormat($"Answerd serialized in : {(System.DateTime.UtcNow - serializeTime).TotalMilliseconds}ms");
-
-                if (pushout.acquiredFacts.Count == 0
-                 || pushout.errors.Length > 0)
-                {
-                    ScrollApplicationCheckingErrorEvent.Invoke(pushout.errors);
-                    PushoutFactFailEvent.Invoke();
-                }
-                else
-                    Popup.HidePopUp(); //close error Window
-
-                yield return __GeneratePushoutFacts(pushout.acquiredFacts);
-            }
-        }
-
-        IEnumerator __GeneratePushoutFacts(List<MMTFact> pushoutFacts)
-        {
-            List<Fact> new_facts = new();
-            Dictionary<string, string> old_to_new = new();
-            System.DateTime parseTime = System.DateTime.UtcNow;
-
-            bool samestep = false;
-            for (int i = 0; i < pushoutFacts.Count; i++)
-            {
-                List<Fact> new_list = Fact.MMTFactory(pushoutFacts[i].MapURIs(old_to_new));
-
-                if (new_list.Count == 0)
-                {
-                    Debug.LogWarning("Parsing on pushout-fact returned empty List -> One of the dependent facts does not exist or parsing failed");
-                    continue;
-                }
-
-                foreach (Fact new_fact in new_list)
-                {
-                    Fact added = FactAdder.AddFactIfNotFound(new_fact, out bool exists, samestep, null, ActiveScroll.label);
-                    if (!exists)
-                    {
-                        new_facts.Add(added);
-                        AnimateExistingFactEvent.Invoke(added.Id, FactWrapper.FactMaterials.Solution);
-                        samestep = true;
-                    }
-                    else
-                    {
-                        // AnimateExistingFactEvent.Invoke(_new, FactWrapper.FactMaterials.Hint); // Automaticly done in FactRecorder
-                        old_to_new.Add(new_fact.Id, added.Id);
-                    }
-                }
-
-                //yield return null;
-            }
-
-            Debug.Log($"Facts parsed within {(System.DateTime.UtcNow - parseTime).TotalMilliseconds}ms");
-            yield break;
-        }
-    }
-
-    public void NewAssignmentTrigger()
-    {
-        //return; //if you read this, delete this line!
-
-        if (ActiveScroll?.ScrollReference == null
-         || NoDynamicScroll.Contains(ActiveScroll.ScrollReference))
-            return;
-
-        if (AutomaticHintGenerationActive || DynamicScrollDescriptionsActive)
-            StartCoroutine(_NewAssignment());
-
-        IEnumerator _NewAssignment()
-        {
-            if (DynamicScrollInQue)
-                yield break; // only need next in que to finish
-
-            DynamicScrollInQue = true;
-            while (!SendingViewDone)
-                yield return null; // if we dont wait => server will crash
-            DynamicScrollInQue = false;
-
-            yield return SendView("/scroll/dynamic");
-
-            if (currentMmtAnswer == null)
-            {
-                Debug.LogError("Dynamic Scroll FAILED");
-            }
-            else
-            {
-                ScrollDynamicInfo scrollDynamicInfo;
-                try
-                {
-                    scrollDynamicInfo = JsonConvert.DeserializeObject<ScrollDynamicInfo>(currentMmtAnswer);
-                    //scrollDynamicInfo = IJSONsavable<ScrollDynamicInfo>.postprocess(scrollDynamicInfo); // DON'T! will remove hints
-                }
-                catch (JsonSerializationException ex)
-                {
-                    Debug.LogException(ex);
-                    Debug.LogError("Could not Deserialize MMT aswer for /scroll/dynamic\n" + currentMmtAnswer);
-                    yield break;
-                }
-
-                ScrollApplicationCheckingError[] errors = scrollDynamicInfo.errors
-                    .Where(err => err.kind != "nonTotal") // expected
-                    .ToArray();
-
-                if (errors.Length > 0)
-                    ScrollApplicationCheckingErrorEvent.Invoke(errors);
-
-                processScrollDynamicInfo(scrollDynamicInfo);
-            }
-        }
-    }
-
-    private IEnumerator SendView(string endpoint)
-    {
-        SendingViewDone = false;
-
-        while (ParameterDisplays == null) // Wait for server
-            yield return null;
-
-        string body = prepareScrollAssignments();
-
-        using UnityWebRequest www = UnityWebRequest.Put(ServerAdress + endpoint, body);
-        www.method = UnityWebRequest.kHttpVerbPOST;
-        www.SetRequestHeader("Content-Type", "application/json");
-
-        System.DateTime sendTime = System.DateTime.UtcNow;
-        yield return www.SendWebRequest();
-        //if (VerboseURI)
-        Debug.LogFormat("Server answerd in : {0}ms"
-            , (System.DateTime.UtcNow - sendTime).TotalMilliseconds);
-
-        if (www.result == UnityWebRequest.Result.ConnectionError
-         || www.result == UnityWebRequest.Result.ProtocolError)
-        {
-            Debug.LogWarning(www.error);
-            currentMmtAnswer = null;
-        }
-        else
-        {
-            while (!www.downloadHandler.isDone)
-                yield return null;
-
-            currentMmtAnswer = www.downloadHandler.text
-                .Replace("\"float\":null", "\"float\":0.0"); // cannot convert null to value type
-        }
-
-        SendingViewDone = true;
-        yield break;
-
-        string prepareScrollAssignments()
-        {
-            List<ScrollAssignment> assignmentList = new();
-
-            for (int i = 0; i < ParameterDisplays.Count; i++)
-            {
-                Fact tempFact = ParameterDisplays[i].Fact;
-                if (tempFact != null)
-                    assignmentList.Add(new ScrollAssignment(ActiveScroll.requiredFacts[i].@ref.uri, tempFact.Id));
-            }
-
-            return JsonConvert.SerializeObject(new ScrollApplication(ActiveScroll.ScrollReference, assignmentList));
-        }
-    }
-
-    private void processScrollDynamicInfo(ScrollDynamicInfo scrollDynamicInfo)
-    {
-        //TODO: more hints available in scrollDynamicInfo.rendered.requiredFacts
-
-        LatestBackwartsCompletions = scrollDynamicInfo.backward_completions.Count > 0
-            ? scrollDynamicInfo.backward_completions[0]
-            : new List<ScrollAssignment>();
-
-        List<string> hintUris = LatestBackwartsCompletions
-            .Select(completion => completion.fact.uri)
-            .ToList();
-
-        //Update Scroll, process data for later hints and update Uri-List for which hints are available
-        _processRenderedScroll(scrollDynamicInfo.rendered, hintUris);
-
-        if (AutomaticHintGenerationActive)
-            //Show that Hint is available for ScrollParameter
-            HintAvailableEvent.Invoke(hintUris);
-
-        return;
-
-        void _processRenderedScroll(Scroll rendered, List<string> hintUris)
-        {
-            if (DynamicScrollDescriptionsActive)
-            { // Update scroll-description
-                Transform scroll = gameObject.transform.GetChild(1).transform;
-                scroll.GetChild(0).GetComponent<TextMeshProUGUI>().text = rendered.description;
-            }
-
-            LatestRenderedHints = new();
-            for (int i = 0; i < rendered.requiredFacts.Count; i++)
-            {
-                RenderedScrollFact RenderedScrollFact = ParameterDisplays
-                    .Find(RSF => RSF.ScrollFactURI == rendered.requiredFacts[i].@ref.uri);
-
-                if (RenderedScrollFact == null)
-                { // e.g. FUNCs
-                    //if(CommunicationEvents.VerboseScroll)
-                    //    Debug.Log($"Descrapancy between requiredFacts and displayed facts:" +
-                    //        $"Could not find display with ref: {rendered.requiredFacts[i].@ref.uri}");
-                    continue;
-                }
-
-                if (DynamicScrollDescriptionsActive)
-                    //Update ScrollParameter label
-                    RenderedScrollFact.Scroll = rendered;
-
-                //If ScrollFact is assigned -> No Hint
-                if (!RenderedScrollFact.IsSet)
-                {
-                    List<Fact> HintFactList = Fact.MMTFactory(rendered.requiredFacts[i]);
-
-                    foreach (Fact HintFact in HintFactList)
-                    {
-                        hintUris.Add(HintFact.Id); // == rendered.requiredFacts[i].@ref.uri
-                        LatestRenderedHints.Add(HintFact);
-                    }
-                }
-            }
-
-            return;
-        }
-    }
-
-    public void animateHint(string scrollParameterUri)
-    {
-        if (FactRecorder.AllFacts.ContainsKey(scrollParameterUri))
-            AnimateExistingFactEvent.Invoke(
-                    scrollParameterUri,
-                    FactWrapper.FactMaterials.Hint
-                );
-
-        Fact hintFact = LatestRenderedHints.Find(x => x.Id == scrollParameterUri); // "Dictionary"
-
-        ScrollAssignment suitableCompletion =
-            LatestBackwartsCompletions.Find((ScrollAssignment x) => x.fact.uri == scrollParameterUri); // "Dictionary"
-
-        if (suitableCompletion != null && suitableCompletion.assignment is OMS assignment)
-        {
-            if (FactRecorder.AllFacts.ContainsKey(assignment.uri))
-            {
-                AnimateExistingFactEvent.Invoke(
-                    assignment.uri,
-                    FactWrapper.FactMaterials.Hint
-                );
-            }
-        }
-        else if (hintFact != null)
-        {
-            if (FactRecorder.FindEquivalent(StageStatic.stage.factState.MyFactSpace, hintFact, out string found_key, out Fact _, out bool _, false))
-                // existing fact -> Animate that
-                AnimateExistingFactEvent.Invoke(
-                    found_key,
-                    FactWrapper.FactMaterials.Hint
-                );
-
-            else
-            {   // Generate new FactRepresentation and animate it
-                AnimateNonExistingFactEvent.Invoke(hintFact);
-                AnimateExistingFactEvent.Invoke(
-                    scrollParameterUri,
-                    FactWrapper.FactMaterials.Hint
-                );
-            }
-        }
-    }
-}
diff --git a/Assets/Scripts/UI/FactExplorer/OpenFactExplorer.cs b/Assets/Scripts/UI/FactExplorer/OpenFactExplorer.cs
index 4f1afd71..ff1434fe 100644
--- a/Assets/Scripts/UI/FactExplorer/OpenFactExplorer.cs
+++ b/Assets/Scripts/UI/FactExplorer/OpenFactExplorer.cs
@@ -109,7 +109,7 @@ private void DoOpenFactExplorer()
     }
 
     private bool DoSetActive()
-        => ScrollDetails.Instance.SetNextEmptyTo(CachedFactWrapper as FactObjectUI);
+        => ActiveScroll.Instance.AssignOrRemoveFactToNextEmptySlot(CachedFactWrapper.Fact) is not null;
     #endregion Implementation
 }
 
diff --git a/Assets/Scripts/UI/InGame/PopupBehavior.cs b/Assets/Scripts/UI/InGame/PopupBehavior.cs
index 6685ced2..6f1826ff 100644
--- a/Assets/Scripts/UI/InGame/PopupBehavior.cs
+++ b/Assets/Scripts/UI/InGame/PopupBehavior.cs
@@ -24,10 +24,25 @@ public string MessageText
     private string errorMessage;
 
     #region UnityMethods
-    void Awake()
+
+    void OnEnable()
     {
-        CommunicationEvents.ScrollApplicationCheckingErrorEvent.AddListener(OnFailedScrollInput);
+        ActiveScroll.OnMMTServerComunicationError.AddListener(OnShowErrorMessage);
+        ActiveScroll.OnScrollDynamicInfoError.AddListener(OnFailedScrollInput);
+        ActiveScroll.OnScrollApplicationError.AddListener(OnFailedScrollInput);
+        ActiveScroll.OnCancelErrorState.AddListener(HidePopUp);
+    }
 
+    void OnDisable()
+    {
+        ActiveScroll.OnMMTServerComunicationError.RemoveListener(OnShowErrorMessage);
+        ActiveScroll.OnScrollDynamicInfoError.RemoveListener(OnFailedScrollInput);
+        ActiveScroll.OnScrollApplicationError.RemoveListener(OnFailedScrollInput);
+        ActiveScroll.OnCancelErrorState.RemoveListener(HidePopUp);
+    }
+    
+    void Awake()
+    {
         CloseButton.onClick.RemoveAllListeners();
         CloseButton.onClick.AddListener(HidePopUp);
 
@@ -60,6 +75,15 @@ public void OnFailedScrollInput(ScrollApplicationCheckingError[] errorInfo)
         MessageText = generateHelpfulMessageAndAnimateScrollParam(errorInfo);
         ShowTimedPopUp();
     }
+    /// <summary>
+    /// this method gets an error message as parameter and shows it in a popup.
+    /// </summary>
+    /// <param name="errorMsg"></param>
+    public void OnShowErrorMessage(string errorMsg)
+    {
+        MessageText = errorMsg;
+        ShowTimedPopUp();
+    }
 
     private string generateHelpfulMessageAndAnimateScrollParam(ScrollApplicationCheckingError[] errorInfo)
     {
@@ -141,7 +165,7 @@ private MMTFact parseFactFromError(ScrollApplicationCheckingError error)
         factUri += "?" + factLabel;
 
         //find the required fact in the active scroll thats invalidly assigned
-        return ScrollDetails.ActiveScroll?.requiredFacts
+        return ActiveScroll.Instance.Scroll.requiredFacts
             .Find(decl => decl.@ref.uri == error.fact.uri);
     }
 }
diff --git a/Assets/Scripts/UI/InGame/ScrollDetails.cs b/Assets/Scripts/UI/InGame/ScrollDetails.cs
new file mode 100644
index 00000000..85f4e63f
--- /dev/null
+++ b/Assets/Scripts/UI/InGame/ScrollDetails.cs
@@ -0,0 +1,111 @@
+using REST_JSON_API;
+using System.Collections.Generic;
+using TMPro;
+using UnityEngine;
+using static CommunicationEvents;
+
+public class ScrollDetails : ScrollView
+{
+    public static ScrollDetails Instance
+    {
+        get => _Instance;
+        set
+        {
+            if (_Instance != value)
+                Destroy(_Instance);
+
+            _Instance = value;
+        }
+    }
+    private static ScrollDetails _Instance;
+
+    public WorldCursor cursor;
+    public GameObject parameterDisplayPrefab;
+    public GameObject mmtAnswerPopUp;
+    private PopupBehavior Popup;
+
+    public static List<RenderedScrollFact> ParameterDisplays { get; private set; }
+
+    void Awake()
+    {
+        Instance = this;
+
+        if (cursor == null)
+            cursor = FindObjectOfType<WorldCursor>();
+
+        Popup = mmtAnswerPopUp.GetComponent<PopupBehavior>();
+        Popup.gameObject.SetActive(true); // force Awake
+    }
+
+    private void OnEnable()
+    {
+        ActiveScroll.OnScrollChanged.AddListener(ShowScroll);
+        ActiveScroll.OnFactAssignmentUpdated.AddListener(OnFactAssignmentUpdated);
+        ActiveScroll.OnScrollDynamicInfoUpdated.AddListener(UpdateScrollDescription);
+    }
+
+    private void OnDisable()
+    {
+        ActiveScroll.OnScrollChanged.RemoveListener(ShowScroll);
+        ActiveScroll.OnFactAssignmentUpdated.RemoveListener(OnFactAssignmentUpdated);
+        ActiveScroll.OnScrollDynamicInfoUpdated.RemoveListener(UpdateScrollDescription);
+    }
+
+    private void OnDestroy()
+    {
+        _Instance = null;
+    }
+
+    /// <summary>
+    /// Update the scroll details screen to display the currently active scroll
+    /// </summary>
+    /// <param name="activeScroll"></param>
+    private void ShowScroll(ActiveScroll activeScroll)
+    {
+        Transform originalScroll = gameObject.transform.GetChild(1);
+        Transform originalScrollView = originalScroll.GetChild(1);
+        Transform originalViewport = originalScrollView.GetChild(0);
+
+        //Clear all current ScrollFacts
+        originalViewport.GetChild(0).gameObject.DestroyAllChildren();
+        Scroll scroll = activeScroll.Scroll;
+
+        originalScroll.GetChild(0).GetComponent<TextMeshProUGUI>().text = scroll.description;
+
+        //ParameterDisplays.ForEach(gameObj => Destroy(gameObj));
+        ParameterDisplays = new();
+        // generate parameter display slots
+        foreach (var slot in activeScroll.Assignments)
+        {
+            GameObject originalObj =
+                Instantiate(parameterDisplayPrefab, originalViewport.GetChild(0));
+
+            RenderedScrollFact originalRSF =
+                originalObj.GetComponentInChildren<RenderedScrollFact>();
+
+            ParameterDisplays.Add(originalRSF);
+
+            originalRSF.Populate(scroll, slot.Key);
+            originalRSF.transform.parent.gameObject.SetActive(slot.Value.IsVisible);
+        }
+    }
+
+    private void UpdateScrollDescription(Scroll rendered)
+    {
+        if (ActiveScroll.Instance.DynamicScrollDescriptionsActive)
+        { // Update scroll-description
+            Transform scroll = gameObject.transform.GetChild(1).transform;
+            scroll.GetChild(0).GetComponent<TextMeshProUGUI>().text = rendered.description;
+        }
+    }
+
+    private void OnFactAssignmentUpdated(string slotUri, ActiveScroll.SlotAssignment slotAssignment)
+    {
+        RenderedScrollFact changed = ParameterDisplays.Find(RSF => RSF.ScrollFactURI == slotUri);
+
+        if (changed != null)
+        {
+            changed.Fact = slotAssignment.fact;
+        }
+    }
+}
diff --git a/Assets/Scripts/InventoryStuff/ScrollDetails.cs.meta b/Assets/Scripts/UI/InGame/ScrollDetails.cs.meta
similarity index 100%
rename from Assets/Scripts/InventoryStuff/ScrollDetails.cs.meta
rename to Assets/Scripts/UI/InGame/ScrollDetails.cs.meta
diff --git a/Assets/Scripts/UI/InGame/ScrollView.cs b/Assets/Scripts/UI/InGame/ScrollView.cs
new file mode 100644
index 00000000..e3e95c89
--- /dev/null
+++ b/Assets/Scripts/UI/InGame/ScrollView.cs
@@ -0,0 +1,5 @@
+using UnityEngine;
+
+public abstract class ScrollView : MonoBehaviour
+{
+}
diff --git a/Assets/Scripts/UI/InGame/ScrollView.cs.meta b/Assets/Scripts/UI/InGame/ScrollView.cs.meta
new file mode 100644
index 00000000..29da970a
--- /dev/null
+++ b/Assets/Scripts/UI/InGame/ScrollView.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 01651b93334ae6f46a3b05f7cfe34543
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/Scripts/UI/InGame/WebViewController.cs b/Assets/Scripts/UI/InGame/WebViewController.cs
index e97f8488..d4c24bb0 100644
--- a/Assets/Scripts/UI/InGame/WebViewController.cs
+++ b/Assets/Scripts/UI/InGame/WebViewController.cs
@@ -12,7 +12,7 @@
 using UnityEngine;
 using UnityEngine.Events;
 
-public class WebViewController : MonoBehaviour
+public class WebViewController : ScrollView
 {
 
 
-- 
GitLab