Added rest of the modules

This commit is contained in:
Christen
2026-03-03 19:47:55 -05:00
parent 81990827c0
commit faf9b62c30
9 changed files with 8313 additions and 206 deletions

View File

@@ -5,40 +5,47 @@ using UnityEngine.UI;
public class ControlPanel : MonoBehaviour
{
[Header("Core UI (assign in Inspector)")]
public TMP_InputField inputField; // was DegreesInputField
public TMP_Dropdown directionDropdown; // was DirectionDropdown
public Button button; // was RotateButton
public TMP_InputField inputField;
public TMP_Dropdown directionDropdown;
public Button button; // existing Send/Rotate button in hierarchy
private enum Mode { None, DC, Display, Gripper }
private enum Mode { None, DC, Display, Speaker }
private Mode _mode = Mode.None;
private DCMotorModule _dc;
private DisplayModule _display;
private GripperModule _gripper;
private SpeakerModule _speaker;
// Keep separate inputs so they don't interfere
private string _savedDCInput = "";
private int _savedDirectionIndex = 0;
private string _savedDisplayInput = "";
private string _savedGripperInput = "";
// Found automatically (no new UI objects)
private TextMeshProUGUI _caption; // DegreesCaption text
private TextMeshProUGUI _buttonText; // RotateButton/Text (TMP)
private GameObject _directionCaptionGO; // DirectionCaption
private GameObject _directionDropdownGO; // DirectionDropdown GameObject
// Found automatically (no hierarchy edits)
private TextMeshProUGUI _caption; // child "DegreesCaption"
private TextMeshProUGUI _buttonText; // child "RotateButton/Text (TMP)"
private GameObject _directionCaptionGO;
private GameObject _directionDropdownGO;
// Created in code (Speaker mode only)
private Button _uploadButton;
private TextMeshProUGUI _uploadButtonText;
private Button _playButton;
private TextMeshProUGUI _playButtonText;
private void Awake()
{
CacheUIFromHierarchy();
// Create these once; hide unless Speaker mode
CreateUploadButtonIfNeeded();
CreatePlayButtonIfNeeded();
if (button != null)
{
button.onClick.RemoveAllListeners();
button.onClick.AddListener(OnButtonClicked);
button.onClick.AddListener(OnMainButtonClicked);
}
// Cache typed values so switching modules restores what you typed
if (inputField != null)
{
inputField.onValueChanged.AddListener(_ => CacheCurrentInput());
@@ -54,37 +61,17 @@ public class ControlPanel : MonoBehaviour
}
}
/// <summary>
/// Call this whenever a DC or Display module is selected.
/// </summary>
public void Initialize(ModuleBase module)
{
CacheCurrentInput();
_dc = module as DCMotorModule;
_display = module as DisplayModule;
_gripper = module as GripperModule;
_speaker = module as SpeakerModule;
if (_dc != null)
{
SwitchMode(Mode.DC);
gameObject.SetActive(true);
return;
}
if (_display != null)
{
SwitchMode(Mode.Display);
gameObject.SetActive(true);
return;
}
if (_gripper != null)
{
SwitchMode(Mode.Gripper);
gameObject.SetActive(true);
return;
}
if (_dc != null) { SwitchMode(Mode.DC); gameObject.SetActive(true); return; }
if (_display != null) { SwitchMode(Mode.Display); gameObject.SetActive(true); return; }
if (_speaker != null) { SwitchMode(Mode.Speaker); gameObject.SetActive(true); return; }
HidePanel();
}
@@ -96,12 +83,13 @@ public class ControlPanel : MonoBehaviour
_mode = Mode.None;
_dc = null;
_display = null;
_gripper = null;
_speaker = null;
SetUploadVisible(false);
SetPlayVisible(false);
}
private void CacheUIFromHierarchy()
{
// Assumes this script is on the parent that contains these children
var captionT = transform.Find("DegreesCaption");
if (captionT != null) _caption = captionT.GetComponent<TextMeshProUGUI>();
@@ -122,39 +110,64 @@ public class ControlPanel : MonoBehaviour
if (_mode == Mode.DC)
{
if (_caption != null) _caption.text = "Degrees";
if (_buttonText != null) _buttonText.text = "Rotate";
SetCaption("Degrees");
SetCaptionVisible(true);
SetMainButtonText("Rotate");
SetDirectionVisible(true);
SetInputVisible(true);
SetUploadVisible(false);
SetPlayVisible(false);
if (inputField != null)
{
inputField.readOnly = false;
inputField.SetTextWithoutNotify(_savedDCInput);
}
if (directionDropdown != null)
directionDropdown.SetValueWithoutNotify(_savedDirectionIndex);
}
else if (_mode == Mode.Display)
{
if (_caption != null) _caption.text = "Text";
if (_buttonText != null) _buttonText.text = "Send";
SetCaption("Text");
SetCaptionVisible(true);
SetMainButtonText("Send");
SetDirectionVisible(false);
SetInputVisible(true);
SetUploadVisible(false);
SetPlayVisible(false);
// Prefer module's current displayText; fallback to last typed text
if (inputField != null)
{
inputField.readOnly = false;
string toShow = _savedDisplayInput;
if (_display != null && !string.IsNullOrEmpty(_display.displayText))
toShow = _display.displayText;
if (inputField != null)
inputField.SetTextWithoutNotify(toShow);
}
else if (_mode == Mode.Gripper)
{
if (_caption != null) _caption.text = "Degrees";
if (_buttonText != null) _buttonText.text = "Apply";
SetDirectionVisible(false);
if (inputField != null)
inputField.SetTextWithoutNotify(_gripper != null ? _gripper.currentAngle.ToString("F0") : _savedGripperInput);
}
else if (_mode == Mode.Speaker)
{
// Speaker UI: no input field (removes the white box behind buttons)
SetMainButtonText("Send Audio");
SetCaptionVisible(false);
SetDirectionVisible(false);
SetInputVisible(false);
// Show speaker buttons
SetUploadVisible(true);
SetPlayVisible(true);
}
}
private void SetCaption(string text)
{
if (_caption != null) _caption.text = text;
}
private void SetMainButtonText(string text)
{
if (_buttonText != null) _buttonText.text = text;
}
private void SetDirectionVisible(bool visible)
@@ -163,6 +176,12 @@ public class ControlPanel : MonoBehaviour
if (_directionDropdownGO != null) _directionDropdownGO.SetActive(visible);
}
private void SetInputVisible(bool visible)
{
if (inputField != null && inputField.gameObject.activeSelf != visible)
inputField.gameObject.SetActive(visible);
}
private void CacheCurrentInput()
{
if (inputField == null) return;
@@ -170,28 +189,19 @@ public class ControlPanel : MonoBehaviour
if (_mode == Mode.DC)
{
_savedDCInput = inputField.text;
if (directionDropdown != null)
_savedDirectionIndex = directionDropdown.value;
if (directionDropdown != null) _savedDirectionIndex = directionDropdown.value;
}
else if (_mode == Mode.Display)
{
_savedDisplayInput = inputField.text;
}
else if (_mode == Mode.Gripper)
{
_savedGripperInput = inputField.text;
}
}
private void OnButtonClicked()
private void OnMainButtonClicked()
{
if (_mode == Mode.DC)
{
if (_dc == null)
{
Debug.LogWarning("ControlPanel: No DC module selected.");
return;
}
if (_dc == null) return;
if (!float.TryParse(inputField.text, out float degrees))
{
@@ -204,30 +214,206 @@ public class ControlPanel : MonoBehaviour
}
else if (_mode == Mode.Display)
{
if (_display == null)
if (_display == null) return;
_display.SetDisplayText(inputField != null ? inputField.text : "");
}
else if (_mode == Mode.Speaker)
{
Debug.LogWarning("ControlPanel: No Display module selected.");
return;
if (_speaker == null) return;
_speaker.SendAudioToHardware(); // Send Audio = upload to hardware (your implementation)
}
}
string text = inputField != null ? inputField.text : "";
_display.SetDisplayText(text); // <- THIS is where Display module gets the text
// -------------------------
// Styling helpers (copy Send button look)
// -------------------------
private void CopyButtonStyle(Button source, Button target)
{
if (source == null || target == null) return;
// Button settings
target.transition = source.transition;
target.colors = source.colors;
target.spriteState = source.spriteState;
target.navigation = source.navigation;
target.interactable = source.interactable;
// Image settings
var srcImg = source.GetComponent<Image>();
var dstImg = target.GetComponent<Image>();
if (srcImg != null && dstImg != null)
{
dstImg.sprite = srcImg.sprite;
dstImg.type = srcImg.type;
dstImg.pixelsPerUnitMultiplier = srcImg.pixelsPerUnitMultiplier;
dstImg.material = srcImg.material;
dstImg.color = srcImg.color;
dstImg.raycastTarget = srcImg.raycastTarget;
}
else if (_mode == Mode.Gripper)
{
if (_gripper == null)
{
Debug.LogWarning("ControlPanel: No Gripper module selected.");
return;
}
if (!float.TryParse(inputField.text, out float degrees))
private void CopyTMPStyle(TextMeshProUGUI source, TextMeshProUGUI target)
{
Debug.LogWarning("ControlPanel: Invalid degree input for Gripper.");
return;
if (source == null || target == null) return;
target.font = source.font;
target.fontSharedMaterial = source.fontSharedMaterial;
target.fontStyle = source.fontStyle;
target.fontSize = source.fontSize;
target.enableAutoSizing = source.enableAutoSizing;
target.color = source.color;
target.enableVertexGradient = source.enableVertexGradient;
target.alignment = source.alignment;
target.enableWordWrapping = source.enableWordWrapping;
target.richText = source.richText;
}
_gripper.SetAngleAndSendControlLibrary(Mathf.Clamp(degrees, 0f, 180f));
}
// -------------------------
// Upload button (code-created)
// -------------------------
private void CreateUploadButtonIfNeeded()
{
if (_uploadButton != null) return;
var go = new GameObject("UploadButton");
go.transform.SetParent(transform, false);
var img = go.AddComponent<Image>();
img.raycastTarget = true;
_uploadButton = go.AddComponent<Button>();
_uploadButton.onClick.AddListener(OnUploadClicked);
// Copy Send button look
CopyButtonStyle(button, _uploadButton);
var textGo = new GameObject("Text (TMP)");
textGo.transform.SetParent(go.transform, false);
_uploadButtonText = textGo.AddComponent<TextMeshProUGUI>();
_uploadButtonText.text = "Upload Audio";
// Copy Send text look
CopyTMPStyle(_buttonText, _uploadButtonText);
_uploadButtonText.alignment = TextAlignmentOptions.Center;
// Layout: above Play
var rt = go.GetComponent<RectTransform>();
rt.anchorMin = new Vector2(0.5f, 0f);
rt.anchorMax = new Vector2(0.5f, 0f);
rt.pivot = new Vector2(0.5f, 0f);
rt.sizeDelta = button != null ? button.GetComponent<RectTransform>().sizeDelta : new Vector2(180f, 40f);
rt.anchoredPosition = new Vector2(0f, 110f);
var trt = textGo.GetComponent<RectTransform>();
trt.anchorMin = Vector2.zero;
trt.anchorMax = Vector2.one;
trt.offsetMin = Vector2.zero;
trt.offsetMax = Vector2.zero;
go.SetActive(false);
}
private void SetUploadVisible(bool visible)
{
if (_uploadButton != null && _uploadButton.gameObject.activeSelf != visible)
_uploadButton.gameObject.SetActive(visible);
}
private void OnUploadClicked()
{
if (_speaker == null) return;
string path = OpenAudioFilePicker();
if (string.IsNullOrEmpty(path)) return;
_speaker.SetAudioFile(path);
// If you want to display filename somewhere without the input field,
// we can put it in the caption temporarily or add a TMP label in code.
}
// -------------------------
// Play button (code-created)
// -------------------------
private void CreatePlayButtonIfNeeded()
{
if (_playButton != null) return;
var go = new GameObject("PlayButton");
go.transform.SetParent(transform, false);
var img = go.AddComponent<Image>();
img.raycastTarget = true;
_playButton = go.AddComponent<Button>();
_playButton.onClick.AddListener(OnPlayClicked);
// Copy Send button look
CopyButtonStyle(button, _playButton);
var textGo = new GameObject("Text (TMP)");
textGo.transform.SetParent(go.transform, false);
_playButtonText = textGo.AddComponent<TextMeshProUGUI>();
_playButtonText.text = "Play Audio";
// Copy Send text look
CopyTMPStyle(_buttonText, _playButtonText);
_playButtonText.alignment = TextAlignmentOptions.Center;
// Layout: between Upload and Send
var rt = go.GetComponent<RectTransform>();
rt.anchorMin = new Vector2(0.5f, 0f);
rt.anchorMax = new Vector2(0.5f, 0f);
rt.pivot = new Vector2(0.5f, 0f);
rt.sizeDelta = button != null ? button.GetComponent<RectTransform>().sizeDelta : new Vector2(180f, 40f);
rt.anchoredPosition = new Vector2(0f, 60f);
var trt = textGo.GetComponent<RectTransform>();
trt.anchorMin = Vector2.zero;
trt.anchorMax = Vector2.one;
trt.offsetMin = Vector2.zero;
trt.offsetMax = Vector2.zero;
go.SetActive(false);
}
private void SetPlayVisible(bool visible)
{
if (_playButton != null && _playButton.gameObject.activeSelf != visible)
_playButton.gameObject.SetActive(visible);
}
private void OnPlayClicked()
{
if (_speaker == null) return;
_speaker.PlayAudioOnHardware();
}
// -------------------------
// File Picker (Editor + builds via SFB)
// -------------------------
private string OpenAudioFilePicker()
{
#if UNITY_EDITOR
return UnityEditor.EditorUtility.OpenFilePanel("Select audio file", "", "wav,mp3,ogg");
#else
var extensions = new[]
{
new SFB.ExtensionFilter("Audio Files", "wav", "mp3", "ogg"),
new SFB.ExtensionFilter("All Files", "*")
};
string[] paths = SFB.StandaloneFileBrowser.OpenFilePanel("Select audio file", "", extensions, false);
return (paths != null && paths.Length > 0) ? paths[0] : "";
#endif
}
private void SetCaptionVisible(bool visible)
{
if (_caption != null && _caption.gameObject.activeSelf != visible)
_caption.gameObject.SetActive(visible);
}
}

View File

@@ -23,14 +23,12 @@ public class LiveViewModulePanel : MonoBehaviour
private bool _sliderDragging;
private ControlPanel panel;
private ModuleBase _lastSelected;
private GameObject sensorTextContainer;
private readonly System.Collections.Generic.List<TextMeshProUGUI> sensorTextLines = new System.Collections.Generic.List<TextMeshProUGUI>();
void Start()
{
CreateSensorTextUI();
ApplyRoundedCornersToPanel();
if (servoAngleSlider != null)
{
@@ -38,7 +36,6 @@ public class LiveViewModulePanel : MonoBehaviour
servoAngleSlider.maxValue = 180f;
servoAngleSlider.direction = Slider.Direction.BottomToTop;
servoAngleSlider.onValueChanged.AddListener(OnServoSliderChanged);
ConfigureSliderForVertical(servoAngleSlider);
var et = servoAngleSlider.GetComponent<EventTrigger>() ?? servoAngleSlider.gameObject.AddComponent<EventTrigger>();
var begin = new EventTrigger.Entry { eventID = EventTriggerType.BeginDrag };
@@ -69,7 +66,6 @@ public class LiveViewModulePanel : MonoBehaviour
if (selected == null)
{
_lastSelected = null;
SetModuleInfo("No module selected");
SetServoSectionActive(false);
SetModuleControlSectionActive(false);
@@ -87,6 +83,7 @@ public class LiveViewModulePanel : MonoBehaviour
var display = selected as DisplayModule;
var distance = selected as DistanceSensorModule;
var imu = selected as IMUSensorModule;
var speaker = selected as SpeakerModule;
if (servo != null)
{
@@ -120,20 +117,13 @@ public class LiveViewModulePanel : MonoBehaviour
return;
}
// DC, Display, or Gripper use the same control section (degrees input + button)
var gripper = selected as GripperModule;
if (dc != null || display != null || gripper != null)
// DC or Display both use the same control section
if (dc != null || display != null || speaker != null)
{
SetServoSectionActive(false);
SetModuleControlSectionActive(true);
SetSensorTextActive(false);
if (selected != _lastSelected)
{
_lastSelected = selected;
panel?.Initialize(selected);
}
return;
}
@@ -143,16 +133,6 @@ public class LiveViewModulePanel : MonoBehaviour
SetSensorTextActive(false);
}
void ApplyRoundedCornersToPanel()
{
var img = GetComponent<Image>();
if (img != null && img.sprite == null)
{
img.sprite = UIRoundedSprite.GetRoundedRectSprite();
img.type = Image.Type.Sliced; // 9-slice keeps corners rounded when panel stretches
}
}
void SetModuleControlSectionActive(bool active)
{
if (moduleControlSection == null || panel == null) return;
@@ -181,59 +161,6 @@ public class LiveViewModulePanel : MonoBehaviour
ServoMotorModule.selectedModule.SetAngleAndSendControlLibrary(value);
}
void ConfigureSliderForVertical(Slider slider)
{
if (slider == null || servoControlSection == null) return;
var sectionRect = servoControlSection.GetComponent<RectTransform>();
if (sectionRect != null)
EnsureDegreeLabels(sectionRect);
}
void EnsureDegreeLabels(RectTransform sectionRect)
{
SetDegreeLabel(sectionRect, "0deg", "0", 0, 14);
SetDegreeLabel(sectionRect, "180deg", "180", 1, -14);
}
void SetDegreeLabel(RectTransform sectionRect, string goName, string text, float anchorY, float posY)
{
var t = sectionRect.Find(goName);
if (t == null)
{
var go = new GameObject(goName);
go.transform.SetParent(sectionRect, false);
var tmp = go.AddComponent<TextMeshProUGUI>();
tmp.text = text;
tmp.fontSize = 16;
tmp.color = Color.white;
tmp.alignment = TextAlignmentOptions.MidlineLeft;
var r = go.GetComponent<RectTransform>();
r.anchorMin = new Vector2(1f, anchorY);
r.anchorMax = new Vector2(1f, anchorY);
r.pivot = new Vector2(0f, 0.5f);
r.anchoredPosition = new Vector2(20, posY);
r.sizeDelta = new Vector2(50, 24);
}
else
{
var tmp = t.GetComponent<TextMeshProUGUI>();
if (tmp != null)
{
tmp.color = Color.white;
tmp.fontSize = 16;
}
var r = t.GetComponent<RectTransform>();
if (r != null)
{
r.anchorMin = new Vector2(1f, anchorY);
r.anchorMax = new Vector2(1f, anchorY);
r.pivot = new Vector2(0f, 0.5f);
r.anchoredPosition = new Vector2(20, posY);
r.sizeDelta = new Vector2(50, 24);
}
}
}
void CreateSensorTextUI()
{
if (moduleInfoText == null) return;

View File

@@ -17,7 +17,6 @@ public class ModuleSpawner : MonoBehaviour
public GameObject distanceSensorModulePrefab;
public GameObject imuSensorModulePrefab;
public GameObject speakerModulePrefab;
public GameObject powerModulePrefab;
public GameObject GetPrefabForType(ModuleType type)
{
@@ -26,12 +25,10 @@ public class ModuleSpawner : MonoBehaviour
{
case ModuleType.BATTERY:
return batteryModulePrefab;
case ModuleType.POWER:
return powerModulePrefab;
case ModuleType.SPLITTER:
return hubModuleMMMFPrefab;
case ModuleType.DC_MOTOR:
return null; // DC module deactivated
return dcMotorModulePrefab; // DC module deactivated
case ModuleType.SERVO_1:
return servoBendModulePrefab;
case ModuleType.SERVO_2:

View File

@@ -4093,6 +4093,11 @@ Transform:
m_Children: []
m_Father: {fileID: 1319424261}
m_LocalEulerAnglesHint: {x: 0, y: -90, z: 0}
--- !u!4 &907893356 stripped
Transform:
m_CorrespondingSourceObject: {fileID: 5278868739616043210, guid: 7731927cf621844b6aad08f3b4f9e278, type: 3}
m_PrefabInstance: {fileID: 1309657867}
m_PrefabAsset: {fileID: 0}
--- !u!1 &910000001
GameObject:
m_ObjectHideFlags: 0
@@ -4664,7 +4669,10 @@ PrefabInstance:
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
m_AddedGameObjects:
- targetCorrespondingSourceObject: {fileID: 4077927847297772526, guid: 46058ab5ebab64d548b4008f54df1f6d, type: 3}
insertIndex: -1
addedObject: {fileID: 907893356}
m_AddedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: 46058ab5ebab64d548b4008f54df1f6d, type: 3}
--- !u!1 &943453643
@@ -9535,8 +9543,7 @@ MonoBehaviour:
displayModulePrefab: {fileID: 9087040773434933035, guid: 1d13960332e6840458c2d63f616e2774, type: 3}
distanceSensorModulePrefab: {fileID: 4820635649742989172, guid: 71a1ec0b30283422eafea6e72a83d947, type: 3}
imuSensorModulePrefab: {fileID: 7670757311786442011, guid: 78f1dcd3d1ad14720bec24368d935b13, type: 3}
speakerModulePrefab: {fileID: 0}
powerModulePrefab: {fileID: 0}
speakerModulePrefab: {fileID: 7364806464279017071, guid: 8bcdcbcb5a03245248cb7f883e80d948, type: 3}
--- !u!4 &2021810912
Transform:
m_ObjectHideFlags: 0
@@ -10415,4 +10422,3 @@ SceneRoots:
- {fileID: 2031445727}
- {fileID: 859822641}
- {fileID: 1319424261}
- {fileID: 1309657867}

File diff suppressed because one or more lines are too long

View File

@@ -11,9 +11,7 @@ Material:
m_Shader: {fileID: 45, guid: 0000000000000000f000000000000000, type: 0}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ValidKeywords:
- _NORMALMAP
- _SPECGLOSSMAP
m_ValidKeywords: []
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 1

View File

@@ -11,9 +11,7 @@ Material:
m_Shader: {fileID: 45, guid: 0000000000000000f000000000000000, type: 0}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ValidKeywords:
- _NORMALMAP
- _SPECGLOSSMAP
m_ValidKeywords: []
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 1

View File

@@ -11,9 +11,7 @@ Material:
m_Shader: {fileID: 45, guid: 0000000000000000f000000000000000, type: 0}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ValidKeywords:
- _NORMALMAP
- _SPECGLOSSMAP
m_ValidKeywords: []
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 1

View File

@@ -11,9 +11,7 @@ Material:
m_Shader: {fileID: 45, guid: 0000000000000000f000000000000000, type: 0}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ValidKeywords:
- _NORMALMAP
- _SPECGLOSSMAP
m_ValidKeywords: []
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 1