summaryrefslogtreecommitdiff
path: root/dear_imgui_and_love.html
diff options
context:
space:
mode:
Diffstat (limited to 'dear_imgui_and_love.html')
-rw-r--r--dear_imgui_and_love.html221
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>