суббота, февраля 28, 2009

Модули vs. компоненты. Flex

В моем опыте Flex-разработки наступает новая стадия взросления.

Ну, условно, у себя я уже могу выделить две стадии (не считая робкого потрагивания и любопытного пощупывания):

  • Код представляет собой голый MXML, на котором и построено всё приложение. Крупные вспомогательные структуры классов и обильные инклюды. В решении относительно сложных задач (либо совсем простых) я полагался только на pure AS проекты и компоненты.
  • Приложение делилось на компоненты (MXML компоненты), каждое окошко, каждый функционально законченный экран были компонентами. Обильное использование компонентов значительно упростило разработку приложений и внесло строгость и порядок, несмотря на довольно пространный список в Components -> Custom.
    Кроме того, такой подход значительно ускоряет разработку, уже засчет того, что визуальный редактор теперь не должен перерисовывать каждый раз всё приложение (а делает он это отнюдь не быстро).

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

Руки так и зачесались попробовать создать модуль в FB. Открываю проект, создаю New->MXML Module. Открывается новый файл mxml, в который я добавляю, к примеру, кнопку, и текстовое поле. Сохраняю. В окошке Components->Custom появился новый компонент, по имени модуля.
Проведем эксперемент. Перетащим его в окно основного приложения. Всё как с обычным компонентом. Компилирую. Замечательно: скомпилировалось два swf - один с именем приложения, другой - с именем модуля.
Для любопытства, копируем один только swf приложения и запускаем: видим кнопку и текстовое поле. Как я и предполагал, наш модуль включен в состав swf приложения как обычный компонент.
Запустим swf модуля. Сразу получаем ошибку "Не удалось найти класс mx.core::SpriteAsset." И не мудрено - модуль не содержит классов, включенных в основное приложение.
Но нам-то от модуля нужно что? Чтобы он не был включен в приложение, а подгружался в процессе работы приложения. Хотя, возможность использовать модуль как обычный MXML-компонент тоже надо отметить.

Ознакомимся вкратце с документацией: Creating Modular Applications. Здесь всё, на достаточно понятном языке, достаточно подробно разжевывается.

Итак. Нас интересовали загрузка модуля и ее мониторинг.
Пожалуйста, смотрим: Loading and unloading modules. За загрузку отвечает класс ModuleLoader. Любопытно, что это наследник VBox. Непонятно, почему именно VBox, а не, к примеру, HBox. Уж я-то вообще ожидал увидеть в этой роли SWFLoader. Ну да ладно. Убираем из кода приложения модуль и вставляем ModuleLoader. Кстати, он присутствует в палитре компонентов Layout.
Вписываем в свойство url имя файла модуля. И что особенно приятно, визуальный редактор сразу отобразил содержимое модуля. Вводим имя другого модуля - пожалста! Отображается другой модуль. Запускаем - всё замечательно отображается и работает.
Теперь по поводу мониторинга. Смотрим Using ModuleLoader events. ModuleLoader генерирует следующие события: setup, ready, loading, unload, progress, error, и urlChanged. Но позвольте! Если progress мы наблюдаем в этом списке, то почему нет open и complete? Что помешало вместо loading генерировать open, а вместо ready - complete?
При таком раскладе, если я использую ProgressBar в режиме mode="event", загрузка успешно мониторится, но ProgressBar не генерирует событие complete, что в некоторых случаях было бы полезно. Ну что ж, никто не мешает нам устранить этот недостаток, создав своего потомка ModuleLoader. Дело поправимое.

Есть еще один компонент, управляющий загрузкой модулей: ModuleManager. Этот класс, как нам обещают, предоставляет больше возможностей по управлению загрузкой модулей чем предыдущий. Но при этом, как утверждается, техника его использования является менее абстрактной чем работа с ModuleLoader.
Да, разработчики Flex не перестают меня удивлять. ModuleManager наследуется от Object и содержит всего два статических метода, что ввело меня в небольшой ступор. Однако, после изучения предложенных примеров, всё встало на свои места. Метод ModuleManager.getModule возвращает объект, удовлетворяющий интерфейсу IModuleInfo, уникальный для каждого управляемого модуля. Этот объект, в дальнейшем, можно использовать для загрузки модуля (метод load) и мониторинга событий загрузки, а затем, для инстанцирования модуля (через свойство factory).
Более подробное изучение этого класса, погружает нас в глубины Flex, что в мои планы пока совсем не входит. Вот уж, другими словами и не скажешь - все намного менее абстрактно. И больше подходит для решения специфических задач.

Ну-с, добро пожаловать в мир модульных приложений. Начинаем действовать!

* * *

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

  • Внедрение шрифта. CSS с внедрением TTF-шрифта определяется в главном приложении. Подгружаемые модули успешно используют эти шрифты. Но, возникла проблема с внедряемыми SWF, в которых используется другой шрифт. При вводе в динамические поля, ничего не отображалось. Тогда я внедрил этот шрифт в модуль. Шрифт стал отображаться. Но, что интересно, когда я собрал Release Build, шрифт опять перестал отображаться. Пришлось оставить Debug-версию модуля. Но это повлекло за собой следующую проблему.
  • Если основное приложение собрано в Release Build, оно некорректно подгружает модуль, собранный в Debug Build. Я думаю, что вообще, по-отдельности модули лучше не обновлять. Я обратил внимание, что размер SWF-файла модуля даже при небольших изменениях, при перекомпиляции заметно меняется. Поэтому, следует, наверное, соблюдать осторожность при компиляции и обновлении модулей.

среда, февраля 25, 2009

В цинковом гробу. Перетаскиваемые окошки

Волею судеб, свалился мне проект такой. Есть несколько флэшек, так называемых виджетов, которые нужно одеть в Zinc так, чтобы еще и под Mac OSX работали.

Во-первых, сразу зарекаюсь - Мак только для мак-девелоперов. Без меня. Такого количества проблем я уже давно не встречал. Возможно, основным виновником их является Zinc, который я смело могу назвать УСЛОВНО кросс-платформенным.

Что сказать про Zinc 3.0? Под красивым брендовым дизайном, симпатичной оболочкой, красивыми заголовками скрывается довольно ограниченный функционал, убогая документация и море непонятностей, глюков и граблей.

В данном посте, приведу один пример, как простая задача решается через "заднее место".
Нужно, чтобы окошко нашего виджета перетаскивалось мышкой за специальную панельку. В документации, нам рассказывают про это так: Creating a Draggable Form. И что мы получаем? Да полный отстой. Мышь теряет окошко, потом, при наведении вдруг опять подхватывается, уже без нашего на то соизволения, в общем, ужас. Как это победить? Нужно повесить 3 обработчика мыши на панельку и сделать это вот как:

dragNDropRenderer.buttonMode = true; dragNDropRenderer.useHandCursor = true;
dragNDropRenderer.addEventListener(MouseEvent.ROLL_OVER, rollOverHandler);
dragNDropRenderer.addEventListener(MouseEvent.ROLL_OUT, rollOutHandler);
dragNDropRenderer.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);

. . .

private function rollOverHandler(event:MouseEvent):void {
mdm.Forms.getFormByName("MainForm").startDrag();
}

private function rollOutHandler(event:MouseEvent):void {
if (!event.buttonDown) {
mdm.Forms.getFormByName("MainForm").stopDrag();
}
}

private function mouseUpHandler(event:MouseEvent):void {
var renderer:DisplayObject = DisplayObject(event.target);
if (!renderer.getBounds(renderer).contains(event.localX,event.localY))
mdm.Forms.getFormByName("MainForm").stopDrag();
}

Конфликт событий мыши распространяется только на нажатие. Отпускание, вполне корректно работает. Кстати, при перетаскивании, обрабатывать нажатие вообще не советую - реакция совершенно непредсказуема. Таким образом, мы отрубаем перетаскивание пир отпускании кнопки, а при отведении мыши от панели проверяем, отпущена ли в данный момент кнопка. Если отпущена - останавливаем перетаскивание. Если нет - ни в коем случае этого не делаем - мышь потеряет окошко в момент таскания.

