diff options
Diffstat (limited to 'dear_imgui_and_love.html')
-rw-r--r-- | dear_imgui_and_love.html | 221 |
1 files changed, 221 insertions, 0 deletions
diff --git a/dear_imgui_and_love.html b/dear_imgui_and_love.html new file mode 100644 index 0000000..bb7e928 --- /dev/null +++ b/dear_imgui_and_love.html @@ -0,0 +1,221 @@ +<!doctype html> +<html lang="en"> +<meta charset="utf-8"> +<meta name="viewport" content="width=device-width, initial-scale=1"> +<meta name="author" content="aki"> +<meta name="tags" content="tutorial, love2d, LÖVE, dear ImGui, imgui, immediate-mode GUI, game development"> +<meta name="created-on" content="2020-08-14 21:47:00+02:00"> +<link rel="icon" type="image/png" href="cylo.png"> +<link rel="stylesheet" href="style.css"> + +<title>Dear ImGui and LÖVE,</title> + +<nav><p><a href="https://ignore.pl">ignore.pl</a></p></nav> + +<article> +<h1>Dear ImGui and LÖVE,</h1> +<p class="subtitle">Published on 2020-08-14 21:47:00+02:00 +<p>Believe it or not, this one was requested. It's surprising considered that just recently I claimed that according to +stats nobody reads this website. It may or may not be a coincident that I have received this request over a phone. +<p>Anyway, <a href="https://github.com/ocornut/imgui">Dear ImGui</a> is a C++ library for graphical user interface. As +for <a href="https://love2d.org/">LÖVE</a>; it's sometimes called Love2D and it's a Lua framework generally meant for +game development. Today we'll learn how to create an immediate-mode graphical user interface for your love2d game with +Dear ImGui. +</p> +<img src="dear_imgui_and_love-1.png" alt="whale whale whale, what a cutie"> +<p>First, we need to have both of them on our machine (duh!). LÖVE is insanely easy to get and install on pretty much +every platform. Just visit <a href="https://love2d.org/"> their website</a> and check download section. If you use a +platform that's not supported straight-away then you probably can handle it by yourself anyway. Dear ImGui, or let's +call it ImGui from now on, is a little bit trickier. You need either a Lua binding for it, or a direct binding for LÖVE. +There're both available and let's settle for the latter one: +<a href="https://github.com/slages/love-imgui">love-imgui</a>. +<p>Now then, I usually just build by myself. On Windows that's bothersome and luckily the maintainer provides binary +packages. The update frequency is rather low, so if you run into issues, you can try to use builds from more active +forks like e.g. <a href="https://github.com/apicici/love-imgui/releases/">apicici/love-imgui</a>. At last, if you feel +adventurous, the manual build will let you down with its simplicity assuming that you have all tools already in place: +<pre> +$ cd love-imgui +$ mkdir build +$ cd build +$ cmake .. +$ make +</pre> +<p>Once you obtained dynamic library file, place it in C-require-path. In LÖVE it defaults to <code>??</code>, which +means that it will look for name passed to <code>require</code> with suffixed platform specific dynamic library +extension in either game's source directory, save directory or any of paths mounted with filesystem module. For you it +means, that you can just put your <code>imgui.dll</code> into the directory with <code>main.lua</code> and it should +work just fine. +<p>Run it and you will see that nothing will happen. That's expected. To make it work you need to "pass" selected LÖVE +callbacks to it. Luckily for us, the README file provides us with an example that we can use as bootstrap. I copied +interesting parts here for your convenience: +<pre> +require "imgui" + +function love.update(dt) + imgui.NewFrame() +end + +function love.quit() + imgui.ShutDown() +end + +function love.textinput(text) + imgui.TextInput(text) + if not imgui.GetWantCaptureKeyboard() then + end +end + +function love.keypressed(key, scancode, isrepat) + imgui.KeyPressed(key) + if not imgui.GetWantCaptureKeyboard() then + end +end + +function love.keyreleased(key, scancode, isrepat) + imgui.KeyReleased(key) + if not imgui.GetWantCaptureKeyboard() then + end +end + +function love.mousemoved(x, y, dx, dy) + imgui.MouseMoved(x, y) + if not imgui.GetWantCaptureMouse() then + end +end + +function love.mousepressed(x, y, button) + imgui.MousePressed(button) + if not imgui.GetWantCaptureMouse() then + end +end + +function love.mousereleased(x, y, button) + imgui.MouseReleased(button) + if not imgui.GetWantCaptureMouse() then + end +end + +function love.wheelmoved(x, y) + imgui.WheelMoved(y) + if not imgui.GetWantCaptureMouse() then + end +end +</pre> +<p>In the <code>if not imgui.GetWant...</code> blocks you should wrap your usual code that handles these events. It's +there to ensure that the input is not propagating to the game if one of imgui's windows has focus. +<p>Finally, we've reached the moment we can make some windows! You write the code for the interface inside +<code>love.draw</code> callback. The general documentation for ImGui can be found in +<a href="https://github.com/ocornut/imgui/blob/master/imgui.cpp">imgui.cpp</a>, each function has a short description +next to its declaration in <a href="https://github.com/ocornut/imgui/blob/master/imgui.h">imgui.h</a>. One more resource +is worth reading: <a href="https://github.com/ocornut/imgui/blob/master/imgui_demo.cpp">imgui_demo.cpp</a>. But still, +those are for C++. Thing is Lua binding reflects all names and things like function parameters almost directly. Let's +walk through some examples to get the gist of it! +<pre> +local bg = { 0.3, 0.4, 0.2 } + +function love.draw() + if imgui.Button("Goodbye, world!") then + love.event.quit() + end + + bg[1], bg[2], bg[3] = imgui.ColorEdit3("Background", bg[1], bg[2], bg[3]) + love.graphics.clear(unpack(bg)) + -- Background colour will be dynamically controlled by ColorEdit3 widget. + + imgui.Render() +end +</pre> +<p>Here's an example using two simple widgets: <code>Button</code> and <code>ColorEdit3</code>. It should illustrate how +immediate-mode GUI works and interacts with the state. One important thing to note is <code>imgui.Render()</code> just +before the function reaches the end. It does exactly what you would expect from it: renders the ImGui's windows to the +screen. Now, let's control ImGui's windows' behaviour: +<pre> +local show_fps = true + +function love.draw() + if show_fps then + show_fps = imgui.Begin("FPS", true, {"ImGuiWindowFlags_NoCollapse"}) + imgui.Text(string.format("FPS: %.2f", love.timer.getFPS())) + imgui.SetWindowSize("FPS", 0, 0) + imgui.End() + end + imgui.Render() +end +</pre> +<p>By default if you create an element ImGui will create "Debug" window for you to hold all of your stuff. Of course, +that's not always desired and so <code>Begin</code> creates a new window. It accepts parameters: title, which serves as +an id, boolean that indicates if window should contain button for closing it, and a set of flags. In C++ flags are +handled via enums and bit-wise operators. In Lua binding use a table and put full name of the enum value. +<code>SetWindowSize</code> makes the window unresizable and zeroes shrinks window to the size of its content. +<pre> +local is_a = false + +function love.draw() + if is_a then + imgui.PushStyleColor("ImGuiCol_Button", 0.7, 0.2, 0.2, 1) + imgui.PushStyleColor("ImGuiCol_ButtonHovered", 0.8, 0.3, 0.3, 1) + imgui.PushStyleColor("ImGuiCol_ButtonActive", 0.9, 0.1, 0.1, 1) + if imgui.Button("Change to B", 90, 0) then + is_a = false + end + imgui.PopStyleColor(3) + else + imgui.PushStyleColor("ImGuiCol_Button", 0.2, 0.7, 0.2, 1) + imgui.PushStyleColor("ImGuiCol_ButtonHovered", 0.3, 0.8, 0.3, 1) + imgui.PushStyleColor("ImGuiCol_ButtonActive", 0.1, 0.9, 0.1, 1) + if imgui.Button("Change to A", 90, 0) then + is_a = true + end + imgui.PopStyleColor(3) + end + + if imgui.IsItemHovered() then + imgui.SetTooltip("Switches between A and B") + end + + imgui.SameLine() + imgui.PushStyleColor("ImGuiCol_Button", 0.3, 0.3, 0.3, 1) + imgui.PushStyleColor("ImGuiCol_ButtonHovered", 0.3, 0.3, 0.3, 1) + imgui.PushStyleColor("ImGuiCol_ButtonActive", 0.3, 0.3, 0.3, 1) + imgui.Button("Disabled button") + imgui.PopStyleColor(3) + + imgui.Text("Well, that's colorful") + + imgui.Render() +end +</pre> +<p>The ad hoc styling is handled with stack-based interface. You just need to find the style name in the source and push +a colour or other property. When it's no longer needed, you pop it. +<p>Quite a number of functions modify either the element that comes after them or the one before. Consider the usage of +<code>IsItemHovered</code> in the example above. It doesn't matter, which button will be drawn, the tool-tip will be +shown if user hovers over the last element that preceding <code>if</code> statement produced. Then there's +<code>SameLine</code> which makes the next element remain on the same line (surprising, isn't it?). +<pre> +local windows = {true, true, true, true, true} + +local +function draw_my_window(n) + if windows[n] then + windows[n] = imgui.Begin(string.format("Window #%d", n), true) + for i, v in ipairs(windows) do + windows[i] = imgui.Checkbox(string.format("Show 'Window #%d'", i), v) + end + imgui.End() + end +end + +function love.draw() + for i=1, #windows do + draw_my_window(i) + end + imgui.Render() +end +</pre> +<p>That's a bit useless, but yeah, you can link the state however you like and use the function in any way you imagine. +In C++, most elements like <code>Checkbox</code> take a pointer. In Lua you can't pass normal values like that in a +simple way and so you usually put a value from previous frame in the place of the pointer and you expect that the +function (the Element) will return the new values that you can use in the next frame. +<p>Aaand, that's about it without going into examples that'd make this post twice as long. +</article> +<script src="https://stats.ignore.pl/track.js"></script> |