Начну с того, что меня всегда удивляет использование в демках и конкурсных работах стандартного vcl. Как же так? Мы рисуем сцену с большим количеством объектов, красивым фоном, эффектами, физически взаимодействующими телами, с помощью графических движков (вроде GlScene, HGE и других), а элементарные кнопки, чекбоксы оставляем на отрисовку устаревающему vcl? Думаю дело в том, что красивый, простой и работающий гуи под GlScene - большая редкость, и поэтому многие просто вставляют в игру стандартные кнопки - кинуть на форму TButton умеют все. С другой стороны, возможно, те, кто начинают делать свой графический гуи, хотят сразу построить навороченный интерфейс с панелями, формами, выводом тысяч символов текста и т.д. Надеюсь, это рабочий подход к программированию, но не мой. Мне всегда кажется, что нужно начинать с чего-то маленького, тогда сразу станут видимыми границы, которых хочется достичь. В общем, я буду создавать, описывать и выкладывать в своем журнале простые элементы гуи, чтобы ими можно было воспользоваться в любую минуту. Обычно одним из самых неприятных моментов в гуи являются скроллбары, поэтому поступим хитро и попробуем организовать прокрутку длинного списка без скроллбаров вообще. Собственно, отсюда и заголовок сообщения - "Жизнь без скроллбаров", это возможно, просто и приятно. Приступим! | ![]() |
Видео динамики прокрутки можно посмотреть здесь, исходники + ехе качаем отсюда.
Конечно, прокручивающийся список мы вынесем в отдельный класс TSimpleListBox, его описание у меня раздулось до такого:
TSimpleListBox = class (TGLBaseSceneObject)
protected
fMatLib: TGLMaterialLibrary;
fFont: TGLCustomBitmapFont;
fItems: TList;
fYPosition: Single; // it's delta y for all list
fHeight: Integer;
fDeltaHeight: Integer; // for each Item
fMaxVisibleCount: Integer;
protected
fMainDummy: TGlDummyCube;
fItemHudText: TGlHudText;
fPanelBack: TGLHudSprite;
fPanelFront: TGLHudSprite;
fPanelSelection: TGLHudSprite;
fMousePosition: TVector;
Procedure InitHuds;
Procedure MainInit(aMatLib: TGLMaterialLibrary; aFont: TGLCustomBitmapFont);
Function GetItemByMouse: Integer;
public
Function GetItemByIndex(aIndex: Integer): TSimpleListItem;
Function AddItem(aItem: TSimpleListItem): Integer;
Function AddItemText(const aText: WideString): TSimpleListItem;
Procedure SetMousePosition(aX, aY: Integer);
Procedure DoRender(var ARci: TRenderContextInfo; ARenderSelf, ARenderChildren: Boolean); override;
procedure DoProgress(const progressTime: TProgressTimes); override;
Constructor Create(AOwner: TComponent; aMatLib: TGLMaterialLibrary; aFont: TGLCustomBitmapFont); reintroduce;
Constructor CreateAsChild(aParentOwner: TGLBaseSceneObject; aMatLib: TGLMaterialLibrary; aFont: TGLCustomBitmapFont);
Destructor Destroy; override;
end;
Основное, что нужно понять - мы наследуемся от TGLBaseSceneObject для того, чтобы не заботиться о рендере, апдейте и прочей иерархической структуры. Все это развязывает нам руки, и мы можем спокойно создать наш список внутри основной программы вот таким образом:
fListBox := TSimpleListBox.CreateAsChild(fMainDummy, fMatLib, fWinFont);
Вот такое волшебство будет нам доступно!
Следующим шагом рисуем элементы нашего списка:
Стоит заметить, что никаких clip-механизмов (чтобы рисовать объекты только в заданной области, обрезая края) мы использовать не будем, а просто оставим небольшую полоску, "загораживающую" вылезающие элементы сверху и снизу. Если внешнюю панельку рисовать раньше элементов списка, то выглядеть все это дело будет вот так кошмарно:
Ну и немножко кода, чтобы не расслабляться:
Procedure TSimpleListBox.DoProgress(const progressTime: TProgressTimes);
var
dy: Single;
i, item: Integer;
begin
dy := (fMousePosition[1] - (Position.Y - fHeight));
if dY < 0 then
dY := 0;
if dY > fHeight * 2 then
dY := fHeight * 2;
if fItems.Count >= fMaxVisibleCount then
fYPosition := fYPosition + progressTime.deltaTime * 3 * (-dY * (fItems.Count - fMaxVisibleCount) * fDeltaheight / fHeight / 2 - fYPosition)
else
fYPosition := 0;
for i := 0 to fItems.Count - 1 do
with GetItemByIndex(i) do
fSelectAlpha := fSelectAlpha - progressTime.deltaTime * 2;
item := GetItemByMouse;
if (item >= 0) and (item < fItems.Count) then
GetItemByIndex(item).fSelectAlpha := 1;
end;
Исходя из позиции мышки, мы начинаем изменять значение fYPosition плавным образом, чтобы наш список начал элегантно прокручиваться. Заодно изменяем значение альфы всех элементов списка.
Ну и отрисовываем наше добро:
Procedure TSimpleListBox.DoRender(var ARci: TRenderContextInfo; ARenderSelf, ARenderChildren: Boolean);
var
i: Integer;
item: TSimpleListItem;
dY: Integer;
begin
dY := Round(-fHeight + fYPosition);
fPanelFront.Position.X := Position.X;
fPanelFront.Position.Y := Position.Y;
fPanelBack.Position.X := Position.X;
fPanelBack.Position.Y := Position.Y - 10;
fPanelSelection.Position.X := Position.X;
fItemHudText.Position.X := Position.X - 80;
fPanelBack.DoRender(ARci, true, false);
for i := 0 to fItems.Count - 1 do
if(i < fItems.Count)
and(dY + fDeltaHeight * i < fHeight + fDeltaHeight / 2)
and(dY + fDeltaHeight * i > -fHeight - fDeltaHeight / 2)
then
begin
fPanelSelection.Position.Y := Position.Y + dY + fDeltaHeight * i;
fItemHudText.Position.Y := Position.Y + dY + fDeltaHeight * i;
item := GetItemByIndex(i);
fItemHudText.Text := item.fText;
with fPanelSelection.Material.GetActualPrimaryMaterial do
FrontProperties.Diffuse.Alpha := item.fSelectAlpha;
fPanelSelection.DoRender(ARci, true, false);
fItemHudText.DoRender(ARci, true, false);
end;
fPanelFront.DoRender(ARci, true, false);
end;
Как мы видим, сначала выставляются всем панелькам позиции, а затем в правильном порядке вызывается для всех них DoRender() - элементы начинают показываться на экране!
Теперь в основной программе остается только:
- создать шрифт (у нас это происходит в InitFont()),
- загрузить материалы (смотрим на InitMaterials())
- создать сам объект-список (fListBox := TSimpleListBox.CreateAsChild(...))
- заполнить его элементами (fListBox.AddItemText())
- постоянно обновлять позицию мыши (fListBox.SetMousePosition(MPos.x, MPos.y))
Возможно, последний пункт стоит самостоятельно выполнять в DoProgress(), но я пока оставил как есть... Видео для ленивых и не желающих качать демо:
Для полноценной работы также необходимо ловить нажатие клавиши мышки, чтобы уметь выбирать элементы списка - но это мы оставим "на потом", ведь для сегодняшней стартовой демки хватит того, что есть!
Вот, собственно, и все...
Кстати, завел новый репозиторий, заметили? Буду там складировать всякие демки, вроде текущей :)



