Найти тему
ZDG

Графический интерфейс программы на языке C #3: Пора подумать о детях

Оглавление

Предыдущая часть:

В этом выпуске придадим предыдущим рассуждениям оформленный вид и продвинемся дальше.

Ну и вот чем можно уже развлечься в текущем варианте (лучше развернуть на полный экран):

Данное демо содержит 900 GUI-элементов, все они отрисовываются с нуля в каждом кадре.

В прошлый раз говорилось о том, что нужен диспетчер для управления всеми объектами GUI, а также что каждый объект должен иметь свой уникальный ID. Сделаю первичный инструментарий диспетчера. Сначала сама его структура:

Тип GUI_UID это специальный тип для хранения уникальных ID (далее UID). Сейчас это просто алиас для 64-битного целого числа. Диспетчер имеет текущее значение uid, а также счётчик объектов item_cnt, и массивы для хранения UID и самих объектов.

Добавление объекта

-2

Здесь акцент сделан именно на push, то есть список объектов рассматриваем как стек, и новый элемент всегда добавляется на его вершину. Это соответствует иерархическому устройству интерфейса, и не думаю, что потребуется вставлять новый объект, скажем, куда-то в середину списка. Если даже и потребуется, это можно сделать потом.

Что здесь происходит? Во-первых, проверяем, не переполнился ли список. GUI_UID_NONE это специальная константа (банально равна 0), обозначающая несуществующий UID, которая возвращается в случае неудачи.

В противном случае объекту назначается текущий неиспользованный UID, после чего значение счётчика uid увеличивается на 1. Копия объекта сохраняется в конец массива items, а его UID сохраняется в конец массива uids. Индекс в обоих массивах один и тот же, поэтому есть чёткое соответствие между UID и объектом. И возвращаем UID объекта для дальнейшего использования.

Удаление объекта

Сделано в двух вариантах. Первый это pop, который опять же работает по принципу стека и всегда удаляет самый верхний элемент:

-3

Стоит обратить внимание на тип GUI_ItemResult:

-4

Это структура из двух полей. Одно поле это код результата (перечислимый тип GUI_Result), второе это объект. Дело в том, что функция GUI_dispatcher_pop_item() возвращает удалённый объект, но в случае ошибки мы должны понять, вернулся к нам нормальный объект или нет. Поэтому возвращаемый объект заворачивается в тип GUI_ItemResult, где есть поле результата.

Второй вариант это собственно удаление произвольно взятого объекта по его UID:

-5

Здесь мы по UID сначала получаем индекс объекта. Опять же, так как индекс это просто целое число, его валидность никак не определить, поэтому используется спецтип GUI_IndexResult с кодом результата:

-6

Далее, если индекс находится не в конце списка, значит образуется разрыв. Нужно подтянуть хвосты массивов uids и items на место освобождённого элемента. Это делается переброской блоков памяти с помощью memcpy().

Как происходит поиск индекса по UID:

-7

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

Коллекции в программировании | ZDG | Дзен

Поэтому то, что я тут делаю, я делаю исключительно потому, что так считаю нужным.

В качестве теста я создал много-много окошек, затем в случайном порядке удаляю какие-то окошки и добавляю новые (они с красными заголовками):

-8

Получение объекта по UID

Здесь уже всё тривиально:

-9

Обновление объекта по UID

Здесь также всё тривиально:

-10

Полный код этой версии я разместил на сайте онлайн-компилятора. Но откомпилировать и запустить в онлайне не получится. Инструкции по компиляции с использованием SDL2 есть в проекте MineSweeper:

Теперь можно попробовать добавить в окна какие-то элементы, например чекбоксы или кнопки. С ними тоже будут некие вопросы хранения, но ещё до этого возникает другая проблема – как, собственно, их описывать? В простейшем случае "окно с кнопкой" ничего сложного нет, но я сразу иду в максимально неудобные варианты, чтобы не было соблазна потом забить на их реализацию.

Вот пример такого варианта:

-11

Здесь у элементов, добавленных в окно, в свою очередь есть собственные дети. Вся иерархическая структура представлена следующим образом:

-12

Для начала нужен внятный способ описания таких деревьев. Есть два пути, не исключающие друг друга:

  1. Задать всё дерево вручную, описав каждый его узел как готовую структуру
  2. Начать с корневого узла и по одному элементу, итеративно, добавлять в него остальные

Далее, каждый узел дерева это структура GUI_Item, и будет присутствовать в списке диспетчера для того, чтобы выводиться на экран и интерактивно взаимодействовать. Значит, дерево в диспетчере нужно будет развернуть в плоский список элементов так, чтобы во-первых отрисовка их происходила в нужном порядке, а во-вторых, чтобы была некая навигация по этому списку. К примеру, одна из двух вкладок всегда будет неактивна, значит её содержимое рисовать не надо, значит надо уметь перепрыгнуть через всё содержимое неактивной вкладки к следующему элементу списка.

Читайте дальше: