Предыдущая часть:
В этом выпуске придадим предыдущим рассуждениям оформленный вид и продвинемся дальше.
Ну и вот чем можно уже развлечься в текущем варианте (лучше развернуть на полный экран):
Данное демо содержит 900 GUI-элементов, все они отрисовываются с нуля в каждом кадре.
В прошлый раз говорилось о том, что нужен диспетчер для управления всеми объектами GUI, а также что каждый объект должен иметь свой уникальный ID. Сделаю первичный инструментарий диспетчера. Сначала сама его структура:
Тип GUI_UID это специальный тип для хранения уникальных ID (далее UID). Сейчас это просто алиас для 64-битного целого числа. Диспетчер имеет текущее значение uid, а также счётчик объектов item_cnt, и массивы для хранения UID и самих объектов.
Добавление объекта
Здесь акцент сделан именно на push, то есть список объектов рассматриваем как стек, и новый элемент всегда добавляется на его вершину. Это соответствует иерархическому устройству интерфейса, и не думаю, что потребуется вставлять новый объект, скажем, куда-то в середину списка. Если даже и потребуется, это можно сделать потом.
Что здесь происходит? Во-первых, проверяем, не переполнился ли список. GUI_UID_NONE это специальная константа (банально равна 0), обозначающая несуществующий UID, которая возвращается в случае неудачи.
В противном случае объекту назначается текущий неиспользованный UID, после чего значение счётчика uid увеличивается на 1. Копия объекта сохраняется в конец массива items, а его UID сохраняется в конец массива uids. Индекс в обоих массивах один и тот же, поэтому есть чёткое соответствие между UID и объектом. И возвращаем UID объекта для дальнейшего использования.
Удаление объекта
Сделано в двух вариантах. Первый это pop, который опять же работает по принципу стека и всегда удаляет самый верхний элемент:
Стоит обратить внимание на тип GUI_ItemResult:
Это структура из двух полей. Одно поле это код результата (перечислимый тип GUI_Result), второе это объект. Дело в том, что функция GUI_dispatcher_pop_item() возвращает удалённый объект, но в случае ошибки мы должны понять, вернулся к нам нормальный объект или нет. Поэтому возвращаемый объект заворачивается в тип GUI_ItemResult, где есть поле результата.
Второй вариант это собственно удаление произвольно взятого объекта по его UID:
Здесь мы по UID сначала получаем индекс объекта. Опять же, так как индекс это просто целое число, его валидность никак не определить, поэтому используется спецтип GUI_IndexResult с кодом результата:
Далее, если индекс находится не в конце списка, значит образуется разрыв. Нужно подтянуть хвосты массивов uids и items на место освобождённого элемента. Это делается переброской блоков памяти с помощью memcpy().
Как происходит поиск индекса по UID:
Да, это банальный перебор массива. И да, специально сообщаю, что я в курсе про хэш, связные списки и прочее. И у меня, внезапно, даже есть целая подборка материалов про коллекции:
Поэтому то, что я тут делаю, я делаю исключительно потому, что так считаю нужным.
В качестве теста я создал много-много окошек, затем в случайном порядке удаляю какие-то окошки и добавляю новые (они с красными заголовками):
Получение объекта по UID
Здесь уже всё тривиально:
Обновление объекта по UID
Здесь также всё тривиально:
Полный код этой версии я разместил на сайте онлайн-компилятора. Но откомпилировать и запустить в онлайне не получится. Инструкции по компиляции с использованием SDL2 есть в проекте MineSweeper:
Теперь можно попробовать добавить в окна какие-то элементы, например чекбоксы или кнопки. С ними тоже будут некие вопросы хранения, но ещё до этого возникает другая проблема – как, собственно, их описывать? В простейшем случае "окно с кнопкой" ничего сложного нет, но я сразу иду в максимально неудобные варианты, чтобы не было соблазна потом забить на их реализацию.
Вот пример такого варианта:
Здесь у элементов, добавленных в окно, в свою очередь есть собственные дети. Вся иерархическая структура представлена следующим образом:
Для начала нужен внятный способ описания таких деревьев. Есть два пути, не исключающие друг друга:
- Задать всё дерево вручную, описав каждый его узел как готовую структуру
- Начать с корневого узла и по одному элементу, итеративно, добавлять в него остальные
Далее, каждый узел дерева это структура GUI_Item, и будет присутствовать в списке диспетчера для того, чтобы выводиться на экран и интерактивно взаимодействовать. Значит, дерево в диспетчере нужно будет развернуть в плоский список элементов так, чтобы во-первых отрисовка их происходила в нужном порядке, а во-вторых, чтобы была некая навигация по этому списку. К примеру, одна из двух вкладок всегда будет неактивна, значит её содержимое рисовать не надо, значит надо уметь перепрыгнуть через всё содержимое неактивной вкладки к следующему элементу списка.
Читайте дальше: