ChipSignal GetSignalUnderMouse()
    {
        ChipSignal signalUnderMouse = null;
        float      nearestDst       = float.MaxValue;

        for (int i = 0; i < signals.Count; i++)
        {
            ChipSignal currentSignal = signals[i];
            float      handleY       = currentSignal.transform.position.y;

            Vector2 handleCentre = new Vector2(transform.position.x, handleY);
            Vector2 mousePos     = InputHelper.MouseWorldPos;

            const float selectionBufferY = 0.1f;

            float halfSizeX = transform.localScale.x / 2f;
            float halfSizeY = (handleSize.y + selectionBufferY) / 2f;
            bool  insideX   = mousePos.x >= handleCentre.x - halfSizeX && mousePos.x <= handleCentre.x + halfSizeX;
            bool  insideY   = mousePos.y >= handleCentre.y - halfSizeY && mousePos.y <= handleCentre.y + halfSizeY;

            if (insideX && insideY)
            {
                float dst = Mathf.Abs(mousePos.y - handleY);
                if (dst < nearestDst)
                {
                    nearestDst       = dst;
                    signalUnderMouse = currentSignal;
                }
            }
        }
        return(signalUnderMouse);
    }
    // Select signal (starts dragging, shows rename field)
    void SelectSignal(ChipSignal signalToDrag)
    {
        // Dragging
        selectedSignals.Clear();
        for (int i = 0; i < signals.Count; i++)
        {
            if (signals[i] == signalToDrag || ChipSignal.InSameGroup(signals[i], signalToDrag))
            {
                selectedSignals.Add(signals[i]);
            }
        }

        isDragging = true;

        dragMouseStartY = InputHelper.MouseWorldPos.y;
        if (selectedSignals.Count % 2 == 0)
        {
            int indexA = Mathf.Max(0, selectedSignals.Count / 2 - 1);
            int indexB = selectedSignals.Count / 2;
            dragHandleStartY = (selectedSignals[indexA].transform.position.y + selectedSignals[indexB].transform.position.y) / 2f;
        }
        else
        {
            dragHandleStartY = selectedSignals[selectedSignals.Count / 2].transform.position.y;
        }

        // Name input field
        nameField.gameObject.SetActive(true);
        nameField.text = (selectedSignals[0]).signalName;
        nameField.Select();
        // Delete button
        deleteButton.gameObject.SetActive(true);
        UpdateButtonAndNameField();
    }
	protected override void FocusLost () {
		highlightedSignal = null;
		selectedSignal = null;

		deleteButton.gameObject.SetActive (false);
		nameField.gameObject.SetActive (false);
		previewSignal.gameObject.SetActive (false);
	}
    // Select signal (starts dragging, shows rename field)
    void SelectSignal(ChipSignal signalToDrag)
    {
        // Dragging
        selectedSignals.Clear();

        for (int i = 0; i < signals.Count; i++)
        {
            if (signals[i] == signalToDrag || ChipSignal.InSameGroup(signals[i], signalToDrag))
            {
                selectedSignals.Add(signals[i]);
            }
        }
        bool isGroup = selectedSignals.Count > 1;

        isDragging = true;

        var wireType = Pin.WireType.Simple;

        if (selectedSignals[0] is InputSignal)
        {
            var signal = selectedSignals[0];
            var pin    = signal.outputPins[0];
            wireType = pin.wireType;
        }
        if (selectedSignals[0] is OutputSignal)
        {
            var signal = selectedSignals[0];
            var pin    = signal.inputPins[0];
            wireType = pin.wireType;
        }

        dragMouseStartY = InputHelper.MouseWorldPos.y;
        if (selectedSignals.Count % 2 == 0)
        {
            int indexA = Mathf.Max(0, selectedSignals.Count / 2 - 1);
            int indexB = selectedSignals.Count / 2;
            dragHandleStartY = (selectedSignals[indexA].transform.position.y + selectedSignals[indexB].transform.position.y) / 2f;
        }
        else
        {
            dragHandleStartY = selectedSignals[selectedSignals.Count / 2].transform.position.y;
        }

        // Enable UI:
        propertiesUI.gameObject.SetActive(true);
        propertiesUI.sizeDelta = new Vector2(propertiesUI.sizeDelta.x, (isGroup) ? propertiesHeightMinMax.y : propertiesHeightMinMax.x);
        nameField.text         = selectedSignals[0].signalName;
        nameField.Select();
        nameField.caretPosition = nameField.text.Length;
        twosComplementToggle.gameObject.SetActive(isGroup);
        twosComplementToggle.isOn = selectedSignals[0].useTwosComplement;
        modeDropdown.gameObject.SetActive(!isGroup);
        deleteButton.interactable = ChipSaver.IsSignalSafeToDelete(currentEditorName, signalToDrag.signalName);
        UpdateUIProperties();

        modeDropdown.SetValueWithoutNotify((int)wireType);
        UpdateUIProperties();
    }
    protected override void FocusLost()
    {
        highlightedSignal = null;
        selectedSignals.Clear();
        propertiesUI.gameObject.SetActive(false);

        HidePreviews();
        currentGroupSize = 1;
    }
	void Delete () {
		if (selectedSignal) {
			onDeleteChip?.Invoke (selectedSignal);
			signals.Remove (selectedSignal);
			Destroy (selectedSignal.gameObject);
			selectedSignal = null;
			FocusLost ();
		}
	}
    protected override void FocusLost()
    {
        highlightedSignal = null;
        selectedSignals.Clear();

        deleteButton.gameObject.SetActive(false);
        nameField.gameObject.SetActive(false);
        HidePreviews();
        currentGroupSize = 1;
    }
    // Handles spawning if user clicks, otherwise displays preview
    void HandleSpawning()
    {
        float containerX = chipContainer.position.x + chipContainer.localScale.x / 2 * ((editorType == EditorType.Input) ? -1 : 1);
        float centreY    = ClampY(InputHelper.MouseWorldPos.y);

        // Spawn on mouse down
        if (Input.GetMouseButtonDown(0))
        {
            bool         isGroup        = currentGroupSize > 1;
            ChipSignal[] spawnedSignals = new ChipSignal[currentGroupSize];

            for (int i = 0; i < currentGroupSize; i++)
            {
                float   posY     = CalcY(InputHelper.MouseWorldPos.y, currentGroupSize, i);
                Vector3 spawnPos = new Vector3(containerX, posY, chipContainer.position.z + forwardDepth);

                ChipSignal spawnedSignal = Instantiate(signalPrefab, spawnPos, Quaternion.identity, signalHolder);
                if (isGroup)
                {
                    spawnedSignal.GroupID = currentGroupID;
                    spawnedSignal.displayGroupDecimalValue = true;
                }
                signals.Add(spawnedSignal);
                spawnedSignals[i] = spawnedSignal;
            }

            if (isGroup)
            {
                groupsByID.Add(currentGroupID, spawnedSignals);
                // Reset group size after spawning
                currentGroupSize = 1;
                // Generate new ID for next group
                // This will be used to identify which signals were created together as a group
                currentGroupID++;
            }
            SelectSignal(signals[signals.Count - 1]);
            onChipsAddedOrDeleted?.Invoke();
        }
        // Draw handle and signal previews
        else
        {
            for (int i = 0; i < currentGroupSize; i++)
            {
                float   posY     = CalcY(InputHelper.MouseWorldPos.y, currentGroupSize, i);
                Vector3 spawnPos = new Vector3(containerX, posY, chipContainer.position.z + forwardDepth);
                DrawHandle(posY, HandleState.Highlighted);
                if (showPreviewSignal)
                {
                    previewSignals[i].gameObject.SetActive(true);
                    previewSignals[i].transform.position = spawnPos - Vector3.forward * forwardDepth;
                }
            }
        }
    }
	void Awake () {
		signals = new List<ChipSignal> ();
		inputBounds = GetComponent<BoxCollider2D> ();
		MeshShapeCreator.CreateQuadMesh (ref quadMesh);
		handleMat = CreateUnlitMaterial (handleCol);
		highlightedHandleMat = CreateUnlitMaterial (highlightedHandleCol);
		selectedHandleMat = CreateUnlitMaterial (selectedHandleCol);
		previewSignal = Instantiate (signalPrefab);
		previewSignal.gameObject.SetActive (false);
		previewSignal.signalName = "Preview";
		previewSignal.transform.SetParent (transform, true);

		deleteButton.onClick.AddListener (Delete);
	}
    public ChipSignal[][] GetGroups()
    {
        var keys = groupsByID.Keys;

        ChipSignal[][] groups = new ChipSignal[keys.Count][];
        int            i      = 0;

        foreach (var key in keys)
        {
            groups[i] = groupsByID[key];
            i++;
        }
        return(groups);
    }
	// Select signal (starts dragging, shows rename field)
	void SelectSignal (ChipSignal signalToDrag) {
		// Dragging
		selectedSignal = signalToDrag;
		isDragging = true;

		dragMouseStartY = InputHelper.MouseWorldPos.y;
		dragHandleStartY = selectedSignal.transform.position.y;

		// Name input field
		nameField.gameObject.SetActive (true);
		nameField.text = (selectedSignal).signalName;
		nameField.Select ();
		// Delete button
		deleteButton.gameObject.SetActive (true);
		UpdateButtonAndNameField ();

	}
 void DeleteSelected()
 {
     for (int i = selectedSignals.Count - 1; i >= 0; i--)
     {
         ChipSignal signalToDelete = selectedSignals[i];
         if (groupsByID.ContainsKey(signalToDelete.GroupID))
         {
             groupsByID.Remove(signalToDelete.GroupID);
         }
         onDeleteChip?.Invoke(signalToDelete);
         signals.Remove(signalToDelete);
         Destroy(signalToDelete.gameObject);
     }
     onChipsAddedOrDeleted?.Invoke();
     selectedSignals.Clear();
     FocusLost();
 }
    // Select signal (starts dragging, shows rename field)
    void SelectSignal(ChipSignal signalToDrag)
    {
        // Dragging
        selectedSignals.Clear();
        for (int i = 0; i < signals.Count; i++)
        {
            if (signals[i] == signalToDrag || ChipSignal.InSameGroup(signals[i], signalToDrag))
            {
                selectedSignals.Add(signals[i]);
            }
        }
        bool isGroup = selectedSignals.Count > 1;

        isDragging = true;

        dragMouseStartY = InputHelper.MouseWorldPos.y;
        if (selectedSignals.Count % 2 == 0)
        {
            int indexA = Mathf.Max(0, selectedSignals.Count / 2 - 1);
            int indexB = selectedSignals.Count / 2;
            dragHandleStartY = (selectedSignals[indexA].transform.position.y + selectedSignals[indexB].transform.position.y) / 2f;
        }
        else
        {
            dragHandleStartY = selectedSignals[selectedSignals.Count / 2].transform.position.y;
        }

        // Enable UI:
        propertiesUI.gameObject.SetActive(true);
        propertiesUI.sizeDelta = new Vector2(propertiesUI.sizeDelta.x, (isGroup) ? propertiesHeightMinMax.y : propertiesHeightMinMax.x);
        nameField.text         = selectedSignals[0].signalName;
        nameField.Select();
        nameField.caretPosition = nameField.text.Length;
        twosComplementToggle.gameObject.SetActive(isGroup);
        twosComplementToggle.isOn = selectedSignals[0].useTwosComplement;
        UpdateUIProperties();
    }
 public void LoadSignal(ChipSignal signal)
 {
     signal.transform.parent = signalHolder;
     signals.Add(signal);
 }
	void HandleInput () {
		Vector2 mousePos = InputHelper.MouseWorldPos;

		mouseInInputBounds = inputBounds.OverlapPoint (mousePos);
		if (mouseInInputBounds) {
			RequestFocus ();
		}

		if (HasFocus) {

			highlightedSignal = GetSignalUnderMouse ();

			// If a signal is highlighted (mouse is over its handle), then select it on mouse press
			if (highlightedSignal) {
				if (Input.GetMouseButtonDown (0)) {
					SelectSignal (highlightedSignal);
				}
			}

			// If a signal is selected, handle movement/renaming/deletion
			if (selectedSignal) {
				if (isDragging) {
					float handleNewY = ClampY (mousePos.y + (dragHandleStartY - dragMouseStartY));
					SetYPos (selectedSignal.transform, handleNewY);
					if (Input.GetMouseButtonUp (0)) {
						isDragging = false;
					}

					// Cancel drag and deselect
					if (Input.GetKeyDown (KeyCode.Escape)) {
						SetYPos (selectedSignal.transform, dragHandleStartY);
						FocusLost ();
					}
				}

				UpdateButtonAndNameField ();

				// Finished with selected signal, so deselect it
				if (Input.GetKeyDown (KeyCode.Return)) {
					FocusLost ();
				}

			}

			previewSignal.gameObject.SetActive (false);
			if (highlightedSignal == null && !isDragging) {
				if (mouseInInputBounds) {
					float handleY = ClampY (mousePos.y);

					DrawHandle (handleY, HandleState.Highlighted);
					float containerX = chipContainer.position.x + chipContainer.localScale.x / 2 * ((editorType == EditorType.Input) ? -1 : 1);
					Vector3 spawnPos = new Vector3 (containerX, handleY, chipContainer.position.z + forwardDepth);

					if (showPreviewSignal) {
						previewSignal.gameObject.SetActive (true);
						previewSignal.transform.position = spawnPos;
					}

					// Spawn
					if (Input.GetMouseButtonDown (0)) {
						ChipSignal spawnedSignal = Instantiate (signalPrefab, spawnPos, Quaternion.identity, signalHolder);
						signals.Add (spawnedSignal);
						SelectSignal (spawnedSignal);
					}
				}
			}
		}
	}
 public static bool InSameGroup(ChipSignal signalA, ChipSignal signalB)
 {
     return((signalA.groupID == signalB.groupID) && (signalA.groupID != -1));
 }
    void HandleInput()
    {
        Vector2 mousePos = InputHelper.MouseWorldPos;

        mouseInInputBounds = inputBounds.OverlapPoint(mousePos);
        if (mouseInInputBounds)
        {
            RequestFocus();
        }

        if (HasFocus)
        {
            highlightedSignal = GetSignalUnderMouse();


            // If a signal is highlighted (mouse is over its handle), then select it on mouse press
            if (highlightedSignal)
            {
                if (Input.GetMouseButtonDown(0))
                {
                    SelectSignal(highlightedSignal);
                }
            }

            // If a signal is selected, handle movement/renaming/deletion
            if (selectedSignals.Count > 0)
            {
                if (isDragging)
                {
                    float handleNewY = (mousePos.y + (dragHandleStartY - dragMouseStartY));
                    bool  cancel     = Input.GetKeyDown(KeyCode.Escape);
                    if (cancel)
                    {
                        handleNewY = dragHandleStartY;
                    }

                    for (int i = 0; i < selectedSignals.Count; i++)
                    {
                        float y = CalcY(handleNewY, selectedSignals.Count, i);
                        SetYPos(selectedSignals[i].transform, y);
                    }

                    if (Input.GetMouseButtonUp(0))
                    {
                        isDragging = false;
                    }

                    // Cancel drag and deselect
                    if (cancel)
                    {
                        FocusLost();
                    }
                }

                UpdateUIProperties();

                // Finished with selected signal, so deselect it
                if (Input.GetKeyDown(KeyCode.Return))
                {
                    FocusLost();
                }
            }

            HidePreviews();
            if (highlightedSignal == null && !isDragging)
            {
                if (mouseInInputBounds)
                {
                    if (InputHelper.AnyOfTheseKeysDown(KeyCode.Plus, KeyCode.KeypadPlus, KeyCode.Equals))
                    {
                        currentGroupSize = Mathf.Clamp(currentGroupSize + 1, 1, maxGroupSize);
                    }
                    else if (InputHelper.AnyOfTheseKeysDown(KeyCode.Minus, KeyCode.KeypadMinus, KeyCode.Underscore))
                    {
                        currentGroupSize = Mathf.Clamp(currentGroupSize - 1, 1, maxGroupSize);
                    }

                    HandleSpawning();
                }
            }
        }
    }