#Что это такое

Immediate Mode Graphical User Interface (ImGui) это паттерн/концепция/идея того как можно организовать реализовать библиотеку для графического интерфейса. Эту концепцию реализуют библиотеки, которые можно подключить и использовать в коде.

Пример библиотек:

  • Dear ImGui — самая популярная библиотека. Все называют её просто ImGui 😑, из-за чего возникает много путаницы. Библиотека изначально написана для C++, но существуют биндинги для JS, Python, Rust, .NET. Я её не использовал.
    • Кстати, существуют две демки по запуску этой библиотеки в браузере через компиляцию C++ в WASM:
  • egui — крайне перспективная библиотека для интерфейса на Rust. Я делал интерфейсы почти только на ней, так что можно считать, что вся данная статья написана под влиянием этой библиотеки. Мне кажется что в Dear ImGui должно быть примерно так же, но это только логичные догадки.
    • А у этой библиотеки официально и из коробки поддерживается запуск в браузере: демка.

Обязательно зайдите в веб-демки и посмотрите как они выглядят.

#Суть

Существуют классические интерфейсы, которые, как я понял, называются Retained Mode GUI. Я не знаю всех их глубин, и работал только с Delphi много лет назад. Насколько я помню с того опыта, чтобы создать кнопку, нужно:

  • Заранее сказать что эта кнопка существует, нарисовать её в специальном окошке, выбрать размеры итд.
  • Поставить коллбэк-функцию с названием вида on_button_click_1337, которая будет вызываться каждый раз когда по кнопке кликают.

В то время как чтобы сделать кнопку в ImGui, нужна всего-лишь одна строка кода:

if ui.button("Next").clicked() {
  // do on click
}

Никаких рисований, никаких коллбэков, нажатие кнопки проверяется прямо сейчас, и если прямо сейчас кнопка не нажата, то и ничего не происходит. От этого и получается название Immediate.

Если вы хотите что-то посложнее, то вот пример, взятый из ридмишки egui:

ui.heading("My egui Application");
ui.horizontal(|ui| {
    ui.label("Your name: ");
    ui.text_edit_singleline(&mut name);
});
ui.add(egui::Slider::new(&mut age, 0..=120).text("age"));
if ui.button("Click each year").clicked() {
    age += 1;
}
ui.label(format!("Hello '{}', age {}", name, age));

×1
png

Если в retained mode вы задаёте для каждого виджета его координаты, то здесь вы описываете каждый виджет сверху-вниз слева-направо. Его соответствующие координаты вычисляются автоматически, всё размещается компактно.

Ещё виджеты можно задавать в цикле или внутри if, и всё будет прекрасно работать:

for i in 0..10 {
    ui.horizontal(|ui| {
        if ui.button(i.to_string()).clicked() {
            ui.label(format!("{} is clicked", i));
        }
    });
}

Здесь будет создано 10 кнопок, которые расположены сверху-вниз, и если кликнуть по одной кнопке, то на момент клика справа от кнопки появляется надпись о том что она кликнута.

Можете представить себе такое в retained mode? Такая возможность позволяет отлично абстрагировать и переиспользовать элементы интерфейса без сложных классов, наследований, Dependency Injection итд.

#Как это работает

Если для retained mode хотя бы приблизительно понятно как он работает, да и мы к нему привыкли, то для immediate mode не очевидно.

  • Интерфейс ImGui вызывается в цикле, каждая итерация цикла считается одним кадром.
  • Перед началом кадра библиотека должна получить все данные касательно положения мыши, нажатых клавиш и прочих вводных.
  • Затем вызывается функция, в которой пользователь библиотеки получает структуру ui, рисует свой интерфейс. В этот момент библиотека в реальном времени формирует все виджеты, для каждого виджета определяет попала ли на него мышь, и формирует вершины и треугольники, которые потом будут рисоваться.
  • Теперь библиотека возвращает все сформированные mesh'и и отдаёт их на рисование бэкенду. Он рисует это на экран.
  • И так повторяется 60 кадров в секунду.

Особенность здесь в том, что мы заранее не знаем будут у нас какие-то виджеты или нет, как бы мы это знали в retained mode, а значит весь интерфейс должен сформировываться полностью заново каждый кадр.

#Плюсы и минусы

Плюсы:
  • Писать интерфейс очень просто
  • Идеально подходит для заимствований Rust
Минусы:
  • Жрёт ресурсы компьютера
  • Невозможность автоматически вычислить layout
  • Вообще на тему плюсов и минусов, советую почитать ридмишку egui, там хорошо расписывается что и как. Некоторые пункты я лишь пересказываю.

    #2Писать интерфейс очень просто

    • Для того чтобы создать кнопку вам нужно всего-лишь написать одну строчку в if'е.
    • Вся информация об интерфейсе хранится в одной переменной — ui, которую вам просто нужно таскать в нужные функции.
    • Можно переиспользовать сложные интерфейсы простым написанием функции, без создания класса, наследования итд.
    • Нельзя получить рассинхрон интерфейса и данных, так как весь интерфейс существует только сейчас. Можно поддерживать существование интерфейса одновременно с данными.
    • Не надо думать декларативно или асинхронно, весь код интерфейса пишется императивно сверху-вниз, из прошлого в будущее.

    Вообще, просто попробуйте сами. Увидите насколько это просто.

    #2Идеально подходит для заимствований Rust

    Rust очень строгий к ссылкам, и я даже не представляю как на нём можно написать retained mode без использования Arc<RefCell<_>>. Ведь там надо чтобы одновременно какое-нибудь поле ввода знало куда ему вводить информацию, и одновременно чтобы эта информация хранилась где-то ещё, кроме самого интерфейса.

    А в ImGui ничего такого не нужно вообще, так как обращение к данным существует только в сейчас, можно передавать обычные ссылки. Давайте посмотрим на то как работает поле ввода:

    ui.text_edit_singleline(&mut name);
    

    В него мы просто кидаем изменяемую ссылку на строку. Если за этот кадр нажались какие-то символы и данная строка изменилась, то она изменится после того как функция завершится.

    #2Жрёт ресурсы компьютера

    Для того чтобы каждый кадр заново вычислять весь интерфейс, требуется много вычислений. В retained mode почти всё можно вычислить заранее, и хранить только вычисленное, в том числе весь массив виджетов, перерисовывать только изменяющиеся части. В ImGui же такое невозможно, всё вычисляется и рисуется заново каждый кадр.

    Это значит потребляется:

    • Оперативная память
    • Ресурсы процессора
    • Батарея

    А ещё это значит что ваша программа не может работать на слабом железе, которое не может совершать такие вычисления 60 раз в секунду. В то время как retained mode на слабом железе работает прекрасно.

    Или это значит что ваша программа будет красть ресурсы у других процессов, которые тоже хотят что-то делать каждый кадр, и в целом программа будет работать медленней.

    Хотя это не настолько критично на современном железе.

    #2Невозможность автоматически вычислить layout

    Ещё одна проблема — мы не знаем что будет дальше. Это одновременно и мощь, потому что вы можете рисовать виджеты в цикле или через if, и одновременно вы не знаете какой итоговый размер будет у всего окна. Поэтому размер окна вычисляется пост-фактум.

    Первый минус этого — ваше окно будет неадекватно реагировать на ресайзинг. Посмотреть на это можно здесь (eng.). Там же есть ответ от создателя библиотеки почему это адекватное поведение для ImGui.

    Второй минус — вам будет сложно засунуть виджеты в таблицу или сетку, потому что для нормальной отрисовки сетки нужно изначально знать размер одной ячейки.

    Вообще, egui как-то с этим справляется, но я бы не стал надеяться что это будет работать идеально.

    #Нужно принять правила игры

    Мы очень привыкли к интерфейсам в retained формате. И при использовании ImGui так и хочется сделать интерфейс похожий на то что ты раньше видел.

    Или хочется поругаться на ImGui что он отличается.

    Но я понял что всё это не нужно, нужно просто принять правила игры. Нужно принять что:

    • У вас будет тратиться процессор.
    • Ваши окна не смогут нормально реагировать на ресайзинг.
    • Вы будете размещать все виджеты сверху-вниз справа-налево, никакого ручного плейсинга по координатам.
    • Ваши окошки будут расти в высоту, а не в ширину, из-за того что вы не сможете адекватно использовать пространство сбоку.

    Надо с этим просто смириться, и думать об интерфейсах в этих ограничениях. Тогда вы можете максимально эффективно использовать ImGui в своей жизни. Ведь не бывает такой простоты за бесплатно.

    #Зачем он нужен

    ImGui со всеми его минусами выглядит так, что он не создан для серьёзных интерфейсов по типу Photoshop, браузеров или мессенджеров. Зачем тогда он может быть нужен?

    #2Пет-проекты

    Вы можете писать пет-проект по нескольким причинам:

    • Для того чтобы научиться какой-то технологии
    • По приколу

    В первом случае он не подходит, а вот во втором очень даже. Обычно при написании пет-проекта хотелось бы тратить на него как можно меньше времени, чтобы получить результат как можно быстрее, ибо на него нельзя тратить 40 часов в неделю, и надо ещё как-то жить человеческую жизнь.

    Вот тут можно прекрасно пойти на компромисс всех минусов ImGui и просто забить на них, делая интерфейс, который просто работает.

    #2Программа на несколько человек

    Скажем вы работаете в какой-то компании, и у вас есть не-программисты, которым для решения каких-то внутренних процессов нужно написать программу именно с графическим интерфейсом, потому что пользоваться консольными утилитами или программированием они не умеют.

    В этом случае понятно что данная программа не будет представлена наружу, она нужна для нескольких человек, и тратить на неё огромные ресурсы для разработки интерфейса в retained mode просто неразумно.

    Поэтому можно позволить себе написать программу для них на ImGui силами одного-двух разработчиков.

    #2Редкая программа

    Скажем, вы делаете свою программу для задания раскладки клавиатуры по типу MSKLC. Сколько в вашей программе суммарно будет сидеть каждый пользователь? Дай бог за всю свою жизнь каждый пользователь просидит в вашей программе 5 часов. Ибо он один-два раза настроит свою раскладку, и успокоится.

    Учитывая такое маленькое время использования, можно пойти на очень много жертв, чтобы не тратить слишком много времени на разработку. Тут можно и сделать программу, которая жрёт батарею, выглядит не идеально, не может нормально ресайзиться и так далее.

    #2Прототип

    Я недавно написал программу для изучения английских слов, и только после того как я её написал и попопользовался несколько дней, я понял какие фичи прям позарез нужны. И до этих фич я бы не смог додуматься без написания программы, каким умным бы я ни был.

    В нашем мире сейчас всё идёт к тому, что истина только в прототипах. Надо как можно раньше выпускать продукт на пользователей и допиливать только после обратной связи.

    Это означает что на ImGui можно написать прототип серьёзной программы или какой-то фичи, опробовать её на практике, найти недостающие фичи, и только потом писать серьёзно, на миллионы пользователей, в retained mode.

    #Что я сделал на ImGui

    #2The Tenet of Life

    Самый первый проект где я познакомился с ImGui — симулятор обратимых клеточных автоматов. Здесь я просто заюзал ImGui, который шёл внутри библиотеки macroquad, для того чтобы написать текст и сделать пару кнопочек.

    ×1
    png

    Казалось бы такая простота, а это кардинально изменило юзабельность программы, ведь раньше все мои программы с графикой управлялись исключительно через нажатия букв на клавиатуре, которые написаны в текстовом файле help.txt. Например, так было в моей программе из 2015 года.

    #2Portal Explorer

    Я рисовал сцены с порталами через код при помощи рейтрейсинга. Чтобы подвинуть или повернуть что-то мне приходилось редактировать код и перекомпилировать проект. В один момент мне это надело и я захотел поворачивать вещи в реальном времени.

    Сделал чтобы через интерфейс можно было вращать матрицы и это отображалось в реальном времени.

    Это показалось слишком просто, я почувствовал силушку богатырскую, взял и напилил целый визуальный редактор на ImGui:

    ×1.7
    png
    Ссылка на твит

    Уже через него можно было редактировать матрицы, объекты и их код в шейдерах. Это на порядок упрощало процесс создания сцен, благодаря чему я стал эффективнее. Главный прикол что я сделал его просто за пару дней и он просто работал. Это самое приятное чувство при использовании ImGui.

    Более подробно об этом можно почитать в моём канале в телеграме, начиная отсюда: @optozorax_dev/351.

    А уже потом я ударился во все тяжкие, начал делать сложные интерфейсы, inline элементы, формулы, и интерфейс который задаёт другой интерфейс.

    Вообще знаете, когда я всё это задумывал, я изначально думал: «Ну сделаю чтобы в структурках на расте можно было писать порталы, и чтобы это потом кодогенерилось. Но, блин, это такой гемор, столько всего надо сделать.», а тут я нафиг сделал не только это, но ещё и сделал ко всему этому интерфейс, я вообще даже не мечтал о таком результате, который есть сейчас.

    Настолько великую силу даёт ImGui. От абсолютного нуля до realtime sophisticated визуального редактора 3D сцен за один проект.

    ×1.4
    png

    #2Learn Words

    Об этом проекте я написал статью. Его особенность в том, что заодно с написанием продукта, я решил проверить гипотезу о том что с ImGui напишу программу очень быстро. И написал. Быстро. На минимально рабочий прототип ушло 6ч чистого времени, а чтобы полностью завершить проект, ушло 40ч чистого времени. И это при учёте того что программа полностью построена на графическом интерфейсе, и там есть довольно сложные элементы.

    Я бы такую программу с retained mode не написал бы никогда.

    ×1.6
    png

    #Схожесть с консольными утилитами

    Обычно люди в качестве пет-проектов, или просто начинающие программисты пишут консольные утилиты, потому что они проще, потому что они не требуют слишком много разбираться и ломать голову.

    ImGui предоставляет точно такие же преимущества. И если раньше школьники начинали с консольных приложений, потому что они проще, то сейчас они могут начинать с ImGui.

    Я уверен, что если школьник сразу начнёт делать свои программы и видеть результат в ImGui, то весь процесс будет для него в 10 раз интересней, чем когда он вводит данные из файла или в консоли. Это увеличивает вовлечённость и потенциал обучения программированию.

    Надеюсь образовательные программы возьмут это на вооружение и будут школьников с первых уроков обучать ImGui.

    #Заключение

    ImGui позволил мне впервые в жизни создать графические интерфейсы. Причём эти интерфейсы не просто делают программу чуть удобнее, чем нажимать буквы на клавиатуре, а кардинально меняют и моё взаимодействие со своим продуктом (визуальный редактор).

    У этого вида интерфейсов есть свои плюсы и минусы. Нужно просто понимать когда его можно использовать, а когда нет; нужно принять все его ограничения, и тогда вы получите максимальные дивиденды.