DancingBars app goal0:00
This experiment will be all about animating in the terminal. We're going to make a simple app called DancingBars, and the goal of it will be to have a screen full of bars, vertical bars, dancing across the screen, just moving up and down and animating. You can imagine a sort of bar graph come to life. So we already have our DancingBars app, and we have a run method that we haven't implemented yet, and we're doing run instead of prompt because we are going to be running a while loop and doing our own animation as opposed to waiting and responding for key presses. So let's implement that method now. So I'll say public function run.
So let's implement that method now. So I'll say public function run. Typically, when we're animating in the terminal, it would look something like this, loosely. You'd have a value, say 1. You'd run a loop, and you would increase the value, and you would sleep. And somewhere in here, you would render. And the renderer would then take that value and do something with it, such as a string repeat, a blank line, value, and then, you know, say like, hello. And then as value increased, hello would just move across the screen to the right. So that would be animating in the terminal.
Defining animation state1:18
And then as value increased, hello would just move across the screen to the right. So that would be animating in the terminal. You're manually running a loop, you're increasing some sort of value, and you are printing something to the screen based on that value, and you're manually creating this animation. Now what's cool is Chewy has a bunch of animation utilities, and we're going to take advantage of that so we don't have to do everything quite so manually. So let's talk about our state. We know we're going to need a collection of bars, so we can go ahead and set that up. Say public, and we'll make this an actual collection of bars. We'll need to know how many bars we can actually print across the screen, depending on the
Say public, and we'll make this an actual collection of bars. We'll need to know how many bars we can actually print across the screen, depending on the screen size. So we'll say public int $barCount is equal to 0. And we're going to be animating these bars from 1, the lowest value, up until the max height that is allowed on the screen. So we are going to need to know how high we can make these bars. So we'll say public int $maxBarHeight is equal to 0 as well. So how do we know what the bar count can be and what the max bar height can be? Well, we're going to calculate that in our renderer, and we're going to pass it back.
Calculating bars from terminal2:19
So how do we know what the bar count can be and what the max bar height can be? Well, we're going to calculate that in our renderer, and we're going to pass it back to the state. So we can go ahead and get rid of this. And the first thing we're going to do is we're going to render. We're going to render once, and we're going to collect this information from the renderer. So let's go into our dancingBarsRenderer, and let's decide how wide our bars should be. How wide they should be printed on the screen. And we can always change this, but let's start with barWidth equals 6.
How wide they should be printed on the screen. And we can always change this, but let's start with barWidth equals 6. We also are probably going to want some spacing between our bars, so let's set that now. barSpacing, and for right now we'll set that to 2. So we'll say if promptBarCount is equal to 0, we know, well, we haven't set this yet. So let's set this up. Our total width is going to be equal to the promptTerminal calls minus 2. Our total bar width is going to be equal to the barWidth plus the barSpacing. And so then we can set up these properties. The promptBarCount is going to be equal to the floor of the total width minus the
And so then we can set up these properties. The promptBarCount is going to be equal to the floor of the totalWidth minus the totalBarWidth. So that's how many bars we can fit across the screen. Now what about the maxBarHeight? Well, we've done this before, but we can say promptMaxBarHeight is going to be equal to the promptTerminalLines minus 6. And then we'll just return this. We'll just return a blank screen, but this will be a millisecond before the next render, so you won't even notice.
Setting up Chewy loop3:49
We'll just return a blank screen, but this will be a millisecond before the next render, so you won't even notice. So now we have our barCount set and our maxBarHeight set just by calling render. We're not going through the normal prompt method, so we need to set up the world ourselves, but Chewy has a little helper for us. We can pull in setsUp and resets, and then we can go ahead and say this setup, and this just takes a callback after the setup's ready, and we'll say this dance. And let's just set that up. protected function dance() void. And let's just take a look at setup.
protected function dance void. And let's just take a look at setup. This basically does exactly what prompts does in setting up the world, it just does it outside of the context of the prompt method. That's all it does. So remember when I was talking about animating manually and you have a while loop? Well, Chewy comes with a little loop helper that helps you register loopable items and run a loop automatically for you. So let's pull in use loops, and this gives us the loops method, this loop, and then this takes a callback, function, and we'll say this render.
So let's pull in use loops, and this gives us the loops method, this loop, and then this takes a callback, function, and we'll say this render. So render on each loop. Let's take a look at what this function does. So all this does is it basically runs this while true, it runs the callback provided, and it takes the result, if the result is false, it'll stop the loop, otherwise it'll loop through our loopables, it'll run the tick method, and then it'll sleep for our specified amount of time, by default, 50,000 microseconds. Before we go too far, let's set up a keypress listener for quit so we can escape our program. Listener equals keypress listener for this listen for quit.
Before we go too far, let's set up a keypressListener for quit so we can escape our program. listener equals keypressListener for this listen for quit. And then we'll just pull that in here, use listener, listener once. So we'll listen for this keypress once per loop. So so far, we're rendering once, we're setting up, and then we're starting the dance. But we need to register some loopables. We need to register our bars that we'll be actually animating. So we already have this set up as a property, let's actually instantiate this property. This bars is going to be equal to collect(range(1)), and now we have the barCount from running the render method.
Creating loopable Bar6:10
This bars is going to be equal to collect range one, and now we have the barCount from running the render method. So we can say this barCount, and then we'll map function new Bar. And bar is something I've set up already here. And this is just a loopable item. So it's a simple class that implements Loopable. Loopable's only required method is tick. And then if we pull in the ticks from Chewy, there's a bunch of methods here, but it does include tick, and it automatically runs on tick and increases our tickCount for us. So if we use the ticks trait, we need to implement onTick ourselves.
include tick, and it automatically runs on tick and increases our tick count for us. So if we use the Ticks trait, we need to implement onTick ourselves. But it comes with a slew of methods that are really helpful, such as onNthTick. So you can say on every 10th tick, on every 20th tick, you can run a callback. You can also specify a time that you would like to pause for, or you can check if it is the nth tick. There's a bunch of helpers in there. So for our bar, we're going to make a public animatable value. And animatable comes from Chewy. It's a helper class that allows you to animate values from one value to another, generally.
And animatable comes from Chewy. It's a helper class that allows you to animate values from one value to another, generally numeric values, in a very fluent way. We're going to explore this a little more in a little bit, but for now, let's instantiate our class. So let's say public function __construct, and we are going to say $this->value equals animatable::from($value). We're going to start everything at one, and then we can specify an upper limit. And what's our upper limit? Well, that's actually our maxBarHeight, but we don't have access to that yet, so let's
And what's our upper limit? Well, that's actually our maxBarHeight, but we don't have access to that yet, so let's pass that in here. So here, we'll say this maxBarHeight. Let's go back to our bar. We'll accept that as an argument, protected int maxHeight, and then we'll set that as our upper limit. This maxHeight, and we'll say our lower limit is 1. And that'll do us for now. So basically, we're setting up a value that starts at 1, can only go up to maxHeight,
And that'll do us for now. So basically, we're setting up a value that starts at one, can only go up to maxHeight, and can only go down to one. Let's go back to our App class. So now that we've set up our collection of bars, let's actually register them as loopable items. So we can say this registerLoopables, and we can actually just pull in the collection just like that. So now every time we loop, each of these bars will fire the tick method, and we can decide what we want to do on that.
So now every time we loop, each of these bars will fire the tick method, and we can decide what we want to do on that. So let's actually decide what do we want to do on that. So remember before, on the tick method, it fires onTick. So let's implement onTick. Quick function onTick, and we can say this.value, whenDoneAnimating, and we can provide a callback. So we can say when we're done animating the value to its intended target, we can say this.value.toggle. And this is the power of this Animatable class.
value toggle. And this is the power of this animatable class. It gives you stuff like whenDoneAnimating, toggle. So toggle will toggle between the upper limit and the lower limit of the animatable value. Otherwise, it will continue animating until it reaches the intended target. So this is it. This is our entire bar loopable class. This will animate. So let's actually make it animate in the renderer. So we're going to build up our bars as columns.
Rendering bar columns9:31
So let's actually make it animate in the renderer. So we're going to build up our bars as columns. So if we say calls equals promptBars map, and we're going to say function, and we know that this is a bar, so we can type in that bar. We're going to create a column. So we'll say column equals an empty collection. For each range, one bar, and we have access to that animatable value because it's public. We can say current, so get the current value of the animatable property as n, column, push, this, green, and we'll just make it green for right now, string, repeat, this bar character for the barWidth.
this, green, and we'll just make it green for right now, string, repeat, this bar character for the barWidth. And so we'll need to pull in barWidth so we have access to that variable. So this will build up a column as high as the current value and as wide as the barWidth. Now we need to make sure that there's white space above the column as high as the maxBarHeight should be so that our terminal doesn't shift as we animate these values. So we'll say while columnCount is less than the promptMaxBarHeight, and let's pull in prompt here, column prepend, we're going to put it above the current column, string, repeat, space, equal to the barWidth. And then we can just go ahead and return the column.
repeat, space, equal to the bar width. And then we can just go ahead and return the column. Now we can take our chewy lines helper from columns, and we can pass in the columns, and we can pass in spacing so we can space them out, barSpacing, and we can get the resulting lines, and we can say each this line. And that's it. Now let's create dancingBars. Let's check it out. php lab dancingBars. And we did it.
Randomizing animation behavior11:41
PHP lab dancing bars. And we did it. They're just toggling back and forth. We are animating in the terminal, and that's all the code that it took. Let's take this a step further and randomize some of this animation. So in our bar loopable, instead of saying this value toggle, let's go this value to random. And what that will do is instead of going back and forth between the upper and lower limit, it will choose a target that's between the upper and lower limit and animate to that instead.
limit, it will choose a target that's between the upper and lower limit and animate to that instead. We haven't changed anything else about the render, anything else at all, but we've only changed to random instead of toggle. Let's see what it looks like. Already you can see this is way cooler. We're just randomly animating a bunch of values in the terminal. We've changed so little, and look at how different this animation already looks. This is so cool. Let's take this a step further.
Adding per-bar colors12:33
This is so cool. Let's take this a step further. When the animation is done, let's randomize the colors of the bars. So we can assign a public string $color to the bar, and we have an array of colors here. We have red, green, yellow, everything that prompts supports. And let's create a little method called setNewColor. So let's say public function setNewColor. This returns void, and it sets the new color equal to $this->colors[array_rand($this->colors)]. Now let's do that up here, $this->setNewColor(). And also when we're done animating, well, let's set a new color here, $this->setNewColor().
Now let's do that up here, this setNewColor. And also when we're done animating, well, let's setNewColor here, this setNewColor. So every time it kicks off and every time it starts animating to a new value, it'll be a different color. So for this, we do need to adjust our rendering mechanism. So let's go back to the renderer. Here we have green as the static color that we're rendering, but we actually are going to now render this as barColor instead. So it'll render as the color that we intend. Let's see what it looks like.
Tuning timing and layout13:43
So it'll render as the color that we intend. Let's see what it looks like. This is so cool. We've adjusted so little, it's an entirely new animation. Amazing. Let's adjust something else. Let's say when you're done animating, pause for 10 ticks until you begin animating again. So we go back to our bar, and we can say pause after, and we'll say 10 ticks. So you're going to wait 10 ticks before animating the next value. Let's check it out.
So you're going to wait 10 ticks before animating the next value. Let's check it out. So you can see each bar is taking a quick pause before it goes to the next value. Again, we've changed the dynamic of the animation just by adjusting one little value. Let's adjust something else. Back in our app, let's go to the loop method. And by default, the loop method sleeps for 50,000 microseconds. But what if we, say, cut that in half and say 25,000? So now this is going to run much faster. Same animation, double the speed.
So now this is going to run much faster. Same animation, double the speed. Let's bring this back to the default, and let's make one more adjustment. Back in our renderer, let's just adjust the barWidth and the barSpacing, and I think it's going to make an entirely different experience. So if we just say 1 and 1, let's see what that looks like. Amazing. This is completely different. You just toggle different variables, and it just creates a completely different experience in the terminal.
You just toggle different variables, and it just creates a completely different experience in the terminal. Let's say we take out the spacing altogether. Let's say the spacing is zero. Completely different. This is so cool, and you can take this as far as you want to, especially with Chewy's animation utilities. It makes it very easy to animate within the terminal.