Спасибо, Лампоголовый, классные вещи пишешь)))
ОтветитьУдалитьвот это 5+
ОтветитьУдалитьочень понравилось!
чуток бы смягчить это все. а то ничего не видно, когда скролл идет с начала списка в самый конец.
вот это зачет!!)
зачетная картинка сопровождает пост
ОтветитьУдалитьКласно!
ОтветитьУдалитьЭто хорошая идея писать свои наследники базового объекта сцены, но вероятно нужен хотя бы средний скил в работе с графикой что бы это делать.
ЗЫ: задний фон хорош.
gltrinix, благодарю! рад, что понравилось!
ОтветитьУдалитьAero, благодарю! по поводу "смягчить" - имеешь ввиду, чтобы прокрутка была медленнее? я просто скорость подбирал так, чтобы было не долго ждать от последнего до первого элемента... возможно, стоит еще подкрутить! благодарю за замечание!
по поводу картинки - да, скорее всего это не последнее сообщение о гуи))
Yarov - благодарю! по поводу наследников от объекта сцены - момент двоякий на самом деле... еще одна трудность при переносе на другой рендер, в принципе... но плюс большой, поэтому остановился пока на таком варианте))
скилл - дело наживное, в любой момент можно прокачаться до желаемого результата!
если у кого будут идеи или примеры интересных элементов гуи - кидайте, буду рад попробовать реализовать на GlScene :)
А я подчерпнул для себя то, что удобней писать свой метод CreateAsChild, а не Init.
ОтветитьУдалитьТолько вот обратил внимание, что интерфейс похож то на яФоновский :)
ОтветитьУдалитьhttp://delphimax.wordpress.com/2010/12/15/iphone-gui-from-a-delphi-perspective/
soofX, да, CreateAsChild удобен:)
ОтветитьУдалитьYarov, iPhone'овцы вообще молодцы, много вкусного придумали! правда я действительно искал всякие флешовые примеры и наткнулся на тот, который вначале сообщения... то есть, как говорится, "все украдено до нас"))
How can i do this in fullscreen mode...
ОтветитьУдалить