Repository navigation
Expand file tree
/
Copy pathHelpUI.cs
More file actions
337 lines (309 loc) · 13.1 KB
/
Copy pathHelpUI.cs
File metadata and controls
337 lines (309 loc) · 13.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
using FastbootEnhance.Core.Help;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
namespace FastbootEnhance
{
/// <summary>
/// The ؟ in the corner of every page and the panel it opens at the side: what the page is
/// for, its steps, what can go wrong for good, and the words it uses. The text is in
/// Help\<language>\<page>.md, inside the exe; languages without their own files get English.
/// </summary>
static class HelpUI
{
/// <summary>Where questions go: the Telegram group.</summary>
const string AskUrl = "https://t.me/BeNeXTBrO";
static MainWindow W => MainWindow.THIS;
static readonly Dictionary<TabItem, string> pages = new Dictionary<TabItem, string>();
static readonly Dictionary<string, HelpDoc> loaded = new Dictionary<string, HelpDoc>(StringComparer.Ordinal);
static TabItem shownFor;
public static bool IsOpen => W.help_layer.Visibility == Visibility.Visible;
public static void init()
{
foreach (TabItem tab in W.main_tabs.Items.OfType<TabItem>())
{
string page = tab.Name.EndsWith("_tab", StringComparison.Ordinal) ? tab.Name.Substring(0, tab.Name.Length - 4) : tab.Name;
pages[tab] = page;
Button button = page == "about" ? W.about_help : addButton(tab);
if (button == null)
continue;
button.Content = Languages.RightToLeft ? "؟" : "?";
button.ToolTip = Properties.Resources.help_tooltip;
AutomationProperties.SetAutomationId(button, "help_" + page);
AutomationProperties.SetName(button, Properties.Resources.help_tooltip);
TabItem target = tab;
button.Click += delegate
{
if (IsOpen && shownFor == target)
close();
else
open(target);
};
}
W.help_close.Click += delegate { close(); };
W.help_scrim.MouseLeftButtonDown += delegate { close(); };
W.help_ask.Click += delegate { openLink(AskUrl); };
// Another page while the help is open: its own help follows.
W.main_tabs.SelectionChanged += (sender, e) =>
{
if (e.OriginalSource == W.main_tabs && IsOpen && W.main_tabs.SelectedItem is TabItem tab && tab != shownFor)
open(tab);
};
}
/// <summary>Esc closes the panel; true when it was open.</summary>
public static bool HandleKey(KeyEventArgs e)
{
if (e.Key != Key.Escape || !IsOpen)
return false;
close();
e.Handled = true;
return true;
}
/// <summary>A ؟ at the outer end of the page's header, after anything the page shows there.</summary>
static Button addButton(TabItem tab)
{
Border header = pageHeader(tab);
if (!(header?.Child is DockPanel dock))
return null;
Button button = new Button
{
Style = (Style)W.FindResource("HelpButton"),
Margin = new Thickness(12, 0, 0, 0),
};
DockPanel.SetDock(button, Dock.Right);
// The first child docked right takes the outer edge.
dock.Children.Insert(0, button);
return button;
}
static Border pageHeader(TabItem tab)
{
Style style = (Style)W.FindResource("PageHeader");
return (tab.Content as Panel)?.Children.OfType<Border>().FirstOrDefault(b => b.Style == style);
}
static void open(TabItem tab)
{
string page;
if (!pages.TryGetValue(tab, out page))
return;
if (W.main_tabs.SelectedItem != tab)
W.main_tabs.SelectedItem = tab;
HelpDoc doc = load(page);
W.help_title.Text = tab.Header as string ?? page;
W.help_body.Children.Clear();
if (doc == null)
W.help_body.Children.Add(paragraph(Properties.Resources.help_missing, "Dim"));
else
render(doc);
W.help_scroll.ScrollToTop();
shownFor = tab;
W.help_layer.Visibility = Visibility.Visible;
placeUnderHeader(tab);
// A page shown for the first time has no size yet while its tab is being selected:
// placed again once it is laid out.
W.Dispatcher.BeginInvoke(new Action(() => { if (shownFor == tab) placeUnderHeader(tab); }),
System.Windows.Threading.DispatcherPriority.Loaded);
LogStore.Append("help: " + page);
}
/// <summary>Under the page's header, so its ؟ stays in sight to close the panel again.</summary>
static void placeUnderHeader(TabItem tab)
{
Border header = pageHeader(tab);
double top = header != null && header.IsLoaded ? header.ActualHeight : 0;
W.help_scrim.Margin = new Thickness(0, top, 0, 0);
W.help_panel.Margin = new Thickness(0, top, 0, 0);
}
static void close()
{
W.help_layer.Visibility = Visibility.Collapsed;
shownFor = null;
}
/// <summary>The page's help in the app's language, else in English; null when there is none.</summary>
static HelpDoc load(string page)
{
string language = Languages.Current.TwoLetterISOLanguageName == "ar" ? "ar" : "en";
string key = language + "/" + page;
HelpDoc doc;
if (loaded.TryGetValue(key, out doc))
return doc;
string text = read("Help." + language + "." + page + ".md") ?? read("Help.en." + page + ".md");
doc = text == null ? null : HelpDoc.Parse(text);
loaded[key] = doc;
return doc;
}
static string read(string resource)
{
using (Stream stream = typeof(HelpUI).Assembly.GetManifestResourceStream(resource))
{
if (stream == null)
return null;
using (StreamReader reader = new StreamReader(stream))
return reader.ReadToEnd();
}
}
// ------------------------------------------------------------------ drawing
static void render(HelpDoc doc)
{
foreach (HelpSection section in doc.Sections)
{
if (section.Items.Count == 0)
continue;
if (section.Kind == HelpSectionKind.Danger)
{
StackPanel inside = new StackPanel();
inside.Children.Add(heading("⚠ " + Properties.Resources.help_danger, "Danger"));
addItems(inside, section);
W.help_body.Children.Add(new Border
{
Background = Palette.Get("DangerSoft"),
BorderBrush = Palette.Get("DangerLine"),
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(10),
Padding = new Thickness(14, 12, 14, 8),
Margin = new Thickness(0, 2, 0, 16),
Child = inside,
});
continue;
}
StackPanel block = new StackPanel { Margin = new Thickness(0, 0, 0, 16) };
block.Children.Add(heading(title(section.Kind), null));
addItems(block, section);
W.help_body.Children.Add(block);
}
}
static string title(HelpSectionKind kind)
{
switch (kind)
{
case HelpSectionKind.Purpose: return Properties.Resources.help_purpose;
case HelpSectionKind.Steps: return Properties.Resources.help_steps;
case HelpSectionKind.Tips: return Properties.Resources.help_tips;
case HelpSectionKind.Terms: return Properties.Resources.help_terms;
default: return Properties.Resources.help_danger;
}
}
static void addItems(Panel into, HelpSection section)
{
bool danger = section.Kind == HelpSectionKind.Danger;
int number = 0;
foreach (HelpItem item in section.Items)
{
switch (item.Kind)
{
case HelpItemKind.Numbered:
number++;
into.Children.Add(marked(number + ".", item.Text, danger));
break;
case HelpItemKind.Bullet:
into.Children.Add(marked("•", item.Text, danger));
break;
case HelpItemKind.Term:
into.Children.Add(term(item.Term, item.Text));
break;
default:
TextBlock p = paragraph(item.Text, danger ? "Text" : null);
p.Margin = new Thickness(0, 0, 0, 6);
into.Children.Add(p);
break;
}
}
}
static FrameworkElement heading(string text, string color)
{
DockPanel row = new DockPanel { Margin = new Thickness(0, 0, 0, 7) };
if (color == null)
{
Ellipse dot = new Ellipse { Width = 7, Height = 7, Fill = Palette.Get("Accent"), Margin = new Thickness(0, 0, 9, 0), VerticalAlignment = VerticalAlignment.Center };
DockPanel.SetDock(dot, Dock.Left);
row.Children.Add(dot);
}
row.Children.Add(new TextBlock
{
Text = text,
FontSize = 14,
FontWeight = FontWeights.SemiBold,
Foreground = Palette.Get(color ?? "Text"),
TextWrapping = TextWrapping.Wrap,
});
return row;
}
/// <summary>A step or bullet: its mark in a narrow column, the text wrapping beside it.</summary>
static FrameworkElement marked(string mark, string text, bool danger)
{
Grid row = new Grid { Margin = new Thickness(0, 0, 0, 5) };
row.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(22) });
row.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
TextBlock m = new TextBlock
{
Text = mark,
FontWeight = FontWeights.Bold,
Foreground = Palette.Get(danger ? "Danger" : "Accent"),
FontSize = 12.5,
LineHeight = 20,
};
TextBlock t = paragraph(text, null);
Grid.SetColumn(t, 1);
row.Children.Add(m);
row.Children.Add(t);
return row;
}
static FrameworkElement term(string word, string meaning)
{
Grid row = new Grid { Margin = new Thickness(0, 0, 0, 5) };
row.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(96) });
row.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
TextBlock w = new TextBlock
{
Text = word,
FontWeight = FontWeights.SemiBold,
Foreground = Palette.Get("Accent"),
FontSize = 12.5,
LineHeight = 20,
TextWrapping = TextWrapping.Wrap,
Margin = new Thickness(0, 0, 8, 0),
};
TextBlock t = paragraph(meaning, null);
Grid.SetColumn(t, 1);
row.Children.Add(w);
row.Children.Add(t);
return row;
}
/// <summary>Wrapped text with its **bold** parts in bold.</summary>
static TextBlock paragraph(string text, string color)
{
TextBlock block = new TextBlock
{
TextWrapping = TextWrapping.Wrap,
FontSize = 12.5,
LineHeight = 20,
Foreground = Palette.Get(color ?? "Text"),
};
foreach (KeyValuePair<string, bool> span in HelpDoc.Spans(text))
{
Run run = new Run(span.Key);
if (span.Value)
run.FontWeight = FontWeights.SemiBold;
block.Inlines.Add(run);
}
return block;
}
static void openLink(string url)
{
try
{
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(url) { UseShellExecute = true });
}
catch (Exception e)
{
ThemedDialog.Show(e.Message);
}
}
}
}