Viewer calls into state machine pointer events.
diff --git a/skia/viewer/src/main.cpp b/skia/viewer/src/main.cpp
index 5f3b22d..2298242 100644
--- a/skia/viewer/src/main.cpp
+++ b/skia/viewer/src/main.cpp
@@ -112,6 +112,29 @@
     }
 }
 
+rive::Mat2D gInverseViewTransform;
+rive::Vec2D lastWorldMouse;
+static void glfwCursorPosCallback(GLFWwindow* window, double x, double y) {
+    float xscale, yscale;
+    glfwGetWindowContentScale(window, &xscale, &yscale);
+    lastWorldMouse = gInverseViewTransform * rive::Vec2D(x * xscale, y * yscale);
+    if (stateMachineInstance != nullptr) {
+        stateMachineInstance->pointerMove(lastWorldMouse);
+    }
+}
+void glfwMouseButtonCallback(GLFWwindow* window, int button, int action, int mods) {
+    if (stateMachineInstance != nullptr) {
+        switch (action) {
+            case GLFW_PRESS:
+                stateMachineInstance->pointerDown(lastWorldMouse);
+                break;
+            case GLFW_RELEASE:
+                stateMachineInstance->pointerUp(lastWorldMouse);
+                break;
+        }
+    }
+}
+
 void glfwErrorCallback(int error, const char* description) { puts(description); }
 
 void glfwDropCallback(GLFWwindow* window, int count, const char** paths) {
@@ -142,13 +165,11 @@
     static bool gPrevMouseButtonDown = false;
     const bool isDown = ImGui::IsMouseDown(ImGuiMouseButton_Left);
 
-    if (mouse.x == gPrevMousePos.x &&
-        mouse.y == gPrevMousePos.y &&
-        isDown == gPrevMouseButtonDown)
+    if (mouse.x == gPrevMousePos.x && mouse.y == gPrevMousePos.y && isDown == gPrevMouseButtonDown)
     {
         return;
     }
-    
+
     auto evtType = rive::PointerEventType::move;
     if (isDown && !gPrevMouseButtonDown) {
         evtType = rive::PointerEventType::down; // we just went down
@@ -161,10 +182,10 @@
 
     SkMatrix inv;
     (void)ctm.invert(&inv);
-    
+
     // scale by 2 for the DPI of a high-res monitor
     const auto pt = inv.mapXY(mouse.x * 2, mouse.y * 2);
-    
+
     const int pointerIndex = 0; // til we track more than one button/mouse
     rive::PointerEvent evt = {
         evtType,
@@ -203,6 +224,8 @@
     }
 
     glfwSetDropCallback(window, glfwDropCallback);
+    glfwSetCursorPosCallback(window, glfwCursorPosCallback);
+    glfwSetMouseButtonCallback(window, glfwMouseButtonCallback);
     glfwMakeContextCurrent(window);
     if (gl3wInit() != 0) {
         fprintf(stderr, "Failed to make initialize gl3w.\n");
@@ -295,12 +318,17 @@
 
             rive::SkiaRenderer renderer(canvas);
             renderer.save();
-            renderer.align(rive::Fit::contain,
-                           rive::Alignment::center,
-                           rive::AABB(0, 0, width, height),
-                           artboardInstance->bounds());
 
-            post_mouse_event(artboardInstance.get(), canvas->getTotalMatrix());
+            rive::Mat2D viewTransform;
+            renderer.computeAlignment(viewTransform,
+                                      rive::Fit::contain,
+                                      rive::Alignment::center,
+                                      rive::AABB(0, 0, width, height),
+                                      artboard->bounds());
+            renderer.transform(viewTransform);
+            // Store the inverse view so we can later go from screen to world.
+            rive::Mat2D::invert(gInverseViewTransform, viewTransform);
+            // post_mouse_event(artboard.get(), canvas->getTotalMatrix());
 
             artboardInstance->draw(&renderer);
             renderer.restore();
@@ -390,8 +418,8 @@
                 ImGui::Columns(1);
             }
             ImGui::End();
-            
             test_messages(artboardInstance.get());
+
         } else {
             ImGui::Text("Drop a .riv file to preview.");
         }