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

Added IO::WantRender bool to facilitate lazy rendering support. #5599

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

MariusH87
Copy link

In order to run more efficiently power-usage wise on browsers and mobile units where battery consumption is an issue we need a way to know when we need to update and render.

This solution have given high improvements when it comes to power consumption as we can check this variable in addition to the key/mouse flags for when we need to render.
It also gives a gain for desktops and other devices for cpu/gpu usage.

@ocornut
Copy link
Owner

ocornut commented Aug 22, 2022

Similar to #4076 #2749 #5116 #5140 #5023 (and more: #5116 (comment))
Looks like there's recurring demand for a feature like this, so I can aim at frankenstening a proper version from the various PR.

@MariusH87
Copy link
Author

That sounds very good, thank you!

@jlmxyz
Copy link

jlmxyz commented Aug 22, 2022

oh! great, do I well understand? imgui will (at least) have a powersaving mode?!?! really, this is THE missing feature that prevent using imGUI to design applications...

@ocornut
Copy link
Owner

ocornut commented Aug 22, 2022

oh! great, do I well understand? imgui will (at least) have a powersaving mode?!?! really, this is THE missing feature that prevent using imGUI to design applications...

I think people seem to misunderstand that you can achieve 98% of the many PR above with 10 lines of code in your main loop, by just updating on user inputs change... many users have been doing it for years.

What the PR do are mostly fixing a few blind spots, standardizing an API for widgets to communicate requests, and standardizing interactions with a backend. All of those can be achieved by the user already (even peeking for blinking spot elapsed time).

EDIT I don't disagree that the official standardized version is essential to move forward. We will add it. I'm just saying you don't need any of those PR to use a system like that.

EDIT 2 All the PR have issues, which is why they are not merged, but there are good ideas in each of them.

@wolfpld
Copy link
Contributor

wolfpld commented Sep 27, 2022

I think people seem to misunderstand that you can achieve 98% of the many PR above with 10 lines of code in your main loop, by just updating on user inputs change... many users have been doing it for years.

Checking ImGui::GetCurrentContext()->InputEventsQueue for events that are non-ImGuiInputEventType_MouseViewport seems to be the most elegant way to do this.

However, it quickly becomes apparent that it is not enough. There are at least two problems that require deeper knowledge of what ImGui is doing.

  1. Cursor blinking in text input boxes. For maximum energy saving you want it disabled anyways.
  2. The popup background overlay actually has a fade-in animation managed by ImGui, which should keep the render loop going until it finishes.

My very out of place question here is how to properly handle the second problem?

@ocornut
Copy link
Owner

ocornut commented Sep 27, 2022

I think the essence of #4076 is to provide those.

The "internal" answer to (2) may be to check if g.DimBgRatio is neither 0.0f neither 1.0f.

While I assume integrating backend-side and example stuff is going to be trickier, I don't mind at least prioritizing the work on providing those "want refresh" values (in the form of both 1 timer and 1 frame count), so at least custom apps can take advantage of them.

@ocornut
Copy link
Owner

ocornut commented Sep 27, 2022

Checking ImGui::GetCurrentContext()->InputEventsQueue for events that are non-ImGuiInputEventType_MouseViewport seems to be the most elegant way to do this.

Two things that may be of use:

  • g.InputEventsTrail imply processed event, may be good to poll that.
  • Event now have a bool IgnoredAsSame flag that mark when an event is disabled.

@wolfpld
Copy link
Contributor

wolfpld commented Sep 27, 2022

The "internal" answer to (2) may be to check if g.DimBgRatio is neither 0.0f neither 1.0f.

Yup, it works. Thanks.

g.InputEventsTrail imply processed event, may be good to poll that.

This is updated in NewFrame(), which I want to skip in the no-refresh path. InputEventsQueue works for me, because it is updated in ImGui_ImplGlfw_NewFrame(), which does not do any state changes (unlike NewFrame()).

@ocornut
Copy link
Owner

ocornut commented Sep 27, 2022 via email

@wolfpld
Copy link
Contributor

wolfpld commented Sep 28, 2022

Ctrl+Tab window switching has a delay that require special handling. Seems like checking for NavWindowingTarget is enough.

@wolfpld
Copy link
Contributor

wolfpld commented Sep 28, 2022

Checking ImGui::GetCurrentContext()->InputEventsQueue for events that are non-ImGuiInputEventType_MouseViewport seems to be the most elegant way to do this.

I have encountered a peculiarity with this approach. The glfw backend (?) is inserting MousePos events into the queue when the mouse is outside the application window. It does not need to be moved. The events are consumed (I see only one MousePos event in the queue per "frame") and the reported position accurately represents the cursor location, relative to the top-left of the application window, possibly with negative numbers. When the mouse is inside the window, there are no MousePos events generated for a stationary mouse position. I have seen this on Windows and Linux.

@ocornut
Copy link
Owner

ocornut commented Sep 28, 2022

The glfw backend (?) is inserting MousePos events into the queue when the mouse is outside the application window.

Correct, when focused or hovered, but they should be flagged with bool IgnoredAsSame = true;
Basically backends vary in how they filter events that may have no effect, and you can't really trust event queue .size > 0 as being a meaningful indicator of changes.

It's not even easy for the backend to do a no-op check because comparing to io.MousePos means you are not considering other events in the queue.

There's filtering done in UpdateInputEvents() that sets e->IgnoredAsSame for variety of event.
I guess what we need to do is refactor the change to be able to set this flag right in the AddXXXEvent() functions.

We could maintain a copy of expected value of fields at one given time, but it would unsync if any field is modified externally. I guess the safest is to do a replay of a given event type on add. Since all events are overwriting, a backward search can be used instead of a full replay. Assuming a non-clogged InputQueue it shouldn't be too bad.

Currently UpdateInputEvents() does:

if (e->Type == ImGuiInputEventType_MousePos)
{
    ImVec2 event_pos(e->MousePos.PosX, e->MousePos.PosY);
    if (IsMousePosValid(&event_pos))
        event_pos = ImVec2(ImFloorSigned(event_pos.x), ImFloorSigned(event_pos.y)); // Apply same flooring as UpdateMouseInputs()
    e->IgnoredAsSame = (io.MousePos.x == event_pos.x && io.MousePos.y == event_pos.y);
    if (!e->IgnoredAsSame)
    {
        // Trickling Rule: Stop processing queued events if we already handled a mouse button change
        if (trickle_fast_inputs && (mouse_button_changed != 0 || mouse_wheeled || key_changed || text_inputted))
            break;
        io.MousePos = event_pos;
        mouse_moved = true;
    }
}