Другие баги - почему под MacOX криво работает LocalConnection, или не открываются Zinc-ом новые окошки, и не распарсивается полученный с сервера XML, мне еще предстоит выяснить. Чувствую, это будут "приятные" сюрпризы.

* * *

В обработчик отпускания мыши добавлена проверка на отсутствие мыши в области панели.

* * *

Продолаются жалобы. Под MACOX не удалось (под Windows всё Ок) передать на сервер запрос с GET-параметрами (URLRequest). Запрос проходит, параметры не передаются. Не имел возможности выяснить, виновен ли в этом Zinc или админы сервера. В итоге, стали передавать параметры через POST, что сразу дало положительный результат.

* * *

Прозрачность. Плашка прозрачностью 1% ложится поверх поверхности со сложным изображением сложной прозрачности. Окно приложения устанавливается прозрачным. И что вы думаете? Эта плашка видна. Она как бы добавляет прозрачности подлежащему изображению. То же самое можно сказать, если поверх ложится растр с прозрачностью. Прозрачная область растра становится видна.
Выход - не допускать "недопрозрачностей", обтравливать растр во флэше.

* * *

Всё. Финиш. Zinc и MacOX - больше несовместимые понятия. Делаю полноэкранное приложение. В окне приложения, справа, где-то на 1/4 ширины, существует "мертвая" зона, в которой мышь перестает оказывать воздействие на интерактивные flash-элементы. Нет, нельзя сказать что совсем не оказывает. После 5-10 нажатий, кнопка срабатывает. Но это же бред. Элементраный пример - делаем во flash плашку размером с экран (1280x800), вставляем SWF в гроб, билдим и наблюдаем эту границу. Кошмар. Под Windows и под MacOX в Safari работает превосходно.
Сделать многооконное приложение с Zinc также не получилось - опять же проблемы с мышью. Интерактив не откликается.

среда, февраля 11, 2009

Как отображать шрифт без сглаживания (bitmap text) во Flex

Flex имеет некоторые ограничения в работе с внедренными шрифтами.
Если во Flash CS, мы можем просто так взять, да и указать тип рендеринга шрифтов "Bitmap text" (без антиалиасинга) и иметь полноценное отображение текста с неполной прозрачностью, наклоном и т.п., то во Flex мы можем только управлять параметрами сглаживания шрифта, или не внедрять шрифт совсем (тогда о прозрачности не может быть и речи).

Но если мне, всё-таки, нужен именно несглаженный полупрозрачный шрифт?
Проблема решается, как и многие другие во Flex, через "одно" место.
В лайфдоках, находим такую статейку: Embedding fonts from SWF files. Здесь рассказывается вообще о внедрении шрифта с использованием Flash CS. Пользуясь таким методом, можно внедрить любой шрифт, поддерживаемый Flash CS.

Однако, здесь не сказано ни слова про внедрение шрифта Bitmap text (без антиалиасинга). И вообще, в документации Flex, про это как-то совсем ничего нет. А ведь это странно и непонятно - Flex-приложения как раз направлены на отображение данных, а всем известно, что данные отображаются лучше несглаженным шрифтом. К чему тогда все эти эффекты с фэйдом и прочими трансформациями, если невнедренный шрифт их не отображает.

Нам на помощь приходит статья всеобщего друга всех флэшеров GSkinnerа Bitmap Fonts in Flex (via Flash). Правда, в ней всё несколько усложнено, но, подозреваю, что это из-за того, что писалось это для Flex 2. Ну а мы то уже на Flex 3, поэтому всё немного проще. Идея в том, что шрифт, внедренный как Bitmap text, меняет название в некую неприглядную форму, типа "Tahoma_12pt_st".

Итак, к чему мы пришли (опишу вкратце мои действия):

1. Создаем SWF-файл fonts.swf (можно любой другой). Версия Flash 9, ActionScript 3.0. Во всех статьях создается файл версии Flash 8, но, почему-то, у меня такой файл Flex не подцеплял/ - не может оттранскодить.

