Начну с того, что меня всегда удивляет использование в демках и конкурсных работах стандартного 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...
ОтветитьУдалить