| .appveyor | ||
| cimgui@2735fb436f | ||
| cparser | ||
| src | ||
| .gitmodules | ||
| appveyor.yml | ||
| base.lua | ||
| CHANGELOG.md | ||
| CONTRIBUTORS.md | ||
| generator.lua | ||
| LICENSE | ||
| README.md | ||
cimgui-love
LÖVE module for Dear ImGui obtained by wrapping cimgui (programmatically generated C-api) using LuaJIT FFI.
Inspired by LuaJIT-ImGui, but does not share code with it. In particular cimgui-love it does not require any libraries other than cimgui, while LuaJIT-ImGui needs SDL for the LÖVE implementation.
Part of the LÖVE implementation (specifically the RenderDrawLists function) is based on love-imgui.
Currently based on version 1.91.2 (docking branch) of Dear ImGui and LÖVE 11.5.
How to use
Grab the appropriate release and put the cimgui folder (feel free to rename it) somewhere in your LÖVE project. Place the cimgui shared library (.so, .dylib, or .dll, depending on the system) where you prefer (not inside the .love archive, though, or you wan't be able to load it) and change package.cpath so that Lua can find it and ffi.load can access it.
If you prefer you can compile cimgui and/or generate the wrappers yourself (useful if imgui/cimgui has been updated and this repository hasn't caught up yet) using the instructions here. Note that if you get the module by cloning the repository the Lua code is in the src folder rather than the cimgui one.
Here is an overview of how to use cmigui in LÖVE:
-- Make sure the shared library can be found through package.cpath before loading the module.
-- For example, if you put it in the LÖVE save directory, you could do something like this:
local lib_path = love.filesystem.getSaveDirectory() .. "/libraries"
local extension = jit.os == "Windows" and "dll" or jit.os == "Linux" and "so" or jit.os == "OSX" and "dylib"
package.cpath = string.format("%s;%s/?.%s", package.cpath, lib_path, extension)
local imgui = require "cimgui" -- cimgui is the folder containing the Lua module (the "src" folder in the git repository)
love.load = function()
imgui.love.Init() -- or imgui.love.Init("RGBA32") or imgui.love.Init("Alpha8")
end
love.draw = function()
-- example window
imgui.ShowDemoWindow()
-- code to render imgui
imgui.Render()
imgui.love.RenderDrawLists()
end
love.update = function(dt)
imgui.love.Update(dt)
imgui.NewFrame()
end
love.mousemoved = function(x, y, ...)
imgui.love.MouseMoved(x, y)
if not imgui.love.GetWantCaptureMouse() then
-- your code here
end
end
love.mousepressed = function(x, y, button, ...)
imgui.love.MousePressed(button)
if not imgui.love.GetWantCaptureMouse() then
-- your code here
end
end
love.mousereleased = function(x, y, button, ...)
imgui.love.MouseReleased(button)
if not imgui.love.GetWantCaptureMouse() then
-- your code here
end
end
love.wheelmoved = function(x, y)
imgui.love.WheelMoved(x, y)
if not imgui.love.GetWantCaptureMouse() then
-- your code here
end
end
love.keypressed = function(key, ...)
imgui.love.KeyPressed(key)
if not imgui.love.GetWantCaptureKeyboard() then
-- your code here
end
end
love.keyreleased = function(key, ...)
imgui.love.KeyReleased(key)
if not imgui.love.GetWantCaptureKeyboard() then
-- your code here
end
end
love.textinput = function(t)
imgui.love.TextInput(t)
if imgui.love.GetWantCaptureKeyboard() then
-- your code here
end
end
love.quit = function()
return imgui.love.Shutdown()
end
-- for gamepad support also add the following:
love.joystickadded = function(joystick)
imgui.love.JoystickAdded(joystick)
-- your code here
end
love.joystickremoved = function(joystick)
imgui.love.JoystickRemoved()
-- your code here
end
love.gamepadpressed = function(joystick, button)
imgui.love.GamepadPressed(button)
-- your code here
end
love.gamepadreleased = function(joystick, button)
imgui.love.GamepadReleased(button)
-- your code here
end
-- choose threshold for considering analog controllers active, defaults to 0 if unspecified
local threshold = 0.2
love.gamepadaxis = function(joystick, axis, value)
imgui.love.GamepadAxis(axis, value, threshold)
-- your code here
end
More info on where to put the shared library
As mentioned earlier, unfortunately you cannot place the shared cimgui library inside the .love archive and then load it through ffi.load. In theory you can put the shared library anywhere on your computer and then point package.cpath to the appropriate path, but you need to be more careful if you plan to distribute the .love archive to other people.
A decent compromise is to put the shared library inside the archive and have LÖVE copy it to the game save folder when it starts (before the cimgui module is loaded) and then point package.cpath there with the help of love.filesystem.getSaveDirectory().
Some notes about FFI
While the wrappers allow you to work with Lua objects most of the time, there are many cases in which you will need to deal with FFI objects directly (e.g., creating them, casting them to other types). In other words, you need to be somewhat familiar with the LuaJIT FFI library in order to use cimgui-love.
Some particular things to keep in mind:
- Don't forget that cdata objects are garbage collected. A case that is particularly easy to miss is when you assign a Lua string to a "const char*" member of a struct: the string is converted to a pointer which will point to stale data if the string is garbage collected (which it will, if you didn't reference it anywhere).
- There is no address-of operator. If you need to pass, say, a
int *to a function you can create it (and optionally initialise it) usingx = ffi.new("int[1]"). You can access the int intself withx[0].
Wrapping convetions
Naming conventions
-
Functions in cimgui starting with
igare wrapped as functions of the same name but with theigremoved. For exampleigShowDemoWindowis wrapped asimgui.ShowDemoWindow. -
Enums are accessible with the same name they have in imgui/cimgui. For example
ImGuiWindowFlags_Noneis wrapped asimgui.ImGuiWindowFlags_None. -
Class member functions (those with a cimgui name of the form
ClassName_FuncName) are wrapped as elements of a table with the class name. For example,ImColor_SetHSVis wrapped asimgui.ImColor.SetHSV. The tableimgui.ClassNameserves as the metatable for FFI cdata of typeClassName, so non-static member functions can be called as methods:local color = imgui.ImColor.HSV(h, s, v, a) color:SetHSV(h1, s1, v1, a1) -- equivalent to imgui.ImColor.SetHSV(color, h1, s1, v1, a1) -
Note that currently the member function
ImGuiTextBuffer_endposes a slight problem since callingimgui.ImGuiTextBuffer.end(textbuffer)ortextbuffer:end()is not allowed by Lua sinceendis a reserved keyword. To access it you can use bothimgui.ImGuiTextBuffer["end"]andimgui.ImGuiTextBuffer.c_end.local textbuffer = imgui.ImGuiTextBuffer() --the following all do the same: imgui.ImGuiTextBuffer["end"](textbuffer) imgui.ImGuiTextBuffer.c_end(textbuffer) textbuffer:c_end()The automatic wrapper generator will use the same convention if similar member functions are introduced in future versions of imgui/cimgui.
-
Class constructors (those with a cimgui name of the form
ClassName_ClassNameorClassName_ClassName_OverloadIdentifier) are wrapped as functions without the initialClassName_(non-overloaded constructors are actually wrapped as the__callmeta-method ofimgui.ClassName, but the result is the same).local ffi = require "ffi" local v = imgui.ImVec2_Nil() print(ffi.typeof(v)) -- ctype<struct ImVec2 &>Note that wrapped constructors return cdata of type
ClassNamerather thanClassName*, and can be passed both toClassNameandClassName*arguments of functions. -
cdata of type
ImVec2andImVec4have been given__add,__sum,__unm,__mul, and__divmeta-methods to simplify their manipulation (they can be added, subtracted, and multiplied/divided by numbers). -
igGET_FLT_MINandigGET_FLT_MAXare not wrapped directly since they always return the same value. Instead, their return values are wrapped as the numbersimgui.FLT_MIN,imgui.FLT_MAX. -
The constructor
ImVector_ImWchar_createis wrapped asimgui.ImVector_ImWchar. -
All the unwrapped functions are accessible through FFI and exposed through
imgui.C, which is the clib returned byffi.load. For exampleimgui.C.igRenderis the unwrapped version ofimgui.Render. The function names inimgui.Care the cimgui ones.
Implementation specific functions
Starting from version 1.87-1 the functions specific to the LÖVE implementation have been moved to the imgui.love table. The implementation functions are
imgui.love.Init(format)formatcan be"RGBA32"or"Alpha8", defaults to"RGBA32"if not provided
imgui.love.BuildFontAtlas(format)formatcan be"RGBA32"or"Alpha8", defaults to"RGBA32"if not provided
imgui.love.SetShader(shader)imgui.love.Update(dt)imgui.love.RenderDrawLists()imgui.love.MouseMoved(x, y)imgui.love.MousePressed(button)imgui.love.MouseReleased(button)imgui.love.WheelMoved(x, y)imgui.love.KeyPressed(key)imgui.love.KeyReleased(key)imgui.love.TextInput(text)imgui.love.Shutdown()imgui.love.GetWantCaptureMouse()imgui.love.GetWantCaptureKeyboard()imgui.love.GetWantTextInput()imgui.love.JoystickAdded(joystick)imgui.love.JoystickRemoved()imgui.love.GamepadPressed(button)imgui.love.GamepadReleased(button)imgui.love.GamepadAxis(axis, value, threshold)
See the example above to figure out how to use them. imgui.love.BuildFontAtlas is used after changing/adding fonts to rebuild the font atlas as a LÖVE texture, and must be used after imgui.love.Init.
In versions earlier than 1.87-1 the implementation functions were located in the imgui table together with the wrappers. If you prefer this convention or you want to upgrade an older project without breaking it you can move all the functions back to the old location by running imgui.love.RevertToOldNames().
Flag helpers
The various flag enums are generally meant to be added using bitwise or, which can be done directly using LuaJIT's bit.bor. To make it easier to write the combined flags, some helper functions are added to the Lua module (one for each type of flag). For example, the ImGuiWindowFlags has the flag helper imgui.love.WindowFlags, a function taking as input the needed flag labels (as strings) and returning their bitwise or.
imgui.love.WindowFlags("NoTitleBar", "NoBackground", "HorizontalScrollbar")
-- same as
local bit = require "bit"
bit.bor(imgui.ImGuiWindowFlags_NoTitleBar, imgui.ImGuiWindowFlags_NoBackground, imgui.ImGuiWindowFlags_HorizontalScrollbar)
Prior to version 1.87-1 the flag helper functions were located in the imgui table. They can be moved back to the old location by running imgui.love.RevertToOldNames().
Shortcut helpers
Note: since version 1.90.7 Dear ImGui implements shortcuts. The old shortcuts feature of cimgui-love is still here for compatibility, but is deprecated and is not recommended for new code.
The Lua module contains some helper functions to easily implement and process keyboard shortcuts defined in MenuItem.
-
shortcut = imgui.love.Shortcut(modifiers, key, action, enabled, global)This function creates a new shortcut and adds it an internal list. It also outputs a table containing a string representation of the keyboard shortcut and the action that should be run.
modifiersis a table containing the modifiers for the shortcut, chosen among "shift", "ctrl", "alt", "gui". This is an optional argument, ifnilis passed to it it will default to{}, i.e., no modifiers.keyshould be a LÖVE KeyConstant to be pressed together with the modifiers. The string representation assumes this is a letter or number key and it may look wrong if it is something different.actionis the function to run when the shortcut is processed.enabledis an optional boolean flag specified whether the shortcut is currently enabled. Useful to make the shortcut only work under certain conditions. Defaults totrue.globalis an optional boolean flag specifying whether the shortcut should be always usable or only when the window it is defined in is focused. Defaults totrue.- The table returned by this function has 3 keys:
shortcut.textis a string representing the keyboard shortcut (e.g., "Ctrl+S")shortcut.actionis the function that was passed to theactionargument ofimgui.love.Shortcut.shortcut.enabledis the boolean flag that was passed to theenabledargument ofimgui.love.Shortcut.
-
imgui.love.RunShortcuts(key)This is the function that starts the processing of the shortcuts. It is meant to be run inside
love.keypressed.keyis the KeyConstant that has just been pressed.
love.draw = function()
if imgui.Begin("test", nil, imgui.ImGuiWindowFlags_MenuBar) then
local action = function() print("Shortcut processed.") end
local shortcut = imgui.love.Shortcut({"ctrl", "shift"}, "s", action, true, false)
if imgui.BeginMenuBar() then
if imgui.BeginMenu("File") then
-- disable MenuItem if shortcut is not enabled
if imgui.MenuItem_Bool("Save", shortcut.text, nil, shortcut.enabled) then
shortcut.action()
end
imgui.EndMenu()
end
imgui.EndMenuBar()
end
end
imgui.End()
imgui.Render()
imgui.love.RenderDrawLists()
end
love.keypressed = function(key, ...)
imgui.love.KeyPressed(key)
if not imgui.love.GetWantCaptureKeyboard() then
imgui.love.RunShortcuts(key)
end
end
Excluded functions
- Functions taking
va_listarguments are not wrapped, since the...versions are easier to use in Lua. - Class destructors (e.g.,
ImVec2_destroy) are not wrapped as the garbage collector takes care of freeing memory allocated by the wrapped constructors. ImVector_ImWchar_*functions other thanImVector_ImWchar_createare not wrapped.
Note: functions that are not wrapped can still be accessed as C functions through imgui.C as described above.
Output arguments
Some cimgui/imgui functions have pointer arguments that are meant as outputs, for example
void igColorConvertRGBtoHSV(float r,float g,float b,float* out_h,float* out_s,float* out_v);
Pointer arguments named out, out_*, or pOut are considered output arguments; they are omitted from the arguments list of the wrapped function and are returned instead. Note that they are returned before the "regular" function output (if any)!
For example the function above is wrapped as
imgui.ColorConvertRGBtoHSV(r, g, b)
and it returns 3 numbers.
Note: only arguments with the specific names listed above are treated like this by the wrapper generator. It's possible that other arguments are also meant as outputs but are not omitted/returned because they have different names. Additionally, some arguments matching the description are not omitted/returned based on their type (see ignored_out_arg.txt for a list).
Default arguments
The wrappers take into account default arguments for the functions. Pass nil to an argument to use the default value, if it has one (see imgui/cimgui headers).
Note: see ignored_defaults.txt for a list of default arguments for which passing nil doesn't work. In the current version there are none, but it's possible some will appear if you run the generator yourself on a newer version of cimgui.
Overloaded function names
Overloaded functions from imgui are renamed in cimgui to wrap them in C, for example BeginChild is split into igBeginChild_Str and igBeginChild_ID. Overloaded functions from cimgui are wrapped individually, so you would call imgui.BeginChild_Str or imgui.BeginChild_ID depending on which arguments you plan to pass to the function. This was done to avoid the overhead of a type check in Lua and to allow for an easy implementation of default arguments.
FontAtlas Texture format
The LÖVE texture for the Dear Imgui FontAtlas can be built in two different formats.
RGBA32
This format uses 4 bytes for each pixel. Unless you are modifying the FontAtlas the pixels will always be white and this format will waste texture memory and bandwitdh.
To use this mode pass "RGBA32" as the format when calling imgui.love.Init or imgui.love.BuildFontAtlas. Passing no format to these functions will also default to "RGBA32".
Alpha8
This format uses 1 byte for each pixel. It saves memory/bandwidth, but does not allow changing colours in the texture.
To use this mode pass "Alpha8" as the format when calling imgui.love.Init or imgui.love.BuildFontAtlas.
FontAtlas shader
By default cimgui-love ignores the shader currently set through love.graphics.setShader. A custom shader can be specified by passing it to the function imgui.love.SetShader; passing nil (or nothing) resets the shader to default.
Note that if using the "Alpha8" texture format the LÖVE PixelFormat will be set to "r8", and that should be take into account by your shader.
Changing fonts
If you change or add fonts you need to rebuild the font atlas with imgui.love.BuildFontAtlas(). For example:
local ffi = require "ffi"
local imio = imgui.GetIO()
local config = imgui.ImFontConfig()
config.FontDataOwnedByAtlas = false -- it's important to set this, or imgui.love.Shutdown() will crash trying to free already freed memory
local font_size = 16
local content, size = love.filesystem.read("example.ttf")
local newfont = imio.Fonts:AddFontFromMemoryTTF(ffi.cast("void*", content), size, font_size, config)
imio.FontDefault = newfont
imgui.love.BuildFontAtlas() -- or imgui.love.BuildFontAtlas("RGBA32") or imgui.love.BuildFontAtlas("Alpha8")
Fonts can also be added from files, but you need to take care of where they are placed as ImFontAtlas_AddFontFromFileTTF cannot access files inside the source .love.
Images
You can add LÖVE images to ImGui with imgui.Image and imgui.ImageButton by passing a Texture object in the user_texture_id field.
local img = love.graphics.newImage("test.png")
local size = imgui.ImVec2_Float(img:getDimensions())
love.draw = function()
if imgui.Begin("test window") then
imgui.Image(img, size)
end
imgui.End()
imgui.Render()
imgui.love.RenderDrawLists()
end
The same applies to the wrappers of other functions taking "ImTextureID" as an argument.
ImDrawCallback
Custom draw callbacks can be defined by passing either a Lua function or an ImDrawCallback cdata (function pointer) to ImDrawList_AddCallback. Passing a Lua function directly is recommended.
-- example 1: pass Lua function as callback
love.draw = function()
imgui.ShowDemoWindow()
local dl = imgui.GetForegroundDrawList_Nil()
-- pass the print function as callback, just as an example
dl:AddCallback(print, nil)
imgui.Render()
imgui.love.RenderDrawLists()
end
-- example 2: pass a function pointer function as callback. Not recommended, unless the function pointer is *not* obtained by casting a Lua function.
local ffi = require "ffi"
-- cast the print function to a function pointer
f = ffi.cast("ImDrawCallback", print)
love.draw = function()
imgui.ShowDemoWindow()
local dl = imgui.GetForegroundDrawList_Nil()
dl:AddCallback(f, nil)
imgui.Render()
imgui.love.RenderDrawLists()
end
Note that ImDrawCallback_ResetRenderState is currently not implemented.
Gamepad
Gamepad navigation is supported (once enabled in io.ConfigFlags). Note that it only works for joysticks that LÖVE recognises as gamepads. The mapping used is the default Xbox 360 mapping from Dear ImGui. You can change mappings using love.joystick.setGamepadMapping.
Compiling the library and generating the wrappers
If you want to compile the shared library yourself clone this repository with
git clone --recursive https://codeberg.org/apicici/cimgui-love.git
and compile cimgui from the cimgui subfolder using CMake.
If you want to generate the wrapper yourself you should do the following after cloning the repository:
git checkoutthe relevant commit in thecimgui/imguinested submodule- run
cimgui/generator/generator.shorcimgui/generator/generator.batdepending on your system - run
generator.luain the base folder of the repository using Lua - compile the shared library in
cimguiusing CMake - the Lua module is in the
srcsubfolder