youtube

Dear, ImGui

`Dear, ImGui’ is a direct-mode gui library that can be rendered using OpenGL, DirectX and other 3d apis. direct-mode means that everything on screen is re-created every frame. This means that there is no need of a view-model diff/patch library to minimize changes to a retained view - such as /react/ or /snabbdom/ in the browser.

The result is very simple descriptive code to generate the view and easy composition of ux controls into a 3D scene.

view

Collect the renderers

vector < observable < shared_ptr < Model >>> renderers ;

Render once per frame, using latest ViewModel

with_latest_from() tracks the latest ViewModel and calls the take_at function to extract the ViewModel during each frame.

// render analysis renderers . push_back ( frames | with_latest_from ( rxu :: take_at < 1 > (), viewModels ) | // ...

Verify that the rendering is on the main thread

auto renderthreadid = this_thread :: get_id (); if ( mainthreadid != renderthreadid ) { cerr << "render on wrong thread!" << endl ; terminate (); }

Create a Window with a default size and position. When moved and resized the position is stored in imgui.ini and that stored value is used the next time the app is opened

EDIT: This tweet caused me to update to the pattern below. The UNWIND handles cleanup on exception. but is dismiss() ed when there is no exception. I tried a couple of different patterns, but I do not like any of them.

ImGui :: SetNextWindowSize ( ImVec2 ( 200 , 100 ), ImGuiSetCond_FirstUseEver ); if ( ImGui :: Begin ( "Live Analysis" )) { RXCPP_UNWIND ( End , [](){ ImGui :: End (); }); // draw window contents End . dismiss (); } ImGui :: End ();

Draw two labels in the window

ImGui :: TextWrapped ( "url: %s" , URL . c_str ()); ImGui :: Text ( "Now: %s, Total Tweets: %d" , utctextfrom (). c_str (), m . total );

Create a collapsing section in the window and open it by default

if ( ImGui :: CollapsingHeader ( "Tweets Per Minute (windowed by arrival time)" , ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_DefaultOpen )) { // ... }

Convert the history of overlapping tweets-per-minute groups into a vector of floats using Range-v3.

vector < float > tpm ; // Range-v3 tpm = m . tweetsperminute | ranges :: view :: transform ([]( int count ){ return static_cast < float > ( count );});

Plot the floats in a chart to visualize tweets-per-minute over time.

ImVec2 plotextent ( ImGui :: GetContentRegionAvailWidth (), 100 ); ImGui :: PlotLines ( "" , & tpm [ 0 ], tpm . size (), 0 , nullptr , 0.0 f , fltmax , plotextent );

the complete expression

// render analysis renderers . push_back ( frames | with_latest_from ( rxu :: take_at < 1 > (), viewModels ) | tap ([ = ]( const ViewModel & vm ){ auto renderthreadid = this_thread :: get_id (); if ( mainthreadid != renderthreadid ) { cerr << "render on wrong thread!" << endl ; terminate (); } auto & m = * vm . m ; auto & words = vm . words ; ImGui :: SetNextWindowSize ( ImVec2 ( 200 , 100 ), ImGuiSetCond_FirstUseEver ); if ( ImGui :: Begin ( "Live Analysis" )) { RXCPP_UNWIND ( End , [](){ ImGui :: End (); }); ImGui :: TextWrapped ( "url: %s" , URL . c_str ()); ImGui :: Text ( "Now: %s, Total Tweets: %d" , utctextfrom (). c_str (), m . total ); // by window if ( ImGui :: CollapsingHeader ( "Tweets Per Minute (windowed by arrival time)" , ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_DefaultOpen )) { vector < float > tpm ; tpm = m . tweetsperminute | ranges :: view :: transform ([]( int count ){ return static_cast < float > ( count );}); ImVec2 plotextent ( ImGui :: GetContentRegionAvailWidth (), 100 ); ImGui :: PlotLines ( "" , & tpm [ 0 ], tpm . size (), 0 , nullptr , 0.0 f , fltmax , plotextent ); } End . dismiss (); } ImGui :: End (); }) | reportandrepeat ());

combine views

rxcpp is lazy. lambda’s are lazy. auto greet = []{cout << "Hello.";} does nothing until called - greet(); . All the rxcpp expressions we have defined thus far are dormant, waiting for a call to subscribe() .

Since all the expressions have been connected in a graph from rxcurl -> parsetweets -> reducers -> models -> viewmodels -> views, the only subscribe() required is to the merged views. This subscribe() will propagate to every observable in the expression.

// subscribe to everything! iterate ( renderers ) | merge ( mainthread ) | subscribe < shared_ptr < Model >> ([]( const shared_ptr < Model >& ){});

main loop

The main loop merely pumps the mainthread rxcpp scheduler (named rl for runloop) and triggers the renderer with_latest_from() expressions by calling sendframe()

// main loop while ( lifetime . is_subscribed ()) { SDL_Event event ; while ( SDL_PollEvent ( & event )) { ImGui_ImplSdlGL3_ProcessEvent ( & event ); if ( event . type == SDL_QUIT ) { lifetime . unsubscribe (); break ; } } if ( ! lifetime . is_subscribed ()) { break ; } ImGui_ImplSdlGL3_NewFrame ( window ); while ( ! rl . empty () && rl . peek (). when < rl . now ()) { rl . dispatch (); } sendframe (); while ( ! rl . empty () && rl . peek (). when < rl . now ()) { rl . dispatch (); } // Rendering glViewport ( 0 , 0 , ( int ) ImGui :: GetIO (). DisplaySize . x , ( int ) ImGui :: GetIO (). DisplaySize . y ); glClearColor ( clear_color . x , clear_color . y , clear_color . z , clear_color . w ); glClear ( GL_COLOR_BUFFER_BIT ); ImGui :: Render (); SDL_GL_SwapWindow ( window ); }

up next

Counting tweets

more on this application

Realtime analysis using the twitter stream API