Controls.md
Арго Фреймворк Википедия
Содержание
- Введение
- Установка Фреймворка
- Приложения Фреймворка
- Создание Приложений
- Примеры Приложений
- Структура Приложений
- Структура Управляющих Элементов *
- Общая Структура Управляющего Элемента
- Заголовок Управляющего Элемента
- Таблица Состояний
- Таблица Событий
- Управляющие Суб-элементы и Макеты
- Динамические Виджеты
- Стек Управляющих Элементов
- Поиск Управляющего Элемента
- Уведомление Родительского Элемента
- Отрисовка Управляющего Элемента
- Динамические Управляющие Элементы
- Общая Структура Управляющего Элемента
- Утилиты Фреймворка
Общая Структура Управляющего Элемента
Управляющие элементы фреймворка являются важнейшей и неотъемлемой частью любого приложения. Многие элементы реализованы как системные ресурсы и могут быть доступны, используя идентификаторы ресурсов или элементы темы. Второй вариант предпочтителен, но пока не все системные элементы доступны через элементы темы, это будет исправляться. Пользовательские приложения, в общем случае, доступа к системным ресурсам не имеют, поэтому механизм доступа через элементы темы необходим.
Однако разработчику часто приходится разрабатывать и свои собственные управляющие элементы, элементы управления, принадлежащие приложению. Это очень облегчает разработку пользовательского интерфейса, поскольку можно реализовать группы управляющих элементов, управлять этими группами (скрывать, удалять, перемещать). Можно разрабатывать элементы как со стандартным поведением (behavior), так и с пользовательским поведением (custom). Механизм поведения элементов очень важен, он определяет как фреймворк обрабатывает данные элементы. Например, элемент, поведение которого определено как behavior=“button”, будет обрабатывться как кнопка, а элемент, определенный с поведением behavior=“text_entry”, будет обработан как поле текстового ввода. Для пользовательских элементов поведение определено как behavior=“custom”, и фреймворк не знает, как его обрабатывать. Поэтому обработка может выполнятся двумя вариантами: либо приложение само управляет поведением элемента, либо указывается специальная DLL библиотека, которая реализует механизм поведения на уровне фреймворка. Последний вариант на практике реализуется редко, библиотеку надо писать по определенным правилам, используя специальный API. Касаться этой темы в данном разделе не буду. Во фреймворке есть примеры использования подобных элементов, можно посмотреть на примеры реализации DLL библиотек.
В любом случае элемент имеет определенную базовую структуру, которая и будет рассмотрена в этом разделе.
Заголовок Управляющего Элемента
Заголовок определяется нодой ‘ctrldef’, которая содержит ряд атрибутов (пример из файла resources/argo/system/fs0/sysres/controls/resp_button.fml):
<ctrldef name="resp_button" behavior="button">
...
</ctrldef>
Нода ‘ctrldef’ это верхняя нода, у неё не может быть родительской ноды, поэтому описание управляющего элемента автономно и не зависит от других определений, такое описание может быть преобразовано в бинарный файл.
Обязательный атрибут ‘name’, в общем случае, фреймворком не используется, но он определяет имя бинарного файла (если не определен опциональный атрибут ‘file’). Обычно, и это хорошая практика, его значение совпадает с именем файла описания без расширения, чаще всего он и используется для определения имени генерируемого бинарного файла. Атрибут ‘file’ обрабатывается парсером по другим правилам, поэтому используется для этой цели, если имя файла нельзя задать в атрибуте ‘name’ (например, имя ‘123_my_widget’ нельзя задать в атрибуте ‘name’, поскольку идентификатор (а это идентификатор) не может начинаться с цифр и ряда других символов). Атрибут ‘file’ эту проблему устраняет, он задает имя файла также без расширения.
Важнейший атрибут ‘behavior’, как и говорилось выше, задает поведение управляющего элемента. Это перечислимый атрибут, его значение определяется некоторым набором текстовых идентификаторов, их достаточно много: button, rbutton, chbutton, rbutton_grp, text_entry, label, overlay, dynlist, droplist, cursor, pixmap и, конечно же, custom. Список периодически расширяется, но, к сожалению, пока не задокументирован. Обработка элементов с разным поведением выполняется по разному и разными модулями фреймворка (точнее плеера). Поэтому при разработке элемента нужно точно знать, что вы хотите от него получить. Если нужен элемент с конкретным поведением, лучше поискать его среди системных, почти на все модели поведения такие элементы есть. Дальше можно либо использовать системный, либо на его основе написать свой. Системные управляющие элементы располагаются в папке resources/argo/system/fs0/sysres/controls/ и их имена, как правило, напоминают их поведение.
Последний и мало используемый атрибут ‘dll’ определяет идентификатор DLL ресурса для пользовательского элемента. DLL подключается так же, как и любой другой ресурс в ресурсном файле приложения или системы, и доступна по данному идентификатору. Менеджер ресурсов загружает библиотеку и находит доступные символы (функции) в ней. При обработке пользовательского элемента фреймворк проверяет наличие таких функций и, соответственно, вызывает, что и обеспечивает нужное поведение такого элемента. Надо отметить, что этот механизм не безопасен, функции работают в контексте фреймворка, поэтому в случае креша в функции будет и креш фреймворка. Это можно обойти механизмом обработки исключений, но текущая его реализация это не позволяет. Вообще элементов, которые используют DLL мало, а будет еще меньше, поскольку они хорошо заменяются элементом динамического списка. Пока еще остается системный элемент resources/argo/system/fs0/sysres/controls/menubar.fml, который использует такую DLL. Но в целом наличие такого механизма очень полезно, поскольку оставляет возможность для разработки очень сложных элементов.
Таблица Состояний
Таблица состояний является обязательной для любого управляющего элемента, даже если состояние лишь одно и вырожденное (не содержит внутренних виджетов и память под его битмап не выделяется). Как правило, состояния не вырождены и определяют битмап, на котором рисуется изображение состояния. Например, для кнопки эти состояния определяют, как выгладит кнопка в неактивном и нажатом состоянии. Переключение состояний определяет визуализацию нажатия кнопки, переключение выполняется по заданным событиям - LBUTTON_DOWN и LBUTTON_UP, которые определяются в таблице событий (см. следующий пункт). Таблица состояний определяется нодой ‘states’, это нода-агрегатор, она не имеет атрибутов, но в описании допускается только одна такая нода. Описание всех состояний выполняется именно в ней. Пример таблицы состояний приведен ниже (используется тот же элемент resources/argo/system/fs0/sysres/controls/resp_button.fml):
<states>
<state id="IDLE" default="true">
<bitmap fill="bgcolor"/>
<shape figure="roundrect" bcolor="@BCOLOR_IDLE_DEF" fcolor="@FCOLOR_IDLE_DEF" thickness="@THICKNESS_DEF" radius="@RADIUS_DEF"/>
</state>
<state id="PRESSED">
<bitmap fill="bgcolor"/>
<shape figure="roundrect" bcolor="@BCOLOR_PRESS_DEF" fcolor="@FCOLOR_PRESS_DEF" thickness="@THICKNESS_DEF" radius="@RADIUS_DEF"/>
</state>
<state id="DISABLED">
<bitmap fill="bgcolor"/>
<shape figure="roundrect" bcolor="@BCOLOR_DIS_DEF" fcolor="@FCOLOR_DIS_DEF" thickness="@THICKNESS_DEF" radius="@RADIUS_DEF"/>
</state>
</states>
Каждое состояние создается нодой ‘state’. Эта нода содержит ряд атрибутов, которые определяют имя, фоновый цвет и флаги состояния. Также нода содержит ряд суб-нод, которые определяют изображения состояния, статические тексты и другие статические суб-виджеты. Состояния не содержат координат или размеров по высоте или ширине элемента, они занимают всю площадь управляющего элемента, заданные при его создании нодой ‘control’. Исключение составляют состояния, отмеченные флагом ‘detached’, их размеры определяются динамическими виджетами, привязанными к данному управляющему элементу.
Атрибут ‘id’ определяет текстовый идентификатор состояния, это обязательный параметр, который широко используется фреймворком и приложениями. Каждое состояние имеет еще и неявный числовой идентификатор, который тоже может использоваться приложением вместо текстового, но его еще надо получить, используя текстовый (использование числового идентификатора позволяет делать неблокирующий вызов для изменения состояния элемента, что ускоряет работу приложения. Использование текстового идентификатора всегда делает блокирующий вызов). Числовые идентификаторы назначаются в порядке объявления состояний: для состояния ‘IDLE’ это будет 0, для состояния ‘PRESSED’ - 1, для состояния ‘DISABLED’ - 2. Однако полагаться на это нельзя, при изменении элемента нумерация состояний может быть другой. Текстовый идентификатор может быть любой, если он удовлетворяет правилам для идентификаторов (отсутствие цифр в начале, специальных символов в начале или конце имени, внутри допускаются цифры и символ подчеркивания - ‘_’).
Атрибут ‘bgcolor’ определяет фоновый цвет битмапа состояния, если таковой определен. Значение цвета числовое, в формате RGB. Например, значение 0xFF0000 определяет красный цвет. Цвет можно также задавать через переменную. Используется этот атрибут редко, есть другой механизм для заливки битмапа (вообще, атрибут выглядит устаревшим, может, будет удален в дальнейшем).
Атрибут ‘default’ со значением ‘true’ определяет состояние по умолчанию, т.е. состояние, в котором будет элемент после создания. Это не обязательный параметр, без этого атрибута состоянием по умолчанию будет первое состояние в таблице. Также нет смысла назначать его нескольким состояниям, работать будет то состояние, которое отмечено флагом самым первым. По умолчанию значение атрибута ‘false’. В состояние по умолчанию элемент может возвращаться автоматически, согласно поведению данного элемента (например, в радио кнопках при выборе одной, остальные возвращаются в состояние по умолчанию).
Атрибут ‘disabled’ со значением ‘true’ определяет состояние, находясь в котором, элемент не может перейти в состояние по умолчанию в ситуации выше, он так и останется в этом состоянии (если элемент заблокирован, он не перейдет в активное состояние). Разумеется, этот флаг несовместим с флагом ‘default’, хотя это и не проверяется.
Атрибут ‘detached’ определяет особое состояние элемента, в этом случае поведение элемента определяется динамическим виджетом, привязанным к элементу. Пока есть только один пример его использования - в элементе с поведением ‘droplist’. В нем состояние ‘EXPANDED’ (развернутый) отмечено этим флагом, и обработка этого состояния определяется динамическим виджетом ‘droplist’ (подробнее о привязанных динамических виджетах будет рассказано ниже).
Состояние может содержать несколько суб-нод, которые определяют графику, отрисовываемую в битмапе состояния. Этими суб-нодами являются следующие статические виджеты: bitmap, region, border, text, shape. Битмап состояния создается при объявлении любой из этих суб-нод (для ‘detached’ состояния и это не обазательно, битмап будет создан в любом случае).
Нода ‘bitmap’ определяет заливку битмапа состояния заданным цветом в зависимости от режима заливки, который определен атрибутом ‘fill’. Этот перечислимый атрибут может принимать следующие значения: ‘color’, ‘bgcolor’ и ‘bgimage’. Значение ‘color’ определяет заливку цветом, который определен атрибутом ‘color’, его значение является целым числом в RGB формате. Режим заливки ‘bgcolor’ определяет заливку изображением на подложке, где размещен элемент. Режим заливки ‘bgimage’ определяет заливку изображением из фоновой картинки, установленной в плеере. В последних двух случаях атрибут ‘color’ игнорируется. Значения для обоих атрибутов могут быть определены через переменную и могут выставлятся по разному для разных управляющих элементов с одним и тем же описанием. Эта нода в состоянии единична, делать две или больше таких нод смысла нет (хотя это и не запрещено) в отличие от других статических виджетов (может, только бордер еще такой).
Нода ‘region’ задает некоторое изображение, которое будет отрисовано в битмапе как, например, изображения иконок. Регион - это статический виджет, который обеспечивает отрисовку графического ресурса по определенным правилам - размеру, выравниванию, режимом отрисовки. Существуют три режима отрисовки: regular - отрисовка делается размер в размер, ограничиваясь размерами битмапа и самой картинки; tile - отрисовка плиткой, маленькая картинка дублируется в битмапе по ширине и высоте, используется в управляющих элементах редко; stretch - изображение сжимается по размерам битмапа (только сжимается). Останавливаться подробно на атрибутах этого виджета я здесь не буду, будет подробная глава, посвященная статическим виджетам.
Нода ‘border’, как правило, используется для отрисовки границ виджета. Для нее тоже нужен графический ресурс-картинка, но используется он по-другому. У бордера есть атрибут ‘offsets’, который задает отступы сверху, снизу, справа и слева. Выбираются 8 участков изображения по этим отступам и их пересечениям, которые формируют границы и углы прямоугольника, и они рисуются в заданных координатах. Также выбирается центральная точка на изображенни вне отступов, и по ней определяется основной цвет заливки. В результате элемент оказывается обрамлен границами, которые присутствуют на исходном изображении, но от размера этого изображения они не зависят, что позволяет сделать управляющие элементы с разными размерами в одинаковом стиле (например, те же кнопки). Этот виджет используется уже редко, но его ценность в малом расходе оперативной памяти, меньшей загрузке процессора при отрисовке и большем быстродействии. Также подробный рассказ о нем будет отдельно.
Нода ‘text’ определяет статический текст на управляющем элементе, используя текстовый ресурс и ресурс шрифта. Текст может рисоваться разным цветом и с разным фоном. Также подробно об этом виджете будет рассказано в дальнейшем. Текст непосредственно рисуется в битмапе состояния (как и все статические виджеты), пока не поддерживается смена языков системы. Вообще использование этого виджета уходит в прошлое, но он все еще может быть востребован в условиях экономии ресурсов.
Самая интересная нода ‘shape’, она позволяет рисовать фигуры (пока только круг или эллипс и скругленный прямоугольник) без графических элементов и с любыми цветами, что делает этот виджет идеальным для управляющих элементов в условиях смены темы. Большая часть используемых управляющих элементов уже реализована с использованием этого виджета. У него много параметров (которые зависят от атрибута ‘figure’), которые определяют цвет и толщину границы, внутреннюю и внешнюю заливку, радиус скругления и, возможно, многие другие параметры в дальнейшем. Все параметры могут быть заданы через переменные, что и делается в примере (символ ‘@’ обозначает переменную). Виджет имеет хороший потенциал для отрисовки и других фигур. Среди недостатков можно выделить сравнительно долгую отрисовку по формулам с использованием операций с плавающей точкой, но это визуально не заметно (да и отрисовка делается только при создании элемента). Лучше может работать только векторная графика, где нет ограничений на форму виджета. Этот подход уже прорабатывется.
Таблица Событий
Таблица событий определяет события, которые происходят с управляющим элементом, и смену состояний по данному событию. Такими событиями могут быть нажатия на кнопку мыши, события сенсорного экрана и также другие. Пока наиболее часто используемыми являются события от левой кнопки мыши LBUTTON_DOWN и LBUTTON_UP (они же и single touch), хотя есть и другие. Таблица событий обязательна и определяется нодой ‘events’. Как и для таблицы состояний это нода-агрегатор, также не имеет атрибутов, в описании допускается только одна такая нода. Описание всех событий выполняется именно в ней. Пример таблицы событий приведен ниже (используется элемент resources/argo/system/fs0/sysres/controls/resp_button.fml):
<events>
<event evt="LBUTTON_DOWN" instate="IDLE" outstate="PRESSED" transition="default"/>
<event evt="LBUTTON_UP" instate="PRESSED" outstate="IDLE" transition="default"/>
</events>
Событие определяется нодой ‘event’, где задано имя события, ожидаемое текущее состояние и состояние, в которое переключится управляющий элемент по этому событию. Есть и другие атрибуты, которые пока не используются или используются редко. Как минимум, одна нода ‘event’ должна быть объявлена.
К обязательным атрибутам относятся атрибуты ‘evt’ - имя события, ‘instate’ - ожидаемое текущее состояние и ‘outstate’ - состояние, в которое переключится управляющий элемент.
Атрибут ‘evt’ задает перечислимое имя события, это фиксированный набор имен, но они, к сожалению, пока не документированы и в открытых заголовочных файлах их также нет (аналоги есть в файле target/include/frm_events.h, но там события называются несколько по другому). В указанном примере событие возникает при нажатии или отжатии левой кнопки мыши в области управляющего элемента. По координатам события определяется управляющий элемент, и событие отправляется именно ему.
Атрибут ‘instate’ определяет текущее состояние, которое ожидается при поступлении события, в примере для события ‘LBUTTON_DOWN’ его значение ‘IDLE’, что совпадает с состоянием по умолчанию. Значения атрибутов ‘instate’ и ‘outstate’ должны совпадать с одним из состояний. Атрибут ‘outstate’ определяет состояние, в которое перейдет управляющий элемент, если поступило указанное событие, и текущее состояние совпадает со значением атрибута ‘instate’. В указанном примере кнопка при нажатии (LBUTTON_DOWN) и в состоянии ‘IDLE’ перейдет в состояние ‘PRESSED’, а при отжатии (LBUTTON_UP) вернется в исходное состояние. При изменении состояния фреймворк отсылает специальное событие приложению, что позволяет ему реагировать на исходное событие (это зависит еще от модели поведения управляющего элемента). Если происходит ситуация когда второе событие (LBUTTON_UP) происходит вне области управляющего элемента (сместили мышку и попали пальцем в небо), то происходит возврат управляющего элемента в исходное состояние, но в приложению отправится уже другое событие - ‘RELEASE’, что и блокирует обработку события, а кнопка вернется в исходное состояние (приложение, как правило, на событие RELEASE и не подписывалось, достаточно того, что LBUTTON_UP не пришло). Этот механизм действует и на другие варианты обработки событий, например, отжатие произошло на другом управляющем элементе - результат будет одинаков.
Справедливости ради, надо отметить, что не все управляющие элементы, точнее модели поведения, поддерживают механизм переключения состояний, в некоторых типах поведения это вообще не заложено. Например, управляющий элемент с типом поведения ‘label’ вообще не имеет механизма переключения состояния. Такой элемент вообще не реагирует на пользовательские события - ему и незачем, он отображает динамический текст, а до событий от пользователя ему нет дела. Ниже приведен пример таблиц состояний и событий для такого элемента (resources/argo/system/fs0/sysres/controls/label.fml)
<states>
<state id="ENABLED" default="true">
<bitmap fill="@FILLMODE_DEF" color="@FILLCOLOR_DEF"/>
</state>
</states>
<events>
<event evt="LBUTTON_DOWN" instate="ENABLED" outstate="ENABLED" transition="default"/>
<event evt="LBUTTON_UP" instate="ENABLED" outstate="ENABLED" transition="default"/>
</events>
По коду видно, что из состояния ‘ENABLED’ он вообще никогда не выйдет (и не сможет выйти, поскольку механизма переключения состояний нет). Однако состояний может быть и несколько, но их переключение будет делаться исключительно приложением, этот механизм работает всегда.
В примере выше присутствует еще атрибут ‘transition’. Это неиспользуемый сейчас атрибут, предназначенный для определения графики перехода из одного состояния в другое, пока этот механизм не сделан. Атрибут опциональный, но присутствует во всех управляющих элементах в силу укоренившейся практики и в качестве напоминания о не сделанной функциональности.
Еще один атрибут ‘trigger’ используется редко, это флаг на событие, которое запускает некоторые процессы в родительском управляющем элементе, в частности такой флаг используется в динамическом списке для сброса всех его элементов в состояние по умолчанию, за исключением собственно элемента, который был в списке выбран. Значение этого атрибута для этого должно быть ‘true’. Пока его использование очень ограничено.
Управляющие Суб-элементы и Макеты
Очень важным свойством управляющих элементов является возможность создания в нем вложенных управляющих элементов. Это значительно расширяет возможности пользовательского интерфейса, поскольку позволяет создавать сложные, композитные управляющие элементы. Вложенные элементы прописываются точно так же, как и в окне приложения. Нода-агрегатор для управляющих элементов отсутствует, поэтому объявлять элемент можно, где угодно в рамках описания. За пример возьмем элемент группы радио кнопок (resources/argo/system/fs0/sysres/controls/rd_button_group_2v.fml):
<ctrldef name="rbutton_group" behavior="rbutton_grp">
<variable name="BUTTON1_DEF_TEXT" vtype="string" value="$SYS.$TENTRY_DEFTEXT_ID"/>
<variable name="BUTTON2_DEF_TEXT" vtype="string" value="$SYS.$TENTRY_DEFTEXT_ID"/>
<control id="rbutton1" resid="$SYS.$RD_BUTTON_TEXT_ID" x="0" y="0" dx="@WIDTH" dy="25" scale="false">
<variable name="LABEL_DEF_TEXT" vtype="string" value="@BUTTON1_DEF_TEXT"/>
</control>
<control id="rbutton2" resid="$SYS.$RD_BUTTON_TEXT_ID" x="0" y="30" dx="@WIDTH" dy="25" scale="false">
<variable name="LABEL_DEF_TEXT" vtype="string" value="@BUTTON2_DEF_TEXT"/>
</control>
<states>
<state id="IDLE" default="true">
</state>
</states>
<events>
<event evt="UPDATE" instate="IDLE" outstate="IDLE" transition="default"/>
</events>
</ctrldef>
Этот элемент содержит две радиокнопки, одна над другой, и обеспечивает переключение этих кнопок. Данный элемент используется в приложении настроек, на вкладке Дата/Время (он и сам является вложенным элементом для более высокоуровнего управляющего элемента). Переключение кнопок выполняется благодаря поведенческой модели элемента - ‘rbutton_grp’. Элемент имеет одно вырожденное состояние и одно событие, которое никогда не случится (может и случится, но эффекта не будет никакого). Поскольку битмап состояния отсутствует, рисоваться этот элемент будет на битмапе родителя.
Вложенные элементы прописываются с помощью ноды ‘control’. В других разделах она уже не раз упоминалась, но я вкратце повторю.
Обязательный атрибут ‘id’ определяет текстовый идентификатор элемента, он очень важен поскольку используется при обращении к элементу (при создании управляюшего события в приложении). Другой обязательный атрибут ‘resid’ определяет идентификатор ресурса вложенного элемента. Атрибуты ‘x’, ‘y’, ‘dx’ и ‘dy’ определяют позицию, ширину и высоту суб-элемента. Автоматическая переменная ‘@WIDTH’ (как и переменная ‘@HEIGHT’) содержат значения ширины и высоты самого элемента группы. Атрибут ‘scale’ со значением ‘false’ говорит о том, что позиция определяется в пикселах (а не процентах, если бы значение атрибута было ‘true’).
Суб-нода ‘variable’ переопределяет переменную ‘LABEL_DEF_TEXT’ значением собственной переменной ‘@BUTTON1_DEF_TEXT’. Собственная переменная может быть переопределена, в свою очередь, при создании элемента группы в родительском элементе.
Для интереса посмотрим как определен элемент под идентификатором ‘$SYS.$RD_BUTTON_TEXT_ID’ (resources/argo/system/fs0/sysres/controls/rd_button_text.fml):
<ctrldef name="radio_button_text" behavior="custom">
<variable name="LABEL_DEF_TEXT" vtype="string" value="$SYS.$TENTRY_DEFTEXT_ID"/>
<variable name="LABEL_DEF_FONT" vtype="string" value="@LABEL_DEF_FONT"/>
<control id="radio_button" resid="!RADIO_BUTTON" x="0" y="0" dx="20" dy="20" scale="false"/>
<control id="label" resid="$SYS.$LABEL_ID" x="25" y="0" dx="(@WIDTH - 25)" dy="25" scale="false">
<variable name="TENTRY_DEF_TEXT" vtype="string" value="@LABEL_DEF_TEXT"/>
<variable name="FONT_DEF" vtype="string" value="@LABEL_DEF_FONT"/>
<variable name="LABEL_Y" vtype="integer" value="1"/>
</control>
<states>
<state id="IDLE" default="true">
<bitmap fill="bgcolor"/>
</state>
</states>
<events>
<event evt="UPDATE" instate="IDLE" outstate="IDLE" transition="default"/>
</events>
</ctrldef>
Как видно, он тоже содержит вложенные элементы: собственно радиокнопку и элемент метки ‘label’. Надо обратить внимание, что поведение этого элемента ‘custom’, т.е. фреймворк не обрабатывает этот элемент, он просто является контейнером для своих вложенных элементов. Сделано так для того, чтобы события нажатия действовали только на радиокнопку для ее выбора, а вот при нажатии в область метки радиокнопка бы не выбиралась. Для пользовательских элементов переключение состояний реализовано, хотя тут и не используется.
Можно еще обратить внимание на ресурсный идентификатор кнопки - ‘!RADIO_BUTTON’. Это элемент темы, под которым скрывается радиокнопка. При выборе темы ресурсный идентификатор может измениться, и будет другая кнопка, хотя в данном случае это и не так. Элемент темы в данном случае используется для поиска неизвестного приложению системного ресурса. Идентификаторы элементов темы, в отличие от идентификаторов ресурсов, известны и могут использоваться в пользовательских приложениях. Такие идентификаторы всегда начинаются с символа восклицательного знака. Если интересно, то под этим идентификаторм скрыт элемент resources/argo/system/fs0/sysres/controls/rd_button_text.fml
Для полноты картины рассмотрим как создается элемент группы радиокнопок, в его родительском элементе applets/settings/resources/controls/timeformat.fml:
<ctrldef name="timeformat" behavior="custom">
<variable name="BORDCOLOR_DEF" vtype="integer" value="!CUST_BORDER_COLOR"/>
<variable name="FILLCOLOR_DEF" vtype="integer" value="!CUST_FILL_COLOR"/>
<variable name="RADIUS_DEF" vtype="integer" value="5"/>
<variable name="THICKNESS_DEF" vtype="integer" value="2"/>
<variable name="LABEL_DEF_TEXT" vtype="string" value="$APP.$TIMEFMT_LABEL_ID"/>
<layout id="timeformat">
<rectangle id="label" x="0" y="0" dx="@WIDTH" dy="25" scale="false"/>
<rectangle id="rbgroup" x="5" y="37" dx="(@WIDTH - 10)" dy="(@HEIGHT - 30 - 3)" scale="false"/>
</layout>
<control id="label" resid="$SYS.$LABEL_ID" layout="timeformat.label">
<variable name="TENTRY_DEF_TEXT" vtype="string" value="@LABEL_DEF_TEXT"/>
<variable name="FONT_DEF" vtype="string" value="$SYS.$ARIAL_REG_12_ID"/>
</control>
<control id="rbgroup" resid="$SYS.$RD_BUTTON_GROUP_2V_ID" layout="timeformat.rbgroup">
<variable name="BUTTON1_DEF_TEXT" vtype="string" value="$APP.$TIMEFMT_24HOURS_ID"/>
<variable name="BUTTON2_DEF_TEXT" vtype="string" value="$APP.$TIMEFMT_12HOURS_ID"/>
<variable name="LABEL_DEF_FONT" vtype="string" value="$SYS.$ARIAL_REG_10_ID"/>
</control>
<states>
<state id="IDLE" default="true">
<bitmap fill="bgcolor"/>
<shape figure="roundrect" bcolor="@BORDCOLOR_DEF" fcolor="@FILLCOLOR_DEF" thickness="@THICKNESS_DEF" radius="@RADIUS_DEF" x="0" y="30" dx="@WIDTH" dy="@HEIGHT" scale="false"/>
</state>
</states>
<events>
<event evt="UPDATE" instate="IDLE" outstate="IDLE" transition="default"/>
</events>
</ctrldef>
В целом ничего нового, но есть существенное нововведение, в элементе используется макет объявленный нодой ‘layout’ с именем ‘timeformat’. Макет задает координаты прямоугольных областей, и эти координаты используются атрибутом ‘layout’ при создании суб-элементов. Координаты для суб-элементов в данном случае не задаются, они берутся из макета.
Это не просто стилистическое улучшение, а одна из лучших идей для определения координат элемента. У макета есть следующие преимущества:
- Координаты всех управляющих элементов сведены вместе, в одной группе определений. Это позволяет избежать ошибок в координатах элементов, не надо искать по всему файлу, где именно объявлен суб-элемент. В дальнейшем это позволит, также формализовать разработку макета в IDE. Макеты также могут использовать переменные, как и координаты управляющего элемента.
- Символическое имя элемента макета может быть использовано в коде приложения при создании динамических элементов. Как правило, в приложении мы не знаем координат, где должен располагаться элемент, их приходилось вычислять, используя ширину и высоту окна, смещения и прочие параметры. Использование символического имени полностью устраняет эту проблему, достаточно его передать при создании динамического элемента или суб-элемента (макеты могут объявляться и в странице приложения, а не только в описании элемента).
- И, наконец, самое главное: хотя сейчас у макета только один атрибут ‘id’, в дальнейшем он будет дополнен атрибутами ориентации экрана и соотношения сторон экрана или страницы (acpect ratio). При создании страницы будет автоматически выбран макет, который удовлетворяет этим параметрам, и координаты элементов будут приведены в соответствие, и это будет абсолютно прозрачно для приложений.
Динамические Виджеты
Динамические виджеты всегда подключаются внутри описания управляющего элемента и они, собственно, чаще всего определяют поведение такого элемента. По сути этим образом происходит подключение модуля в плеере, который обслуживает поведение элемента. Например, нельзя реализовать функциональность поля для ввода текста (text_entry), не подключив динамический виджет ‘dyntext’. Подключение большинства динамических виджетов требует и соответствующего поведения для данного управляющего элемента. Однако некоторые из них могут подключаться к любому виджету без влияния на его поведение, это виджеты-декораторы, к ним относятся метка (label) и оверлей (overlay). В примере для кнопки (resources/argo/system/fs0/sysres/controls/resp_button.fml) подключен как раз виджет метки:
<ctrldef name="resp_button" behavior="button">
<variable name="TEXT_DEF" vtype="string" value="$SYS.$EMPTY_STRING"/>
<variable name="BGCOLOR_DEF" vtype="integer" value="0x080408"/>
<variable name="FGCOLOR_DEF" vtype="integer" value="0xFFFFFF"/>
...
<label id="label" x="5" y="3" dx="(@WIDTH - 10)" dy="(@HEIGHT - 10)" scale="false"
fgcolor="@FGCOLOR_DEF" bgcolor="@BGCOLOR_DEF" fontid="@FONT_DEF"
deftext="@TEXT_DEF" wrap="@WRAP_DEF" halign="@HALIGN_DEF" valign="@VALIGN_DEF" rtl="@RTL_DEF"/>
<states>
...
</states>
<events>
...
</events>
</ctrldef>
Как видно, метка присутствует, но поведение элемента остается ‘button’. Можно было бы и просто добавить суб-элемент $SYS.$LABEL_ID, но в этом случае управление кнопкой было бы несколько затруднено. Элемент с поведением ‘label’ чаще добавляется на страницу в виде элемента верхнего уровня (элемент, у которого нет родителя-элемента) - это всякого рода заголовки, например. Однако может добавлятся с той же целью и в сложные композитные пользовательские управляющие элементы, так проще управлять такими виджетами. Употребление того или иного варианта остается исключительно на вкус разработчика.
Что касается оверлеев, то они по определению живут на других управляющих элементах (оверлеи - это, например, крыжики на иконках в Idle Screen приложении), поэтому пока управляющего элемента с таким поведением нет (поведения тоже нет). Очень вероятно, что такой элемент скоро появится.
Эти динамические виджеты не имеют логики смены состояний, и управляются только со стороны приложения.
Но вернемся к примеру. У метки, как и у любого виджета, есть обязательный идентификатор ‘id’, он широко используется при операциях с виджетом. В рамках одного управляющего элемента может быть несколько виджетов метки с уникальными идентификаторами, так что поиск нужного необходим. Также виджет имеет неявный числовой идентификатор, который назначается виджету по порядку вхождения, начиная с нуля. Числовой идентификатор возвращается через API, если он не валиден (меньше 0), но задан тектовый идентификатор, далее можно использовать числовой идентификатор, без указания текстового. Это правило справедливо для всех динамических виджетов (за исключением виджета выпадающего списка (droplist), он в элементе всегда в единичном экземпляре).
Далее заданы координаты метки, тут надо остановиться поподробнее. Все динамические виджеты привязаны к управляющему элементу, но не привязаны к состоянию. Иначе говоря, виджеты рисуются на подложке управляющего элемента поверх нарисованного битмара состояния. Поэтому при изменении состояния виджеты перерисовываются всегда (все, что есть в управляющем элементе), что и обеспечивает их визуализацию. Координаты задают позицию виджета внутри управляющего элемента.
Атрибуты ‘fgcolor’, ‘bgcolor’ и ‘fontid’ задают цвета текста, фона и идентификатор используемого шрифта, начальные установки могут быть переопределены через API метки, ну и, само собой, через переменные самого элемента. Остальные атрибуты определяют текст по умолчанию, режим переноса строк (wrapping), выравнивание текста по горизонтали и вертикали и режим RTL - right-to-left для специфичных языков (арабский, идиш). Есть и другие атрибуты, но здесь я их касаться не буду.
Все это нужно для указания заголовка кнопки, который может меняться по необходимости или общей смене языка системы.
Это лишь один частный пример использования динамического виджета. Использование других виджетов я пока рассматривать не буду, тут лучше смотреть конкретные реализации для каждого динамического виджета. Для того, чтобы рассмотреть все динамические виджеты, нужна отдельная глава, кроме того существующие виджеты дополняются и ожидаются новые.
Стек Управляющих Элементов
Как видно из примеров выше, управляющие элементы образуют некоторую иерархию или, говоря по современному :), стек управляющих элементов. В примере было указано 4 уровня иерархии, на самом деле их пять, но это не суть. Глубина иерархии может быть больше или меньше, но остается один главный вопрос - как эта машинерия взаимодействует между собой. На этом лирическая часть закончилась, и началось скучное описание почти на пальцах.
Любой элемент может иметь элемент-родитель, может и не иметь, тогда он будет корневым элементом в иерархии. Любой элемент может иметь дочерние элементы, также может не иметь и будет конечным элементом. Взаимодействие всегда выполняется по иерархии: родитель может управлять дочерними элементами, дочерний элемент может информировать родителя. Элементы одного уровня иерархии никогда друг с другом не взаимодействуют, они друг про друга просто не знают. Существуют несколько причин для взаимодействия внутри иерархии: поиск управляющего элемента; реакция найденного элемента и информирование родителя; реакция родительского элемента на информацию от дочернего элемента и модификация других дочерних элементов; отрисовка всей иерархии.
Поиск Управляющего Элемента
Поиск управляющего элемента выполняется в плеере всякий раз, когда приходит событие привязанное к координатам - нажатие/отжатие кнопки мыши, события сенсорного экрана и тому подобное. Сначала ищется (или выбирается) активное окно, потом ищется (или также выбирается) активная страница - это выполняется другими механизмами, поиском по оконному стеку или стеку страниц. После того как окно и страница найдены, начинается поиск управляющего элемента. Механизм ищет корневой управляющий элемент в таблице управляющих элементов страницы путем сравнения координат события и координат элемента. Если поиск неудачен, то он продолжается уже в списке динамических управляющих элементов (не надо путать с динамическими виджетами внутри элемента управления). Если элемент не найден, то дальнейшее развитие событий зависит от типа события, проще говоря, нажатие или отжатие случилось. Если было событие нажатия, то ничего не произойдет, элемент не выбран, последующее событие отжатия также будет игнорировано и обрабатываться не будет. В случае отжатия, если элемент не найден, или найден, но не тот, где зарегистрировано событие нажатия, произойдет сброс активного состояния элемента (RELEASE). Это как вы нашли кнопку мышкой, нажали, а потом мышку сдвинули с кнопки и отжали. Кнопка при этом не сработает, но состояние изменить она уже успеет, реальное действие выполняется, обычно, по событию отжатия. Кнопка просто вернется в исходное состояние, и на этом текущее действие закончится. Это, разумеется, частный случай, но механизм обрабатывает и другие негативные варианты.
Но, допустим, корневой управляющий элемент найден. Если у него нет суб-элементов (статических и динамических), то поиск завершится успехом. В другом случае поиск прололжится сначала в таблице суб-элементов, а потом среди динамических элементов. Если поиск неудачен, то произойдет обработка события корневым элементом, если он это поддерживает. Если нет, то поиск будет признан удачным, но никаких действий далее не произойдет. Если суб-элемент найден, то поиск продолжится по данному алгоритму, до тех пор пока не будет найден конечный элемент.
Когда найден конечный элемент в иерархии (допустим, это радиокнопка из примера выше), и он отреагировал на это событие - изменил свое состояние, то требуется, чтобы элемент отрисовали, и чтобы он уведомил родителя, что его состояние изменилось. Это сведено с одному процессу - уведомление родительского управляющего элемента.
Уведомление Родительского Элемента
Если управляющий элемент является корневым, то уведомлять некого - сразу происходит отрисовка, рисуется ВСЕГДА только корневой элемент. Если элемент не корневой, то он должен уведомить родителя, что его состояние изменилось и надо его перерисовать. У почти любого типа управляющего элемента есть своя функция нотификации, т.е. уведомления. Суб-элемент всегда имеет возможность вызвать эту функцию родителя, причем передав только два параметра - самоидентификатор и событие, которое вызвало изменение. Самоидентификатор - это параметр, по которому определяется сам элемент и его родитель. Вдаваться в подробностия я не буду, должны же оставаться какие либо тайны :). В функции по нему идентифицируется сам дочерний элемент и его родитель, а также родитель родителя, если таковой есть. Сама по себе нотификация - это тоже параметр, что-то снизу изменилось так, что требуется реакция, а, значит, и перерисовка. Событие, которое вызвало изменения, тоже важно.
Функции нотификации реализованы по разному для разных моделей поведения, иногда не делают ничего, иногда делают много всего. Нотификации пользовательских элементов, например, вызывают функции обратного вызова из DLL пользовательского элемента, если DLL определена, а вот кнопки ее лишены, поскольку практически никогда не имеют дочерних элементов (это спорный момент). Но если функция определена, то она почти всегда делает одну операцию - уведомление собственного родителя (это еще зависит от переданного события) или вызывает механизм отрисовки, если элемент корневой.
Однако функции могут делать и другие операции, например, модифицировать состояния других дочерних элементов, кроме вызвавшего элемента, как правило, переводить их в состояние по умолчанию, так работают функции группы радиокнопок и динамического списка. Есть и другие варианты. При изменении состояния элемента со стороны приложения эти функции также вызываются, нарисовать изменения все равно необходимо. Поведение функций может зависеть от переданного события, это тоже влияет на функциональность.
Функции нотификации - неотъемлемая часть поведения элемента, к ним привлечено особое внимание. Они и выполняют функцию реакции родительского элемента на информацию от дочернего элемента и модификацию других дочерних элементов.
Отрисовка Управляющего Элемента
Как уже было сказано выше, отрисовка выполняется только для корневого элемента. Однако перерисовываются и все элементы, находящиеся в иерархии: после отрисовки элемента, рисуются все суб-элементы в таблице элементов и все динамические элементы управления в списке элементов. Перерисовка делается для всех элементов, независимо менялся тот или нет. Это, конечно, излишняя нагрузка на процессор, но пока так.
Существует ряд путей для того, чтобы исправить ситуацию, некоторые из них в какой-то степени реализованы, например, механизм dirty rectangles. Есть наметки на использование update флагов. Но если со статическими состояниями все понятно, то с динамическими виджетами все сложнее. Например, вычисление dirty rectangle для виджета динамического текста крайне сложно - при вводе символа в конец строки, без добавления новой строки, это, по сути, прямоугольник символа, а при вводе в начало уже существующий строки - это будет прямоугольник всей строки, поскольку все символы сдвинутся. Это, не говоря, о добавлении новой строки, когда сдвигаются все строки ниже, соответсвенно, прямоугольник будет на все сдвинутые строки. Это просто в качестве примера, что иногда не все просто, и для разных виджетов подходы могут быть разными.
Динамические Управляющие Элементы
Речь в этом разделе в основном шла о статических управляющих элементах, т.е. об элементах, которые создаются в файлах описания страницы и описаниях родительских элементов. Динамические управляющие элементы упоминались, но редко, только чтобы было понятно, что такие есть.
Дело в том, что с точки зрения описаний элементов, и с точки зрения интеграции элемента в ресурсы приложения, или системы, разницы никакой нет, никто заранее не говорит, что вот этот элемент будет точно статическим, а вот этот точно динамическим (хотя при разработке UI приложений, всеже, это как-то планируется). Разница лишь в том, как элемент создается. Статический, как уже говорилось выше, создается в файле описания. Динамический элемент создается уже в процессе работы приложения. Можно оставить описание окна практически пустым, а у приложения будет насыщенный пользовательский интерфейс, созданный только динамическими элементами.
Цель этого раздела - прояснить, в чем преимущества, и в чем недостатки того и другого варианта применения элементов.
Динамические элементы по сравнению со статическими обладают следующими преимуществами:
- Динамические элементы могут быть скрыты и показаны вновь, скрытый элемент ни на что не реагирует, можно на одном месте разместить целый набор элементов и показывать тот или иной элемент по мере необходимости.
- Можно менять позицию динамического элемента, или его позиция вычисляется автоматически, например в динамическом списке.
- Можно добавлять и удалять динамические элементы, это часто используется в динамических списках (сам список при этом может быть и статическим).
- У динамических элементов есть набор флагов для дополнительных операций, например, при скрытии динамического элемента он должен стереться с подложки, поэтому у него может быть отдельный битмап для заливки исходным фоном, что определяется флагом. Кстати, состояние, когда элемент скрыт, тоже определяется флагом.
- Элемент в скрытом состоянии может менятся, но покажется это только тогда, когда будет показан элемент.
Практически ничего из списка выше реализовать в статическом элементе нельзя. Но по сравнению со статическими элементами есть и недостатки:
- Динамический элемент надо создавать в процессе работы приложения, на это тратится время, в то время как статический элемент создается на этапе запуска приложения. Кроме того, фукция создания элемента довольно сложна, а для статического все делается фреймворком.
- Надо в приложении сохранять его идентификатор, который возвращает функция создания элемента. Он используется в операциях настройки элемента (регистрация событий), добавления динамических суб-элементов, и, наконец, удаления элемента (хотя удалять его не обязательно, если он нужен до момента закрытия приложения, при закрытии приложения элемент будет удален автоматически).
- Часто надо сохранять идентификаторы событий от элемента (для динамических суб-элементов), чтобы приложение реагировало на эти события.
- Динамические элементы имеют меньший приоритет, поэтому их нельзя создавать поверх статических, статический элемент будет перекрыт ровно до того момента пока координаты события не попадут в область этого элемента.
Все это для статических элементов не требуется.
Из этого можно сделать следующие выводы:
- Элемент создается статически, если он должен быть в окне всегда, не должен скрываться, должен быть доступен всегда (или даже находиться в disabled состоянии, но в видимом). Такой элемент может содержать динамические суб-элементы (например элементы списка).
- Элемент создается динамически, если его необходимо скрывать, и он рисуется в области, где могут быть расположены другие элементы. Динамический элемент, в свою очередь, может содержать статические суб-элементы.
- Динамический элемент создается, если возникает необходимость смены его позиции, пока, правда, нет API для смены позиции, да и используется это редко. Но, например, существует элемент с поведением ‘cursor’, где его позиция меняется постоянно, он используется всегда с динамическим текстом.
- Динамический элемент не дожен перекрывать статический, иначе будут нежданчики.