Update widget state on viewport change

Below, I’ve provided a minimal running example for a problem I encounter frequently in my projects. To reproduce, do the following:

  1. Place the mouse over the border of one of the border widgets.
  2. Press “t” to toggle the viewports of the two renderers.
  3. Press the left mouse button and drag the border of the border widget.
  4. Observe that the border widget being changed is the one that was “selected” before the “t” was pressed, not the one in the current renderer.

The problem occurs, because the widget is in an interactable state when the “t” is pressed. This state is only updated when the mouse is moved. In this example, the mouse is not moved, but the content below the mouse pointer is changed, so the widget state is outdated. I encounter this kind of problem in similar ways for slightly other use cases. What is the preferred way to deal with this problem?

#include <vtkObjectFactory.h>
#include <vtkRenderer.h>
#include <vtkInteractorStyleUser.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkBorderWidget.h>

static double constexpr viewportTop[ 4 ]{ 0., .5, 1., 1. };
static double constexpr viewportBottom[ 4 ]{ 0., 0., 1., .5 };

class InteractorStyle : public vtkInteractorStyleUser
{
public:
  static InteractorStyle * New();
  vtkTypeMacro( InteractorStyle, vtkInteractorStyleUser );

  void setEnvironment( vtkRenderer * renderer1, vtkRenderer * renderer2 )
  {
    m_renderer1 = renderer1;
    m_renderer2 = renderer2;
  }

  void OnChar() override
  {
    if( 't' == Interactor->GetKeyCode() )
    {
      m_toggled = !m_toggled;
      m_renderer1->SetViewport( m_toggled ? viewportBottom : viewportTop );
      m_renderer2->SetViewport( m_toggled ? viewportTop : viewportBottom );
      Interactor->Render();
    }
    else InteractorStyle::OnChar();
  }
  
private:
  vtkRenderer * m_renderer1{};
  vtkRenderer * m_renderer2{};
  bool m_toggled{ false };
};

vtkStandardNewMacro( InteractorStyle );

int main( int, char * [] )
{
  vtkNew< vtkRenderWindow > renderWindow;

  vtkNew< InteractorStyle > interactorStyle;
  vtkNew< vtkRenderWindowInteractor > renderWindowInteractor;
  renderWindowInteractor->SetInteractorStyle( interactorStyle );
  renderWindowInteractor->SetRenderWindow( renderWindow );

  vtkNew< vtkRenderer > renderer1;
  vtkNew< vtkRenderer > renderer2;
  renderer1->SetBackground( 0.3, 0.3, 0.3 );
  renderer2->SetBackground( .5, .5, .5 );
  renderer1->SetViewport( viewportTop );
  renderer2->SetViewport( viewportBottom );
  renderWindow->AddRenderer( renderer1 );
  renderWindow->AddRenderer( renderer2 );

  vtkNew< vtkBorderWidget > borderWidget1;
  vtkNew< vtkBorderWidget > borderWidget2;
  borderWidget1->SetInteractor( renderWindowInteractor );
  borderWidget2->SetInteractor( renderWindowInteractor );
  borderWidget1->CreateDefaultRepresentation();
  borderWidget2->CreateDefaultRepresentation();
  borderWidget1->SetDefaultRenderer( renderer1 );
  borderWidget2->SetDefaultRenderer( renderer2 );
  borderWidget1->SetEnabled( true );
  borderWidget2->SetEnabled( true );

  interactorStyle->setEnvironment( renderer1, renderer2 );

  renderWindowInteractor->Start();

  return EXIT_SUCCESS;
}

Since have been no replies yet, I figured I come up with a suggestion that I couldn’t get to work. Maybe someone can help me out with that approach.

I figured, as the state is not updated, because the content is moved instead of the mouse, one way to deal with this could be to manually trigger a mouse event. I tried calling

InvokeEvent( vtkCommand::MouseMoveEvent );

inside the OnChar() function, but it doesn’t work. So the question would be: How do I effectively trigger a manual MouseMoveEvent?

