Skip to content

Commit 44a9983

Browse files
committed
Initial work on tutorial wizard, not ready for general use yet. See #950.
1 parent 8f6c12a commit 44a9983

15 files changed

Lines changed: 464 additions & 16 deletions

src/ngscopeclient/AddInstrumentDialog.cpp

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* *
33
* ngscopeclient *
44
* *
5-
* Copyright (c) 2012-2024 Andrew D. Zonenberg *
5+
* Copyright (c) 2012-2026 Andrew D. Zonenberg and contributors *
66
* All rights reserved. *
77
* *
88
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the *
@@ -35,6 +35,7 @@
3535

3636
#include "ngscopeclient.h"
3737
#include "AddInstrumentDialog.h"
38+
#include "MainWindow.h"
3839

3940
using namespace std;
4041

@@ -44,17 +45,22 @@ using namespace std;
4445
AddInstrumentDialog::AddInstrumentDialog(
4546
const string& title,
4647
const string& nickname,
47-
Session& session,
48+
Session* session,
49+
MainWindow* parent,
4850
const string& driverType)
49-
: Dialog(title, string("AddInstrument") + to_string_hex(reinterpret_cast<uintptr_t>(this)), ImVec2(600, 150))
50-
, m_session(session)
51+
: Dialog(
52+
title,
53+
string("AddInstrument") + to_string_hex(reinterpret_cast<uintptr_t>(this)),
54+
ImVec2(600, 150),
55+
session,
56+
parent)
5157
, m_nickname(nickname)
5258
, m_selectedDriver(0)
5359
, m_selectedTransport(0)
5460
{
5561
SCPITransport::EnumTransports(m_transports);
5662

57-
m_drivers = session.GetDriverNamesForType(driverType);
63+
m_drivers = session->GetDriverNamesForType(driverType);
5864
}
5965

6066
AddInstrumentDialog::~AddInstrumentDialog()
@@ -72,13 +78,16 @@ AddInstrumentDialog::~AddInstrumentDialog()
7278
*/
7379
bool AddInstrumentDialog::DoRender()
7480
{
81+
auto tutorial = m_parent->GetTutorialWizard();
82+
7583
ImGui::InputText("Nickname", &m_nickname);
7684
HelpMarker(
7785
"Text nickname for this instrument so you can distinguish between multiple similar devices.\n"
7886
"\n"
7987
"This is shown on the list of recent instruments, to disambiguate channel names in multi-instrument setups, etc.");
8088

81-
Combo("Driver", m_drivers, m_selectedDriver);
89+
bool dropdownOpen = false;
90+
Combo("Driver", m_drivers, m_selectedDriver, &dropdownOpen);
8291
HelpMarker(
8392
"Select the instrument driver to use.\n"
8493
"\n"
@@ -88,7 +97,22 @@ bool AddInstrumentDialog::DoRender()
8897
"\n"
8998
"Check the user manual for details of what driver to use with a given instrument.");
9099

91-
Combo("Transport", m_transports, m_selectedTransport);
100+
//Show speech bubble for tutorial
101+
bool showedBubble = false;
102+
if(tutorial &&
103+
(tutorial->GetCurrentStep() == TutorialWizard::TUTORIAL_02_CONNECT) &&
104+
(m_drivers[m_selectedDriver] != "demo") &&
105+
!dropdownOpen )
106+
{
107+
auto pos = ImGui::GetCursorScreenPos();
108+
ImVec2 anchorPos(pos.x + 10*ImGui::GetFontSize(), pos.y);
109+
tutorial->DrawSpeechBubble(anchorPos, ImGuiDir_Up, "Select the \"demo\" driver");
110+
showedBubble = true;
111+
}
112+
else if(dropdownOpen) //suppress further bubbles if dropdown is active
113+
showedBubble = true;
114+
115+
Combo("Transport", m_transports, m_selectedTransport, &dropdownOpen);
92116
HelpMarker(
93117
"Select the SCPI transport for the connection between your computer and the instrument.\n"
94118
"\n"
@@ -104,6 +128,21 @@ bool AddInstrumentDialog::DoRender()
104128
}
105129
);
106130