2. Создаем текстовые Dynamic-поля, в каждое из которых добавляем хотя-бы один символ нужного нам начертания. Соответственно, если все начертания нам нужны, то будет 4 текстовых поля: Normal, Bold, Italic, Bold Italic.

3. Устанавливаем размер шрифта, который мы будем использовать в Flex-приложении. Дело в том, что в приложении, корректно может быть отображен только один размер внедренного шрифта - тот который мы сейчас укажем. Если указать другой размер, шрифт некрасиво размажется. Я указываю 12.

4. Указываем в Embed... диапазоны, которые нам надо внедрить.
Кстати, можно пойти другим путем и, вместо текстовых полей, создать в библиотеке 4 символа-шрифта (New font...) и всё будет точно так же, за исключением того, что шрифт внедрится весь - без ограничений, что плохо для объема.

5. Компилируем и добавляем в папку проекта.

6. В CSS файле проекта (я отвел для этого специальный файл fonts.css) указываем следующий код:
/* CSS file */
@font-face {
src: url("file://./assets/fonts.swf");
fontFamily: "Tahoma_12pt_st";
fontStyle: normal;
fontWeight: normal;
}

@font-face {
src: url("file://./assets/fonts.swf");
fontFamily: "Tahoma_12pt_st";
fontStyle: normal;
fontWeight: bold;
}

@font-face {
src: url("file://./assets/fonts.swf");
fontFamily: "Tahoma_12pt_st";
fontStyle: italic;
fontWeight: normal;
}

@font-face {
src: url("file://./assets/fonts.swf");
fontFamily: "Tahoma_12pt_st";
fontStyle: italic;
fontWeight: bold;
}


7. Теперь, мы можем в любом стиле указать:
Text.Regular {
fontFamily: "Tahoma_12pt_st";
fontSize:12px;
fontAntiAliasType:normal;
}

Важно указать параметр fontAntiAliasType:normal. Если этого не сделать, шрифт в некоторых случаях будет отображаться размыто, а если это выделяемый текст - при его выделении будет твориться что-то невообразимое.
Ну и конечно, не забыть уточнить размер шрифта - иначе всё поедет.

8. Ну и собственно, применяем этот стиль к компоненту:
<mx:Text
width="100%"
styleName="Regular"
>
<mx:htmlText><![CDATA[<b>Максимальный</b> размер загружаемого файла — 3 Мб. Допустимые форматы: GIF, JPG, BMP, TIFF, PNG.]]>></mx:htmlText>
</mx:Text>

Если нам понадобится шрифт другого размера - уж не поленитесь, повторите всё со второго пункта - так уж положено.

* * *

Да, забыл добавить про то, что здорово помогает в разборках со шрифтами такой кусочек кода (взят из статьи):
var fontList:Array = Font.enumerateFonts(false);
for (var i:uint=0; i<fontList.length; i++) {
trace("font: "+fontList[i].fontName);
}

понедельник, февраля 09, 2009

Прогресс-бар на любой вкус. Flex

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

Скинирование позволяет заменить лишь изображения не залитой и залитой полос. Ну а если мне не нужна полоса?
Порыскав по интернету, пришел к выводу, что все усиленно заморачивались на проблему подмены предзагрузчика flash/flex-приложений, а вот видоизменить ProgressBar, почему-то никому не понадобилось.

Ну что ж, раз готового материала не нашлось, придется мастерить ручками.

Продолжение следует...

пятница, февраля 06, 2009

Неэффектные эффекты. Flex

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

По началу, сделал просто появляющуюся/исчезающую панель, используя ее свойства showEffect/hideEffect. По событию наведения мыши делал visible=true, при отведении - наоборот. Эффекты:
<mx:Fade id="PanelFadeIn" alphaFrom="0" alphaTo="1" duration="400"/>
<mx:Fade id="PanelFadeOut" alphaFrom="1" alphaTo="0" duration="400" startDelay="1000"/>

Полная фигня получилась. Эффекты конфликтовали и панель появлялась и исчезала непредсказуемым образом.
Чтобы образумить эффекты, пришлось их останавливать (stop()) перед тем как переключать visible. Так более-менее заработало.

