Skip to content

Commit d240593

Browse files
levinli303claude
andcommitted
Make start/end time in body overview clickable links to celestia://settime
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d2a6b2e commit d240593

4 files changed

Lines changed: 97 additions & 11 deletions

File tree

CelestiaAppComponent/SelectionHelper.cpp

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,43 +110,61 @@ namespace winrt::CelestiaAppComponent::implementation
110110
lines.push_back(to_string(LocalizationHelper::Localize(L"Has atmosphere", L"Indicate that an object has atmosphere")));
111111
}
112112

113+
return JoinLines(lines);
114+
}
115+
116+
std::vector<CelestiaAppComponent::OverviewTimeLink> GetBodyTimeLinks(CelestiaBody const& body, CelestiaAppCore const&)
117+
{
118+
std::vector<CelestiaAppComponent::OverviewTimeLink> results;
113119
auto timeline{ body.Timeline() };
114120
if (timeline.PhaseCount() > 0)
115121
{
116122
using namespace Windows::Globalization::DateTimeFormatting;
117123
DateTimeFormatter dateFormatter{ L"shortdate shorttime" };
124+
DecimalFormatter numberFormatter;
125+
numberFormatter.FractionDigits(0);
126+
numberFormatter.IsGrouped(true);
118127

119128
auto startJulianDay = timeline.PhaseAtIndex(0).StartJulianDay();
120129
auto endJulianDay = timeline.PhaseAtIndex(0).EndJulianDay();
121130
if (!std::isinf(startJulianDay))
122131
{
132+
hstring label;
133+
hstring timeString;
123134
if (startJulianDay < CelestiaHelper::MinRepresentableJulianDay() || startJulianDay > CelestiaHelper::MaxRepresentableJulianDay())
124135
{
125-
startJulianDay = std::round(startJulianDay * 10000.0) / 10000.0;
126-
lines.push_back(fmt::sprintf(to_string(LocalizationHelper::Localize(L"Start Julian day: %s", L"Template for displaying when start time cannot be correctly formatted by the system")), to_string(numberFormatter.FormatDouble(startJulianDay))));
136+
auto rounded = std::round(startJulianDay * 10000.0) / 10000.0;
137+
timeString = numberFormatter.FormatDouble(rounded);
138+
label = to_hstring(fmt::sprintf(to_string(LocalizationHelper::Localize(L"Start Julian day: %s", L"Template for displaying when start time cannot be correctly formatted by the system")), to_string(timeString)));
127139
}
128140
else
129141
{
130142
auto startTime = CelestiaHelper::DateTimeFromJulianDay(startJulianDay);
131-
lines.push_back(fmt::sprintf(to_string(LocalizationHelper::Localize(L"Start time: %s", L"Template for the start time of a body, usually a spacecraft")), to_string(dateFormatter.Format(startTime))));
143+
timeString = dateFormatter.Format(startTime);
144+
label = to_hstring(fmt::sprintf(to_string(LocalizationHelper::Localize(L"Start time: %s", L"Template for the start time of a body, usually a spacecraft")), to_string(timeString)));
132145
}
146+
results.push_back({ label, timeString, L"celestia://settime?julianDay=" + to_hstring(startJulianDay) });
133147
}
134148
if (!std::isinf(endJulianDay))
135149
{
150+
hstring label;
151+
hstring timeString;
136152
if (endJulianDay < CelestiaHelper::MinRepresentableJulianDay() || endJulianDay > CelestiaHelper::MaxRepresentableJulianDay())
137153
{
138-
endJulianDay = std::round(endJulianDay * 10000.0) / 10000.0;
139-
lines.push_back(fmt::sprintf(to_string(LocalizationHelper::Localize(L"End Julian day: %s", L"Template for displaying when end time cannot be correctly formatted by the system")), to_string(numberFormatter.FormatDouble(endJulianDay))));
154+
auto rounded = std::round(endJulianDay * 10000.0) / 10000.0;
155+
timeString = numberFormatter.FormatDouble(rounded);
156+
label = to_hstring(fmt::sprintf(to_string(LocalizationHelper::Localize(L"End Julian day: %s", L"Template for displaying when end time cannot be correctly formatted by the system")), to_string(timeString)));
140157
}
141158
else
142159
{
143160
auto endTime = CelestiaHelper::DateTimeFromJulianDay(endJulianDay);
144-
lines.push_back(fmt::sprintf(to_string(LocalizationHelper::Localize(L"End time: %s", L"Template for the end time of a body, usually a spacecraft")), to_string(dateFormatter.Format(endTime))));
161+
timeString = dateFormatter.Format(endTime);
162+
label = to_hstring(fmt::sprintf(to_string(LocalizationHelper::Localize(L"End time: %s", L"Template for the end time of a body, usually a spacecraft")), to_string(timeString)));
145163
}
164+
results.push_back({ label, timeString, L"celestia://settime?julianDay=" + to_hstring(endJulianDay) });
146165
}
147166
}
148-
149-
return JoinLines(lines);
167+
return results;
150168
}
151169

