feat(desktop): add in-world OS terminal and desktop windows

This commit is contained in:
Alexa Amundson
2025-11-25 15:46:54 -06:00
parent ffc2842bd0
commit 7226295d4b
4 changed files with 257 additions and 0 deletions

View File

@@ -0,0 +1,72 @@
using UnityEngine;
namespace BlackRoad.Worldbuilder.Desktop
{
/// <summary>
/// Controls the in-world "desktop": toggling visibility, focusing windows, etc.
/// For now it's mostly a container that can show/hide the entire OS.
/// </summary>
public class DesktopManager : MonoBehaviour
{
public static DesktopManager Instance { get; private set; }
[Header("Desktop Root")]
[SerializeField] private GameObject desktopRoot;
[Header("Windows")]
[SerializeField] private DesktopWindow[] windows;
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
if (desktopRoot != null)
desktopRoot.SetActive(false);
}
public void ToggleDesktop()
{
if (desktopRoot == null) return;
bool newState = !desktopRoot.activeSelf;
desktopRoot.SetActive(newState);
if (newState)
{
BringAnyWindowToFront();
}
}
public void ShowDesktop(bool visible)
{
if (desktopRoot == null) return;
desktopRoot.SetActive(visible);
if (visible)
{
BringAnyWindowToFront();
}
}
private void BringAnyWindowToFront()
{
if (windows == null || windows.Length == 0) return;
foreach (var w in windows)
{
if (w != null && w.gameObject.activeSelf)
{
w.transform.SetAsLastSibling();
return;
}
}
}
}
}

View File

@@ -0,0 +1,85 @@
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace BlackRoad.Worldbuilder.Desktop
{
/// <summary>
/// Simple draggable, closable window for the "desktop" UI.
/// Attach to the root Panel of a window and wire up title + close button.
/// </summary>
public class DesktopWindow : MonoBehaviour, IPointerDownHandler, IBeginDragHandler, IDragHandler
{
[Header("UI")]
[SerializeField] private RectTransform headerArea;
[SerializeField] private Button closeButton;
[SerializeField] private Text titleText;
private RectTransform _rect;
private Canvas _canvas;
private Vector2 _dragOffset;
public string Title
{
get => titleText != null ? titleText.text : name;
set { if (titleText != null) titleText.text = value; }
}
private void Awake()
{
_rect = GetComponent<RectTransform>();
_canvas = GetComponentInParent<Canvas>();
if (closeButton != null)
closeButton.onClick.AddListener(Close);
}
public void Close()
{
gameObject.SetActive(false);
}
public void OnPointerDown(PointerEventData eventData)
{
// Bring to front
transform.SetAsLastSibling();
}
public void OnBeginDrag(PointerEventData eventData)
{
if (!IsInHeader(eventData)) return;
RectTransformUtility.ScreenPointToLocalPointInRectangle(
_rect,
eventData.position,
eventData.pressEventCamera,
out _dragOffset);
}
public void OnDrag(PointerEventData eventData)
{
if (!IsInHeader(eventData)) return;
if (_canvas == null) return;
Vector2 localPoint;
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(
_canvas.transform as RectTransform,
eventData.position,
eventData.pressEventCamera,
out localPoint))
{
_rect.localPosition = localPoint - _dragOffset;
}
}
private bool IsInHeader(PointerEventData eventData)
{
if (headerArea == null) return true;
return RectTransformUtility.RectangleContainsScreenPoint(
headerArea,
eventData.position,
eventData.pressEventCamera);
}
}
}

View File

@@ -0,0 +1,30 @@
using UnityEngine;
using BlackRoad.Worldbuilder.Interaction;
namespace BlackRoad.Worldbuilder.Desktop
{
/// <summary>
/// In-world terminal object that toggles the DesktopManager OS UI when used.
/// Attach to a console mesh with a collider.
/// </summary>
public class InWorldTerminal : Interactable
{
private void Awake()
{
#if UNITY_EDITOR
var so = new UnityEditor.SerializedObject(this);
so.FindProperty("displayName").stringValue = "BlackRoad Console";
so.FindProperty("verb").stringValue = "Open";
so.ApplyModifiedPropertiesWithoutUndo();
#endif
}
public override void Interact(GameObject interactor)
{
if (DesktopManager.Instance != null)
{
DesktopManager.Instance.ToggleDesktop();
}
}
}
}

View File

@@ -0,0 +1,70 @@
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using BlackRoad.Worldbuilder.Archive;
namespace BlackRoad.Worldbuilder.Desktop
{
/// <summary>
/// Example app window that displays a rolling log of system info.
/// </summary>
public class LogWindowController : MonoBehaviour
{
[SerializeField] private Text logText;
[SerializeField] private int maxLines = 100;
[SerializeField] private float updateInterval = 2f;
private float _timer;
private readonly StringBuilder _sb = new StringBuilder();
private void OnEnable()
{
_sb.Clear();
AppendLine("[System Log] Session started.");
}
private void Update()
{
_timer += Time.deltaTime;
if (_timer >= updateInterval)
{
_timer = 0f;
AppendStatusLine();
}
}
private void AppendStatusLine()
{
var stats = WorldStatsTracker.Instance;
if (stats == null) return;
string time = stats.GetTimeSummary();
string pop = stats.GetPopulationSummary();
string cosmos = stats.GetCosmosSummary();
AppendLine($"{time} | {pop} | {cosmos}");
}
private void AppendLine(string line)
{
_sb.AppendLine(line);
// trim lines
var text = _sb.ToString();
var lines = text.Split('\n');
if (lines.Length > maxLines)
{
int start = lines.Length - maxLines;
_sb.Clear();
for (int i = start; i < lines.Length; i++)
{
if (!string.IsNullOrEmpty(lines[i]))
_sb.AppendLine(lines[i]);
}
}
if (logText != null)
logText.text = _sb.ToString();
}
}
}