131+
//Show speech bubble for tutorial
132+
if(tutorial &&
133+
(tutorial->GetCurrentStep() == TutorialWizard::TUTORIAL_02_CONNECT) &&
134+
(m_transports[m_selectedTransport] != "null") &&
135+
!dropdownOpen &&
136+
!showedBubble)
137+
{
138+
auto pos = ImGui::GetCursorScreenPos();
139+
ImVec2 anchorPos(pos.x + 10*ImGui::GetFontSize(), pos.y);
140+
tutorial->DrawSpeechBubble(anchorPos, ImGuiDir_Up, "Select the \"null\" transport");
141+
showedBubble = true;
142+
}
143+
else if(dropdownOpen) //suppress further bubbles if dropdown is active
144+
showedBubble = true;
145+
107146
ImGui::InputText("Path", &m_path);
108147
HelpMarker(
109148
"Transport-specific description of how to connect to the instrument.\n",
@@ -170,6 +209,6 @@ SCPITransport* AddInstrumentDialog::MakeTransport()
170209

171210
bool AddInstrumentDialog::DoConnect(SCPITransport* transport)
172211
{
173-
m_session.CreateAndAddInstrument(m_drivers[m_selectedDriver], transport, m_nickname);
212+
m_session->CreateAndAddInstrument(m_drivers[m_selectedDriver], transport, m_nickname);
174213
return true;
175214
}

src/ngscopeclient/AddInstrumentDialog.h

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* *
33
* ngscopeclient *
44
* *
5-
* Copyright (c) 2012-2024 Andrew D. Zonenberg *
5+
* Copyright (c) 2012-2026 Andrew D. Zonenberg and contributors *
66
* All rights reserved. *
77
* *
88
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the *
@@ -44,7 +44,8 @@ class AddInstrumentDialog : public Dialog
4444
AddInstrumentDialog(
4545
const std::string& title,
4646
const std::string& nickname,
47-
Session& session,
47+
Session* session,
48+
MainWindow* parent,
4849
const std::string& driverType);
4950
virtual ~AddInstrumentDialog();
5051

@@ -55,8 +56,6 @@ class AddInstrumentDialog : public Dialog
5556

5657
virtual bool DoConnect(SCPITransport* transport);
5758

58-
Session& m_session;
59-
6059
//GUI widget values
6160
std::string m_nickname;
6261
int m_selectedDriver;

src/ngscopeclient/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ add_executable(ngscopeclient
108108
TextureManager.cpp
109109
TriggerGroup.cpp
110110
TriggerPropertiesDialog.cpp
111+
TutorialWizard.cpp
111112
VulkanWindow.cpp
112113
WaveformArea.cpp
113114
WaveformGroup.cpp

src/ngscopeclient/Dialog.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,10 @@ void Dialog::RenderErrorPopup()
136136

137137
/**
138138
@brief Displays a combo box from a vector<string>
139+
140+
@param open optional boolean indicating the dropdown is open
139141
*/
140-
bool Dialog::Combo(const string& label, const vector<string>& items, int& selection)
142+
bool Dialog::Combo(const string& label, const vector<string>& items, int& selection, bool* open)
141143
{
142144
string preview;
143145
ImGuiComboFlags flags = 0;
@@ -155,6 +157,9 @@ bool Dialog::Combo(const string& label, const vector<string>& items, int& select
155157
//Render the box
156158
if(ImGui::BeginCombo(label.c_str(), preview.c_str(), flags))
157159
{
160+
if(open)
161+
*open = true;
162+
158163
for(int i=0; i<(int)items.size(); i++)
159164
{
160165
bool selected = (i == selection);
@@ -168,6 +173,8 @@ bool Dialog::Combo(const string& label, const vector<string>& items, int& select
168173
}
169174
ImGui::EndCombo();
170175
}
176+
else if(open)
177+
*open = false;
171178
return changed;
172179
}
173180

src/ngscopeclient/Dialog.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ class Dialog
5959
{ return m_title + "###" + m_id; }
6060

6161
//TODO: this might be better off as a global method?
62-
static bool Combo(const std::string& label, const std::vector<std::string>& items, int& selection);
62+
static bool Combo(const std::string& label, const std::vector<std::string>& items, int& selection, bool* open = nullptr);
6363
static bool UnitInputWithImplicitApply(
6464
const std::string& label,
6565
std::string& currentValue,

src/ngscopeclient/MainWindow.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,13 @@ void MainWindow::InitializeDefaultSession()
231231

232232
//Dock it
233233
m_initialWorkspaceDockRequest = w;
234+
235+
//Spawn the tutorial if requested
236+
if(m_session.GetPreferences().GetBool("Help.Wizards.first_run_wizard"))
237+
{
238+
m_tutorialDialog = make_shared<TutorialWizard>(&m_session, this);
239+
AddDialog(m_tutorialDialog);
240+
}
234241
}
235242

236243
void MainWindow::CloseSession()
@@ -266,6 +273,7 @@ void MainWindow::CloseSession()
266273
m_historyDialog = nullptr;
267274
m_preferenceDialog = nullptr;
268275
m_persistenceDialog = nullptr;
276+
m_tutorialDialog = nullptr;
269277
m_manageInstrumentsDialog = nullptr;
270278
m_initialWorkspaceDockRequest = nullptr;
271279
m_graphEditor = nullptr;
@@ -1166,6 +1174,8 @@ void MainWindow::OnDialogClosed(const std::shared_ptr<Dialog>& dlg)
11661174
m_persistenceDialog = nullptr;
11671175
if(m_notesDialog == dlg)
11681176
m_notesDialog = nullptr;
1177+
if(m_tutorialDialog == dlg)
1178+
m_tutorialDialog = nullptr;
11691179
if(m_graphEditor == dlg)
11701180
{
11711181
m_graphEditorGroups = m_graphEditor->GetGroupIDs();

src/ngscopeclient/MainWindow.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
#include "ProtocolAnalyzerDialog.h"
4848
#include "StreamBrowserDialog.h"
4949
#include "TriggerPropertiesDialog.h"
50+
#include "TutorialWizard.h"
5051
#include "Workspace.h"
5152
#include "imgui_markdown.h"
5253

@@ -230,6 +231,10 @@ class MainWindow : public VulkanWindow
230231
void SetStartupSession(const std::string& path)
231232
{ m_startupSession = path; }
232233

234+
///@brief Gets a pointer to the tutorial wizard (if we have one open)
235+
std::shared_ptr<TutorialWizard> GetTutorialWizard()
236+
{ return m_tutorialDialog; }
237+
233238
protected:
234239
virtual void DoRender(vk::raii::CommandBuffer& cmdBuf);
235240

@@ -364,6 +369,9 @@ class MainWindow : public VulkanWindow
364369
///@brief Lab notes
365370
std::shared_ptr<Dialog> m_notesDialog;
366371

372+
///@brief Tutorial flow
373+
std::shared_ptr<TutorialWizard> m_tutorialDialog;
374+
367375
///@brief Filter graph editor
368376
std::shared_ptr<FilterGraphEditor> m_graphEditor;
369377

src/ngscopeclient/MainWindow_Menus.cpp

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,8 @@ void MainWindow::ViewMenu()
236236
*/
237237
void MainWindow::AddMenu()
238238
{
239+
auto menuStartPos = ImGui::GetCursorScreenPos();
240+
239241
if(ImGui::BeginMenu("Add"))
240242
{
241243
//Make a reverse mapping: timestamp -> instruments last used at that time
@@ -272,6 +274,19 @@ void MainWindow::AddMenu()
272274

273275
ImGui::EndMenu();
274276
}
277+
278+
//Add hint bubble here during the tutorial, but only if menu is not open
279+
//(we don't want to block the user's view of said menu)
280+
else if(m_tutorialDialog && (m_tutorialDialog->GetCurrentStep() == TutorialWizard::TUTORIAL_01_ADDINSTRUMENT) )
281+
{
282+
auto menuEndPos = ImGui::GetCursorScreenPos();
283+
284+
ImVec2 anchorPos(
285+
(menuStartPos.x + menuEndPos.x)/2,
286+
menuStartPos.y + 2*ImGui::GetFontSize());
287+
288+
m_tutorialDialog->DrawSpeechBubble(anchorPos, ImGuiDir_Up, "Add an oscilloscope to your session");
289+
}
275290
}
276291

277292
/**
@@ -293,8 +308,17 @@ void MainWindow::DoAddSubMenu(
293308
m_dialogs.emplace(make_shared<AddInstrumentDialog>(
294309
string("Add ") + typePretty,
295310
defaultName,
296-
m_session,
311+
&m_session,
312+
this,
297313
typeInternal));
314+
315+
//Move to the next step of the tutorial if needed
316+
if((typeInternal == "oscilloscope") &&
317+
m_tutorialDialog &&
318+
(m_tutorialDialog->GetCurrentStep() == TutorialWizard::TUTORIAL_01_ADDINSTRUMENT) )
319+
{
320+
m_tutorialDialog->AdvanceToNextStep();
321+
}
298322
}
299323
ImGui::Separator();
300324

@@ -782,6 +806,16 @@ void MainWindow::HelpMenu()
782806
{
783807
if(ImGui::BeginMenu("Help"))
784808
{
809+
ImGui::BeginDisabled(m_tutorialDialog != nullptr);
810+
if(ImGui::MenuItem("Tutorial..."))
811+
{
812+
m_tutorialDialog = make_shared<TutorialWizard>(&m_session, this);
813+
AddDialog(m_tutorialDialog);
814+
}
815+
ImGui::EndDisabled();
816+
817+
ImGui::Separator();
818+
785819
if(ImGui::MenuItem("About..."))
786820
AddDialog(make_shared<AboutDialog>(this));
787821

src/ngscopeclient/PreferenceSchema.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,17 @@ void PreferenceManager::InitializeDefaults()
172172
.Label("Icon color")
173173
.Description("Color for icon captions"));
174174

175+
auto& ahelp = appearance.AddCategory("Help");
176+
ahelp.AddPreference(
177+
Preference::Color("bubble_outline_color", ColorFromString("#00ff00"))
178+
.Label("Bubble outline color")
179+
.Description("Color for tutorial bubble outlines"));
180+
181+
ahelp.AddPreference(
182+
Preference::Color("bubble_fill_color", ColorFromString("#202020e0"))
183+
.Label("Bubble fill color")
184+
.Description("Color for tutorial bubble fill"));
185+
175186
auto& stream = appearance.AddCategory("Stream Browser");
176187
stream.AddPreference(
177188
Preference::Enum("numeric_value_display", NUMERIC_DISPLAY_MONO_FONT)
@@ -543,6 +554,13 @@ void PreferenceManager::InitializeDefaults()
543554
.Description("Maximum number of recent .scopesession file paths to save in history")
544555
.Unit(Unit::UNIT_COUNTS));
545556

557+
auto& help = this->m_treeRoot.AddCategory("Help");
558+
auto& wizards = help.AddCategory("Wizards");
559+
wizards.AddPreference(
560+
Preference::Bool("first_run_wizard", true)
561+
.Label("First-run wizard")
562+
.Description("Enable the first-run tutorial wizard"));
563+
546564
auto& misc = this->m_treeRoot.AddCategory("Miscellaneous");
547565
auto& menus = misc.AddCategory("Menus");
548566
menus.AddPreference(

0 commit comments

Comments
 (0)