@@ -136,7 +136,40 @@ ExitStatus App::Application::run() {
136136 const ImVec2 canvas_p0 = ImGui::GetCursorScreenPos ();
137137 const ImVec2 canvas_sz = ImGui::GetContentRegionAvail ();
138138 const auto canvas_p1 = ImVec2 (canvas_p0.x + canvas_sz.x , canvas_p0.y + canvas_sz.y );
139- const ImVec2 origin (canvas_p0.x + canvas_sz.x * 0 .5f , canvas_p0.y + canvas_sz.y * 0 .5f );
139+ // --- PANNING IMPLEMENTATION BEGIN ---
140+ static bool isPanning = false ;
141+ static ImVec2 lastMousePos = {0 .0f , 0 .0f };
142+ static ImVec2 originOffset = {0 .0f , 0 .0f };
143+
144+ // Get mouse position
145+ ImVec2 mousePos = ImGui::GetMousePos ();
146+
147+ // Detect click start only when cursor is inside the graphing area
148+ if (ImGui::IsWindowHovered () && ImGui::IsMouseClicked (ImGuiMouseButton_Left)) {
149+ isPanning = true ;
150+ lastMousePos = mousePos;
151+ }
152+
153+ // Stop panning when mouse released
154+ if (ImGui::IsMouseReleased (ImGuiMouseButton_Left)) {
155+ isPanning = false ;
156+ }
157+
158+ // While dragging, update origin offset
159+ if (isPanning && ImGui::IsMouseDragging (ImGuiMouseButton_Left)) {
160+ ImVec2 delta = ImVec2 (mousePos.x - lastMousePos.x , mousePos.y - lastMousePos.y );
161+ originOffset.x += delta.x ;
162+ originOffset.y += delta.y ;
163+ lastMousePos = mousePos;
164+ }
165+
166+ // Apply offset to origin
167+ const ImVec2 origin (
168+ canvas_p0.x + canvas_sz.x * 0 .5f + originOffset.x ,
169+ canvas_p0.y + canvas_sz.y * 0 .5f + originOffset.y
170+ );
171+ // --- PANNING IMPLEMENTATION END ---
172+
140173 float lineThickness = 6 .0f ;
141174
142175 // --- Thin integer gridlines with adaptive spacing ---
@@ -228,12 +261,13 @@ ExitStatus App::Application::run() {
228261 const double t_step = 0.02 ;
229262
230263 for (t = t_min; t <= t_max; t += t_step) {
231- const double vx = expr_fx.value ();
232- const double vy = expr_gx.value ();
233-
234-
235- ImVec2 screen_pos (origin.x + static_cast <float >(vx * zoom),
236- origin.y - static_cast <float >(vy * zoom));
264+ const double vx = expr_fx.value ();
265+ const double vy = expr_gx.value ();
266+ // The 'origin' variable already includes the pan offset.
267+ ImVec2 screen_pos (
268+ origin.x + static_cast <float >(vx * zoom),
269+ origin.y - static_cast <float >(vy * zoom)
270+ );
237271 points.push_back (screen_pos);
238272 }
239273
@@ -264,11 +298,11 @@ ExitStatus App::Application::run() {
264298
265299 if (parser.compile (func_str, expression)) {
266300 // grid parameters
267- const double x_min = -canvas_sz.x / ( 2 * zoom) ;
268- const double x_max = canvas_sz.x / ( 2 * zoom) ;
269- const double y_min = -canvas_sz.y / ( 2 * zoom) ;
270- const double y_max = canvas_sz.y / ( 2 * zoom) ;
271-
301+ const double x_min = ( -canvas_sz.x * 0 . 5f - originOffset. x ) / zoom ;
302+ const double x_max = ( canvas_sz.x * 0 . 5f - originOffset. x ) / zoom ;
303+ const double y_min = ( -canvas_sz.y * 0 . 5f + originOffset. y ) / zoom ;
304+ const double y_max = ( canvas_sz.y * 0 . 5f + originOffset. y ) / zoom ;
305+
272306 // adaptive step size with performance limit
273307 const double step = std::max (0.025 , 1.5 / zoom);
274308 const ImU32 inequality_color = IM_COL32 (100 , 150 , 255 , 180 );
@@ -346,10 +380,10 @@ ExitStatus App::Application::run() {
346380
347381 if (compile_ok) {
348382 // grid parameters
349- const double x_min = -canvas_sz.x / ( 2 * zoom) ;
350- const double x_max = canvas_sz.x / ( 2 * zoom) ;
351- const double y_min = -canvas_sz.y / ( 2 * zoom) ;
352- const double y_max = canvas_sz.y / ( 2 * zoom) ;
383+ const double x_min = ( -canvas_sz.x * 0 . 5f - originOffset. x ) / zoom ;
384+ const double x_max = ( canvas_sz.x * 0 . 5f - originOffset. x ) / zoom ;
385+ const double y_min = ( -canvas_sz.y * 0 . 5f + originOffset. y ) / zoom ;
386+ const double y_max = ( canvas_sz.y * 0 . 5f + originOffset. y ) / zoom ;
353387 const double step = std::max (0.008 , 1.0 / zoom); // dynamic step based on zoom level
354388
355389 const ImU32 implicit_color = IM_COL32 (64 , 199 , 128 , 255 );
@@ -473,12 +507,20 @@ ExitStatus App::Application::run() {
473507 exprtk::parser<double > parser;
474508 parser.compile (function, expression);
475509
476- for (x = -canvas_sz.x / (2 * zoom); x < canvas_sz.x / (2 * zoom); x += 0.05 ) {
477- const double y = expression.value ();
478-
479- ImVec2 screen_pos (origin.x + x * zoom, origin.y - y * zoom);
480- points.push_back (screen_pos);
481- }
510+ // Calculate the visible x-range in world-space, accounting for the pan
511+ const float world_x_min = (-canvas_sz.x * 0 .5f - originOffset.x ) / zoom;
512+ const float world_x_max = ( canvas_sz.x * 0 .5f - originOffset.x ) / zoom;
513+
514+ // Evaluate the function across the correct visible world-range
515+ for (x = world_x_min; x <= world_x_max; x += 0.05 ) {
516+ const double y = expression.value ();
517+ // The 'origin' variable already includes the pan offset.
518+ ImVec2 screen_pos (
519+ origin.x + static_cast <float >(x * zoom),
520+ origin.y - static_cast <float >(y * zoom)
521+ );
522+ points.push_back (screen_pos);
523+ }
482524
483525 draw_list->AddPolyline (points.data (),
484526 points.size (),
0 commit comments