152170
hstring GetStarOverview(CelestiaStar const& star, CelestiaAppCore const& appCore)
@@ -233,4 +251,17 @@ namespace winrt::CelestiaAppComponent::implementation
233251
}
234252
return LocalizationHelper::Localize(L"No overview available.", L"No overview for an object");
235253
}
254+
255+
com::array<CelestiaAppComponent::OverviewTimeLink> SelectionHelper::GetTimeLinks(CelestiaSelection const& selection, CelestiaAppCore const& appCore)
256+
{
257+
auto obj = selection.Object();
258+
if (obj == nullptr) return {};
259+
auto body = obj.try_as<CelestiaBody>();
260+
if (body != nullptr)
261+
{
262+
auto links = GetBodyTimeLinks(body, appCore);
263+
return com::array<CelestiaAppComponent::OverviewTimeLink>(links);
264+
}
265+
return {};
266+
}
236267
}

CelestiaAppComponent/SelectionHelper.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ namespace winrt::CelestiaAppComponent::implementation
1717
struct SelectionHelper : SelectionHelperT<SelectionHelper>
1818
{
1919
static hstring GetOverview(CelestiaComponent::CelestiaSelection const& selection, CelestiaComponent::CelestiaAppCore const& appCore);
20+
static com::array<CelestiaAppComponent::OverviewTimeLink> GetTimeLinks(CelestiaComponent::CelestiaSelection const& selection, CelestiaComponent::CelestiaAppCore const& appCore);
2021
};
2122
}
2223

CelestiaAppComponent/SelectionHelper.idl

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,17 @@
1010

1111
namespace CelestiaAppComponent
1212
{
13+
struct OverviewTimeLink
14+
{
15+
String Label;
16+
String TimeString;
17+
String URL;
18+
};
19+
1320
[default_interface]
1421
runtimeclass SelectionHelper
1522
{
1623
static String GetOverview(CelestiaComponent.CelestiaSelection selection, CelestiaComponent.CelestiaAppCore appCore);
17-
}
24+
static OverviewTimeLink[] GetTimeLinks(CelestiaComponent.CelestiaSelection selection, CelestiaComponent.CelestiaAppCore appCore);
25+
};
1826
}

CelestiaWinUI/InfoUserControl.xaml.cpp

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,57 @@ namespace winrt::CelestiaWinUI::implementation
6060
if (strong_this == nullptr) return;
6161
auto name = strong_this->appCore.Simulation().Universe().NameForSelection(strong_this->selection);
6262
auto detail = SelectionHelper::GetOverview(strong_this->selection, strong_this->appCore);
63+
auto timeLinks = SelectionHelper::GetTimeLinks(strong_this->selection, strong_this->appCore);
6364
auto url = strong_this->selection.InfoURL();
64-
strong_this->DispatcherQueue().TryEnqueue(Microsoft::UI::Dispatching::DispatcherQueuePriority::Normal, [strong_this, name, detail, url]()
65+
strong_this->DispatcherQueue().TryEnqueue(Microsoft::UI::Dispatching::DispatcherQueuePriority::Normal, [strong_this, name, detail, timeLinks, url]()
6566
{
6667
strong_this->NameLabel().Text(name);
67-
strong_this->DetailLabel().Text(detail);
68+
strong_this->DetailLabel().Inlines().Clear();
69+
if (!detail.empty())
70+
{
71+
Microsoft::UI::Xaml::Documents::Run detailRun;
72+
detailRun.Text(detail);
73+
strong_this->DetailLabel().Inlines().Append(detailRun);
74+
}
75+
for (auto const& link : timeLinks)
76+
{
77+
if (strong_this->DetailLabel().Inlines().Size() > 0)
78+
{
79+
Microsoft::UI::Xaml::Documents::Run newline;
80+
newline.Text(L"\n");
81+
strong_this->DetailLabel().Inlines().Append(newline);
82+
}
83+
auto label = std::wstring_view(link.Label);
84+
auto timeStr = std::wstring_view(link.TimeString);
85+
auto pos = label.find(timeStr);
86+
if (pos != std::wstring_view::npos)
87+
{
88+
Microsoft::UI::Xaml::Documents::Run prefixRun;
89+
prefixRun.Text(hstring(label.substr(0, pos)));
90+
strong_this->DetailLabel().Inlines().Append(prefixRun);
91+
92+
Microsoft::UI::Xaml::Documents::Hyperlink hyperlink;
93+
hyperlink.NavigateUri(Uri(link.URL));
94+
Microsoft::UI::Xaml::Documents::Run linkRun;
95+
linkRun.Text(hstring(timeStr));
96+
hyperlink.Inlines().Append(linkRun);
97+
strong_this->DetailLabel().Inlines().Append(hyperlink);
98+
99+
auto suffix = label.substr(pos + timeStr.size());
100+
if (!suffix.empty())
101+
{
102+
Microsoft::UI::Xaml::Documents::Run suffixRun;
103+
suffixRun.Text(hstring(suffix));
104+
strong_this->DetailLabel().Inlines().Append(suffixRun);
105+
}
106+
}
107+
else
108+
{
109+
Microsoft::UI::Xaml::Documents::Run fallbackRun;
110+
fallbackRun.Text(link.Label);
111+
strong_this->DetailLabel().Inlines().Append(fallbackRun);
112+
}
113+
}
68114
if (!url.empty())
69115
{
70116
strong_this->LinkButton().NavigateUri(Uri(url));

0 commit comments

Comments
 (0)