{"author_link":"\/users\/nondev","author_name":"NonDev","author_uid":"nondev","comments":[],"epoch":1760015619,"event":"LD58","format":"md","ldjam_node_id":422645,"likes":3,"metadata":{"p_key":"197411","p_author":"NonDev","p_authorkey":"1373015","p_urlkey":"429335","p_title":"MiniLayout","p_cat":"LDJam ","p_event":"LD58","p_time":"1760015619","p_likes":"3","p_comments":"0","p_status":"WAYBACK","us_key":"1373015","us_name":"NonDev","us_username":"nondev","event_start":"1759449600","event_key":"112","event_name":"Ludum Dare 58"},"node":{"_collation":{"body_sanitizer":"TextUtils::SanitizeHTML via existing importer","event":"LD58","removed_author":false},"_superparent":415614,"_trust":1,"author":373015,"body":"Since LD57 I've made a bunch of little tools and libraries for my engine (built on top of SDL2) to help shave off annoying or time consuming parts of game dev. I thought I would do a few posts talking about them. The first one is for creating immediate mode UIs, simply called MiniLayout (and LAYN internally which stands for **Lay**outs in **N**im). It's inspired by [Nic Barker's Clay](https:\/\/www.nicbarker.com\/clay) after watching a video on him talking through his algorithm.\n\nProgramming interfaces is a slog. I find them especially limiting in a jam since often the quickest approach was to just hard code things, making any changes later on a time consuming chore. My goal was to end up with something with minimal boilerplate. Just describe what you want to draw, automatically support things like text, sprites and handling interactions.\n\nHere's a simple example and the code used to generate it:\n\n![media_layout_demo2.png](\/\/\/raw\/711\/b5\/z\/6f132.png)\n\n```\nframe:\n  style game.sprites.fancy_corners_frame\n  size 160, HUG\n  direction VERTICAL\n  padding 16\n  gap 8\n  shadow 0, 2\n  shadow_color CLIGHTGREY\n\n  for i in 0 ..< 3:\n    frame:\n      interactive button_tag[i]\n      if hovered(): style game.sprites.row_hovered\n      else: style game.sprites.row\n      width FILL\n      padding 8\n      gap 8\n      align CENTER\n      sprite: content game.sprites.little_face\n      text: content \"Hello world!\"\n\ndraw(game.layout)\n```\n\nTo quickly explain what some of this means:\n- A `frame` is a container. By default it hugs its content, lays them out horizontally and has no fill color or style. (A style is just a `Sprite` pointer that is then used for ninepatch drawing.)\n- Sections like `frame:`, `sprite:`, `text:` are macros which begin a new `Element` of that type, make any further `Element` declared within its children and automatically call `close_element()` at the end of the scope block.\n- Interactive elements are given a string tag, like `interactive \"my cool button\"` which can then be later used to check the state of that interactive element. (`if get_hovered(game.layout) == \"my cool button\": ...`)\n- The library knows the current element scope as you declare them, so you can easily set parameters based on the interaction state with simple checks like `if hovered(): ...`, `if pressed(): ...`, etc.\n- Dimensions can be set to `FILL` to automatically grow to fill any remaining space in the parent.\n\nCalling `draw()`, which takes a pointer to the MiniLayout struct, is used to finalize the layout and do the calculations before drawing all the elements. The multiple passes used for calculations are very similar to Clay (so you can watch the video above for more in depth explanation for most of these):\n- Widths are grown and shrank.\n- Text is wrapped.\n- Heights are adjusted to fit content.\n- Heights are grown and shrank.\n- Positions are calculated.\n- Scroll requests are handled.\n- Interactive elements are cached for the next frame.\n\nThe last one is really important because otherwise you have this problem: How do you know what's interacted with during a frame if you don't know how the layout will be calculated until all the layout is defined and drawn? So my solution was that all interactive elements have a lightweight copy of some of their important details (like position, interactive tag, size) kept for the next frame, which does introduce a one frame latency but I think that's a mild tradeoff for all the other wins introduced.\n\nThe lack of boilerplate is achieved through a few means: First, Nim has a lot of flexibility in its syntax. `foo(bar)`, `bar.foo()` and `foo bar` are all the same and just based on your preference. While I don't normally use the last style, it's great here to reduce noise and give the layout areas a distinct different style than the rest of my code. Second, MiniLayout internally has a single private struct that all the parameter procedure calls operate on. So `width 100`, which is the same as `width(100)`, operates on `LAYN.target.cons.size.x` where `target` is a pointer to the current `Element` in scope.\n\nA side benefit to this system is you can define and draw multiple layouts per frame as you like, in between drawing the rest of the game. There's no conflicts because after drawing we no longer care about any of MiniLayout's state (except for cached interactive elements).\n\nThere's tons more not covered here. Like frames can be set as scrollable and automatically clip their scrolled content, interactive elements can automatically be navigated through on controller\/keyboard with no additional code, absolute positioning with parameters like `position RIGHT, TOP`, text wrapping, etc, etc, but thought I'd keep it brief.\n\nIt's been a blast working on it so far and excited to use it more in the future!","comments":3,"comments-timestamp":"2025-10-09T13:36:09Z","created":"2025-10-09T11:54:25Z","files":[],"files-timestamp":0,"id":422645,"love":3,"love-timestamp":"2025-10-09T15:28:20Z","meta":[],"modified":"2025-10-09T15:28:20Z","name":"MiniLayout","node-timestamp":"2025-10-09T13:14:04Z","parent":418825,"parents":[1,5,9,415614,418825],"path":"\/events\/ludum-dare\/58\/the-tooth-fae\/minilayout","published":"2025-10-09T13:13:39Z","scope":"public","slug":"minilayout","subsubtype":"","subtype":"","type":"post","version":1338427},"node_metadata":{"n_key":"422645","n_urlkey":"429335","n_parent":"418825","n_path":"\/events\/ludum-dare\/58\/the-tooth-fae\/minilayout","n_slug":"minilayout","n_type":"post","n_subtype":"","n_subsubtype":"","n_author":"373015","n_created":"1760010865","n_modified":"1760023700","n_version":"1338427","n_status":"WAYBACK"},"source_url":"https:\/\/ldjam.com\/events\/ludum-dare\/58\/the-tooth-fae\/minilayout","text":"Since LD57 I've made a bunch of little tools and libraries for my engine (built on top of SDL2) to help shave off annoying or time consuming parts of game dev. I thought I would do a few posts talking about them. The first one is for creating immediate mode UIs, simply called MiniLayout (and LAYN internally which stands for **Lay**outs in **N**im). It's inspired by [Nic Barker's Clay](https:\/\/www.nicbarker.com\/clay) after watching a video on him talking through his algorithm.\n\nProgramming interfaces is a slog. I find them especially limiting in a jam since often the quickest approach was to just hard code things, making any changes later on a time consuming chore. My goal was to end up with something with minimal boilerplate. Just describe what you want to draw, automatically support things like text, sprites and handling interactions.\n\nHere's a simple example and the code used to generate it:\n\n![media_layout_demo2.png](\/\/\/raw\/711\/b5\/z\/6f132.png)\n\n```\nframe:\n  style game.sprites.fancy_corners_frame\n  size 160, HUG\n  direction VERTICAL\n  padding 16\n  gap 8\n  shadow 0, 2\n  shadow_color CLIGHTGREY\n\n  for i in 0 ..< 3:\n    frame:\n      interactive button_tag[i]\n      if hovered(): style game.sprites.row_hovered\n      else: style game.sprites.row\n      width FILL\n      padding 8\n      gap 8\n      align CENTER\n      sprite: content game.sprites.little_face\n      text: content \"Hello world!\"\n\ndraw(game.layout)\n```\n\nTo quickly explain what some of this means:\n- A `frame` is a container. By default it hugs its content, lays them out horizontally and has no fill color or style. (A style is just a `Sprite` pointer that is then used for ninepatch drawing.)\n- Sections like `frame:`, `sprite:`, `text:` are macros which begin a new `Element` of that type, make any further `Element` declared within its children and automatically call `close_element()` at the end of the scope block.\n- Interactive elements are given a string tag, like `interactive \"my cool button\"` which can then be later used to check the state of that interactive element. (`if get_hovered(game.layout) == \"my cool button\": ...`)\n- The library knows the current element scope as you declare them, so you can easily set parameters based on the interaction state with simple checks like `if hovered(): ...`, `if pressed(): ...`, etc.\n- Dimensions can be set to `FILL` to automatically grow to fill any remaining space in the parent.\n\nCalling `draw()`, which takes a pointer to the MiniLayout struct, is used to finalize the layout and do the calculations before drawing all the elements. The multiple passes used for calculations are very similar to Clay (so you can watch the video above for more in depth explanation for most of these):\n- Widths are grown and shrank.\n- Text is wrapped.\n- Heights are adjusted to fit content.\n- Heights are grown and shrank.\n- Positions are calculated.\n- Scroll requests are handled.\n- Interactive elements are cached for the next frame.\n\nThe last one is really important because otherwise you have this problem: How do you know what's interacted with during a frame if you don't know how the layout will be calculated until all the layout is defined and drawn? So my solution was that all interactive elements have a lightweight copy of some of their important details (like position, interactive tag, size) kept for the next frame, which does introduce a one frame latency but I think that's a mild tradeoff for all the other wins introduced.\n\nThe lack of boilerplate is achieved through a few means: First, Nim has a lot of flexibility in its syntax. `foo(bar)`, `bar.foo()` and `foo bar` are all the same and just based on your preference. While I don't normally use the last style, it's great here to reduce noise and give the layout areas a distinct different style than the rest of my code. Second, MiniLayout internally has a single private struct that all the parameter procedure calls operate on. So `width 100`, which is the same as `width(100)`, operates on `LAYN.target.cons.size.x` where `target` is a pointer to the current `Element` in scope.\n\nA side benefit to this system is you can define and draw multiple layouts per frame as you like, in between drawing the rest of the game. There's no conflicts because after drawing we no longer care about any of MiniLayout's state (except for cached interactive elements).\n\nThere's tons more not covered here. Like frames can be set as scrollable and automatically clip their scrolled content, interactive elements can automatically be navigated through on controller\/keyboard with no additional code, absolute positioning with parameters like `position RIGHT, TOP`, text wrapping, etc, etc, but thought I'd keep it brief.\n\nIt's been a blast working on it so far and excited to use it more in the future!","title":"MiniLayout","wayback_source":[]}