It helps to think about the flow of events when a mouse move occurs. The VTK render window interactor is notified of mouse events from the OS event system. Such events are then massaged into the VTK’s definition of events ex: vtkCommand::MouseMoveEvent The VTK event-observer system then propagates those through out VTK, to interactor style, widgets, render window, etc.

For the event to be effective, you have to invoke it on the interactor, not on the style. Here’s working code. Also fixed a stack overflow caused by the line of code which executes when the keycode is not t inside your InteractorStyle::OnChar :wink:

// main.cpp
#include <vtkCommand.h>
#include <vtkObjectFactory.h>
#include <vtkRenderer.h>
#include <vtkInteractorStyleUser.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkBorderWidget.h>

static double constexpr viewportTop[ 4 ]{ 0., .5, 1., 1. };
static double constexpr viewportBottom[ 4 ]{ 0., 0., 1., .5 };

class InteractorStyle : public vtkInteractorStyleUser
{
public:
  static InteractorStyle * New();
  vtkTypeMacro( InteractorStyle, vtkInteractorStyleUser );

  void setEnvironment( vtkRenderer * renderer1, vtkRenderer * renderer2 )
  {
    m_renderer1 = renderer1;
    m_renderer2 = renderer2;
  }

  void OnChar() override
  {
    if( 't' == Interactor->GetKeyCode() )
    {
      m_toggled = !m_toggled;
      m_renderer1->SetViewport( m_toggled ? viewportBottom : viewportTop );
      m_renderer2->SetViewport( m_toggled ? viewportTop : viewportBottom );
      Interactor->InvokeEvent(vtkCommand::MouseMoveEvent);
      Interactor->Render();
    }
    else Superclass::OnChar();
  }
  
private:
  vtkRenderer * m_renderer1{};
  vtkRenderer * m_renderer2{};
  bool m_toggled{ false };
};

vtkStandardNewMacro( InteractorStyle );

int main( int, char * [] )
{
  vtkNew< vtkRenderWindow > renderWindow;

  vtkNew< InteractorStyle > interactorStyle;
  vtkNew< vtkRenderWindowInteractor > renderWindowInteractor;
  renderWindowInteractor->SetInteractorStyle( interactorStyle );
  renderWindowInteractor->SetRenderWindow( renderWindow );

  vtkNew< vtkRenderer > renderer1;
  vtkNew< vtkRenderer > renderer2;
  renderer1->SetBackground( 0.0, 0.0, 1.0 );
  renderer2->SetBackground( .5, .5, .5 );
  renderer1->SetViewport( viewportTop );
  renderer2->SetViewport( viewportBottom );
  renderWindow->AddRenderer( renderer1 );
  renderWindow->AddRenderer( renderer2 );

  vtkNew< vtkBorderWidget > borderWidget1;
  vtkNew< vtkBorderWidget > borderWidget2;
  borderWidget1->SetInteractor( renderWindowInteractor );
  borderWidget2->SetInteractor( renderWindowInteractor );
  borderWidget1->CreateDefaultRepresentation();
  borderWidget2->CreateDefaultRepresentation();
  borderWidget1->SetDefaultRenderer( renderer1 );
  borderWidget2->SetDefaultRenderer( renderer2 );
  borderWidget1->SetEnabled( true );
  borderWidget2->SetEnabled( true );

  interactorStyle->setEnvironment( renderer1, renderer2 );

  // render once to show the widgets.
  renderWindow->Render();
  renderWindowInteractor->Start();

  return EXIT_SUCCESS;
}
# CMakeLists.txt
cmake_minimum_required(VERSION 3.12)
project(BorderWidgetDiscourse)
find_package(VTK COMPONENTS
  InteractionStyle
  InteractionWidgets
  RenderingCore
  RenderingOpenGL2
  RenderingUI)
add_executable(main main.cpp)
target_link_libraries(main PRIVATE ${VTK_LIBRARIES})

Tested by:

$ cmake -S . -B out -DVTK_DIR=/path/to/vtk/build
$ cmake --build out
$ ./out/main
1 Like

Thank you very much, it works like a charm!

Thanks, even in a minimal example, I managed to sneak in a really stupid bug :slight_smile: .