Instead we would need to change AddMousePosEvent() to do a backward search.
In fact AddKeyAnalogEvent() ALREADY does that because with analog gamepad input it felt sane to filter earlier.

@ocornut
Copy link
Owner

ocornut commented Sep 28, 2022

Not well tested but the gist of that idea would be:

static ImGuiInputEvent* FindLastInputEvent(ImGuiInputEventType type)
{
    ImGuiContext& g = *GImGui;
    for (int n = g.InputEventsQueue.Size - 1; n >= 0; n--)
        if (g.InputEventsQueue[n].Type == type)
            return &g.InputEventsQueue[n];
    return NULL;
}

Then add in AddMousePosEvent():

    // Filter duplicate
    ImGuiInputEvent* last_event = FindLastInputEvent(ImGuiInputEventType_MousePos);
    ImVec2 last_mouse_pos = last_event ? ImVec2(last_event->MousePos.PosX, last_event->MousePos.PosY) : g.IO.MousePos;
    if (IsMousePosValid(&event_pos))
        last_mouse_pos = ImVec2(ImFloorSigned(last_mouse_pos.x), ImFloorSigned(last_mouse_pos.y)); // Apply same flooring as UpdateMouseInputs()
    if (last_mouse_pos.x == x && last_mouse_pos.y == y)
        return;

Or possibly still add the event but immediately mark it with e->IgnoredAsSame = true;

EDIT added flooring.

@wolfpld
Copy link
Contributor

wolfpld commented Sep 28, 2022

IgnoredAsSame is not in the latest release, which I'm using. Just saying.

@ocornut
Copy link
Owner

ocornut commented Sep 28, 2022

Right, it was added since. Anyhow, if you can test the AddMousePosEvent() idea locally and confirm it works, I can work on finishing the fuller thing.

@wolfpld
Copy link
Contributor

wolfpld commented Sep 28, 2022

It seems to be working, i.e. no events are put into queue when the mouse is not moving either in or outside the application window.

Shouldn't however the behavior be to report mouse position (and click, wheel, etc) events only inside the window?

@ocornut
Copy link
Owner

ocornut commented Sep 28, 2022 via email

ocornut added a commit that referenced this pull request Sep 29, 2022
…ill filter earlier in next commit. (#5599)

Making it a separate commit as this leads to much indentation change.
@ocornut
Copy link
Owner

ocornut commented Sep 29, 2022

@wolfpld Pushed cleanup (fac8295) + filtering (cc5058e), it turns out that IgnoredAsSame is not necessary.
I added some basic testing but this is not thoroughly tested.
(Also merged to docking and added 69beaa1)

@wolfpld
Copy link
Contributor

wolfpld commented Sep 29, 2022

Thanks!

fangshun2004 added a commit to fangshun2004/imgui that referenced this pull request Sep 29, 2022
commit cc5058e
Author: ocornut <[email protected]>
Date:   Thu Sep 29 21:59:32 2022 +0200

    IO: Filter duplicate input events during the AddXXX() calls. (ocornut#5599, ocornut#4921)

commit fac8295
Author: ocornut <[email protected]>
Date:   Thu Sep 29 21:31:36 2022 +0200

    IO: remove ImGuiInputEvent::IgnoredAsSame (revert part of 839c310), will filter earlier in next commit. (ocornut#5599)

    Making it a separate commit as this leads to much indentation change.

commit 9e7f460
Author: ocornut <[email protected]>
Date:   Thu Sep 29 20:42:45 2022 +0200

    Fixed GetKeyName() for ImGuiMod_XXX values, made invalid MousePos display in log nicer.  (ocornut#4921, ocornut#456)

    Amend fd408c9

commit 0749453
Author: ocornut <[email protected]>
Date:   Thu Sep 29 19:51:54 2022 +0200

    Menus, Nav: Fixed not being able to close a menu with Left arrow when parent is not a popup. (ocornut#5730)

commit 9f6aae3
Author: ocornut <[email protected]>
Date:   Thu Sep 29 19:48:27 2022 +0200

    Nav: Fixed race condition pressing Esc during popup opening frame causing crash.

commit bd2355a
Author: ocornut <[email protected]>
Date:   Thu Sep 29 19:25:26 2022 +0200

    Menus, Nav: Fixed using left/right navigation when appending to an existing menu (multiple BeginMenu() call with same names). (ocornut#1207)

commit 3532ed1
Author: ocornut <[email protected]>
Date:   Thu Sep 29 18:07:35 2022 +0200

    Menus, Nav: Fixed keyboard/gamepad navigation occasionally erroneously landing on menu-item in parent when the parent is not a popup. (ocornut#5730)

    Replace BeginMenu/MenuItem swapping g.NavWindow with a more adequate ImGuiItemFlags_NoWindowHoverableCheck.
    Expecting more subtle issues to stem from this.
    Note that NoWindowHoverableCheck is not supported by IsItemHovered() but then IsItemHovered() on BeginMenu() never worked: fix should be easy in BeginMenu() + add test is IsItemHovered(), will do later

commit d5d7050
Author: ocornut <[email protected]>
Date:   Thu Sep 29 17:26:52 2022 +0200

    Various comments

    As it turns out, functions like IsItemHovered() won't work on an open BeginMenu() because LastItemData is overriden by BeginPopup(). Probably an easy fix.

commit e74a50f
Author: Andrew D. Zonenberg <[email protected]>
Date:   Wed Sep 28 08:19:34 2022 -0700

    Added GetGlyphRangesGreek() helper for Greek & Coptic glyph range. (ocornut#5676, ocornut#5727)

commit d17627b
Author: ocornut <[email protected]>
Date:   Wed Sep 28 17:38:41 2022 +0200

    InputText: leave state->Flags uncleared for the purpose of backends emitting an on-screen keyboard for passwords. (ocornut#5724)

commit 0a7054c
Author: ocornut <[email protected]>
Date:   Wed Sep 28 17:00:45 2022 +0200

    Backends: Win32: Convert WM_CHAR values with MultiByteToWideChar() when window class was registered as MBCS (not Unicode). (ocornut#5725, ocornut#1807, ocornut#471, ocornut#2815, ocornut#1060)

commit a229a7f
Author: ocornut <[email protected]>
Date:   Wed Sep 28 16:57:09 2022 +0200

    Examples: Win32: Always use RegisterClassW() to ensure windows are Unicode. (ocornut#5725)

commit e0330c1
Author: ocornut <[email protected]>
Date:   Wed Sep 28 14:54:38 2022 +0200

    Fonts, Text: Fixed wrapped-text not doing a fast-forward on lines above the clipping region. (ocornut#5720)

    which would result in an abnormal number of vertices created.

commit 4d4889b
Author: ocornut <[email protected]>
Date:   Wed Sep 28 12:42:06 2022 +0200

    Refactor CalcWordWrapPositionA() to take on the responsability of minimum character display. Add CalcWordWrapNextLineStartA(), simplify caller code.

    Should be no-op but incrementing IMGUI_VERSION_NUM just in case.
    Preparing for ocornut#5720

commit 5c4426c
Author: ocornut <[email protected]>
Date:   Wed Sep 28 12:22:34 2022 +0200

    Demo: Fixed Log & Console from losing scrolling position with Auto-Scroll when child is clipped. (ocornut#5721)

commit 12c0246
Author: ocornut <[email protected]>
Date:   Wed Sep 28 12:07:43 2022 +0200

    Removed support for 1.42-era IMGUI_DISABLE_INCLUDE_IMCONFIG_H / IMGUI_INCLUDE_IMCONFIG_H. (ocornut#255)

commit 73efcec
Author: ocornut <[email protected]>
Date:   Tue Sep 27 22:21:47 2022 +0200

    Examples: disable GL related warnings on Mac + amend to ignore list.

commit a725db1
Author: ocornut <[email protected]>
Date:   Tue Sep 27 18:47:20 2022 +0200

    Comments for flags discoverability + add to debug log (ocornut#3795, ocornut#4559)

commit 325299f
Author: ocornut <[email protected]>
Date:   Wed Sep 14 15:46:27 2022 +0200

    Backends: OpenGL: Add ability to #define IMGUI_IMPL_OPENGL_DEBUG. (ocornut#4468, ocornut#4825, ocornut#4832, ocornut#5127, ocornut#5655, ocornut#5709)

commit 56c3eae
Author: ocornut <[email protected]>
Date:   Tue Sep 27 14:24:21 2022 +0200

    ImDrawList: asserting on incorrect value for CurveTessellationTol (ocornut#5713)

commit 04316bd
Author: ocornut <[email protected]>
Date:   Mon Sep 26 16:32:09 2022 +0200

    ColorEdit3: fixed id collision leading to an assertion. (ocornut#5707)

commit c261dac
Author: ocornut <[email protected]>
Date:   Mon Sep 26 14:50:46 2022 +0200

    Demo: moved ShowUserGuide() lower in the file, to make main demo entry point more visible + fix using IMGUI_DEBUG_LOG() macros in if/else.

commit 51bbc70
Author: ocornut <[email protected]>
Date:   Mon Sep 26 14:44:26 2022 +0200

    Backends: SDL: Disable SDL 2.0.22 new "auto capture" which prevents drag and drop across windows, and don't capture mouse when drag and dropping. (ocornut#5710)

commit 7a9045d
Author: ocornut <[email protected]>
Date:   Mon Sep 26 11:55:07 2022 +0200

    Backends: WGPU: removed Emscripten version check (currently failing on CI, ensure why, and tbh its redundant/unnecessary with changes of wgpu api nowadays)

commit 83a0030
Author: ocornut <[email protected]>
Date:   Mon Sep 26 10:33:44 2022 +0200

    Added ImGuiMod_Shortcut which is ImGuiMod_Super on Mac and ImGuiMod_Ctrl otherwise. (ocornut#456)

commit fd408c9
Author: ocornut <[email protected]>
Date:   Thu Sep 22 18:58:33 2022 +0200

    Renamed and merged keyboard modifiers key enums and flags into a same set:. ImGuiKey_ModXXX -> ImGuiMod_XXX and ImGuiModFlags_XXX -> ImGuiMod_XXX. (ocornut#4921, ocornut#456)

    Changed signature of GetKeyChordName() to use ImGuiKeyChord.
    Additionally SetActiveIdUsingAllKeyboardKeys() doesn't set ImGuiKey_ModXXX but we never need/use those and the system will be changed in upcoming commits.

# Conflicts:
#	imgui.cpp
#	imgui.h
#	imgui_demo.cpp
fangshun2004 added a commit to fangshun2004/imgui that referenced this pull request Sep 30, 2022
commit 5884219
Author: cfillion <[email protected]>
Date:   Wed Sep 28 23:37:39 2022 -0400

    imgui_freetype: Assert if bitmap size exceed chunk size to avoid buffer overflow. (ocornut#5731)

commit f2a522d
Author: ocornut <[email protected]>
Date:   Fri Sep 30 15:43:27 2022 +0200

    ImDrawList: Not using alloca() anymore, lift single polygon size limits. (ocornut#5704, ocornut#1811)

commit cc5058e
Author: ocornut <[email protected]>
Date:   Thu Sep 29 21:59:32 2022 +0200

    IO: Filter duplicate input events during the AddXXX() calls. (ocornut#5599, ocornut#4921)

commit fac8295
Author: ocornut <[email protected]>
Date:   Thu Sep 29 21:31:36 2022 +0200

    IO: remove ImGuiInputEvent::IgnoredAsSame (revert part of 839c310), will filter earlier in next commit. (ocornut#5599)

    Making it a separate commit as this leads to much indentation change.

commit 9e7f460
Author: ocornut <[email protected]>
Date:   Thu Sep 29 20:42:45 2022 +0200

    Fixed GetKeyName() for ImGuiMod_XXX values, made invalid MousePos display in log nicer.  (ocornut#4921, ocornut#456)

    Amend fd408c9

commit 0749453
Author: ocornut <[email protected]>
Date:   Thu Sep 29 19:51:54 2022 +0200

    Menus, Nav: Fixed not being able to close a menu with Left arrow when parent is not a popup. (ocornut#5730)

commit 9f6aae3
Author: ocornut <[email protected]>
Date:   Thu Sep 29 19:48:27 2022 +0200

    Nav: Fixed race condition pressing Esc during popup opening frame causing crash.

commit bd2355a
Author: ocornut <[email protected]>
Date:   Thu Sep 29 19:25:26 2022 +0200

    Menus, Nav: Fixed using left/right navigation when appending to an existing menu (multiple BeginMenu() call with same names). (ocornut#1207)

commit 3532ed1
Author: ocornut <[email protected]>
Date:   Thu Sep 29 18:07:35 2022 +0200

    Menus, Nav: Fixed keyboard/gamepad navigation occasionally erroneously landing on menu-item in parent when the parent is not a popup. (ocornut#5730)

    Replace BeginMenu/MenuItem swapping g.NavWindow with a more adequate ImGuiItemFlags_NoWindowHoverableCheck.
    Expecting more subtle issues to stem from this.
    Note that NoWindowHoverableCheck is not supported by IsItemHovered() but then IsItemHovered() on BeginMenu() never worked: fix should be easy in BeginMenu() + add test is IsItemHovered(), will do later

commit d5d7050
Author: ocornut <[email protected]>
Date:   Thu Sep 29 17:26:52 2022 +0200

    Various comments

    As it turns out, functions like IsItemHovered() won't work on an open BeginMenu() because LastItemData is overriden by BeginPopup(). Probably an easy fix.

commit e74a50f
Author: Andrew D. Zonenberg <[email protected]>
Date:   Wed Sep 28 08:19:34 2022 -0700

    Added GetGlyphRangesGreek() helper for Greek & Coptic glyph range. (ocornut#5676, ocornut#5727)

commit d17627b
Author: ocornut <[email protected]>
Date:   Wed Sep 28 17:38:41 2022 +0200

    InputText: leave state->Flags uncleared for the purpose of backends emitting an on-screen keyboard for passwords. (ocornut#5724)

commit 0a7054c
Author: ocornut <[email protected]>
Date:   Wed Sep 28 17:00:45 2022 +0200

    Backends: Win32: Convert WM_CHAR values with MultiByteToWideChar() when window class was registered as MBCS (not Unicode). (ocornut#5725, ocornut#1807, ocornut#471, ocornut#2815, ocornut#1060)

commit a229a7f
Author: ocornut <[email protected]>
Date:   Wed Sep 28 16:57:09 2022 +0200

    Examples: Win32: Always use RegisterClassW() to ensure windows are Unicode. (ocornut#5725)

commit e0330c1
Author: ocornut <[email protected]>
Date:   Wed Sep 28 14:54:38 2022 +0200

    Fonts, Text: Fixed wrapped-text not doing a fast-forward on lines above the clipping region. (ocornut#5720)

    which would result in an abnormal number of vertices created.

commit 4d4889b
Author: ocornut <[email protected]>
Date:   Wed Sep 28 12:42:06 2022 +0200

    Refactor CalcWordWrapPositionA() to take on the responsability of minimum character display. Add CalcWordWrapNextLineStartA(), simplify caller code.

    Should be no-op but incrementing IMGUI_VERSION_NUM just in case.
    Preparing for ocornut#5720

commit 5c4426c
Author: ocornut <[email protected]>
Date:   Wed Sep 28 12:22:34 2022 +0200

    Demo: Fixed Log & Console from losing scrolling position with Auto-Scroll when child is clipped. (ocornut#5721)

commit 12c0246
Author: ocornut <[email protected]>
Date:   Wed Sep 28 12:07:43 2022 +0200

    Removed support for 1.42-era IMGUI_DISABLE_INCLUDE_IMCONFIG_H / IMGUI_INCLUDE_IMCONFIG_H. (ocornut#255)

commit 73efcec
Author: ocornut <[email protected]>
Date:   Tue Sep 27 22:21:47 2022 +0200

    Examples: disable GL related warnings on Mac + amend to ignore list.

commit a725db1
Author: ocornut <[email protected]>
Date:   Tue Sep 27 18:47:20 2022 +0200

    Comments for flags discoverability + add to debug log (ocornut#3795, ocornut#4559)

commit 325299f
Author: ocornut <[email protected]>
Date:   Wed Sep 14 15:46:27 2022 +0200

    Backends: OpenGL: Add ability to #define IMGUI_IMPL_OPENGL_DEBUG. (ocornut#4468, ocornut#4825, ocornut#4832, ocornut#5127, ocornut#5655, ocornut#5709)

commit 56c3eae
Author: ocornut <[email protected]>
Date:   Tue Sep 27 14:24:21 2022 +0200

    ImDrawList: asserting on incorrect value for CurveTessellationTol (ocornut#5713)

commit 04316bd
Author: ocornut <[email protected]>
Date:   Mon Sep 26 16:32:09 2022 +0200

    ColorEdit3: fixed id collision leading to an assertion. (ocornut#5707)

commit c261dac
Author: ocornut <[email protected]>
Date:   Mon Sep 26 14:50:46 2022 +0200

    Demo: moved ShowUserGuide() lower in the file, to make main demo entry point more visible + fix using IMGUI_DEBUG_LOG() macros in if/else.

commit 51bbc70
Author: ocornut <[email protected]>
Date:   Mon Sep 26 14:44:26 2022 +0200

    Backends: SDL: Disable SDL 2.0.22 new "auto capture" which prevents drag and drop across windows, and don't capture mouse when drag and dropping. (ocornut#5710)

commit 7a9045d
Author: ocornut <[email protected]>
Date:   Mon Sep 26 11:55:07 2022 +0200

    Backends: WGPU: removed Emscripten version check (currently failing on CI, ensure why, and tbh its redundant/unnecessary with changes of wgpu api nowadays)

commit 83a0030
Author: ocornut <[email protected]>
Date:   Mon Sep 26 10:33:44 2022 +0200

    Added ImGuiMod_Shortcut which is ImGuiMod_Super on Mac and ImGuiMod_Ctrl otherwise. (ocornut#456)

commit fd408c9
Author: ocornut <[email protected]>
Date:   Thu Sep 22 18:58:33 2022 +0200

    Renamed and merged keyboard modifiers key enums and flags into a same set:. ImGuiKey_ModXXX -> ImGuiMod_XXX and ImGuiModFlags_XXX -> ImGuiMod_XXX. (ocornut#4921, ocornut#456)

    Changed signature of GetKeyChordName() to use ImGuiKeyChord.
    Additionally SetActiveIdUsingAllKeyboardKeys() doesn't set ImGuiKey_ModXXX but we never need/use those and the system will be changed in upcoming commits.

# Conflicts:
#	docs/CHANGELOG.txt
#	imgui.h
#	imgui_demo.cpp
BramblePie pushed a commit to BramblePie/imgui that referenced this pull request Oct 26, 2022
…ill filter earlier in next commit. (ocornut#5599)

Making it a separate commit as this leads to much indentation change.
@wolfpld
Copy link
Contributor

wolfpld commented Oct 26, 2022

There seems to be some discrepancy with how different types of key strokes are processed. When the focus is in a text input box, and a key is held down, repeated key events will be reported, producing e.g. "aaaaaaaaa" input. However, if one would want to remove the entered text and holds down backspace or delete keys, only the first input event is reported. If the mouse is moved (i.e. when screen redraw requests are being issued) whilst the backspace/delete key is pressed, the repeated key press events are reported as expected.

(For example, open https://tracy.nereid.pl/, click on the "Find zone" button on top. Then press and hold any key to enter text. Then press backspace and observe only the first event is handled. Then move the mouse, while still holding backspace.)

@ocornut
Copy link
Owner

ocornut commented Oct 26, 2022

Character Input events repeats are submitted by the backend.
In the case of holding backspace from the point of view of backend there's one key down event, and the widget logic handle repeat.

(1) We could make some effort at making IsKeyPressed() with "repeat" automatically request refresh at a future expected "repeat" point of time when the key is down.

However it would be problematic the same way just using IsKeyDown(). Imagine that sorts of code:

if (IsKeyDown(ImGuiKey_Left))
    scroll_x -= speed * io.Deltatime.

From the backend point of view there's no changes.
(2.A) We'd have to either request user code to explicitly request a refresh, either (2.B) require application to keep refreshing when any mouse button or keyboard key is held.

Since not all UI will animate and it is more reasonable to request animating user code to notify the system, I guess we could implement (1) and (2.A). They both boil down to the same thing: outputting one timer (in second) and one counter (in frames) which may be set to request refresh, which gets us back to #4076.

(2.B) is the "lazy" workaround which will solve most cases automatically but at higher refresh cost. It has the advantage that you can use it today without any mod to core lib. Other solutions will need adding that counter/timer. If we ignore the backend+examples side of #4076 the core-imgui side boils down to a few dozens lines.

EDIT
The pseudo-code for (1) would be:

IsKeyPressed()
{
   if (key is down && repeat wanted)
   { 
      calculate time to next "repeat" event
      schedule a refresh (io.WantRenderRemainingTime = ImMin(io.WantRenderRemainingTime, time_to_next_repeat_event);
   } 
}

@wolfpld
Copy link
Contributor

wolfpld commented Oct 26, 2022

In ImGui_ImplGlfw_KeyCallback there is an early exit no-op path:

    if (action != GLFW_PRESS && action != GLFW_RELEASE)
        return;

So GLFW_REPEAT events are explicitly ignored for all keys.

I'm not sure I understand the solutions you propose.

  1. Non-backspace keys already are already properly repeating.
  2. The input field is already being able to be successfully updated when new text is added, without an explicit refresh request, or keeping the refresh awake all the time.

It's just the backspace and delete keys that break things for some reason.

@ocornut
Copy link
Owner

ocornut commented Oct 26, 2022

So GLFW_REPEAT events are explicitly ignored for all keys.

This is system repeat rate which we don't use (vs we uses varying repeat rate, and app/user can use varying repeat rate too).

It's just the backspace and delete keys that break things for some reason.

It happens with all KEYS (e.g. I see same problem holding left or right arrow). For CHARACTERS we always follow OS repeat which are sending events.

Lazy solution: you keep refreshing if focused and any key is held.
This is not optimal but easy to implement today. (e.g. iterate ImGuiKey_NamedKey_BEGIN to ImGuiKey_NamedKey_END and check IsKeyDown()).

Correct solution Dear ImGui needs to output fields for this, e.g.

  • bool io.WantRender : want render next frame (this is what you are currently computing "externally")
  • float io.WantRenderAfterTimeElapsed want render after giving time amount.

For example,

  • if you enable cursor blinking (*) we'll set io.WantRenderAfterTimeElapsed to the next frame where cursor would change.
  • if you use IsKeyPressed() with repeat and key is down, we'll set io.WantRenderAfterTimeElapsed to next repeat event time.
  • (*) in reality for blinking I know of a faster solution.

EDIT If you look at e.g. /pull/4076/files you'll find one possible implementation of that. (I don't think the PR has the adequate "final" API but should help the grok the idea, which is pretty simple).

@jlmxyz
Copy link

jlmxyz commented Nov 13, 2022

Hi,
I took the MariusH87/master branch and rebased my local branch to ocornut/docking
at this time the io.WantRenderAfterTimeElapsed is not yet implemented

I made the following modifications :

diff --git a/examples/example_glfw_opengl3/main.cpp b/examples/example_glfw_opengl3/main.cpp
index e7d43d1e..db53464e 100644
--- a/examples/example_glfw_opengl3/main.cpp
+++ b/examples/example_glfw_opengl3/main.cpp
@@ -118,8 +118,14 @@ int main(int, char**)
         // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.
         // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data.
         // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
-        glfwPollEvents();
-
+        if(ImGui::GetIO().WantRender)
+          {
+            glfwPollEvents();
+          }
+        else
+          {
+            glfwWaitEventsTimeout(2);
+          }
         // Start the Dear ImGui frame
         ImGui_ImplOpenGL3_NewFrame();
         ImGui_ImplGlfw_NewFrame();

  1. this indeed allow reduce CPU usage to the min
  2. since no io.WantRenderAfterTimeElapsed, all animated widgets aren't working (updated on timeouts)

in my opinion bool io.WantRender and float io.WantRenderAfterTimeElapsed are duplicated :
io.WantRenderAfterTimeElapsed should be set to 0.0 if immediate re-render is wanted, else should be set to some meaningful value if timed animation, else should be set to MAX(float) if nothing need updating....

some setting function should wrap the WantRenderAfterTimeElapsed and looks like

    float SetWantRenderAfterTimeElapsed (float aNewTime)
    {
          float res = aNewTime
          if (aNewTime < g.IO.WantRenderAfterTimeElapsed)
                  g.IO.WantRenderAfterTimeElapsed = aNewTime
          else
                res = aNewTime - g.IO.WantRenderAfterTimeElapsed
    }

this rise the issue of a widget that require rendering at special interval
ex : blinking cursor : every 1s
: animated ploting : variable (depends on refresh rate let say every 2s)
: video frame : 1/30s....

so imagine we have theses 3 widgets, that videoFrame asked for a refresh 1/30s later, and cursor want to update the refresh also, both can be out of sync...

maybe a better solution is to use C++ timers like https://en.cppreference.com/w/cpp/thread/sleep_for

one issue is that framework like glfwWaitEventsTimeout are second timeout based... so for a 1/30 video frame update, it can't be used....

one option could be not rely on underlying framework but re-implement a wrapper on top it, something like that (return True if redraw is required)

bool ImGui::EventProccess()
{
      if (!window.isVisible())
           return False;
     glfwPollEvents(); //will call the callback if new event are available
      if (g.IO.NextTimeout.elapsed())
            return True;
      if ( ! g.InputEventsQueue.empty())
           return True;
      return False;
}

void main()
{
    ....
    if ( ImGui::EventProccess())
   {
       doTheDrawing();
   }
}

issues :

  1. will only prevent redarw (not endless looping in main)
  2. might require some vector to store several timeout
    solution :
  3. user selectable "refresh rate" (eg check for input every XXXms, which is already done using the VSync

@jlmxyz
Copy link

jlmxyz commented Dec 13, 2022

I'm thinking of a thing....

instead of a ImGui::GetIO().WantRender boolean
why not provide a ImGui::GetIO().NextUpdateTime as a std::chrono::time_point?

the idea behind this is to move time related operations in main app
the widget keep record of the time before a graphical update (ex : a blinking cursor, changing each second, will store the information that it must re-display itself at std::chrono::system_clock.now() + xxxx , where xxxx correspond to a second at system_clock level (might be better to use high_resolution_clock?) and update the NextUpdateTime if NextUpdateTime > later than computed NextUpdateTime (the widget that require the shortest update cause the wake up)
other widget that require update at a certain time do the same

at end of re-display, the ImGui::GetIO().NextUpdateTime now contain the time at which the re-display must occurs to be certain that the displayed content will be accurate

now, back to the event handler part... it's up to the app programmer to create the logic to handle both event and re-display, for example GLFW allow timeout at second resolution, so you can't rely on timeout to return, you might require to start the event handling in a thread and have this thread wake up the GUI display thread... SDL allow ms timeout, so it might be used since at 60fps a re-display occurs each 16ms....

so, let's back after a re-display is started (either because an event occurred or because the app slept and woke at NextUpdateTime) each widget will look at current time, and the next time they need to update and recompute the NextUpdateTime....

you might want to add a jitters information, for example a blinking cursor don't require accurate wake-up... a jitters of 100ms won't be noticed by user, same for a plot widget.... but a video frame widget need accurate wake-up
so let say we have a blinking cursor requesting to wake-up at XXXX+-100ms, and the plot widget ask for a re-display at XXXX+50ms +-100ms, one of theses two times can be selected, we could use the average time so that the jitters is accurate to none, but minimized... but now the video-render widget ask for a re-display at XXXX-10ms +-0 then the NextUpdateTime should be set to XXXX-10ms

redisplay occurs then at XXXX-10ms and either the blinking choose to sync in advance, either it will ask for a new wakeup... I've no idea at which is best....

Don't know if we can rely on display VSync for timing and retrieve VSync Hz info... if yes, NextUpdateTime could be an int in 1/VSyncHz, and we can slimply skeep redisplay and just sleep current thread for 1/VSyncHz until an event occurs or NextUpdateTime-- == 0....

using this approach let the app designer choose how to perform the power saving mode...

*** EDIT ***
this is almost what @ocornut was saying when proposing io.WantRenderAfterTimeElapsed so I think that we can drop the WantRender bool and only provide the time information...

*** EDIT 2 ***
according to https://www.glfw.org/docs/3.2/input_guide.html#events glfwWaitEventsTimeout has floating point timeout.... so we can avoid thread and use glfwWaitEventsTimeout(NextUpdateTime - std::chrono::system_clock.now()) to be either re-displayed by a event or because the widget require it...

@MariusH87
Copy link
Author

I see your point with the NextUpdateTime. But what I don't see is how this negates the need for WantRender. WantRender is supposed to tell me if something in ImGui changed so we're in need of a new render. I think this is a separate thing from the NextUpdateTime used for e.g. animations etc.

@sergeyn
Copy link
Contributor

sergeyn commented Dec 14, 2022

I see your point with the NextUpdateTime. But what I don't see is how this negates the need for WantRender.

It negates the need because setting WantRender to true is same as setting NextUpdateTime to 0. There's no need for 2 things to control the same functionality.

@MariusH87
Copy link
Author

Okay, but as long as ImGui does not drive any kind of animations internally (correct me if I'm wrong), is this not more of an application thing the NextUpdateTime? We are running full on lazy, and am not updating anything before some kind of input occurs. Is not WantRender more in line with others in ImGui like WantMouseCapture etc.. just to keep ImGui as simple as possible just with a value telling when it needs update or other? All animations are driven by own update logic in the application.

@wolfpld
Copy link
Contributor

wolfpld commented Dec 21, 2022

It happens with all KEYS (e.g. I see same problem holding left or right arrow). For CHARACTERS we always follow OS repeat which are sending events.

I understood the problem correctly only after implementing a custom backend that had to deal with keymaps. So, for the sake of future people encountering this issue:

Text input is hard. You need to have two separate systems for handling keyboard events.

The first system is simple and only represents the state of keys being pressed. So, for example, you have keys 'Q', 'W', or 'E'. There are no separate lowercase keys for 'q', 'w', or 'e', as the physical key can only be pressed or not, with no influence by the external state.

The second system has to take into account all the possible key mappings. The most basic mapping would be key 'A' producing the character 'a'. However, the sequence of keys 'Shift' + 'A' would produce the character 'A'. Or 'Alt' + 'A' would result in 'ą' when the Polish keyboard layout is enabled. The composition engine is not limited to simultaneous key presses. For example, the sequence of separate key presses 'Shift' + '`' (~), and then 'a' also produces 'ą' on Windows. You can probably imagine this is even more complex with Arabic or Asian text. Or maybe there's no keyboard at all. What if text input is provided by machine recognition of handwriting? ImGui doesn't care about all these nuances and simply expects to receive Unicode characters.

The original problem results from ImGui combining both systems to handle input box entry. The complex composition system supports key repeat. The simple key-pressed system, which is used for keys such as backspace, enter, or the navigation arrows, does not.

It may be worth mentioning that keyboard layouts are a thing (e.g., QWERTY vs. AZERTY). I'm not sure on which layer the scancode translation would be done. The hardware doesn't care about what is printed on keycaps, keyboard layouts are an OS-level feature. Would the simple key-pressed-or-not system receive the 'Q' key when 'A' is pressed, because the layout is AZERTY? Does it depend on the underlying OS or backend implementation? What would be the scancode for the 'ą/ę' key, which is present in the layout mimicing the traditional Polish typewriter key arrangement?

@ocornut
Copy link
Owner

ocornut commented Dec 24, 2022

@wolfpld Currently away but replying on my mobile.
An extra problem for eg. Shortcut handling is that most systems don’t emit translated character information while holding mods. This is why we need to use translation mapping at Key level too.

Since 1.87 (i think) we clarified and finished aligning all backends so that they are responsible for submitting translated Keys. So when you press CTRL+A the A key should match your current keyboard layout.
The limitation of this system is that it requires the translated keys to be part of our enum. So it allows handling CTRL+A matching layout but not eg. CTRL+ą.

SDL differenciate scancodes (keyboard physical location) from keycodes (after translation) but I think it would be equally unable to handle a CTRL+ combination with any character.

But otherwise if you exclude the specific case of mods being held, all systems are sending Characters (eg: ą/ę) that can be used for text inputs and will work with any languages.

Win32 menu handling/accelerators systems are using low-level functions to obtains the right characrer even when a keyboard modifier is held. I am pretty sure we could do the same using Win32 api but its unlikely to be possible with SDL and GLFW.

@jlmxyz
Copy link

jlmxyz commented Jan 7, 2023

@ocornut I'm trying to do a draft proposal using time, and I notice that there is lot of time information in imGui
most of the time computation is computed using g.Time += g.IO.DeltaTime; which in turn is a constant DeltaTime = 1.0f / 60.0f;
=> a display can not be SYNC at 60fps
=> a display sync isn't reliable timing info
=> a redraw can take longer than 1/60s so there can be a time drift...

  1. is there a reason of not using C++11 std::chrono?
  2. can I make a refactoring using real hardware clocks? using std::chrono::system_clock?

@ocornut
Copy link
Owner

ocornut commented Jan 7, 2023

Thank you for the offer but please don’t draft more proposals. We don’t need more contents/PR we need higher-quality PRs.
Your points above seem mistaken; delta time is variable; there’s no reason for drifting; we don’t use C++ standard library.

Between the work of Corentin and some of the discussion with Bartosz (with perhaps amends to come) I have enough good reference to use for an official version of it. I am just lacking bandwidth and support and need to prioritize work.

I can see there’s increased pressure for this to be in mainline and I imagine we’ll do the work for it in 1.9x.

@jlmxyz
Copy link

jlmxyz commented Jan 7, 2023

ok, as you want.... I see now that DeltaTime is computed by SDL/GLFW.... I know that the STL isn't the most interesting C++ library (in particular string and unicode support is just BIIIIP), but not sure that SDL/GLFW/... are good approach also... they aren't consistent, don't give access to high precision clock... some libs are better than others and I would say that using the STL when the feature is available is better when the design is well done and provide good system portability...

@Seneral
Copy link

Seneral commented Mar 31, 2024

@ocornut I am also interested in this, am currently doing a normal workaround like this:

  • On event or when my application needs updates, I update ImGui with one or more new frames (and then render)
  • And separately, depending on how fast I need my main content to update, I render the existing UI render data
    (my "main content" here is OpenGL code that I call from within callback draw commands, works really well to integrate both 2D and 3D scenes into the UI)

This works, but of course doesn't address the "blinking cursor issue" - that of visually changing events that traditionally require a full ImGui update every frame. Especially when my main content is not visible and I only update on events.

First, I want to propose the idea that layout changes should always either be initiated by a user action, or a result of a process that the user expects (e.g. the code will explicitly request the UI to update).
Visual changes, like a cursor flashing, animations, or even labels of simple, fast updating data, should not need a full ImGui frame to update.
Instead, if you have something that only changes visually, the workflow should be to add a callback draw command and draw it on-demand - e.g. for a blinking cursor, you can then render based on when it's drawn.

Here's a quick and dirty demo for a label that changes around 120 times a second that now is updated even when the UI stack only updates on user input events (typical GLFW loop checking for InputEventsQueue.empty()), else just renders existing draw data).

The label:

ImGui::AlignTextToFramePadding();
static OnDemandState frameLabel = AddOnDemandText("Frame 00000");
ImGui::GetWindowDrawList()->AddCallback([](const ImDrawList* dl, const ImDrawCmd* dc)
{
    RenderOnDemandText(&GetUI().onDemandDrawList, frameLabel, "Frame %d", GetApp().frameNumber);
}, nullptr);
// Instead of:
//ImGui::Text("Frame %d", GetApp().frameNumber);

Custom Text implementation (simplified for my use case):

struct OnDemandState
{
    ImVec2 pos;
    ImVec2 clipMin, clipMax;
    ImFont *font;
    float fontSize;
};

OnDemandState AddOnDemandItem(ImGuiID id, const ImRect &bb)
{
    OnDemandState state;
    state.pos = bb.Min;
    state.clipMin = bb.Min;
    state.clipMax = bb.Max;
    ImGui::ItemSize(bb, 0.0f);
    ImGui::ItemAdd(bb, id);
    return state;
}

OnDemandState AddOnDemandText(const char* maxText)
{
    ImGuiWindow* window = ImGui::GetCurrentWindow();
    if (window->SkipItems)
        return {};
    const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
    const ImVec2 text_size = ImGui::CalcTextSize(maxText, NULL, true, 0.0f);
    OnDemandState state = AddOnDemandItem(ImGui::GetID(maxText), ImRect(text_pos, text_pos + text_size));
    state.font = ImGui::GetFont();
    state.fontSize = ImGui::GetFontSize();
    return state;
}

void RenderOnDemandText(ImDrawList *drawList, const OnDemandState &state, const char* fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    RenderOnDemandTextV(drawList, state, fmt, args);
    va_end(args);
}

void RenderOnDemandTextV(ImDrawList *drawList, const OnDemandState &state, const char* fmt, va_list args)
{
    const char* text, *text_end;
    ImFormatStringToTempBufferV(&text, &text_end, fmt, args);
    drawList->PushTextureID(state.font->ContainerAtlas->TexID);
    drawList->AddText(state.font, state.fontSize, state.pos, ImGui::GetColorU32(ImGuiCol_Text), text, text_end);
    drawList->PopTextureID();
}

User rendering code:

GetUI().onDemandDrawList._ResetForNewFrame();
GetUI().onDemandDrawList.PushClipRectFullScreen();

// Render 2D UI with callbacks at appropriate places for on-demand items
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());

ImGuiViewport *viewport = ImGui::GetMainViewport();
const bool is_minimized = (viewport->Flags & ImGuiViewportFlags_IsMinimized) != 0;
ImDrawData draw_data = {};
draw_data.Valid = true;
draw_data.CmdListsCount = 0;
draw_data.TotalVtxCount = draw_data.TotalIdxCount = 0;
draw_data.DisplayPos = viewport->Pos;
draw_data.DisplaySize = is_minimized ? ImVec2(0.0f, 0.0f) : viewport->Size;
draw_data.FramebufferScale = ImGui::GetIO().DisplayFramebufferScale;
draw_data.OwnerViewport = viewport;
ImGui::AddDrawListToDrawDataEx(&draw_data, &draw_data.CmdLists, &GetUI().onDemandDrawList);
ImGui_ImplOpenGL3_RenderDrawData(&draw_data);

Of course, this always renders the up-to-date data ontop, a proper implementation would actually render in-place in RenderOnDemand*, but then it would have to directly interface with the render backend - or you do it for your application manually (render-backend-specific) and then emit a ResetRenderState command right after. There's no way to modify the existing draw list with the vertex data already packed and uploaded to the GPU.
Or, each window has it's own onDemandBuffer and the backends just get two cmdLists per window, with the second being modified on-demand by the first. That would not need backend support afaik. Though it would still render ontop of the window itself.

Anyway, this is just a push to consider animations not part of the UI update, but as part of the rendering, and for animating UI code to be aware of that distinction.

But my problem isn't actually the CPU, and this would only help CPU usage, which at 0.1-0.3% for just the GUI is really not a problem for me (I'm very happy with how ImGui performs on the CPU).
No, what I care about is the GPU. I got a new laptop with a 2k 165Hz screen and a good iGPU (780M), but my application absolutely hammers the iGPU to 70% usage even with just an empty window before any event based optimisations.
Needless to say, the fans always kick into high gear as if I was gaming - and the worst part, it wasn't too noticeable on my old laptop, so some devs might not notice it on their setup, but some users will feel the impact much more.

The first proposition would still help anyway, with some animated updates now having screen coordinates associated with it, we could then consider to only render the area of the screen that the animation is part of.
This would definitely require backend support, currently I found no way to tell the OpenGL3 backend to keep glViewport as is but update the clip rect. But I tested the potential benefit of this by using a modified ImGui_ImplOpenGL3_RenderDrawData that clips any draw call not overlapping my custom ondemand clip rect and sets glScissors to the overlap if they do. I use this modified version only for the in-between, 165Hz updates for the small demo label, and I confirmed it's only rendering that part (for both the existing draw UI data and the onDemand draw data).

radeontop reports the bottleneck is still the "clip rectangle" stage of the graphics pipeline, but it dropped from 70% use to 30%. Most other pipelines (fragment based) dropped from 50% to 10%. So there's definitely a lot to gain, though it's still less than what I would have expected from rendering just a tiny part of the screen. Maybe the UI draw calls are batched so well that the CPU clipping doesn't do much, and the glScissors still has to clip and discard a ton of vertices, keeping that the main bottleneck.
If you have any further ideas that I should try, please do tell.

Now one final concern for me is that some events like mouse move would still trigger a full window re-render.
Leaving those at native refresh rate of 165Hz means moving the mouse still consumes 70% of the iGPU without any additional knowledge of whether any content on the screen actually changed. For my part, I'd rather have a higher CPU load, than just blindly rendering it all when not even a hover state changed. Somebody mentioned comparing past and current draw list for changes, but that seems a bit too brutish for me, though I would be able to spare the CPU cycles if it meant lowering GPU usage.

Of course this is all a lot to consider and you might already have your idea on how to address this. Maybe this input will help you, maybe you already decided. Let me know if you want a full code example or anything.

@gslqy
Copy link

gslqy commented Sep 3, 2024

For me, I prefer to provide interfaces like IO.IsDirty and MarkDirty. I'm working on a game editor and have done some research using ImGui. Its API is similar to Unity's GUILayout.

GUILayout::Button(GUIContent content, GUIStyle style, params GUILayoutOption[] options);
GUILayout::Label(GUIContent content, GUIStyle style, params GUILayoutOption[] options);

I adopted Yoga as the layout engine. In each GUILayout::Func, I create a Yoga node, and after Yoga calculates the layout, I pass the position information to ImGui for rendering. It works pretty well and allows me to precisely control the position, size, and other aspects of UI elements. Based on this, it's easy to implement a stack layout.

Currently, I'm troubled by performance issues because Yoga's layout calculation is very time-consuming. My approach is to accumulate the hash of all elements in the current frame and compare it with the previous frame to determine whether re-layout is needed (obviously, this could lead to bugs, such as cases where the hash doesn't change but the UI size does). If ImGui had an interface to indicate whether a redraw is needed, it would be perfect.

I'm sorry that I can't provide the code at the moment, as my current backend uses Qt RHI, and as my research needs evolved, the code became quite messy. If anyone is interested, I will tidy it up when I have some time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants