In the last tutorial, Use "ncurses" to Draw the Train, we used ncurses to draw our image of choice on a text-based terminal window, and ran into several problems, most of which we have now fixed. The problems remaining are:
Any part of the image off the right-edge of the screen wraps around to the left edge.
When the image reaches the left edge, it doesn't continue off the screen, but instead crashes the program.
This lesson will focus on creating a function, "my_mvaddstr()", instead of using the standard "mvaddstr()" function that is a part of ncurses, which will allow the image to be drawn anywhere on screen without the problems that would otherwise occur when parts of the train are off the screen edges. We'll do this by trimming off the pieces of each line that would otherwise print in the off-screen areas.
We can start by replacing our existing call to "mvaddstr()" with a call to our soon-to-exist new function, "mymvaddstr()":
Replace the Call to "mvaddstr()" with a Call to "my_mvaddstr()"
Create the New "my_mvaddstr()" Function
And now we create the new function, at the bottom of "display.rs". Notice that this fumction does not need to be public to other project modules; it's only used by the "draw_frame()" function in this module.
This should not change the functioning of our program in any way; it just sets up a dummy function to see if the call to the function works. If you compile and run the program, it should behave exactly as it did before.
But now that we have a working skeleton for this function, we can modify it to do some bounds-checking. Let's do that now.
In order to check for the bounds, this new function will need to know the limits of the screen dimensions. It will know that the left-edge is zero, and that the top edge is zero. But we'll need to provide it with the right- and bottom-edges. We could pass those two values from the "main()" function to our "draw_image()" function, and then pass them from the "draw_image()" function to our new "my_mvaddstr()" function. That might even be the best way. But since the ncurses functions are available to all the functions within the "main.rs" module, we can also do it another way: we can simply call the ncurses' getmaxyx() function again from within our new "my_mvaddstr()" function. We'll do it both ways, so that you can see it both ways.
Method 1 - Passing the Screen Boundaries from main() to draw_image() to my_mvaddstr()
Again, you should see no difference, yet, in how your program runs. Now let's try the other method. Make sure to undo the changes you may have made in the Method 1 above before doing the method below.
Method 2 - Getting Screen Boundaries from ncurses in the my_mvaddstr() function
So Which Method?
So which method should we use? With the second method, we have to call the "getmaxyx()" function every time the "my_mvaddstr()" function is called, which likely increases the "expense" of the code, costing more CPU cycles. On the other hand, that means that each time the "my_mvaddstr()" function is called, it gets updated with the size of the terminal window, so that if the terminal window size changes during the running of the program, the "my_mvaddstr()" function will learn about the new size. But the "col" value back in "main()" does not, so that's probably not of much value to us. The second method is also perhaps a bit easier to program, as it doesn't involve passing the two parameters from function to function to function. But back on the first hand, these values are needed back in "main()" to calculte the "row" and "col" values, so we'll need to get the screen size ther in "main()" anyway. I think the best option is probably Mathod 1, because that would limit the "my_mvaddstr()" to having only one point-of-entry for the needed data.
So to get back on-track, make your "main.rs" and "display.rs" files look like the following. (Notice I removed the "#![allow(unused)]" line; that may generate more noise during compilation, but those warnings are there for a reason; let's not close our eyes to them.)
Trim the Line to Fit within the Screen Boundaries
Since we're going to be trimming the line, we need a line that can be trimmed, that is, one that is mutable. Since a slice (&str) is not mutable by nature, we'll have to convert it to an owned String that is mutable by nature:
There are three conditions we need to check:
Is the line being printed off the top edge of the terminal window?
Is any part of the line off the right edge of the terminal window?
Is any part of the line off the left edge of the terminal window?
We'll have a section for each one of those questions in our new function. They are below. Note that we need to make the incoming parameter "col" mutable.
This program should compile and run the image across the screen, coming in from the right edge, and going off the left edge
Except ...
If you use the D51 or C51 train, you'll notice that as soon as the train's nose hits the left edge of the screen, the wheels start turning in reverse. Uh oh.
This is because our math produces a different order of frames, once the "col" variable goes negative. Rats! We'll have to figure out how to re-code that segment of our program. The below should do it: