08 Feb 2025
Dear ImGui - это библиотека, в которой можно очень легко и быстро сделать интерфейс. В ней используется очень необычный подход.
Например, если Вы хотите добавить нарисовать кнопку, то достаточно пары строчек кода:
if imgui.button("Текст кнопки"):
print("Нажали кнопку!")
Это просто верх лаконичности и удобства для программиста. Никаких MVC, MVVM и прочих страшных заклинаний с развесистыми иерархиями объектов. Самое то, чтобы в прототипе накидать несколько кнопок, и уже через минуту уже тыкать их мышкой.
Библиотека сделана для C++ и нужд геймдева, но я буду сразу писать про обёртку для Python. Потому что Python мне ближе, и других удобных способов для рисования интерфейса я не нашёл.
Итак, библиотека может работать поверх разных штук (например, поверх opengl или pygame). Я выбрал glfw.
Сначала надо поставить библиотеку, причём указать что хотим использовать с glfw.
apt install imgui[glfw]
Дальше надо сделать окно c помощью glfw. Я так подробно описываю шаги, потому что в примерах для pyimgui прыгают сразу к рисованию кнопочек, а без создания окна ничего не заработает.
import imgui
from imgui.integrations.glfw import GlfwRenderer
import glfw
from typing import List
if not glfw.init():
print("Could not initialize OpenGL context")
exit(1)
window = glfw.create_window(1280, 720, "Simple ImGui Example", None, None)
if not window:
glfw.terminate()
print("Could not create GLFW window")
exit(1)
glfw.make_context_current(window)
imgui.create_context()
renderer = GlfwRenderer(window)
imgui.get_io().ini_file_name = None
Вот этот код создаёт окно и только после этого мы готовы в нём рисовать.
Дальше можно взять код из примера с imgui и увидеть, что внутри нашего окна появилось какое-то фейковое окошечко, которое можно двигать внутри окна от ОС. Это конечно хорошо, но мне хотелось, чтобы это фейковое окошечко было размером с окно ОС и двигать его было нельзя. Ну то есть, я хочу просто иметь рабочую область размерном с окно, которое создал в glfw.
Кроме того, если делать подвижные окошки внутри большого окна, то их состояние сохранится как раз в ini_file_name. Поскольку это всё не нужно, я это явно отключил.
Теперь напишем функцию рисования и цикл:
def draw_frame(): ...
while not glfw.window_should_close(app.window):
glfw.poll_events()
renderer.process_inputs()
draw_frame()
glfw.swap_buffers(app.window)
Зададим, чтобы наше “окошко” от imgui было по размеру как окно ОС, никуда не двигалось и не сворачивалось. Я это пишу подробно, потому что, например, chatgpt о такой возможности не знал и мне всё равно пришлось читать документацию и разбираться.
def draw_frame():
imgui.new_frame()
w, h = glfw.get_window_size(window)
imgui.set_next_window_size(w, h)
imgui.set_next_window_position(0, 0)
with imgui.begin("Main Window", False,
imgui.WINDOW_NO_RESIZE |
imgui.WINDOW_NO_MOVE |
imgui.WINDOW_NO_COLLAPSE |
imgui.WINDOW_NO_TITLE_BAR |
imgui.WINDOW_MENU_BAR):
pass # тут будет рисование всяких кнопок
imgui.render()
self.renderer.render(imgui.get_draw_data())
И тут я скажу ещё об одной особенности питона. В С++ версии библиотеки требуется явно вызывать методы для begin и end, например ImGui::BeginMenuBar()
и ImGui::EndMenuBar()
. При этом довольно легко ошибиться и закрыть не то, либо закрыть неправильное количество раз. Нейронка об этом опять же не знает, но к счастью разработчики библиотеки на питоне сделали поддержку конструкции with
Т.е., вместо
imgui.begin_menu_bar()
# some code
imgui.end_menu_bar()
Можно (и нужно) писать так:
with imgui.begin_menu_bar():
# some code
или даже так:
with imgui.begin_menu_bar() as menu_bar:
if menu_bar.opened:
with imgui.begin_menu('File') as file_menu:
if file_menu.opened:
clicked, state = imgui.menu_item("Save", '', False, True)
if clicked:
print("Save")
Кстати, в документации в menu_item предлается второй строчкой передавать шорткат типа ‘Ctrl+S’, он будет показан в UI, но библиотека imgui никак не обрабатывает нажатия клавиш, и вызов сохранения по ctrl+S надо будет сделать самостоятельно. Чтобы не вводить никого в заблуждение, я в качестве второго аргумента передал пустую строчку.
Все элементы типа кнопок и текста располагаются друг под другом.
Если хочется расположить их на одной линии, то надо вызвать imgui.same_line()
, например так:
if imgui.button("Кнопка 1"):
print("нажали 1")
imgui.same_line()
if imgui.button("Кнопка 2"):
print("нажали 2")
Подобно тому, как я создал окно размером с экран, можно сделать дочерний объект, например, в половину ширины окна:
with imgui.begin_child("Left panel", width=w // 2, border=True):
# код для создания элементов внутри левой панели
imgui.same_line()
with imgui.begin_child("Right panel", width=w // 2, border=True):
# код для создания элементов внутри правой панели
В итоге получится что-то такое:
Что прикольно, этот код выполняется каждый кадр. Так что всю сложную логику для взаимного расположения объектов можно просто и явно написать в виде кода, ширина панелей вычисляется заново каждый кадр.
На самом деле при вызовах типа “if imgui.button()” библиотека сохраняет все-все управляющие вызовы, и только потом при вызове imgui.render()
выполнит команды и нарисует интерфейс.
И вообще, эта библиотека для меня является примером лаконичного и изящного программного интерфейса. Её легко освоить и очень удобно использовать.
В геймдеве часто используют всякие нестандартные, но при этом интересные решения, dear imgui одно из них.
Полезные ссылки: