summaryrefslogtreecommitdiff
path: root/dear_imgui_and_love.html
blob: bb7e928116a8ca1a946bd01993b8144b8f06bcbb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
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>