Editor/widgets/ChangesWidget.cs
#nullable enable
using System;
using Editor;
using Sandbox.Diagnostics;
using Sandbox.git;
using Sandbox.git.models;

namespace Sandbox.widgets;

public class ChangesWidget : Widget {
	const float MinContentHeight = 120f;
	const string? NoRepoLabel = null;

	private readonly GitStore _store;
	private readonly ScrollArea _scroller;
	private readonly Label _statusLabel;
	private static readonly Logger Logger = new Logger("SandGit[ChangesWidget]");

	public ChangesWidget(Widget parent, GitStore store) : base(parent) {
		_store = store ?? throw new ArgumentNullException(nameof(store));

		Layout = Layout.Column();
		Layout.Spacing = 4f;

		var topRow = new Widget(this) { Layout = Layout.Row() };
		topRow.Layout.Spacing = 2f;
		_statusLabel = new Label("", topRow) { MinimumWidth = 0 };
		topRow.Layout.Add(_statusLabel);
		topRow.Layout.AddStretchCell();

		Layout.Add(topRow);

		_scroller = new ScrollArea(this);
		_scroller.MinimumHeight = MinContentHeight;
		Layout.Add(_scroller, 1); // stretch to fill remaining area

		_scroller.Canvas = new Widget(_scroller);
		_scroller.Canvas.Layout = Layout.Column();
		_scroller.Canvas.Layout.AddStretchCell();

		_store.OnDataChanged += UpdateFromStore;
		UpdateFromStore();
	}

	protected override void OnClosed() {
		_store.OnDataChanged -= UpdateFromStore;
		base.OnClosed();
	}

	void UpdateFromStore() {
		if ( !IsValid )
			return;
		if ( _store.IsLoading || _store.RepositoryType is not RegularRepositoryType ) {
			_statusLabel.Text = _store.IsLoading ? "Loading…" : NoRepoLabel;
			RebuildChangeRows(null);
			return;
		}

		var fullStatus = _store.FullStatus;
		if ( fullStatus == null ) {
			_statusLabel.Text = NoRepoLabel;
			RebuildChangeRows(null);
			return;
		}

		_statusLabel.Text = FormatStatusLine(fullStatus);
		RebuildChangeRows(fullStatus);
	}

	void RebuildChangeRows(FullStatusResult? fullStatus) {
		var canvas = new Widget(_scroller);
		canvas.Layout = Layout.Column();

		if ( fullStatus != null ) {
			var files = fullStatus.WorkingDirectory.Files;
			for ( var i = 0; i < files.Count; i++ ) {
				var f = files[i];
				var pathDisplay = f.OldPath != null ? f.OldPath + " → " + f.Path : f.Path;
				var statusStr = f.Kind.ToString().ToLowerInvariant();
				var row = new ChangeRow(pathDisplay, statusStr) { Index = i };
				canvas.Layout.Add(row);
			}
		}

		canvas.Layout.AddStretchCell();
		_scroller.Canvas = canvas;
	}

	static string FormatStatusLine(FullStatusResult fullStatus) {
		var files = fullStatus.WorkingDirectory.Files;
		return files.Count == 0 ? "No pending changes." : "Changes: (" + files.Count + ")";
	}
}

public class ChangeRow : Frame {
	readonly string _path;
	readonly string _status;
	public int Index { get; set; }

	public ChangeRow(string path, string status) : base(null) {
		_path = path ?? "";
		_status = status ?? "";
		MinimumSize = 24;
		Cursor = CursorShape.Finger;
	}

	protected override void OnPaint() {
		var r = new Rect(0, Size);
		var c = _status switch {
			"modified" => Theme.Yellow,
			"added" => Theme.Green,
			"deleted" => Theme.Red,
			_ => Theme.Blue,
		};
		if ( IsUnderMouse )
			c = c.Lighten(0.3f);

		Paint.ClearPen();
		Paint.SetBrush(c.Darken(0.8f + (Index & 1) * 0.02f).Desaturate(0.3f));
		Paint.DrawRect(r);

		r = r.Grow(-8f, 0);
		Paint.SetPen(c);
		var textSize = Paint.DrawText(r, _path, TextFlag.LeftCenter);
		r.Top = textSize.Bottom - 4;
		r = r.Shrink(0, 0, 0, 8);
		Paint.SetPen(c.Darken(0.3f));
		Paint.DrawText(r, "[" + _status + "]", TextFlag.LeftBottom);
	}
}