feat(desktop): add in-world OS terminal and desktop windows
This commit is contained in:
72
Assets/Scripts/Desktop/DesktopManager.cs
Normal file
72
Assets/Scripts/Desktop/DesktopManager.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
85
Assets/Scripts/Desktop/DesktopWindow.cs
Normal file
85
Assets/Scripts/Desktop/DesktopWindow.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
Assets/Scripts/Desktop/InWorldTerminal.cs
Normal file
30
Assets/Scripts/Desktop/InWorldTerminal.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
70
Assets/Scripts/Desktop/LogWindowController.cs
Normal file
70
Assets/Scripts/Desktop/LogWindowController.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user