Попробовал другой способ - есть два стэйта, в которых панель находится в разных положениях. При наведении на контейнер мыши, стейт сменяется и панель перемещается в другое место, при отведении - возвращается на прежнее.
Сначала применил свойство moveEffect, в которое указывается имя эффекта <mx:Move id="PanelMotion" /> Получаю опять ерунду. В начале вроде бы всё корректно - уезжает, приезжает, но после нескольких таких движений, что-то там начинает заедать. Метод не годится.

Применяем другую тактику - используем transitions. Сразу всё встает на свои места:
<mx:transitions>
<mx:Transition fromState="Normal" toState="PanelIsShown">
<mx:Move duration="200" target="{сontrolPanel}"/>
</mx:Transition>
<mx:Transition fromState="PanelIsShown" toState="Normal">
<mx:Move duration="200" target="{сontrolPanel}"/>
</mx:Transition>
</mx:transitions>
Можно конечно обойтись одним блоком fromState="*" toState="*", но в некоторых ситуациях, панель "промигивает" в неположенных местах. Но это, конечно, зависит от специфики конкретного приложения.

В итоге, появление/убирание панели сделать средствами Flex удалось, но не так, как хотелось бы. Дело в том, что я хотел сделать небольшую паузу перед тем, как панель уберется. Сделал я следующее: второй блок transitions заменил на:
<mx:Sequence target="{сontrolPanel}">
<mx:Pause duration="1000"/>
<mx:Move duration="200"/>
</mx:Sequence>

Это вызвало две идиотские проблемы:
1. Такой способ наотрез отказывается работать, если в state меняется не непосредственно координата, а, к примеру, свойства right/left: <mx:SetStyle target="{сontrolPanel}" name="right" value="50"/> - анимация просто отсутствует.

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

* * *

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

Или я что-то не допонял, или работа с эффектами оставляет желать лучшего...
Разочарован :(

* * *

Вот пример с аналогичной панелькой:
Sliding Canvas with Flex. Исходный код тут. Работает вполне стабильно. Замечательно. И главное, у меня такие штуки проходили почему то криво... Сам виноват. Поленился сделать такую же проверку transitionHappening. А проверял свойство isPlaying, а оно как-то не правильно показывает если стоит startDelay. Другой подход с принудительной остановкой stop() тоже не проходит - надо было изначально блокировать переключение visible, как в примере. А всё лень виновата - добавлять лишние скрипты не хочется :).
Хотя, с другой стороны, управлять положением компонентов через механизм стэйтов правильнее (универсальнее).

* * *

В итоге, всё получилось с применением showEffect/hideEffect. Получилось вполне стабильно и без неприятных побочных эффектов:


public function hideControlPanel():void {
panelShowEffect.stop();
panelHideEffect.stop();
if (controlPanel.visible)
controlPanel.visible=false;
}

public function showControlPanel():void {
if (!this.enabled) return;
panelShowEffect.stop();
panelHideEffect.stop();
if (!controlPanel.visible)
controlPanel.visible=true;
}

. . .

<mx:canvas width="{controlPanel.width}" height="100%" clipcontent="true" rollover="showControlPanel()" rollout="hideControlPanel()" creationcomplete="hideControlPanel()" backgroundcolor="#FFFFFF" backgroundalpha="0">

. . .

<mx:Move id="panelShowEffect" xTo="0" duration="200"/>
<mx:Move id="panelHideEffect" startDelay="1000" duration="200" xTo="{-controlPanel.width}"/>

. . .

<mx:VBox
id="controlPanel"
height="100%"
showEffect="{panelShowEffect}"
hideEffect="{panelHideEffect}"
x="{-controlPanel.width}"
visible="false"
>

. . . Содержимое панели . . .

</mx:VBox>

. . .

</mx:Canvas>



Единственная проблема -пришлось отказаться от эффекта WipeUp/Down, примененного к компоненту, вложенному в выезжающую панель, из-за его нестабильной работы.