Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: Allow storing user data inside windows and to imgui.ini file #2564

Closed
almic opened this issue May 18, 2019 · 6 comments
Closed
Labels
settings .ini persistance

Comments

@almic
Copy link

almic commented May 18, 2019

I could not find this already, so I'm sorry if this is duplicating another issue.

What this means:
Provide a way to store custom data structures inside the window objects themselves, which are already publicly available through the imgui context in Windows. In short, this will let programmers access data about windows without abstracting their own wrapping class.

Why this is better than using a wrapping class:
For my current use case, I would like to initialize a window with additional data that can change at start-up and during run-time. It is possible to create a wrapper that saves a pointer or ID for the window along with the additional data, but this now means I would have to also track the windows and effectively rewrite the current implementation for saving window information to the imgui.ini file. Instead, it would be easier to allow developers to save (or serialize) custom data structures for the windows which is already saved by dear imgui.

Why this is better than hard-coding everything into dear imgui:
It would obviously be a lot of work to do things like storing the state of every widget inside a window, simply due to the fact that saving the layout of each window alone would bloat the .ini file and create a lot of unnecessary double-checking during window drawing. Letting the developer choose what data to save for a window and where it should go is easier for everyone.

What this could look like:
First, here is how it looks in use, I explain it afterwards and show what code changes might be needed to allow this.

MyClass.h

#include <imgui.h>
class MyClass : public ImGuiWindowClass
{
public:
    int special_integer;
    char some_user_inputs[100];

    MyClass()
        : ImGuiWindowClass()
    {
    }

    virtual void Save(ImGuiTextBuffer* buf) override
    {
        buf->appendf("SpecialInt=%d\n", special_integer);
        buf->appendf("UserInput=%s\n", some_user_inputs);
    }

    virtual void Load(const char* line) override
    {
        int i;
        char cstr[100];

        if (sscanf(line, "SpecialInt=%d", &i) == 1)
        {
            special_integer = i;
        }
        else if (sscanf(line, "UserInput=%[^\n\r]", cstr) == 1)
        {
            some_user_inputs = cstr;
        }
    }
}

main.cpp

// Before the render loop...
bool show_window = true;
MyClass* userDataWindowClass = new MyClass();
char* userString = userDataWindowClass->some_user_inputs;
int* userInteger = &userDataWindowClass->special_integer;

// Somewhere inside the render loop...
ImGui::SetNextWindowClass(userDataWindowClass);
ImGui::Begin("My User Data Window", &show_window, 0);

ImGui::InputText("Some Option", userString, IM_ARRAYSIZE(userString));
ImGui::SliderInt("Pick a number", userInt, 10, 100);

Sorry if this code has bugs, I just wrote it manually inside the issue box and didn't actually test it. This is just to give you an idea of how it can be used.

How it could work:
I'm not as familiar with the code base, but from initial scanning it seems like there wouldn't be very many changes needed to support this.

The functions Save and Load would need to be added as virtual functions to the ImGuiWindowClass class/ struct. As well, a pointer like ImGuiWindowClass* WindowClass would have to be added to the ImGuiWindowSettings struct.

Inside the current SettingsHandlerWindow_WriteAll function that is responsible for building the imgui.ini file, all that would need to be added is a call to settings->WindowClass->Save(buf). This might need a new buffer for the call specifically, and could only be added if the buffer actually contains data. This would probably best be added last using a header similar to [Window] maybe something like [UserData], of which is considered finished loading custom data when the next [Window] line appears.

Then inside the SettingsHandlerWindow_ReadLine function you'd add a entry_data->Load(line) function call, which is only called when something like [UserData] is found and a switch is flipped.

From what I can see, this is all that really needs to be done. Like I wrote earlier, this is all already possible to do, but it requires us developers to reinvent the wheel so-to-speak and we'd end up having the imgui.ini file as well as our own .ini-like file. This way, we can simply add our data to a derived class which dear imgui will automatically save and load for us.

I would love to see this implemented! If you give me the green light I'd happily create a pull request to add this 🙂

@ocornut
Copy link
Owner

ocornut commented May 19, 2019

(reposted with missing code blurb)

Hello,

This would probably best be added last using a header similar to [Window] maybe something like [UserData], of which is considered finished loading custom data when the next [Window] line appears.

Yes you can do that.

I am not sure to understand why you don't just add a new .ini handler in the g.SettingsHandlers this is what it is for.

You can maintain your own key>value store where value is MyStruct and make that persist in the .ini file this way. The boilerplate needed is quite small, there's no reason to store your value in the same .ini block as the one imgui uses for its stuff.

Just copy what ImGui::Initialize() does and add your own handler with a TypeName of UserData.

// Add .ini handle for UserData type
ImGuiSettingsHandler ini_handler;
ini_handler.TypeName = "UserData";
ini_handler.TypeHash = ImHashStr("UserData");
ini_handler.ReadOpenFn = MyUserData_ReadOpen;
ini_handler.ReadLineFn = MyUserData_ReadLine;
ini_handler.WriteAllFn = MyUserData_WriteAll;
ImGui::AddSettingsHandler(&ini_handler);

You can store and write the same title, so your .ini file would look like:

[Window][Hello, world!]
Pos=0,19
Size=1264,366
Collapsed=0

[Window][ImGui Demo]
Pos=0,421
Size=1264,340
Collapsed=0

[UserData][Hello, world!]
UserInteger=1

[UserData][ImGui Demo]
UserInteger=1

(Linking to #437)

@almic
Copy link
Author

almic commented May 20, 2019

Okay, I see now. I'm hesitant to do this though which is why I would suggest giving "dedicated support" for this type of stuff. Mostly because if the system ever changes, even just slightly, then I may have to rewrite the implementation. But if this is what you recommend, then I'll do it.

@almic almic closed this as completed May 20, 2019
@ocornut
Copy link
Owner

ocornut commented May 21, 2019

From my point of view I need to avoid taking too much unnecessary responsability and that means not providing too many guarantees. This is what imgui_internal.h is for. If I start providing guarantees (as with anything in imgui.h) I have to care and maintain those features forever while trying to not break them and I am already swamped in work.

@slajerek
Copy link

slajerek commented Dec 20, 2021

I wonder what is the proper way now of extending ImGuiWindow with user data? I am doing now by manually patching sources by adding at the end of ImGuiWindow definition in imgui_internal.h

	// any user data assigned to this ImGuiWindow
	void *UserData;

I need the userData to extend some window features which are not yet available in ImGui. Thanks in advance.

@thedmd
Copy link
Contributor

thedmd commented Dec 20, 2021

There is currently no direct way to extend ImGuiWindow, but you can use hooks to extend ImGuiContext.

What I did is I defined two structures:

  • ImGuiLayoutWindowState - extra state associated with ImGuiWindows
  • ImGuiLayoutState - extra state associated with ImGuiContext, hold list of ImGuiLayoutWindowState

Then I ended up with:

static ImGuiLayoutWindowState* GetCurrentWindowLayoutState();

(https://github.com/thedmd/imgui/blob/827eb6306d4d54b3190a24de495c995359eb9005/imgui_stacklayout.cpp#L333)

Basically what the function does is:

  • get or create ImGuiLayoutState for current ImGuiContext
  • get or create ImGuiLayoutWindowState for current ImGuiWindow (via ID)

ImGuiLayoutState use ImGuiContextHook to cleanup after itself when context is destroyed or to react to NewFrame/EndFrame. There are also some extra events available too you may find interesting for your use case.

ImGuiLayoutState use context hooks for cleanup and to sync with NewFrame/EndFrame. You can attach to events you care about.

Ideally there should be a way to re-parse ini file to extenstion activated after new ImGuiSettingsHandler is registered., so your extension will load their state as expected. So far it is not a thing, so be prepared for making workarounds.
Note: You will get ini data if your ImGuiSettingsHandler is installed before ImGuiContext is created.

This is the current state afaik.

gerlachch added a commit to gerlachch/dots-explorer that referenced this issue Apr 22, 2022
Note that this is achieved by registering a custom .ini handler, which
is based on a description by the owner of Dear ImGui (see [1]).

References:

 - [1] ocornut/imgui#2564
@moebiussurfing
Copy link

Hello,
there is some easy workaround to include the Tree states (collapse) into the .ini settings file?
To make the state persistent as the Windows are.

m9710797 added a commit to openMSX/openMSX that referenced this issue Mar 20, 2023
For example which windows (debuggables, sound settings, ...) were open,
which are the last used paths in file-open-dialogs, or how the OSD icons
or reversebar were configured, are now remembered after closing and
restarting openMSX.

This uses functionality from imgui_internal.h, this API is not
guaranteed to remain the same between different Dear ImGui versions.
Though it is a solution recommended by the Dear ImGui developers
themselves. For example see:
    ocornut/imgui#2564

I'm not happy about the current implementation. The main reason is that
currently we duplicate the to-be-serialized information in two
locations. And we have to keep those in sync. This is especially true
for the more dynamic info (like the 'debuggable' windows that are open,
this list depends on the specific MSX machine). One of the strong points
in the "Immediate Mode GUI paradigm" is that there is no duplicated
information. And thus there cannot be bugs in keeping the information in
sync. The current implementation violates that principle.

The reason why currently the information is duplicated is because of
object lifetime issues. Just before 'Dear ImGui' is shutdown, it still
writes the imgui.ini file, we want our extra information in that file.
But in the current code, when we shutdown 'Dear ImGui' we've already
deleted the 'ImGuiLayer' object which contains the information we want
to save. My first solution was to move the information to a new object
'ImGuiManager' which does have a longer lifetime than 'Dear ImGui'. But
as stated in the previous paragraph: currently this design leads to some
duplicated information that needs to be kept in sync.

I plan to refactor this in the following patches. But I already wanted
to push the current new functionality.

I already wanted to refactor 'ImGuiLayer' so that's it's split over
several smaller files. E.g. separate file for the debugger stuff, the
setting stuff, the media stuff, ... I'll give that higher priority, and
try to do it in a way that also resolves the lifetime issues.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
settings .ini persistance
Projects
None yet
Development

No branches or pull requests

5 participants