Editor/BlenderBridge/BridgeStatusDot.cs

An editor UI widget that shows a small colored status dot reflecting the Blender bridge server state. It polls periodically on a background timer, requests a main-thread repaint, and updates color and tooltip based on BlenderBridgeServer.IsRunning, HasActiveClient, and Port.

Networking
using System;
using Editor;
using Sandbox;

namespace BlenderBridge
{
	/// <summary>
	/// Small colored dot that mirrors BlenderBridgeServer state.
	/// Red = stopped. Yellow = running, no client. Green = client connected.
	/// Designed to be embedded next to a title label; functionality (click, tooltip, popover) can grow on this class.
	/// </summary>
	public class BridgeStatusDot : Widget
	{
		private Label _dot;
		private System.Threading.Timer _pollTimer;
		private volatile bool _needsRefresh = true;

		private bool _lastRunning;
		private bool _lastClient;

		public BridgeStatusDot() : base( null )
		{
			ToolTip = "Bridge status";
			FixedWidth = 16;
			FixedHeight = 16;

			_dot = new Label( "●" );
			_dot.SetStyles( "color: #f87171; font-size: 14px;" );

			var row = Layout.Row();
			row.Add( _dot );
			Layout = row;

			_pollTimer = new System.Threading.Timer( _ => RequestRefresh(), null, 500, 500 );
		}

		/// <summary>
		/// Timer tick, on a threadpool thread. _needsRefresh is only consumed
		/// in OnPaint and nothing else ever requests a repaint, so an
		/// unattended dot would show stale state indefinitely. Hop to the main
		/// thread and ask for one explicitly.
		/// </summary>
		private void RequestRefresh()
		{
			_needsRefresh = true;
			GameTask.RunInThreadAsync( async () =>
			{
				await GameTask.MainThread();
				if ( IsValid )
					Update();
			} );
		}

		public override void OnDestroyed()
		{
			_pollTimer?.Dispose();
			_pollTimer = null;
			base.OnDestroyed();
		}

		protected override void OnPaint()
		{
			base.OnPaint();
			if ( !_needsRefresh ) return;
			_needsRefresh = false;
			Refresh();
		}

		private void Refresh()
		{
			if ( _dot == null ) return;

			var running = BlenderBridgeServer.IsRunning;
			var client = BlenderBridgeServer.HasActiveClient;

			if ( running == _lastRunning && client == _lastClient ) return;
			_lastRunning = running;
			_lastClient = client;

			string color;
			string tip;
			if ( !running )      { color = "#f87171"; tip = "Bridge: stopped"; }
			else if ( client )   { color = "#4ade80"; tip = $"Bridge: 1 client connected · port {BlenderBridgeServer.Port}"; }
			else                 { color = "#fbbf24"; tip = $"Bridge: idle · port {BlenderBridgeServer.Port}"; }

			_dot.SetStyles( $"color: {color}; font-size: 14px;" );
			ToolTip = tip;
		}
	}
}