Skip to content

Commit 83c6f66

Browse files
sebgodclaude
andcommitted
Add tianwen-gui: N.I.N.A.-style integrated GUI with Planner tab
New TianWen.UI.Gui project using SDL3+Vulkan with left sidebar navigation: - Left sidebar (P/V/S/E) with active tab highlighting and hover states - Top status bar with profile name, night window times, and clock - Planner tab: altitude chart (AltitudeChartRenderer), scrollable target list with proposed/selected highlighting, details panel - Keyboard: ↑↓ navigate, Enter toggle proposal, P priority, S schedule - Mouse: sidebar tab switching, target list click selection, scroll wheel Also extracts TransformFactory from CLI's LocationResolver into Abstractions so both CLI and GUI can create Transform from profile mount URI. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4b96889 commit 83c6f66

File tree

8 files changed

+1116
-36
lines changed

8 files changed

+1116
-36
lines changed
Lines changed: 6 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,24 @@
1-
using System.Globalization;
2-
using System.Web;
31
using TianWen.Lib.Astrometry.SOFA;
42
using TianWen.Lib.Devices;
3+
using TianWen.UI.Abstractions;
54

65
namespace TianWen.Lib.CLI.Plan;
76

87
/// <summary>
9-
/// Resolves site latitude/longitude/elevation from the active profile's mount URI.
8+
/// CLI wrapper around <see cref="TransformFactory"/> that reports errors via <see cref="IConsoleHost"/>.
109
/// </summary>
1110
internal static class LocationResolver
1211
{
13-
/// <summary>
14-
/// Creates a <see cref="Transform"/> configured with the site location from the profile's mount URI.
15-
/// </summary>
1612
public static Transform? ResolveFromProfile(
1713
IConsoleHost consoleHost,
1814
Profile profile,
1915
TimeProvider timeProvider)
2016
{
21-
var data = profile.Data;
22-
if (data is null || data.Value.Mount == NoneDevice.Instance.DeviceUri)
17+
var transform = TransformFactory.FromProfile(profile, timeProvider, out var error);
18+
if (error is not null)
2319
{
24-
consoleHost.WriteError("Profile has no mount configured. Use 'tianwen profile set-mount' first.");
25-
return null;
20+
consoleHost.WriteError(error);
2621
}
27-
28-
var query = HttpUtility.ParseQueryString(data.Value.Mount.Query);
29-
var latStr = query[DeviceQueryKey.Latitude.Key];
30-
var lonStr = query[DeviceQueryKey.Longitude.Key];
31-
var elevStr = query[DeviceQueryKey.Elevation.Key];
32-
33-
if (latStr is null || lonStr is null ||
34-
!double.TryParse(latStr, CultureInfo.InvariantCulture, out var lat) ||
35-
!double.TryParse(lonStr, CultureInfo.InvariantCulture, out var lon))
36-
{
37-
consoleHost.WriteError("Mount URI does not contain latitude/longitude. Use 'tianwen profile set-site' to configure.");
38-
return null;
39-
}
40-
41-
var elevation = elevStr is not null && double.TryParse(elevStr, CultureInfo.InvariantCulture, out var elev)
42-
? elev
43-
: 0.0;
44-
45-
return new Transform(timeProvider)
46-
{
47-
SiteLatitude = lat,
48-
SiteLongitude = lon,
49-
SiteElevation = elevation,
50-
SiteTemperature = 15,
51-
DateTimeOffset = timeProvider.GetLocalNow()
52-
};
22+
return transform;
5323
}
5424
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using System.Globalization;
3+
using System.Web;
4+
using TianWen.Lib.Astrometry.SOFA;
5+
using TianWen.Lib.Devices;
6+
7+
namespace TianWen.UI.Abstractions;
8+
9+
/// <summary>
10+
/// Creates a <see cref="Transform"/> from a profile's mount URI (latitude, longitude, elevation).
11+
/// Shared between CLI and GUI.
12+
/// </summary>
13+
public static class TransformFactory
14+
{
15+
/// <summary>
16+
/// Creates a Transform from the profile's mount URI query params.
17+
/// Returns null with an error message if the profile has no mount or no lat/lon.
18+
/// </summary>
19+
public static Transform? FromProfile(Profile profile, TimeProvider timeProvider, out string? error)
20+
{
21+
error = null;
22+
var data = profile.Data;
23+
if (data is null || data.Value.Mount == NoneDevice.Instance.DeviceUri)
24+
{
25+
error = "Profile has no mount configured. Use 'tianwen profile set-mount' first.";
26+
return null;
27+
}
28+
29+
var query = HttpUtility.ParseQueryString(data.Value.Mount.Query);
30+
var latStr = query[DeviceQueryKey.Latitude.Key];
31+
var lonStr = query[DeviceQueryKey.Longitude.Key];
32+
var elevStr = query[DeviceQueryKey.Elevation.Key];
33+
34+
if (latStr is null || lonStr is null ||
35+
!double.TryParse(latStr, CultureInfo.InvariantCulture, out var lat) ||
36+
!double.TryParse(lonStr, CultureInfo.InvariantCulture, out var lon))
37+
{
38+
error = "Mount URI does not contain latitude/longitude. Use 'tianwen profile set-site' to configure.";
39+
return null;
40+
}
41+
42+
var elevation = elevStr is not null && double.TryParse(elevStr, CultureInfo.InvariantCulture, out var elev)
43+
? elev
44+
: 0.0;
45+
46+
return new Transform(timeProvider)
47+
{
48+
SiteLatitude = lat,
49+
SiteLongitude = lon,
50+
SiteElevation = elevation,
51+
SiteTemperature = 15,
52+
DateTimeOffset = timeProvider.GetLocalNow()
53+
};
54+
}
55+
}

src/TianWen.UI.Gui/GuiAppState.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using TianWen.Lib.Devices;
2+
3+
namespace TianWen.UI.Gui;
4+
5+
public enum GuiTab
6+
{
7+
Planner,
8+
Viewer,
9+
Session,
10+
Equipment
11+
}
12+
13+
public class GuiAppState
14+
{
15+
public GuiTab ActiveTab { get; set; } = GuiTab.Planner;
16+
public Profile? ActiveProfile { get; set; }
17+
public bool NeedsRedraw { get; set; } = true;
18+
public (float X, float Y) MouseScreenPosition { get; set; }
19+
public string? StatusMessage { get; set; }
20+
}

0 commit comments

Comments
 (0)