09 апреля 2012

Gui: простая анимированная кнопка

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

Что мы имеем? 
Две картинки двух состояний кнопки:


А также в нашем распоряжении здравый смысл, Delphi, GlScene и желание добиться результата...

Что хотим?
Необходимо анимировать кнопку при наведении курсора мыши.
Если говорить о конечном результате, то код рендера кнопки выглядит до безумия простым:

Procedure TGUIAlphaButton.DoRender(var ARci: TRenderContextInfo; ARenderSelf, ARenderChildren: Boolean);
begin
  // устанавливаем позицию кнопки 
  fHudSprite.Position.x := Position.x;
  fHudSprite.Position.y := Position.y;

  // рисуем неактивное состояние кнопки 
  fHudSprite.Material := fShowMaterial;
  fHudSprite.Width := fShowMaterial.Texture.Image.Width;
  fHudSprite.Height := fShowMaterial.Texture.Image.Height;
  fHudSprite.AlphaChannel := 1;
  fHudSprite.DoRender(ARci, true, false);

  // поверх рисуем активное состояние кнопки
  fHudSprite.Material := fActiveMaterial;
  fHudSprite.Width := fActiveMaterial.Texture.Image.Width;
  fHudSprite.Height := fActiveMaterial.Texture.Image.Height;
  fHudSprite.AlphaChannel := (fAnimationTime - cst_animTimeShow) / (cst_animTimeActive - cst_animTimeShow);
  fHudSprite.DoRender(ARci, true, false);
end;

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


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

TGUIBaseObject
Чтобы иметь меньше проблем с рендером и обновлением наших gui-объектов, я решил отнаследовать этот базовый  класс от TGLCustomSceneObject. Возможно, это временное решение, и в конечном счете мы уйдем от этого ненужного хвоста, но сейчас, в нашем старте, это сильно облегчит жизнь...
Итак, TGUIBaseObject - это самый простой класс, на данный момент имеет только "команду" объекта, то есть числовой идентификатор, по которому можно будет легко находить нужный gui-объект в каком-нибудь списке.

TGUIBaseAnimatedObject
Класс, в котором появляется анимация интерфейса. Имеет три конечных состояния: Hide, Show, Active. То есть скрытый (невидимый), показанный и активный (при наведении мыши). Вся идея в том, что объект данного класса переключается между этими тремя состояниями не моментально, а плавно, используя для этого счет времени в переменной fAnimationTime.

TGUIBaseInteractiveObject
Интерактивный gui-объект. Здесь появляется понятие hit-области (необходима для правильного реагирования на события мыши). В дальнейшем именно в этом классе мы реализуем события на нажатие, наведение и уход мыши.

TGUIBaseButton
Чует мое сердце, что этот промежуточный класс пригодится! Но пока он пуст, пропуская через себя наследование напрямую.

TGUICustomHudButton
Класс, в котором мы жестко привязываемся к GlScene (конечно, базовый класс тоже завязан на этот графический движок, но мы опять же держим в голове, что до этого момента привязки были минимальны). Именно здесь мы добавляем HudSprite, который будет отображаться на экране.

TGUIAlphaButton
Вот мы и добрались до первого наследника от TGUICustomHudButton, в котором мы как раз реализуем вышеприведенный код с вычислением альфы нашего спрайта.

Вот такая цепочка наследования у нас получилась... зачем такие сложности? Например, захочется нам сделать обычный спрайт, который будет висеть на экране, тогда мы легко отнаследуемся от TGUIBaseObject и будем хранить его в том же списке, что и остальные gui-объекты. Захотим сделать спрайт с нестандартной хит-областью, тогда перекинем наследование на TGUIBaseInteractiveObject и будем довольны уже готовым полем fHitArea. Можно сделать целые подменю, которые будут плавно сменять друг друга, имея в своем распоряжении fAnimationTime и три заветных состояния: скрыто, показано, активно. При этом дублирование кода будет минимальным. Захотели кнопку, "разбухающую" при наведении? Просто отнаследовались от TGUICustomHudButton и изменили scale картинки.
Лично у меня в голове все выглядит замечательно :)

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

  fRightBtn := TGUIAlphaButton.CreateAsChild(fMainDummy);
  fRightBtn.SetMaterials('btn_1', 'btn_2', fMatLib);
  fRightBtn.Position.SetPoint(435, 200, 0);
  fRightBtn.Show;

Установили материалы, позицию и включаем Show.
Обновляем кнопку одной строчкой:

  fRightBtn.SetMouseState(MPos.x, MPos.y, IsKeyDown(vk_LButton));

Скриншот результата:


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

Уф-ф-ф-ф... начало положено, а это самое главное и сложное! Демку можно скачать здесь, или обновитесь через репозиторий (23ья ревизия).

Сообщения, схожие по тематике:

2 коммент.:

  1. О да! то что нужно :)) спасибо большое)

    ОтветитьУдалить
  2. А на какой версии GLScene это собиралось? У меня какие-то непонятные глюки с альфа-каналом.

    ОтветитьУдалить