AnimTestbed/3rdparty/imgui-node-editor/examples/widgets-example/widgets-example.cpp

432 lines
19 KiB
C++

// ===================================================================================================================
// Widget Example
// Drawing standard ImGui widgets inside the node body
//
// First, some unsorted notes about which widgets do and don't draw well inside nodes. Run the examples to see all the allowed widgets.
//
// - Child windows with scrolling doesn't work in the node. The child window appears in a normal node,
// and scrolls, but its contents are floating around in the wrong location, and they are not scaled.
// Note that you can put scrolling child windows into "deferred popups" (see next item).
// - Listboxes and combo-boxes only work in nodes with a work-around: deferring the popup calls until after the node drawing is
// completed. Look to the popup-demo for an example.
// - Headers and trees work inside the nodes only with hacks. This is because they attempt to span the "avaialbe width"
// and the nodes can't tell these widgets how wide it is. The work-around is to set up a fake
// table with a static column width, then draw your header and tree widgets in that column.
// - Clickable tabs don't work in nodes. Tabs appear, but you cannot actually change the tab, so they're functionally useless.
// - Editable text areas work, but you have to manually manage disabling the editor shorcuts while typing is detected.
// Look around for the call to ed::EnableShortcuts() for an example.
// - Most of the cool graph widgets can't be used because they are hard-coded in ImGui to spawn tooltips, which don't work.
# include <imgui.h>
# include <imgui_internal.h>
# include <imgui_node_editor.h>
# include <application.h>
namespace ed = ax::NodeEditor;
# ifdef _MSC_VER
# define portable_strcpy strcpy_s
# define portable_sprintf sprintf_s
# else
# define portable_strcpy strcpy
# define portable_sprintf sprintf
# endif
struct Example:
public Application
{
using Application::Application;
struct LinkInfo
{
ed::LinkId Id;
ed::PinId InputId;
ed::PinId OutputId;
};
void OnStart() override
{
ed::Config config;
config.SettingsFile = "Widgets.json";
m_Context = ed::CreateEditor(&config);
}
void OnStop() override
{
ed::DestroyEditor(m_Context);
}
void OnFrame(float deltaTime) override
{
static bool firstframe = true; // Used to position the nodes on startup
auto& io = ImGui::GetIO();
// FPS Counter Ribbon
ImGui::Text("FPS: %.2f (%.2gms)", io.Framerate, io.Framerate ? 1000.0f / io.Framerate : 0.0f);
ImGui::Separator();
// Node Editor Widget
ed::SetCurrentEditor(m_Context);
ed::Begin("My Editor", ImVec2(0.0, 0.0f));
int uniqueId = 1;
// Basic Widgets Demo ==============================================================================================
auto basic_id = uniqueId++;
ed::BeginNode(basic_id);
ImGui::Text("Basic Widget Demo");
ed::BeginPin(uniqueId++, ed::PinKind::Input);
ImGui::Text("-> In");
ed::EndPin();
ImGui::SameLine();
ImGui::Dummy(ImVec2(250, 0)); // Hacky magic number to space out the output pin.
ImGui::SameLine();
ed::BeginPin(uniqueId++, ed::PinKind::Output);
ImGui::Text("Out ->");
ed::EndPin();
// Widget Demo from imgui_demo.cpp...
// Normal Button
static int clicked = 0;
if (ImGui::Button("Button"))
clicked++;
if (clicked & 1)
{
ImGui::SameLine();
ImGui::Text("Thanks for clicking me!");
}
// Checkbox
static bool check = true;
ImGui::Checkbox("checkbox", &check);
// Radio buttons
static int e = 0;
ImGui::RadioButton("radio a", &e, 0); ImGui::SameLine();
ImGui::RadioButton("radio b", &e, 1); ImGui::SameLine();
ImGui::RadioButton("radio c", &e, 2);
// Color buttons, demonstrate using PushID() to add unique identifier in the ID stack, and changing style.
for (int i = 0; i < 7; i++)
{
if (i > 0)
ImGui::SameLine();
ImGui::PushID(i);
ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(i / 7.0f, 0.6f, 0.6f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(i / 7.0f, 0.7f, 0.7f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(i / 7.0f, 0.8f, 0.8f));
ImGui::Button("Click");
ImGui::PopStyleColor(3);
ImGui::PopID();
}
// Use AlignTextToFramePadding() to align text baseline to the baseline of framed elements (otherwise a Text+SameLine+Button sequence will have the text a little too high by default)
ImGui::AlignTextToFramePadding();
ImGui::Text("Hold to repeat:");
ImGui::SameLine();
// Arrow buttons with Repeater
static int counter = 0;
float spacing = ImGui::GetStyle().ItemInnerSpacing.x;
ImGui::PushButtonRepeat(true);
if (ImGui::ArrowButton("##left", ImGuiDir_Left)) { counter--; }
ImGui::SameLine(0.0f, spacing);
if (ImGui::ArrowButton("##right", ImGuiDir_Right)) { counter++; }
ImGui::PopButtonRepeat();
ImGui::SameLine();
ImGui::Text("%d", counter);
// The input widgets also require you to manually disable the editor shortcuts so the view doesn't fly around.
// (note that this is a per-frame setting, so it disables it for all text boxes. I left it here so you could find it!)
ed::EnableShortcuts(!io.WantTextInput);
// The input widgets require some guidance on their widths, or else they're very large. (note matching pop at the end).
ImGui::PushItemWidth(200);
static char str1[128] = "";
ImGui::InputTextWithHint("input text (w/ hint)", "enter text here", str1, IM_ARRAYSIZE(str1));
static float f0 = 0.001f;
ImGui::InputFloat("input float", &f0, 0.01f, 1.0f, "%.3f");
static float f1 = 1.00f, f2 = 0.0067f;
ImGui::DragFloat("drag float", &f1, 0.005f);
ImGui::DragFloat("drag small float", &f2, 0.0001f, 0.0f, 0.0f, "%.06f ns");
ImGui::PopItemWidth();
ed::EndNode();
if (firstframe)
{
ed::SetNodePosition(basic_id, ImVec2(20, 20));
}
// Headers and Trees Demo =======================================================================================================
// TreeNodes and Headers streatch to the entire remaining work area. To put them in nodes what we need to do is to tell
// ImGui out work area is shorter. We can achieve that right now only by using columns API.
//
// Relevent bugs: https://github.com/thedmd/imgui-node-editor/issues/30
auto header_id = uniqueId++;
ed::BeginNode(header_id);
ImGui::Text("Tree Widget Demo");
// Pins Row
ed::BeginPin(uniqueId++, ed::PinKind::Input);
ImGui::Text("-> In");
ed::EndPin();
ImGui::SameLine();
ImGui::Dummy(ImVec2(35, 0)); // magic number - Crude & simple way to nudge over the output pin. Consider using layout and springs
ImGui::SameLine();
ed::BeginPin(uniqueId++, ed::PinKind::Output);
ImGui::Text("Out ->");
ed::EndPin();
// Tree column startup -------------------------------------------------------------------
// Push dummy widget to extend node size. Columns do not do that.
float width = 135; // bad magic numbers. used to define width of tree widget
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));
ImGui::Dummy(ImVec2(width, 0));
ImGui::PopStyleVar();
// Start columns, but use only first one.
ImGui::BeginColumns("##TreeColumns", 2,
ImGuiOldColumnFlags_NoBorder |
ImGuiOldColumnFlags_NoResize |
ImGuiOldColumnFlags_NoPreserveWidths |
ImGuiOldColumnFlags_NoForceWithinWindow);
// Adjust column width to match requested one.
ImGui::SetColumnWidth(0, width
+ ImGui::GetStyle().WindowPadding.x
+ ImGui::GetStyle().ItemSpacing.x);
// End of tree column startup --------------------------------------------------------------
// Back to normal ImGui drawing, in our column.
if (ImGui::CollapsingHeader("Open Header"))
{
ImGui::Text("Hello There");
if (ImGui::TreeNode("Open Tree")) {
static bool OP1_Bool = false;
ImGui::Text("Checked: %s", OP1_Bool ? "true" : "false");
ImGui::Checkbox("Option 1", &OP1_Bool);
ImGui::TreePop();
}
}
// Tree Column Shutdown
ImGui::EndColumns();
ed::EndNode(); // End of Tree Node Demo
if (firstframe)
{
ed::SetNodePosition(header_id, ImVec2(420, 20));
}
// Tool Tip & Pop-up Demo =====================================================================================
// Tooltips, combo-boxes, drop-down menus need to use a work-around to place the "overlay window" in the canvas.
// To do this, we must defer the popup calls until after we're done drawing the node material.
//
// Relevent bugs: https://github.com/thedmd/imgui-node-editor/issues/48
auto popup_id = uniqueId++;
ed::BeginNode(popup_id);
ImGui::Text("Tool Tip & Pop-up Demo");
ed::BeginPin(uniqueId++, ed::PinKind::Input);
ImGui::Text("-> In");
ed::EndPin();
ImGui::SameLine();
ImGui::Dummy(ImVec2(85, 0)); // Hacky magic number to space out the output pin.
ImGui::SameLine();
ed::BeginPin(uniqueId++, ed::PinKind::Output);
ImGui::Text("Out ->");
ed::EndPin();
// Tooltip example
ImGui::Text("Hover over me");
static bool do_tooltip = false;
do_tooltip = ImGui::IsItemHovered() ? true : false;
ImGui::SameLine();
ImGui::Text("- or me");
static bool do_adv_tooltip = false;
do_adv_tooltip = ImGui::IsItemHovered() ? true : false;
// Use AlignTextToFramePadding() to align text baseline to the baseline of framed elements
// (otherwise a Text+SameLine+Button sequence will have the text a little too high by default)
ImGui::AlignTextToFramePadding();
ImGui::Text("Option:");
ImGui::SameLine();
static char popup_text[128] = "Pick one!";
static bool do_popup = false;
if (ImGui::Button(popup_text)) {
do_popup = true; // Instead of saying OpenPopup() here, we set this bool, which is used later in the Deferred Pop-up Section
}
ed::EndNode();
if (firstframe) {
ed::SetNodePosition(popup_id, ImVec2(610, 20));
}
// --------------------------------------------------------------------------------------------------
// Deferred Pop-up Section
// This entire section needs to be bounded by Suspend/Resume! These calls pop us out of "node canvas coordinates"
// and draw the popups in a reasonable screen location.
ed::Suspend();
// There is some stately stuff happening here. You call "open popup" exactly once, and this
// causes it to stick open for many frames until the user makes a selection in the popup, or clicks off to dismiss.
// More importantly, this is done inside Suspend(), so it loads the popup with the correct screen coordinates!
if (do_popup) {
ImGui::OpenPopup("popup_button"); // Cause openpopup to stick open.
do_popup = false; // disable bool so that if we click off the popup, it doesn't open the next frame.
}
// This is the actual popup Gui drawing section.
if (ImGui::BeginPopup("popup_button")) {
// Note: if it weren't for the child window, we would have to PushItemWidth() here to avoid a crash!
ImGui::TextDisabled("Pick One:");
ImGui::BeginChild("popup_scroller", ImVec2(100, 100), true, ImGuiWindowFlags_AlwaysVerticalScrollbar);
if (ImGui::Button("Option 1")) {
portable_strcpy(popup_text, "Option 1");
ImGui::CloseCurrentPopup(); // These calls revoke the popup open state, which was set by OpenPopup above.
}
if (ImGui::Button("Option 2")) {
portable_strcpy(popup_text, "Option 2");
ImGui::CloseCurrentPopup();
}
if (ImGui::Button("Option 3")) {
portable_strcpy(popup_text, "Option 3");
ImGui::CloseCurrentPopup();
}
if (ImGui::Button("Option 4")) {
portable_strcpy(popup_text, "Option 4");
ImGui::CloseCurrentPopup();
}
ImGui::EndChild();
ImGui::EndPopup(); // Note this does not do anything to the popup open/close state. It just terminates the content declaration.
}
// Handle the simple tooltip
if (do_tooltip)
ImGui::SetTooltip("I am a tooltip");
// Handle the advanced tooltip
if (do_adv_tooltip) {
ImGui::BeginTooltip();
ImGui::Text("I am a fancy tooltip");
static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f };
ImGui::PlotLines("Curve", arr, IM_ARRAYSIZE(arr));
ImGui::EndTooltip();
}
ed::Resume();
// End of "Deferred Pop-up section"
// Plot Widgets =========================================================================================
// Note: most of these plots can't be used in nodes missing, because they spawn tooltips automatically,
// so we can't trap them in our deferred pop-up mechanism. This causes them to fly into a random screen
// location.
auto plot_id = uniqueId++;
ed::BeginNode(plot_id);
ImGui::Text("Plot Demo");
ed::BeginPin(uniqueId++, ed::PinKind::Input);
ImGui::Text("-> In");
ed::EndPin();
ImGui::SameLine();
ImGui::Dummy(ImVec2(250, 0)); // Hacky magic number to space out the output pin.
ImGui::SameLine();
ed::BeginPin(uniqueId++, ed::PinKind::Output);
ImGui::Text("Out ->");
ed::EndPin();
ImGui::PushItemWidth(300);
// Animate a simple progress bar
static float progress = 0.0f, progress_dir = 1.0f;
progress += progress_dir * 0.4f * ImGui::GetIO().DeltaTime;
if (progress >= +1.1f) { progress = +1.1f; progress_dir *= -1.0f; }
if (progress <= -0.1f) { progress = -0.1f; progress_dir *= -1.0f; }
// Typically we would use ImVec2(-1.0f,0.0f) or ImVec2(-FLT_MIN,0.0f) to use all available width,
// or ImVec2(width,0.0f) for a specified width. ImVec2(0.0f,0.0f) uses ItemWidth.
ImGui::ProgressBar(progress, ImVec2(0.0f, 0.0f));
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::Text("Progress Bar");
float progress_saturated = (progress < 0.0f) ? 0.0f : (progress > 1.0f) ? 1.0f : progress;
char buf[32];
portable_sprintf(buf, "%d/%d", (int)(progress_saturated * 1753), 1753);
ImGui::ProgressBar(progress, ImVec2(0.f, 0.f), buf);
ImGui::PopItemWidth();
ed::EndNode();
if (firstframe) {
ed::SetNodePosition(plot_id, ImVec2(850, 20));
}
// ==================================================================================================
// Link Drawing Section
for (auto& linkInfo : m_Links)
ed::Link(linkInfo.Id, linkInfo.InputId, linkInfo.OutputId);
// ==================================================================================================
// Interaction Handling Section
// This was coppied from BasicInteration.cpp. See that file for commented code.
// Handle creation action ---------------------------------------------------------------------------
if (ed::BeginCreate())
{
ed::PinId inputPinId, outputPinId;
if (ed::QueryNewLink(&inputPinId, &outputPinId))
{
if (inputPinId && outputPinId)
{
if (ed::AcceptNewItem())
{
m_Links.push_back({ ed::LinkId(m_NextLinkId++), inputPinId, outputPinId });
ed::Link(m_Links.back().Id, m_Links.back().InputId, m_Links.back().OutputId);
}
}
}
}
ed::EndCreate();
// Handle deletion action ---------------------------------------------------------------------------
if (ed::BeginDelete())
{
ed::LinkId deletedLinkId;
while (ed::QueryDeletedLink(&deletedLinkId))
{
if (ed::AcceptDeletedItem())
{
for (auto& link : m_Links)
{
if (link.Id == deletedLinkId)
{
m_Links.erase(&link);
break;
}
}
}
}
}
ed::EndDelete();
ed::End();
ed::SetCurrentEditor(nullptr);
firstframe = false;
//ImGui::ShowMetricsWindow();
//ImGui::ShowDemoWindow();
}
ed::EditorContext* m_Context = nullptr;
ImVector<LinkInfo> m_Links; // List of live links. It is dynamic unless you want to create read-only view over nodes.
int m_NextLinkId = 100; // Counter to help generate link ids. In real application this will probably based on pointer to user data structure.
};
int Main(int argc, char** argv)
{
Example exampe("Widgets", argc, argv);
if (exampe.Create())
return exampe.Run();
return 0;
}