Недавно один из форумчан glscene.ru спрашивал о способе отображения панельки с изменяемой шириной. Простым масштабированием (Scale) спрайта правильного эффекта не всегда можно добиться. Например, нам нужны закругленные края или небольшая тень от панельки. При масштабировании эти эффекты безобразно увеличатся и будут портить всю картинку. Как же быть в случае, если заранее ширина или высота такой панельки неизвестна и просто нарисовать png-шку нужного размеры мы не сможем? На ум приходит собирать итоговую картинку из нескольких элементов - углы, края и серединка. Вот такую панельку, состоящую из 9ти кусочков мы и будем сегодня делать на GlScene. |
В общем, ничего хитрого, все просто и прямолинейно. Поэтому сегодня будет мало слов, много кода...
Итак, создаем основной класс панельки, опишем его в следующем виде:
TSimplePanel = class (TGLHudSprite)
protected
fCorner, fHalfCorner: Integer;
Procedure MainInit;
public
Function NCHitTest(aPosition: TVector): TNCSizing;
Function IsHit(aPosition: TVector): Boolean;
Procedure DoRender(var ARci: TRenderContextInfo; ARenderSelf, ARenderChildren: Boolean); override;
Constructor Create(AOwner: TComponent); reintroduce;
Constructor CreateAsChild(aParentOwner: TGLBaseSceneObject);
end;
где NCHitTest() и IsHit() нужны для возможности изменения размеров панельки при помощи мыши в realtime-режиме!
Заглянем в главный метод DoRender() и найдем для себя базовый метод отрисовки 9ти кусочков нашей панельки:
Procedure TSimplePanel.DoRender(var ARci: TRenderContextInfo; ARenderSelf, ARenderChildren: Boolean);
var
left, top: integer;
eWidth, eHeight: Single;
ePosition: TVector;
Procedure RenderHud(x, y, w, h: integer; a, b, c, d: Single);
begin
Position.SetPoint(left + x, top + y, 0);
with Material.GetActualPrimaryTexture do
begin
MappingMode := tmmObjectLinear;
MappingSCoordinates.SetVector(1 / w * a, 0, 0, b);
MappingTCoordinates.SetVector(0, 1 / h * c, 0, d);
end;
Width := w;
Height := h;
inherited DoRender(ARci, true, false);
end;
var
w, h: integer;
begin
eWidth := Width;
eHeight := Height;
ePosition := Position.AsVector;
left := Round(Position.x - Trunc(Width / 2));
top := Round(Position.y - Trunc(Height / 2));
w := (Round(Width) - fCorner * 2) div 2;
h := (Round(Height) - fCorner * 2) div 2;
RenderHud(fHalfCorner, fHalfCorner, fCorner, fCorner, 1/4, 1/8, 1/4, 7/8);
// ... и так далее для всех 9ти элементов панели
RenderHud(fCorner + w, fHalfCorner, 2 * w, fCorner, 1/2, 0.5, 1/4, 7/8);
RenderHud(fCorner + 2 * w + fHalfCorner, fHalfCorner, fCorner, fCorner, 1/4, 7/8, 1/4, 7/8);
RenderHud(fHalfCorner, h + fCorner, fCorner, 2 * h, 1/4, 1/8, 1/2, 0.5);
RenderHud(fCorner + w, h + fCorner, 2 * w, 2 * h, 0.5, 0.5, 1/2, 0.5);
RenderHud(fCorner + 2 * w + fHalfCorner, h + fCorner, fCorner, 2 * h, 1/4, 7/8, 1/2, 0.5);
RenderHud(fHalfCorner, 2 * h + fCorner + fHalfCorner, fCorner, fCorner, 1/4, 1/8, 1/4, 1/8);
RenderHud(fCorner + w, 2 * h + fCorner + fHalfCorner, 2 * w, fCorner, 1/2, 0.5, 1/4, 1/8);
RenderHud(fCorner + 2 * w + fHalfCorner, 2 * h + fCorner + fHalfCorner, fCorner, fCorner, 1/4, 7/8, 1/4, 1/8);
Width := eWidth;
Height := eHeight;
Position.SetPoint(ePosition);
end;Выглядит ужасно, согласен. Но это код из разряда "один раз написал и забыл", поэтому пока трогать и рефакторить его не собираюсь. По сути запоминаем текущие размеры панельки, ренедерим необходимые элементы, и выставляем исходные размеры.
Теперь об использовании данного класса...
Достаточно по обыкновению определить основные свойства и на экране появится симпатичная панелька:
fPanel := TSimplePanel.CreateAsChild(fMainDummy); fPanel.Material.MaterialLibrary := fMatLib; fPanel.Material.LibMaterialName := 'white'; fPanel.Position.SetPoint(600, 200, 0); fPanel.Width := 200; fPanel.Height := 200;
В нашем случае будет что-то вроде такого
Для красоты и интерактивности я позволил себе добавить список различных "скинов". Сам список - это элемент, который мы создали ранее. Поэтому демка принимает следующий вид:
Код самой программы вроде нетруден, поэтому я в дополнение просто приведу методы, которые решил добавить в качестве украшения - панельку можно resize-ить за стороны в любой момент! А также таскать саму панельку за любое произвольное место с зажатой левой кнопкой мыши.
Весь механизм по сути заключается в двух методах (UpdateMoving и UpdateSizing):
Procedure TfrmMain.UpdateSizing(MPos: TVector; DeltaPos: TVector);
begin
if not fDoSizing then
begin
fSizing := fPanel.NCHitTest(MPos);
if (fSizing = cs_Left) or (fSizing = cs_Right) then
fViewer.Cursor := crSizeWE
else if (fSizing = cs_Top) or (fSizing = cs_Bottom) then
fViewer.Cursor := crSizeNS
else
fViewer.Cursor := crDefault;
end
else
begin
if fSizing = cs_Left then
begin
fPanel.Width := fPanel.Width - Trunc(DeltaPos[0] / 2) * 2;
fPanel.Position.X := fPanel.Position.X + Trunc(DeltaPos[0] / 2);
end
else if fSizing = cs_Right then
begin
fPanel.Width := fPanel.Width + Trunc(DeltaPos[0] / 2) * 2;
fPanel.Position.X := fPanel.Position.X + Trunc(DeltaPos[0] / 2);
end
else if fSizing = cs_Top then
begin
fPanel.Height := fPanel.Height - Trunc(DeltaPos[1] / 2) * 2;
fPanel.Position.Y := fPanel.Position.Y + Trunc(DeltaPos[1] / 2);
end
else if fSizing = cs_Bottom then
begin
fPanel.Height := fPanel.Height + Trunc(DeltaPos[1] / 2) * 2;
fPanel.Position.Y := fPanel.Position.Y + Trunc(DeltaPos[1] / 2);
end;
end;
end;
Procedure TfrmMain.UpdateMoving(MPos: TVector; DeltaPos: TVector);
begin
if fMousePressed and fPanel.IsHit(MPos) and not fDoSizing then
with fPanel.Position do
begin
SetPoint(x + DeltaPos[0], y + DeltaPos[1], 0);
if x <= 380 then
x := 380
else if x > 700 then
x := 700;
if y <= 100 then
y := 100
else if y > 500 then
y := 500;
end;
end;
Видео происходящего:
Думаю, на этом все, как обычно исходники качаем отсюда...
Всем удачи!
Спасибо, Lampogolovii.
ОтветитьУдалитьgltrinix, всегда рад! если будут идеи еще каких гуи-элементов - буду очень признателен...
ОтветитьУдалитьКорни подхода вечно живут в web-верстке под css <3.0. Поэтому геймдевелоперам, имеющим опыт верстки, такой подход практически родной :)
ОтветитьУдалить