Layout is harder than you think..
- Views
- 128K
- 127,794
- Outlier
- ×0.5
- Median 243K
- Length
- 21:12
- Published
- Jun 24
- Wednesday
- Comments
- 305
- 3K likes
Script
How it's written
- Words
- 4,664
- Words a minute
- 220
- Reading time
- 20 min
- Segments
- 15
The hook
0:00–0:26All right, so we're a couple weeks now into building with Odin and I have built a layout system. I'm trying this little experimental one right here seeing how I feel a bunch of just about a bunch of just like functions where you can start and stop elements and all that and we're going to go over this and the outcome of me building this is something that looks like this. Look at those nicely aligned boxes. I can also go out here and produce out a card. Look at that nice looking card. I can even go in here and
Structure
All right, so we're a couple weeks now into building with Odin and I have built a layout system. I'm trying this little experimental one right here seeing how I feel a bunch of just about a bunch of just like functions where you can start and stop elements and all that and we're going to go over this and the outcome of me building this is something that looks like this. Look at those nicely aligned boxes. I can also go out here and produce out a card. Look at that nice looking card. I can even go in here and produce out a deck that animates. Look at that. Would you look at that? That looks pretty dang good. So today I thought I would just talk about how I'm doing the layout system because it's well, it's pretty fun. And honestly, I'm I I am just it just absurdly excited about this kind of code cuz I think it's going to make things a lot easier or more fun to work with. All right, so let's talk about layout systems. For The thing is is most people when you when you hear the term layout system, you probably think of something like the DOM. Now, the DOM, any element you see on the page is a real element that you can reference, right? It is a DOM node. I can go in there and I can say, "Hey, create div." And I can attach it to my body and then it's going to be on there. That DOM node, I can do something like on click, right? And I can do I can listen to all sorts of different on events and then react to that specific element say being hovered, being entered, being exited. I think they call blur on focused and blurred in in the web world, but nonetheless, that is that is that is the DOM UI and this is called retained mode because the
Watch from 0:00entire tree, everything that exists, the entire UI is not generated when you need to make a change. Instead, you actually have pieces of memory and elements that you actually alter, which then update the UI. Whereas, a different type of UI, the UI that I built is called immediate mode. Now, the very first time I built this game, Mordoria, I actually built it with retained mode UI. There was a bunch of things that made it super super duper hard and I ended up with solving a lot of problems with event buses. But this one I think I will be able to get out of it without any of that stuff. And so this is called an immediate mode UI. And how this works is effectively, or at least how I'm going to use it, which is not necessarily the only way to use it, is every single frame I'm going to produce the entire game's UI. Now there's only going to be so much UI going on at any one point. Maybe I'll have like a tower being highlighted. Maybe there will be a bad guy that's being highlighted showing how much health or whatever he has. Maybe I'll have some gold that's being incremented or I'm looking at the stats of an individual tower. But nonetheless, or there could also be a whole bunch of cards on the map, right, at the same time. But nonetheless, there's only so many elements here, something like, I don't know, 50 to 100 at max. And so regenerating those is going to take virtually no effort to be able to do.
Watch from 1:30The real cool or hard part about a layout system is actually doing all the math. Now I watched a video called how Clay's layout algorithm works. I actually watched that video and attempted to copy a good portion of its features. Now I'll give you the TLDR, but you should go watch it if you're actually interested in how immediate mode, like world-class immediate mode kind of layout systems work. Now so for me, what I did is I only needed the few things. I wanted to be able to create a box and then horizontally align a series of elements inside of that box. Next, I wanted to be able to do the same thing, but maybe vertically align a series of those elements within the box. That makes sense. I also wanted to be able to create a box and then be able to center a div inside of that box. I know, centering, the world's most impossible problem. So I just wanted to be able to do like a few things. And so the the rules that I came up with is that every leaf node has effectively three different ways it could have a size. First, it could be a fixed-size item. Hey, this thing's a 50 by 50 square that exists. Two, it could have text. There are just text on the screen and that's how big the item is. Or three, it has a sprite and the sprite has a defined size. That's how big the item is. The end. And so, from these three different types, I can build the rest of the system actually pretty dang easy.
Watch from 2:46So, to give you kind of like the quick idea of how a layout system can work is that first I take a box, right? And then inside of it I create three items. Now, I don't know where those three items should be placed, right? I don't know anything about them. I just know they have a fixed size and what I ideally would like at the end of the day is those three boxes centered. Now, some are obviously going to be closer to the edge, some will be less close to the edge, and others will be you know, the most far, right? That's kind of what I would expect to have happen. And so, what you have to do when you do a layout system is you have to walk down this UI tree and you need to walk down it in a post order traversal. That means you get all the way down to the leafs, calculate the size of just the children. Now, since the children all have a defined size, that's easy. But then any of the auto laid out boxes, which by the way this box should be like this big, right? It should not be that big right there. That box, it depends on how big your kids are. And you since you already know the size of your kids, that all you need to do is know the size of the padding on each side. You need to know the size of the gap in between the kids and that is your width. It's actually not that hard and the height is also interesting cuz it's just the padding and then it's the height of the biggest child element. And this is the exact same thing as you would do vertically. The only difference of course is that you would use the width, the biggest width as how wide your box should be and then the height should be the sum of your kids height plus the gap plus the padding. And this is it. This is largely part of the algorithm you have to calculate to be able to do this kind of work. There's a
Watch from 4:09lot of other stuff, but this would be the sizing, you know, side of things, which actually was not all that bad. Now, here's the deal though, after you figure out the sizes, you were then able to figure out the positions and that makes total sense, right? Because if you have three elements inside of a box and you know their three sizes, that means you put the first one in and then you go, "Okay, well, based on the padding right here that you can see, plus the width of the first kid, plus the gap, that's where the next one starts." And then that is going to be, "Okay, the width plus the gap, that's where the next one is going to start." And you're able to easily lay things out. And then if you do any sort of alignment while you're doing positioning, you can say, "Okay, I'm trying to center it in the off-axis." By the way, there's the off-axis, which is the opposite direction as the alignment direction. The off-axis, all I have to do is say, "Okay, take your parent's height minus your kid's height, right? The element's height, and then divide that by two, then boom, you got this height right here." Okay, so you already know how to center them. It's not even that hard, which is this code right here, which is actually just a touch longer. Okay, it's a little bit longer, okay? Because there's also alignment. Look at it, would you look at all that code? But hey, I wrote all that code, it feels good. Like I know what's going on here, like I know what's happening right here and this makes me happy. And so this was actually my first time ever building an immediate mode UI and I can see why there's some advantages. Now, you're probably thinking, "Well, what are the advantages? What are the tradeoffs?"
Watch from 5:40Well, one of the big tradeoffs is actually how mouse works. Now, let me give you an obvious and simple uh example of why this could be complicated. So, as I explained, to be able to do a layout like this, you first have to calculate the size of every item, then you can calculate where they are at in the universe. Now, what happens if I have a mouse right here and when that happens, I want this element to scale. How would I know the mouse is inside of that box if I haven't already laid out the size and then the positioning? You can kind of see there's a bit of a chicken-and-egg problem. I have a mouse at this position the screen. Well, how do I know this thing need to be expanded? Well, I only know it needs to be expanded because I already know where it's at. Well, I only know where it's at by rendering everything out. Well, if I render everything out, then I don't know that the mouse is there, so I don't say, "Hey, become big." So, it's almost like, do you have to lay it out twice? Is that the answer? No, you ignorant That's of course that's not the answer. You're completely wrong. So, this is how I solve it. So, I take the root and I save my root of my layout. And so, at the very beginning first frame, I go and get the mouse info. Of course, the mouse info is just going to raylib saying, "Hey, give me your XY and if the first button is down." After that, I go in here and calculate the mouse based off the previous root. Obviously, if there is no root, we just leave. Else, I do this beautiful descend algorithm, which is actually pretty simple, and I just get to the very bottom of the tree and say, "Hey, if the mouse is inside of my rendered rectangle, then I can do things
Watch from 7:06like, 'Hey, are you held? Are you entered? Are you exited? If you are you hovered? Have you been dragging?' Like, I can just do all the calculations in one simple little spot, and it just it just works. It's it's simple. It's nice. It makes me feel happy, honestly. And it's not even that much code. Like, the entire mouse library, including this gigantic What's it called? The bugging statement and this gigantic resetting statement and this mouse in rectangle statement, which are all pretty large, actually only ends up being less than 200 lines of code. And it's really easy for me to understand. So, that means the first time I try to do anything with the mouse, for the first frame, I can't do anything. Totally okay. But, the next frame, I know where everything was. I test my mouse against it, and then when everything renders, it goes, "Okay, was the mouse in me? It was. I need to start expanding." And so, that way, I get this nice beautiful effect where everything knows where the mouse is and what state it is in inside of rendering. And this is kind of like the big bonus of immediate mode. See with retained mode you have to have all these like function handlers that like when the mouse enters you have to keep state, right? You have to be like, "Okay, I am I'm actually hovered right now. Okay, I'm hovered." And I have to wait till some other function somewhere else executes and says, "Okay, I'm no longer hovered anymore." So, you have to do all this kind of like state management that's really, really confusing. But instead with immediate mode, I construct the UI and during construction I can go, "Am I hovered? Oh, I am hovered during this moment, then I should be 2x the size."
Watch from 8:37And mixing in animations honestly is not all that hard. It's pretty straightforward because once you have the whole idea of hey, what is my state at this current moment and I only build my UI based off that state, it actually makes everything else pretty dang simple. I am super shocked at how much I like immediate mode UIs. But of course at that point I wasn't happy, so I created this little little helper item because when you're building stuff, right? So, if I go to my components card and I go and look at what it what it takes to build, if you go all the way down here that's like a previous one that I vibe coded up just to see what it would take. Like here is my card being built and I I build up the params, all this kind of stuff. That means every element I build has to have these like large kind of parameter objects and they're kind of just ugly and I don't really like reading code like this. It just feels annoying and if I want to do any sort of calculations, it's kind of like, you know, annoying to do those calculations when you have these big structs and all that. And so every single like every single element construction just looks like this, which I find very unappealing as a programmer. Now, obviously as an AI, this is very, very easy. If I just showed my AI and built up a nice couple examples, I guarantee you I could generate the crap out of this. Not even a problem at all, right? Well, buddy, I'm not an AI and I actually have this belief. It's kind of a silly belief that code should feel aesthetic. I should really like the experience. So, I came up with this silly, silly, really, really silly idea and I don't know if it's actually going to be any good, but here's the idea. At the very top of my my Odin file, I have
Watch from 10:07this kind of like module level variable. Honestly, I could probably I should probably just call it private. I don't want anyone touching that, okay? That would be gross. Please don't hate. Please don't touch my privates, okay? Very inappropriate. Anyways, what I do is I have a start function. This starts a UI building process. And what it does is it's going to allocate on the temporary allocator a bunch of layout handles. All right. It's okay. I I'm sure I could do this better, honestly. I think I could do this better. But for now, let's just stick with this cuz I just wanted to, you know, whip out something. In my current index equals -1. Then, I call start element. When start element happens, I get the index into elements. I create a new layout handle with the temp temporary allocator. Very good, by the way. For those that don't know what a temporary allocator are, it's like one of the coolest things ever. So, in managed memory languages, what ends up happening is that you have to go and you have to go and call malloc on a bunch of items. And these items get allocated somewhere off in the heap that you don't know where they're at. And then you have to remember, okay, hey, my structure, I need to deallocate this cuz if you don't, malloc will think, okay, this thing exists forever. I will I can't ever use that memory. It's forever there. Forever, forever, forever. And they call this, obviously, leaking memory. It's very easy to do this in a managed language, but it's even easier to do this in you're the manager language. And Odin's a you're the manager language, but it has this idea of a temporary allocator. So does Jai, by the way. Beautiful concept.
Watch from 11:38Absolutely love it. Which is at any point, I can call free on the temporary allocator and everything that was allocated by it goes away. So, as you can see here, here's my main game loop. I can just go free at the end of every loop and everything that I allocated during that loop, it's gone. I didn't have to keep track of it. I didn't have to worry about it. It's just this nice, really simple allocator cuz if you think about it, that means the internals to that allocator can be dirt simple. Just allocate to the left, right? I don't have to even think about it. Or actually, technically, I guess I look at it as allocate to the right. Allocate to the right. I I even pointed to the right. But, just keep on allocating to the right. And then when they say done, you go, "Okay, done." Back to zero. Keep on allocating. It's It's actually a really really really beautiful concept. Absolutely love it. Anyway, sorry for the side quest there for a second. But, then I create these elements, these element handles, right? And it has Okay, I have no parent. Here's my index. Here's my parameters. That big ugly parameter object. I don't have any children. I'm going to say I'm on just on axis X. That's what it is. Then I'm going to append this to my dynamic elements array. Again, I think I could just hard code this and I don't have to do all this, but for now, whatever. If my index is greater than zero and my current index is greater than zero, that means I am already in the tree starting element. So, I I'm I I'm actually a child somewhere. And so, that means I can actually get the parent thing and say, "Okay, hey, one of your children is me, and one of my and my parent is you."
Watch from 12:56And now I've kind of created this nice tree situation in the order in which you call start element. End element just does the inverse, right? It goes, "Okay, grab my current element. If the index is zero, we're done. We're out of the tree." Else, the current index is the parent index. Okay, we're going to jump back one. I'm using the term index because again, I was thinking about doing like a hard coded array and just handing out references into this one single contiguous piece of memory. I might just go to that. So, I'm kind of keeping the index idea around. I don't really know what I'm doing, okay? I'm a noob. So, therefore, from there, I can actually go, again, temporary allocator, I can create a dynamic array, get all the children elements from it, and go and call my layout system with the next set and create a new element. So, I'm actually creating elements up the tree as those functions are being called. Then lastly, I just create a series of really nice functions that can work with the current element. Like, hey, I want to be able to create a background color. Okay, grab the current element, set its parameters to the background color. You must at least have an element. I should never make this mistake in code. There should always be an open element going on right there. By the way, asserts on conditions that cannot be broken, absolutely love it, by the way. One of the best things you can do. So, with that, I was able to create this nice little UI right here, which is okay, I'm going to start a row.
Watch from 14:28Okay, it's horizontal. I want this padding, I want this gap. Okay, for every element, I want to start the element, give it a size, give it a background color, and the element. And then I'm just going to return out the results of end element, which of course is one of my layout items, and bada bing, bada boom, I just created a UI. And of course, that's the UI that I was talking about earlier. That little pretty thing right there. Anyways, I wanted to talk to you about that because I've never built a UI like this. I've never tried this kind of unique, I guess, kind of style of building a UI. I just wanted to see what it would feel like. See if I actually like it. I'm going to incorporate it into a game, write thousands of lines of code of UI code, and I'll be able to tell you at the end, was I happy with this style for how simple my game is. My guess is that I'm going to be pretty happy about it because this is code that I consider beautiful, right? Like I look at that and I go, I like the looks of that code. Now, do I need to be able to do this like temporary allocator new stuff? Probably not, honestly. I think I could do something like max elements, right? And just be like, hey, there is a maximum amount of elements that you can have, and that's just that. And then I can just point to each one of them, is my guess. So, instead of doing a make, I just set my index back down to -1, and then I just simply have to have something like, you know, count, which is going to have to be zero. And then it's going to be like, okay, well, count's zero, so I'm just going to get instead of getting the length of the elements, I get the current count, and then I can just point to this thing, and then it's all in just one nice little array, and I don't have to do any of this make temporary allocations. But I also have a sneaking suspicion that none
Watch from 15:44of that is going to matter at all. I have 50 items and that means I'm going to draw draw like a few of them uh second. I don't know. A few hundred, 3,000, 3,000 operations in a second. Easy peasy pumpkin squeezy. Whatever that amount of work is, it's going to be only a fraction of the power that React requires anyways and I think I'm going to like it more. And I wanted to talk to you guys about this because I really do think the immediate mode style UI actually is just super duper cool. Honestly, it kind of it it is a little bit like React in the sense that React wanted to be this really declarative UI that you can just read from top to bottom. Now, I know I've read a lot of React, that's not actually what happens, but you get the idea. You should be able to be like, "Oh, you're building this thing and then you're building that thing and then you're building that thing." Oh, yeah, it's very straightforward. And you read it declaratively. I understand this is very imperative, but in a sense it also feels very declarative. Like, I know that I'm starting an element. I have these things. I'm building these things one at a time. It feels very like visually obvious what is happening here. And the thing that again, I said this in the beginning that I loved the most is when it comes to like handling mouse operations, I just in one frame make one decision. I don't have to have crap spread around everywhere and this state kind of schmear everywhere saying, "Okay, well, me the card, I was dragged. So, therefore, I need to tell the deck that I was dragged." The the deck goes, "Okay, how far was the card drag drag it
Watch from 17:16it?" Oh, the card was drag it it outside of its range, therefore it needs to leave me. I better go tell the event bus that I am actively about to play a card and so then the card knows that it's about to be actively played, so then the card needs to sparkle. It's like, "My gosh, that is so much weird indirection of like how you tell different components what to do." Instead, I can just simply have a function now that's like, "Okay, hey, what's the mouse state? Oh, okay, dragging is happening. Oh, it's a card. Okay, the card is being dragged. Okay, it's actually within the playable range. Hey, I'm going to draw the card like this. The card, it's playable currently. And deck, you look like this currently." Like, I can make decisions outside of everything in this really smooth, easy way. Just it makes me feel good because you make decisions in a non-stateful way, just in the snapshot of the state. Beautiful. That's it. That's all I wanted to talk about. I know this video was a bit rambly, but I had I'm having a lot of fun, okay? And it looks like you guys are also enjoying that. 1.1 thousand subs, hey. Appreciate. This was my first video into Odin. Now, I'm doing the second one, so I hope I don't know. I hope everyone enjoys it.
Watch from 18:46Hey, next next week though, I will we'll figure out something better to talk about, okay? Because I think I'm going to start getting into components, building a lot of components, and handling mouses, and all that kind of fun stuff. I also want to talk about snapshot testing. I think I'm pretty happy with how I'm doing that, where I can say, "Okay, hey, I want to be able to test all these different kind of card states, and I want to be able to test all these different layout states, and I want to make sure they all look the same, no matter what." And so, if I make changes to my program, I want all those images to be identical. And so, that what that means is I can actually go like this without displaying them. I can just say, "Hey, go test it." And it was like, "Okay, yep." Nothing changed. Your previous time you rendered that, it's the same as this current time. So, I'm doing like some golden testing. Uh cuz you know, here's one big problem I have, which is you end up getting a game big enough or you get a program big enough, you start to kind of forget what everything you need to test to make sure everything's good. You kind of want just to be told what's wrong. And also, in the day and age of agents, the more information you can tell them, kind of the better. So, this makes me happy. Hey, the name. I will talk about snapshots again. Bye-bye.
Watch from 20:02Full transcript
- 0:00
All right, so we're a couple weeks now into building with Odin and I have built a layout system. I'm trying this little experimental one right here seeing how I feel a bunch of just about a bunch of just like functions where you can start and stop elements and all that and we're going to go over this and the outcome of me building this is something that looks like this. Look at those nicely aligned boxes. I can also go out here and
- 0:21
produce out a card. Look at that nice looking card. I can even go in here and produce out a deck that animates. Look at that. Would you look at that? That looks pretty dang good. So today I thought I would just talk about how I'm doing the layout system because it's well, it's pretty fun. And honestly, I'm I I am just it just absurdly excited about this kind of code cuz I think it's
- 0:42
going to make things a lot easier or more fun to work with. All right, so let's talk about layout systems. For The thing is is most people when you when you hear the term layout system, you probably think of something like the DOM. Now, the DOM, any element you see on the page is a real element that you can reference, right? It is a DOM node. I can go in there and I can say, "Hey,
- 1:02
create div." And I can attach it to my body and then it's going to be on there. That DOM node, I can do something like on click, right? And I can do I can listen to all sorts of different on events and then react to that specific element say being hovered, being entered, being exited. I think they call blur on focused and blurred
- 1:23
in in the web world, but nonetheless, that is that is that is the DOM UI and this is called retained mode because the entire tree, everything that exists, the entire UI is not generated when you need to make a change. Instead, you actually have pieces of memory and elements that you actually alter, which then update the UI. Whereas, a different type of UI,
- 1:45
the UI that I built is called immediate mode. Now, the very first time I built this game, Mordoria, I actually built it with retained mode UI. There was a bunch of things that made it super super duper hard and I ended up with solving a lot of problems with event buses. But this one I think I will be able to get out of it without any of that stuff. And so this is called an immediate mode UI. And how this works is effectively, or at
- 2:07
least how I'm going to use it, which is not necessarily the only way to use it, is every single frame I'm going to produce the entire game's UI. Now there's only going to be so much UI going on at any one point. Maybe I'll have like a tower being highlighted. Maybe there will be a bad guy that's being highlighted showing how much health or whatever he has. Maybe I'll have some gold that's being incremented
- 2:28
or I'm looking at the stats of an individual tower. But nonetheless, or there could also be a whole bunch of cards on the map, right, at the same time. But nonetheless, there's only so many elements here, something like, I don't know, 50 to 100 at max. And so regenerating those is going to take virtually no effort to be able to do. The real cool or hard part about a
- 2:48
layout system is actually doing all the math. Now I watched a video called how Clay's layout algorithm works. I actually watched that video and attempted to copy a good portion of its features. Now I'll give you the TLDR, but you should go watch it if you're actually interested in how immediate mode, like world-class immediate mode kind of layout systems
- 3:10
work. Now so for me, what I did is I only needed the few things. I wanted to be able to create a box and then horizontally align a series of elements inside of that box. Next, I wanted to be able to do the same thing, but maybe vertically align a series of those elements within the box. That makes sense. I also wanted to be able to create a box and then be able to center
- 3:30
a div inside of that box. I know, centering, the world's most impossible problem. So I just wanted to be able to do like a few things. And so the the rules that I came up with is that every leaf node has effectively three different ways it could have a size. First, it could be a fixed-size item. Hey, this thing's a 50 by 50 square that exists. Two, it could have text.
- 3:52
There are just text on the screen and that's how big the item is. Or three, it has a sprite and the sprite has a defined size. That's how big the item is. The end. And so, from these three different types, I can build the rest of the system actually pretty dang easy. So, to give you kind of like the quick idea of how a layout system can work is
- 4:12
that first I take a box, right? And then inside of it I create three items. Now, I don't know where those three items should be placed, right? I don't know anything about them. I just know they have a fixed size and what I ideally would like at the end of the day is those three boxes centered. Now, some are obviously going to be closer to the edge, some will be less close to the edge, and others will be you know, the
- 4:33
most far, right? That's kind of what I would expect to have happen. And so, what you have to do when you do a layout system is you have to walk down this UI tree and you need to walk down it in a post order traversal. That means you get all the way down to the leafs, calculate the size of just the children. Now, since the children all have a defined size, that's easy. But then any of the
- 4:53
auto laid out boxes, which by the way this box should be like this big, right? It should not be that big right there. That box, it depends on how big your kids are. And you since you already know the size of your kids, that all you need to do is know the size of the padding on each side. You need to know the size of the gap in between the kids and that is your width. It's actually not that hard
- 5:14
and the height is also interesting cuz it's just the padding and then it's the height of the biggest child element. And this is the exact same thing as you would do vertically. The only difference of course is that you would use the width, the biggest width as how wide your box should be and then the height should be the sum of your kids height plus the gap plus the padding. And this
- 5:35
is it. This is largely part of the algorithm you have to calculate to be able to do this kind of work. There's a lot of other stuff, but this would be the sizing, you know, side of things, which actually was not all that bad. Now, here's the deal though, after you figure out the sizes, you were then able to figure out the positions and that makes total sense, right? Because if you have three elements inside of a box and
- 5:56
you know their three sizes, that means you put the first one in and then you go, "Okay, well, based on the padding right here that you can see, plus the width of the first kid, plus the gap, that's where the next one starts." And then that is going to be, "Okay, the width plus the gap, that's where the next one is going to start." And you're able to easily lay things out. And then if you do any sort of alignment while
- 6:18
you're doing positioning, you can say, "Okay, I'm trying to center it in the off-axis." By the way, there's the off-axis, which is the opposite direction as the alignment direction. The off-axis, all I have to do is say, "Okay, take your parent's height minus your kid's height, right? The element's height, and then divide that by two, then boom, you got this height right here." Okay, so you already know how to
- 6:39
center them. It's not even that hard, which is this code right here, which is actually just a touch longer. Okay, it's a little bit longer, okay? Because there's also alignment. Look at it, would you look at all that code? But hey, I wrote all that code, it feels good. Like I know what's going on here, like I know what's happening right here and this makes me happy. And so this was actually my first time ever building an
- 7:00
immediate mode UI and I can see why there's some advantages. Now, you're probably thinking, "Well, what are the advantages? What are the tradeoffs?" Well, one of the big tradeoffs is actually how mouse works. Now, let me give you an obvious and simple uh example of why this could be complicated. So, as I explained, to be able to do a layout like this, you first have to calculate the size of every
- 7:20
item, then you can calculate where they are at in the universe. Now, what happens if I have a mouse right here and when that happens, I want this element to scale. How would I know the mouse is inside of that box if I haven't already laid out the size and then the positioning? You can kind of see there's a bit of a chicken-and-egg problem. I
- 7:41
have a mouse at this position the screen. Well, how do I know this thing need to be expanded? Well, I only know it needs to be expanded because I already know where it's at. Well, I only know where it's at by rendering everything out. Well, if I render everything out, then I don't know that the mouse is there, so I don't say, "Hey, become big." So, it's almost like, do you have to lay it out twice? Is that the answer? No, you ignorant That's of course that's not the answer.
- 8:02
You're completely wrong. So, this is how I solve it. So, I take the root and I save my root of my layout. And so, at the very beginning first frame, I go and get the mouse info. Of course, the mouse info is just going to raylib saying, "Hey, give me your XY and if the first button is down." After that, I go in here and calculate the mouse based off the previous root. Obviously, if there
- 8:23
is no root, we just leave. Else, I do this beautiful descend algorithm, which is actually pretty simple, and I just get to the very bottom of the tree and say, "Hey, if the mouse is inside of my rendered rectangle, then I can do things like, 'Hey, are you held? Are you entered? Are you exited? If you are you hovered? Have you been dragging?' Like, I can just do all the calculations in
- 8:45
one simple little spot, and it just it just works. It's it's simple. It's nice. It makes me feel happy, honestly. And it's not even that much code. Like, the entire mouse library, including this gigantic What's it called? The bugging statement and this gigantic resetting statement and this mouse in rectangle statement, which are all pretty large,
- 9:06
actually only ends up being less than 200 lines of code. And it's really easy for me to understand. So, that means the first time I try to do anything with the mouse, for the first frame, I can't do anything. Totally okay. But, the next frame, I know where everything was. I test my mouse against it, and then when everything renders, it goes, "Okay, was the mouse in me? It was. I need to start
- 9:27
expanding." And so, that way, I get this nice beautiful effect where everything knows where the mouse is and what state it is in inside of rendering. And this is kind of like the big bonus of immediate mode. See with retained mode you have to have all these like function handlers that like when the mouse enters you have to keep state, right? You have to be like, "Okay, I am I'm actually hovered right now. Okay, I'm hovered." And I have to wait till some other
- 9:48
function somewhere else executes and says, "Okay, I'm no longer hovered anymore." So, you have to do all this kind of like state management that's really, really confusing. But instead with immediate mode, I construct the UI and during construction I can go, "Am I hovered? Oh, I am hovered during this moment, then I should be 2x the size." And mixing in animations honestly is not
- 10:09
all that hard. It's pretty straightforward because once you have the whole idea of hey, what is my state at this current moment and I only build my UI based off that state, it actually makes everything else pretty dang simple. I am super shocked at how much I like immediate mode UIs. But of course at that point I wasn't happy, so I created this little little helper item
- 10:31
because when you're building stuff, right? So, if I go to my components card and I go and look at what it what it takes to build, if you go all the way down here that's like a previous one that I vibe coded up just to see what it would take. Like here is my card being built and I I build up the params, all this kind of stuff. That means every element I build has to have these like large kind of parameter objects and they're kind of just ugly and I don't
- 10:51
really like reading code like this. It just feels annoying and if I want to do any sort of calculations, it's kind of like, you know, annoying to do those calculations when you have these big structs and all that. And so every single like every single element construction just looks like this, which I find very unappealing as a programmer. Now, obviously as an AI, this is very, very easy. If I just showed my AI and
- 11:12
built up a nice couple examples, I guarantee you I could generate the crap out of this. Not even a problem at all, right? Well, buddy, I'm not an AI and I actually have this belief. It's kind of a silly belief that code should feel aesthetic. I should really like the experience. So, I came up with this silly, silly, really, really silly idea and I don't know if it's actually going
- 11:33
to be any good, but here's the idea. At the very top of my my Odin file, I have this kind of like module level variable. Honestly, I could probably I should probably just call it private. I don't want anyone touching that, okay? That would be gross. Please don't hate. Please don't touch my privates, okay? Very inappropriate. Anyways, what I do is I have a start function. This starts a UI building process. And what it does is it's going to allocate on the
- 11:55
temporary allocator a bunch of layout handles. All right. It's okay. I I'm sure I could do this better, honestly. I think I could do this better. But for now, let's just stick with this cuz I just wanted to, you know, whip out something. In my current index equals -1. Then, I call start element. When start element happens, I get the index into elements. I create a new layout handle with the temp temporary allocator. Very good, by
- 12:17
the way. For those that don't know what a temporary allocator are, it's like one of the coolest things ever. So, in managed memory languages, what ends up happening is that you have to go and you have to go and call malloc on a bunch of items. And these items get allocated somewhere off in the heap that you don't know where they're at. And then you have to remember, okay, hey, my structure, I need to deallocate this cuz if you don't, malloc will think, okay, this
- 12:37
thing exists forever. I will I can't ever use that memory. It's forever there. Forever, forever, forever. And they call this, obviously, leaking memory. It's very easy to do this in a managed language, but it's even easier to do this in you're the manager language. And Odin's a you're the manager language, but it has this idea of a temporary allocator. So does Jai, by the way. Beautiful concept. Absolutely love it. Which is at any
- 12:58
point, I can call free on the temporary allocator and everything that was allocated by it goes away. So, as you can see here, here's my main game loop. I can just go free at the end of every loop and everything that I allocated during that loop, it's gone. I didn't have to keep track of it. I didn't have to worry about it. It's just
- 13:20
this nice, really simple allocator cuz if you think about it, that means the internals to that allocator can be dirt simple. Just allocate to the left, right? I don't have to even think about it. Or actually, technically, I guess I look at it as allocate to the right. Allocate to the right. I I even pointed to the right. But, just keep on allocating to the right.
- 13:40
And then when they say done, you go, "Okay, done." Back to zero. Keep on allocating. It's It's actually a really really really beautiful concept. Absolutely love it. Anyway, sorry for the side quest there for a second. But, then I create these elements, these element handles, right? And it has Okay, I have no parent. Here's my index. Here's my parameters. That big ugly parameter object. I don't have any children. I'm going to say I'm on just
- 14:01
on axis X. That's what it is. Then I'm going to append this to my dynamic elements array. Again, I think I could just hard code this and I don't have to do all this, but for now, whatever. If my index is greater than zero and my current index is greater than zero, that means I am already in the tree starting element. So, I I'm I I'm actually a child somewhere. And so, that means I
- 14:21
can actually get the parent thing and say, "Okay, hey, one of your children is me, and one of my and my parent is you." And now I've kind of created this nice tree situation in the order in which you call start element. End element just does the inverse, right? It goes, "Okay, grab my current element. If the index is zero, we're done. We're out of the tree." Else, the current index is the
- 14:43
parent index. Okay, we're going to jump back one. I'm using the term index because again, I was thinking about doing like a hard coded array and just handing out references into this one single contiguous piece of memory. I might just go to that. So, I'm kind of keeping the index idea around. I don't really know what I'm doing, okay? I'm a noob. So, therefore, from there, I can actually go, again, temporary allocator, I can create a dynamic array, get all
- 15:04
the children elements from it, and go and call my layout system with the next set and create a new element. So, I'm actually creating elements up the tree as those functions are being called. Then lastly, I just create a series of really nice functions that can work with the current element. Like, hey, I want to be able to create a background color. Okay, grab the current element, set its
- 15:25
parameters to the background color. You must at least have an element. I should never make this mistake in code. There should always be an open element going on right there. By the way, asserts on conditions that cannot be broken, absolutely love it, by the way. One of the best things you can do. So, with that, I was able to create this nice little UI right here, which is okay, I'm going to start a row. Okay, it's horizontal. I want this
- 15:46
padding, I want this gap. Okay, for every element, I want to start the element, give it a size, give it a background color, and the element. And then I'm just going to return out the results of end element, which of course is one of my layout items, and bada bing, bada boom, I just created a UI. And of course, that's the UI that I was talking about earlier. That little pretty thing right
- 16:06
there. Anyways, I wanted to talk to you about that because I've never built a UI like this. I've never tried this kind of unique, I guess, kind of style of building a UI. I just wanted to see what it would feel like. See if I actually like it. I'm going to incorporate it into a game, write thousands of lines of code of UI code, and I'll be able to tell you at the end, was I happy with this style for how simple my game is. My
- 16:26
guess is that I'm going to be pretty happy about it because this is code that I consider beautiful, right? Like I look at that and I go, I like the looks of that code. Now, do I need to be able to do this like temporary allocator new stuff? Probably not, honestly. I think I could do something like max elements, right? And just be like, hey, there is a maximum amount of elements that you can have, and that's just that. And then I
- 16:48
can just point to each one of them, is my guess. So, instead of doing a make, I just set my index back down to -1, and then I just simply have to have something like, you know, count, which is going to have to be zero. And then it's going to be like, okay, well, count's zero, so I'm just going to get instead of getting the length of the elements, I get the current count, and then I can just point to this thing, and
- 17:09
then it's all in just one nice little array, and I don't have to do any of this make temporary allocations. But I also have a sneaking suspicion that none of that is going to matter at all. I have 50 items and that means I'm going to draw draw like a few of them uh second. I don't know. A few hundred, 3,000, 3,000 operations in a second. Easy peasy pumpkin squeezy. Whatever
- 17:31
that amount of work is, it's going to be only a fraction of the power that React requires anyways and I think I'm going to like it more. And I wanted to talk to you guys about this because I really do think the immediate mode style UI actually is just super duper cool. Honestly, it kind of it it is a little bit like React in the sense that React wanted to be this really declarative UI
- 17:52
that you can just read from top to bottom. Now, I know I've read a lot of React, that's not actually what happens, but you get the idea. You should be able to be like, "Oh, you're building this thing and then you're building that thing and then you're building that thing." Oh, yeah, it's very straightforward. And you read it declaratively. I understand this is very imperative, but in a sense it also feels very declarative. Like, I know that I'm starting an element. I have these
- 18:14
things. I'm building these things one at a time. It feels very like visually obvious what is happening here. And the thing that again, I said this in the beginning that I loved the most is when it comes to like handling mouse operations, I just in one frame make one decision. I don't have to have crap spread around everywhere and this state kind of
- 18:34
schmear everywhere saying, "Okay, well, me the card, I was dragged. So, therefore, I need to tell the deck that I was dragged." The the deck goes, "Okay, how far was the card drag drag it it?" Oh, the card was drag it it outside of its range, therefore it needs to leave me. I better go tell the event bus that I am actively about to play a card
- 18:55
and so then the card knows that it's about to be actively played, so then the card needs to sparkle. It's like, "My gosh, that is so much weird indirection of like how you tell different components what to do." Instead, I can just simply have a function now that's like, "Okay, hey, what's the mouse state? Oh, okay,
- 19:16
dragging is happening. Oh, it's a card. Okay, the card is being dragged. Okay, it's actually within the playable range. Hey, I'm going to draw the card like this. The card, it's playable currently. And deck, you look like this currently." Like, I can make decisions outside of everything in this really smooth, easy way. Just it makes me feel good because you make
- 19:37
decisions in a non-stateful way, just in the snapshot of the state. Beautiful. That's it. That's all I wanted to talk about. I know this video was a bit rambly, but I had I'm having a lot of fun, okay? And it looks like you guys are also enjoying that. 1.1 thousand subs, hey. Appreciate. This was my first video into Odin. Now, I'm doing the second one, so
- 19:58
I hope I don't know. I hope everyone enjoys it. Hey, next next week though, I will we'll figure out something better to talk about, okay? Because I think I'm going to start getting into components, building a lot of components, and handling mouses, and all that kind of fun stuff. I also want to talk about snapshot testing. I think I'm pretty happy with how I'm doing that, where I can say, "Okay, hey, I want to be able
- 20:20
to test all these different kind of card states, and I want to be able to test all these different layout states, and I want to make sure they all look the same, no matter what." And so, if I make changes to my program, I want all those images to be identical. And so, that what that means is I can actually go like this without displaying them. I can just say, "Hey, go test it." And it was like, "Okay, yep."
- 20:40
Nothing changed. Your previous time you rendered that, it's the same as this current time. So, I'm doing like some golden testing. Uh cuz you know, here's one big problem I have, which is you end up getting a game big enough or you get a program big enough, you start to kind of forget what everything you need to test to make sure everything's good. You kind of want just to be told what's wrong. And also,
- 21:01
in the day and age of agents, the more information you can tell them, kind of the better. So, this makes me happy. Hey, the name. I will talk about snapshots again. Bye-bye.
Description
Twitch : / theprimeagen
Discord: / discord
Support me (by becoming a backend dev): https://boot.dev/prime
Get production ready SQLite with Turso: https://turso.tech/deeznuts




