Cześć pracuję nad stworzeniem nowego języka programowania o nazwie „Avocado”, który rozwijam w środowisku Lazarus (Free Pascal). Język Avocado jest językiem ogólnego przeznaczenia jest kompilowany i aktualnie pozwala na tworzenie aplikacji konsolowych.
Prace nad Avocado oraz zintegrowanym środowiskiem programistycznym (IDE) rozpoczęły się 19 lutego 2025 roku. Unikalną cechą tego języka jest możliwość pisania komend w języku polskim, a także kompilacja kodu do pliku .exe.
Lecz da się zrobić aby programy były na Linux, Mac, Android, iOS, FreeBSD, Web bo Lazarus wspiera kompilację na te platformy.
Język Avocado jest dostępny bezpłatnie zarówno dla projektów komercyjnych, jak i niekomercyjnych.
Realizowane 18 funkcji.
Język Avocado jest translowany do Free Pascal, a dalej kompilowany, co oznacza, że jest tak szybki jak C lub Rust, lecz składnię ma jak w Pythonie.
Język jest w trakcie rozwoju.
Nowe funkcje które dodałem ostatnio.
Komentarze //
Deklaracja zmiennych Integer i Real została podzielona i ma osobne nazwy. Integer teraz nazywa się Liczbac od słowa Liczba całkowita, Real to Liczbar od słowa liczba rzeczywista
Konwersje
Konwertuje liczbę rzeczywistą w tekst LiczbarWTekst()
Przykład.
Liczbar a = 5.5
Tekst b = LiczbarWTekst(a)
//drukujemy wynik
Druk(b)
Konwertuje liczbę rzeczywistą w liczbę całkowitą LiczbarWc()
Liczbar a = 5.5
Liczbac b = LiczbarWc(a)
//drukujemy wynik
Druk(b)
//Wynik 5
Konwertuje liczbę całkowitą w liczbę rzeczywistą LiczbacWr()
Liczbac a = 5
Liczbar b = LiczbacWr(a)
Druk(b)
//wynik 5.0000000000000000E+000
Konwertuje liczbę całkowitą w tekst LiczbacWTekst()
Liczbac a = 5
Tekst b = LiczbacWTekst(a)
//Działa dodawanie
Liczbac suma = a+a
//Jeśli chcesz dodać zmienną tekstową do liczby całkowitej trzeba konwertować TekstWLiczbac()
Liczbac d = TekstWLiczbac(b)
//Teraz można dodawać
Liczbac suman = a+d
Druk(suma)
Druk(suman)
//wynik
//10
//10
Konwertuje tekst w liczbę całkowitą TekstWLiczbac()
Tekst a = '5'
Liczbac b = TekstWLiczbac(a)
Druk(b)
//Wynik będzie 5
Dodamy dodawanie po konwersji
Tekst a = '5'
Liczbac b = TekstWLiczbac(a)
Liczbac suma = b +10
Druk(b)
Druk(suma)
//Wynik
//5
//15
Obsługiwane są operacje arytmetyczne (+, -, /, *). dla liczb rzeczywistych i całkowitych
//Dodawanie
Liczbac a = 5
Liczbac b = 15
Liczbac suma = a+b
Druk(suma)
//20
//Odejmowanie
Liczbac a = 5
Liczbac b = 15
Liczbac suma = a-b
Druk(suma)
//-10
//Mnożenie
Liczbac a = 5
Liczbac b = 15
Liczbac suma = a*b
Druk(suma)
//75
//Dzielenie
Liczbac a = 5
Liczbac b = 15
Liczbar suma = b/a
Druk(suma)
//3.0000000000000000E+000
Wprowadzanie danych przez użytkownika funkcja wpr()
Tekst name = wpr('Twoje imie ')
Tekst surname = wpr('Twoje nazwisko ')
Tekst wynik = (name + ' ' + surname)
Druk(wynik)
Funkcja jesli .. wtedy inaczej (if then esle)
Liczbac a = 5
jesli a > 10 wtedy druk('a jest większa od 10') inaczej druk('a jest mniejsza lub równa 10')
//a jest mniejsza lub równa 10
Konkatenacja – operacja łączenia dwóch lub więcej ciągów znaków (napisów, stringów) w jeden dłuższy ciąg. +
Tekst imie = ' Jan'
Tekst nazwisko = ' Kowalski'
Tekst klient = imie + nazwisko
Druk(klient)
//Jan Kowalski
Prosty konsolowy kalkulator w Avocado to 13 linijek kodu, a w Free Pascal to 49 linijek kodu.
Druk('Kalkulator')
Liczbac a = wpr('Liczba 1 ')
Liczbac b = wpr('Liczba 2 ')
Liczbac suma = 0
Liczbar sumar = 0.0
Liczbac znak = wpr('Wybierz typ operacji 1(+), 2(-), 3(/), 4()')
jesli znak = 1 wtedy suma = a+b
jesli znak = 2 wtedy suma = a-b
jesli znak = 3 wtedy sumar = a/b
jesli znak = 4 wtedy suma = a b
liczbac s = LiczbacWTekst(suma)
Tekst sr = LiczbarWTekst(sumar)
jesli znak = 3 wtedy Druk(sr) inaczej Druk(s)
Więcej informacji tu https://dimitalart.pl/index.php/2025/03/10/avocado/
Witaj! 😄 Pracuję nad stworzeniem nowego języka programowania o nazwie „Avocado”, który rozwijam w środowisku […]
https://dimitalart.pl/index.php/2025/03/10/avocado/Wczoraj zacząłem pisać tutaj wpis na temat postępów nad implementacją systemu mapowania inputu własnego pomysłu, a dokładniej o zrobieniu dodatkowej gałki analogowej z myszy. Niestety wpis wyszedł za długi zbyt skomplikowany, a do tego edytor się snów zesrał, więc piszę jeszcze raz.
Główna mechanika gry będzie (za pięć lat…) przystosowana do grania na kontrolerze lub klawiaturze — albo analog, albo zestaw czterech przycisków na kontrolerze (np. D-Pad), albo zestaw klawiszy na klawiaturze. Czyli typowo, tak jak to zwykle wygląda obecnie i tak jak to wyglądało w erze SNES-a. Jednak mnóstwo graczy gra w różne produkcje myszą (np. w MMORPG-i), więc postanowiłem dodać możliwość wykorzystania myszy do chodzenia bohaterem.
I tu jest pewien problem — gra będzie typu ”action adventure”, a nie ”point & click”, więc systemowy kursor podczas grania nie będzie widoczny. A nie będzie widoczny, bo grając nie będzie się klikało w obiekty znajdujące się na mapie. Jak więc wykorzystać mysz do sterowania? Cóż — zrobić z niej wirtualną gałkę analogową. :]
Skoro mysz ma działać jak gałka analogowa, musi dostarczać pozycję gałki — w formie wartości liczbowych (współrzędne, tak jak zwykle w obsłudze gałek analogowych), ale też przyda się kąt wychylenia (w radianach) oraz liczbowa wartość kierunku (jak w D-Padach). Dzięki temu gałka będzie mogła działać zarówno jako analog jak i D-Pad. W zależności od potrzeb, będzie można przesunąć postać bohatera albo płynnie o zadany kąt, albo w jednym z ośmiu kierunków (to będzie mógł sobie gracz skonfigurować).
Gałka na fizycznym kontrolerze dostarcza swoją pozycję w formie dwóch liczb X
i Y
jako SInt16
— to samo trzeba zrobić w przypadku wirtualnej gałki. Żeby to było możliwe, gałka musi posiadać określony, kwadratowy obszar, w którym punkt centralny oznacza gałkę wycentrowaną. Rozmiar obszaru musi być na tyle mały, aby mieścił się w oknie. Do tego przydadzą się pola CursorX
i CursorY
do przechowywania aktualnej pozycji gałki — na ich podstawie można łatwo policzyć kierunek wychylenia oraz kąt.
Gałka ta ma działać w ten sposób, że przesunięcie myszy w którąś stronę będzie przesuwać wirtualny kursor w obszarze gałki. Odpowiednio dalekie przesunięcie kursorsa od punktu centralnego spowoduje, że bohater zacznie się w tym kierunku poruszać po mapie i ten ruch będzie automatyczny. Aby dało się ten ruch przerwać, obszar musi posiadać martwą strefę w centralnej części (w formie koła) — jeśli kursor znajdzie się w tej strefie, to kierunek zostanie określony jako none
(odpowiednik puszczenia D-Pada/analogu na kontrolerze).
Struktura gałki wygląda tak:
type
TGame_WindowStick = record private
SizeStick: TGame_SInt32;
SizeDeadZone: TGame_SInt32;
RadiusStick: TGame_SInt32;
RadiusDeadZone: TGame_SInt32;
Center: TGame_SInt32;
CursorX: TGame_SInt32;
CursorY: TGame_SInt32;
Angle: TGame_Float;
Direction: TGame_SInt32;
Active: TGame_Bool;
end;
Oprócz danych wymienionych wyżej, jest jeszcze flaga Active
— jeśli gracz nie używa myszy do grania, to jej stan nie jest aktualizowany. Jak widać wyżej, struktura gałki nie zawiera informacji na temat okna, do którego przynależy — i nie powinna, bo to okno ma używać gałki, a nie na odwrót.
Każde okno w grze posiada wskaźnik na strukturę gałki oraz współrzędne jej obszaru względem okna-rodzica. Aby móc aktualizować stan gałki, należy wiedzieć w które miejsce jej obszaru przesunąć wirtualny kursor. Jak to zrobić? Wystarczy obsłużyć zdarzenie SDL_MOUSEMOTION
— po otrzymaniu informacji o ruchu myszy w obrębie okna, współrzędne systemowego kursora należy przeliczyć na relatywne współrzędne w obszarze gałki. Te współrzędne wpisuje się do pól CursorX
i CursorY
(przycięte do rozmiaru obszaru), a następnie zaktualizować kąt i kierunek gałki:
SDL_MOUSEMOTION:
if Game_WindowStickIsActive(Window^.Stick) then
begin
NewStickX := AEvent^.Motion.X - Window^.BoundsStick.X;
NewStickY := AEvent^.Motion.Y - Window^.BoundsStick.Y;
Game_WindowStickSetPosition(Window^.Stick, NewStickX, NewStickY);
end;
Obszar BoundsStick
posiada współrzędne obszaru gałki w oknie. U mnie jest to nieco bardziej skomplikowane, bo obszar klienta okna może być zgodny z rozmiarem okna, a może też być mniejszy. Wszystko dlatego, że klatka gry zawsze jest renderowana z zachowaniem proporcji 16:10
(zgodnych z tylnym buforem) — jeśli systemowe okno ma inne proporcje, to pojawiają się pasy po bokach lub na górze i dole:
Skoro systemowy kursor podczas grania jest niewidoczny, to gracz nie widzi gdzie on jest. To powoduje, że łatwo jest ruszyć wyjechać kursorem poza okno i kliknąć np. w pulpit lub okno innego programu — a to dezaktywuje okno gry i zdenerwuje gracza. Dlatego też trzeba kontrolować pozycję systemowego kursora w oknie.
Aby ograniczyć ruch kursora do konkretnego okna lub jego obszaru, SDL posiada funkcję SDL_SetWindowGrab
— podanie wartości SDL_ENABLE
powoduje, że nie będzie się dało wyjechać kursorem poza wewnętrzny obszar okna. Działa to bez względu na styl okna, a więc jeśli jest malutkie, ale też w trybie pełnoekranowym oraz ekskluzywnym trybie wideo. Jeśli wirtualny analog jest nieaktywny (czyli jeśli gracz obsługuje zasobnik z przedmiotami), to kursor jest utrzymywany w obszarze klienta okna.
A co jeśli analog jest włączony? Wtedy systemowy kursor jest ukrywany i obszar po którym kursor może się przemieszczać jest zmniejszany do rozmiaru obszaru wirtualnej gałki, czyli niewielkiego kwadratu po środku okna. Aby móc ustalić własny obszar po którym może się poruszać kursor w oknie, należy wykorzystać funkcję SDL_SetWindowMouseRect
.
Podczas aktywacji gałki, systemowy kursor jest ukrywany i przenoszony w takie miejsce w oknie, aby było zgodne z współrzędnymi wirutalnego kursora w obszarze gałki. Jeśli gracz włączy zasobnik z przedmiotami, a tym samym dezaktywuje wirtualną gałkę, systemowy kursor jest pokazywany w centralnym punkcie okna — dzięki temu gracz nie będzie musiał pamiętać gdzie był kursor podczas obsługi zasobnika.
Centrowanie kursora podczas wyłączania gałki jest tymczasowe — w przyszłości to zachowanie może być zmienione, aby współpracowało z kanapowym trybem kooperacji. No bo nie może być tak, że dwóch graczy gra na jednym ekranie i ten który gra myszą, jeździ kursorem po całym oknie — to będzie drugiemu graczowi przeszkadzać.
A co jeśli chcemy mieć możliwość zmiany czułości gałki? A to akurat proste — im wyższa czułość, tym mniejszy rozmiar obszaru gałki. I to samo w drugą stronę — im większy rozmiar obszaru gałki, tym dłuższą drogę musi pokonać kursor, a więc tym niższa czułość.
Tu jest pewien problem, bo okno gry można zmniejszyć do rozmiaru tylnego bufora (czyli do 358×224
), a rozmiar gałki może być znacznie większy. Aby zapobiec problemom ze sterowaniem, okno przechowuje aktualny rozmiar gałki (ustalony przez gracza), ale jeśli jest on większy niż obszar klienta okna, to go przycina — ten przycięty rozmiar przekazuje gałce.
Niżej krótki gif prezentujący działanie gałki oraz kontrolę ruchu i widoczności systemowego kursora:
Różnokolorowe obszary widoczne na animacji służą jedyne do debugowania kodu okna i analogu.
To co mam obecnie na razie wystarczy, ale w przyszłości dodam jeszcze jedną rzecz. Aby łatwiej było kontrolować w końcu niewidoczny kursor w obszarze gałki, dodam automatyczne przyciąganie wirtualnego kursora do konkretnych punktów wewnąrz obszaru gałki — punktów ośmiu głównych kierunków oraz do centrum. Dzięki temu mikro-ruchy myszą (wynikające np. z mocniejszego klikania) nie będą powodować przypadkowej zmiany kierunku bohatera. Dopiero mocniejszy ruch myszą będzie powodował zmianę kierunku.
Na razie tego nie implementuję — nie ma jeszcze silnika, więc nie mam za bardzo jak tego ficzera testować i kalibrować.
Obecnie cisnę fundamenty gry, aby zapewnić sobie jak najwięcej możliwości, jeśli chodzi o tryby okna oraz mapowanie inputu. Na razie nie ogłaszam się z tym za bardzo, ale po przygotowaniu wstępnej wersji silnika gry (co zajmie mi jeszcze kilka miesięcy), chciałbym zająć się tworzeniem tej gry na pełny etat. Planuję więc w niedalekiej przyszłości skorzystać z Patronite i prowadzić vlog na YouTube i nie tylko pierdzielić o postępach, ale też omawiać bardziej szczegółowo ”flaki”, mówiąc co i dlaczego robię (bez owijania w bawełnę), jak to ma wpływać na gameplay i rozrysowywać to wszystko w jakimś programie (podobnie jak to robi np. One Lone Coder). No i oczywiście prowadzić swojego Discorda, coby mieć stały kontakt z patronami i społecznością.
Przy okazji, tytuł okna zawiera docelową nazwę gry — ta będzie się nazywać Project: Kids, a pierwsza odsłona nosić będzie slogan When the Nightmare Comes. Nie chcę spoilerować o fabule za wczasu (skoro g**no jeszcze mam), ale w grze będzie się sterowało dzieciakiem (lub kilkoma, w trybie kooperacji, bez split screenu) i robiło rozpierdziel na półotwartej mapie, walcząc z kosmitami-najeźdźcami w post-apokaliptycznym świecie, będącym efektem koszmaru głównego bohatera, w jedną z burzliwych nocy. Mechanika ma zapewniać sporo możliwości, zapewniając bogaty wachlarz funkcji dotyczących sterowania. Całość oczywiście w formie pikselartowej, ale ze wsparciem obracania kamery.
Więcej informacji w mam nadzieję niedalekiej przyszłości. ;)
@Spine: tutaj bardziej mi chodziło o to, że mogę sobie stworzyć fonty jako zestawy tekstur, dlatego że rozdzielczość tylnego bufora jest zawsze stała — dokładnie 358×224
piksele (rozdzielczość pionowa zgodna z NES/SNES). Każdy znak podstawowego fontu będzie miał 8×8
pikseli, co elegancko pasuje do pikselartowej oprawy i bardzo łatwo się takie fonty obsługuje (brak kerningu itp.).
Po drugie, fonty nie będą jednokolorowe — nie tylko będą posiadać czarną otoczkę (kontrastującą z dowolnym tłem), ale również będą mogły posiadać zawartość gradientową czy jakąkolwiek inną. Aby renderować takie literki w dowolnym kolorze, wystarczy ustawić kolor modulacji renderera (funkcją SDL_SetTextureColorMod
) i gotowe. Niektóre fonty (głównie ozdobne, np. na potrzeby menu) będa posiadały stałą zawartość i nie będą modulowane podczas renderowania.
Natomiast powód nieużywania fontów typu .ttf
jest taki, że SDL nie wspiera ich renderowania bezpośrednio na teksturach. Aby wyrenderować tekst, SDL najpierw tworzy powierzchnię software'ową (surface
), a więc alokuje pamięć (a to ssie), następnie trzeba powierzchnię przekonwertować na teksturę (co znów wymaga alokacji pamięci i transferu danych do pamięci GPU) i dopiero wtedy można ją gdzieś wyrenderować. A potem trzeba zwolnić pamięć pomocniczej powierzchni i tekstury — kolejne marnowanie mocy obliczeniowej.
Lekko licząc, renderowanie znaków w formie osobnych tekstur jest setki razy szybsze od funkcji z modułu SDL_ttf
. :D
Nikt jeszcze nie pisał, więc… od dwóch tygodni jest dostępna nowa wersja — Lazarus 2.2.0 z FPC 3.2.2. ;)
https://forum.lazarus.freepascal.org/index.php/topic,57752.0.html
spróbuj z nowszą wersją gdb: dla x86: https://sourceforge.net/projects/lazarus/files/Lazarus%20Windows%2032%20bits/Alternative%20GDB/GDB%209.2%20-%20Modified%20for%20unicode/ dla x86_64: https://sourceforge.net/projects/lazarus/files/Lazarus%20Windows%2064%20bits/Alternative%20GDB/GDB%209.2%20-%20Modified%20for%20unicode/
Kolejny wpis na temat bazgrołów graficznych w Lazarusie, bo dawno nic na ten temat nie pisałem. :)
Powoli kończę prace nad narzędziem do organizowania i wygodnego przeprowadzania stacjonarnych mistrzostw w Tetrisa. Obecnie finalizuję implementację animowanego banera, który u dołu okna podglądu będzie wyświetlał różne teksty i adresy kont w serwisach społecznościowych (małą próbkę pokazałem w wątku Łączenie dwóch obrazów w jeden na podstawie zadanego poziomu – można sobie zobaczyć o co chodzi). Aby użytkownik mógł sobie konfigurować zawartość i zachowanie takiego banera, potrzebny jest interfejs do modyfikowania jego ustawień, z jednoczesnym podglądem końcowego efektu. I małą część tego interfejsu przedstawię w tym wpisie.
Niektóre etykiety banera służą do wyświetlania tekstu, którego fragmenty mogą być malowane używając jednego z dwóch zadanych kolorów – w celu wizualnego podziału linijki tekstu na fragmenty ”godne uwagi”, coby nie cudować z interpunkcją. Czyli w skrócie: mamy linijkę tekstu, w tym tekście wstawione są znaki określające ”swap” bieżącego koloru (tym znakiem jest |
), mamy współrzędne określające pozycję docelową tekstu i mamy dwa kolory (startowy i dodatkowy). Wynikiem działania ma być dwukolorowy tekst wyrenderowany na zadanym płótnie.
procedure DrawTextStriped(ACanvas: TCanvas; AX, AY: Integer; AColorA, AColorB: TColor; const AText: String);
var
Stripes: TStringList;
Stripe: String;
var
StripeColors: array [Boolean] of TColor;
StripeColorIndex: Boolean = False;
begin
Stripes := TStringList.Create();
try
if ExtractStrings(['|'], [], PChar(AText), Stripes, True) > 0 then
begin
StripeColors[False] := AColorA;
StripeColors[True] := AColorB;
for Stripe in Stripes do
begin
ACanvas.Font.Color := StripeColors[StripeColorIndex];
ACanvas.TextOut(AX, AY, Stripe);
AX += ACanvas.TextWidth(Stripe);
StripeColorIndex := not StripeColorIndex;
end;
end;
finally
Stripes.Free();
end;
end;
Procedurka bardzo prosta w działaniu. Do pomocniczej listy łańcuchów wypakowujemy fragmenty tekstu, rozdzielone według znaku markera (koniecznie z zachowaniem białych znaków, dlatego drugi parametr otrzymuje pusty zbiór). Kolory pakujemy do macierzy indeksowanej wartością logiczną (bo najłatwiej odwrócić indeks bieżącego koloru – wystarczy prosta negacja) i ustawiamy indeks bieżącego koloru na pierwszą komórkę (o indeksie False
). Następnie w pętli renderujemy bieżący fragment, inkrementujemy pozycję horyzontalną o pikselową szerokość fragmentu i odwracamy (negujemy) indeks koloru. To wszystko.
Przykładowe wykorzystanie powyższej procedury do namalowania tekstu na płótnie formularza, w zdarzeniu OnPaint
okna:
procedure TMainForm.FormPaint(ASender: TObject);
begin
Canvas.Brush.Color := clWindow;
Canvas.FillRect(ClientRect);
Canvas.Font.Name := 'Gotham Black';
Canvas.Font.Size := 19;
DrawTextStriped(Canvas, 16, 16, clWindowText, clHighlight, 'LOREM IPSUM |DOLOR SIT| AMET');
end;
Efekt działania:
Fajnie, możemy już malować ”pasiasty” tekst, no ale użytkownik musi go jakoś wprowadzać i widzieć podgląd w kontrolkach, a nie na gołym płótnie okna – dlatego czas na przygotowanie interfejsu.
Do podglądu etykiety skorzystamy z kontrolki typu TPaintBox
, bo służy do malowania różnych pierdół i przy okazji nie obsługuje focusa. Jakie tło wybrać? Przecież jednolite będzie wyglądać brzydko, w przypadku gdy kolor tła będzie inny niż komponentu nadrzędnego (lub okna). Do wprowadzania tekstu wykorzystamy zwykły TEdit
, więc idąc za ciosem, niech nasz paintbox też wygląda jak pole edycyjne.
No dobrze, ale jak namalować obramowanie pola edycyjnego w paintbox? W końcu nie wiadomo jakich kolorów używa system… Tu z pomocą przychodzi obiekt ThemeSevices
z modułu Themes
. ;)
Jego użycie jest banalne – wystarczy pobrać ”detale” za pomocą metody GetElementDetails
, podając enum określający interesujący nas element. Tak pozyskane detale następnie należy podać metodzie DrawElement
, razem z uchwytem płótna oraz obszarem docelowym:
procedure DrawBannerBackground(ACanvas: TCanvas; ARect: TRect);
var
Details: TThemedElementDetails;
begin
Details := ThemeServices.GetElementDetails(teEditTextNormal);
ThemeServices.DrawElement(ACanvas.Handle, Details, ARect);
end;
Tej procedurki używamy wewnątrz zdarzenia OnPaint
kontrolki paintbox:
procedure TMainForm.CBannerPaintBoxPaint(ASender: TObject);
var
PaintBox: TPaintBox absolute ASender;
begin
DrawBannerBackground(PaintBox.Canvas, PaintBox.ClientRect);
end;
W rezultacie na ekranie zobaczymy jedno pole, które faktycznie jest edycyjnym (górne) oraz drugie, które służy tylko do podglądu (dolne):
Tło mamy z głowy – teraz ostatnia rzecz, czyli namalowanie tekstu wewnątrz niby-edita.
Procedurkę do renderowania pasiastego tekstu już mamy, więc pozostało wyznaczyć odpowiednią pozycję tekstu i go namalować. Ale tym razem wyrenderujemy go wyśrodkowanego w poziomie i z odpowiednio wypozycjonowanego w pionie, aby zachować równe odstępy od krawędzi. No to machnijmy drugą procedurę:
procedure DrawBannerLabel(ACanvas: TCanvas; ARect: TRect; AColorA, AColorB: TColor; const AText: String);
var
Text: String;
TextWidth, TextLeft, TextTop: Integer;
begin
ACanvas.Font.Name := 'Gotham Black';
ACanvas.Font.Size := 19;
ACanvas.Brush.Style := bsClear;
Text := AText.Trim();
TextWidth := ACanvas.TextWidth(StringReplace(Text, '|', '', [rfReplaceAll]));
TextLeft := ARect.Left + (ARect.Width - TextWidth) div 2;
TextTop := ARect.Top + 3;
DrawTextStriped(ACanvas, TextLeft, TextTop, AColorA, AColorB, Text);
end;
Tutaj trochę magii. Nie możemy centrować surowego tekstu z parametru, dlatego że ten zawiera znaki markera, które nie będą renderowane na ekranie. Dlatego przed zmierzeniem szerokości tekstu, najpierw należy z niego usunąć te znaki – stąd użycie StringReplace
. Wywołanie przygotowanej procedury należy dodać do zdarzenia OnPaint
i tak samo jak wcześniej określić parametry, ale zamiast gołego tekstu, należy przekazać zawartość pola edycyjnego:
procedure TMainForm.CBannerPaintBoxPaint(ASender: TObject);
var
PaintBox: TPaintBox absolute ASender;
begin
DrawBannerBackground(PaintBox.Canvas, PaintBox.ClientRect);
DrawBannerLabel(PaintBox.Canvas, PaintBox.ClientRect, clWindowText, clHighlight, CBannerEdit.Text);
end;
To wszystko – teraz wystarczy wywołać Invalidate
paintboxa w zdarzeniu OnChange
pola edycyjnego i można cieszyć oko kontrolką pokazującą na żywo końcowy efekt. Normalnie technologia WYSIWYG. ;)
Dzięki wykorzystaniu tła systemowej kontrolki oraz kolorów zgodnych ze skórką, zawartość paintboxa zawsze będzie wyglądać dobrze, nieważne na jakim systemie i konfiguracji. Ba, nawet jeśli użytkownik zmieni schemat podczas działania programu, to jego okna zostaną odmalowane po takiej zmianie, a więc i komponenty dostosują swój wygląd do nowych ustawień.
Jak widać roboty niewiele, a efekt końcowy całkiem przyzwoity. Oczywiście to co opisałem wyżej do tylko wierzchołek góry lodowej – w swoim projekcie wykorzystuję znacznie bardziej zaawansowane renderowanie banera (tekst z ikonkami, opcjonalnie gradientowe tło), a także podobną technikę stosuję do renderowania podglądu profilu gracza, zawierającego najróżniejszy tekst, separator, flagę i ”naklejki” ze specjalnymi osiągnięciami. Natomiast systemowe kolory wykorzystywane są nie tylko do tekstu, ale też do barwienia półprzezroczystych grafik naklejek, a podczas wprowadzania danych, aktywny element jest renderowany w kolorze zaznaczenia. Ale to już inna bajka – więcej na jej temat już niebawem.
To co podałem wyżej powinno wystarczyć do prostych zastosowań, nakreślić sposób wykorzystania ”renderera” systemowych komponentów oraz skłonić do tworzenia programów desktopowych w taki sposób, aby zachować kompatybilność z ustawioną przez użytkownika skórką. ;)
#fpc #free-pascal #lazarus
Spójrz na to inaczej - ci, którzy do czegoś doszli w realnym życiu, mają lepsze zajęcia, niż siedzenie 24/7 na forum. Czyli ludzie z największą aktywnością to są po prostu przegrywy, których jedynym życiowym osiągnięciem jest duża ilość postów na 4P :P
@Pepe: to że jestem zalogowany przez 12h dziennie, wcale nie oznacza, że przez tyle czasu przeglądam forum. A tak naprawdę, gdyby policzyć, na faktycznym czytaniu i pisaniu na forum nie spędzam więcej niż godzinę dziennie. ;)
Niektórzy z Was naprawdę się przykładają do swojej roli (a przecież w tym czasie moglibyście tworzyć coś komercyjnego, lub cokolwiek)...
Jeśli o mnie chodzi to większość technicznych wpisów na blogu zawiera informacje i źródła wyciągnięte z opracowywanych projektów. Np. ostatnie kilka wpisów dotyczą bieżącego projektu, czyli narzędzia do Tetrisa (renderowanie, łatanie bugów kontrolki CheckListBox
, półprzezroczysty Splash Screen itp.).
Z chęcią bym napisał coś więcej, opisał implementację konkretnego edytorka (używanego w głównym projekcie, bo te są znacznie bardziej zaawansowane), ale trwało by to dość długo, a po drugie, musiałbym pisać na raty i porozbijać całość na kilka wpisów (bo te mają ograniczenie długości), co zjadłoby jeszcze więcej czasu. Doba musiała by mieć 30 godzin.
Pamiętacie mój dawny wpis na blogu, w którym opisałem kilka swoich przemyśleń na temat modernizacji składni Pascala? Traktował on m.in. o fuzji bloków kontrolnych, określeniu skoku iteratora pętli for
oraz o deklaracji zmiennych w dowolnym miejscu ciała podprogramu, na wzór najpopularniejszych języków programowania.
No i ostatnia propozycja, czyli punkt B., właśnie wchodzi w życie – dla stałych i zmiennych, ogólnych i dla pętli. ;)
Introducing Inline Variables in the Delphi Language
The coming 10.3 version of Delphi introduces a very handy feature to the language, local inline variables with local scope and type inference.
The Delphi language in 10.3 has a fairly core change in the way it allows far more flexibility in the declaration of local variables, their scope and lifetime. This is a change that breaks a key tenet of the original Pascal language, but offers a significant number of advantages, reducing unneeded code in several cases.
Jeśli o mnie chodzi, to jest to świetna wiadomość.
Przez chwilę miałem nadzieję na to, że może jednak będę miał możliwość używania nowych konstrukcji – w końcu FPC cały czas jest w pełni kompatybilny ze składnią Delphi. Niestety moja nadzieja gaśnie z każdą chwilą, ze względu na wypowiedzi ważnych graczy w tym wątku…
Wychodzi na to, że deweloperzy zmian nie doceniają, nie chcą implementować inline'owanych deklaracji zmiennych, a co gorsze (dla całego projektu, nie dla mnie), uznają tę nowość za dobry powód do zerwania kompatybilności z Delphi. Wolą zostać przy pradawnej, zardzewiałej składni oraz twierdzić, że jedyne słuszne i najlepsze rozwiązanie to to obecne. Taki beton, o który rozbija się w drobny mak większość sensownych pomysłów modernizacji składni. :/
Sam w dalszym ciągu uważam, że połączenie wewnętrznych deklaracji lokalnych zmiennych w połączeniu z inferencją typów to funkcjonalność, która powinna zostać dodana do składni już dawno temu i której mi ciągle cholernie brakuje.
#fpc #free-pascal #lazarus #delphi
Ogólnie nie jestem zwolennikiem deklaracji zmiennych gdzie się ma ochotę ale zrobienie wyjątku np dla pętli for byłoby świetnym rozwiązaniem for var i := 1 to 10 do writeln(i);
Koniecznie z wykorzystaniem inferencji i koniecznie dla obu typów pętli – for to/downto do
i for in
.
for var Item in Collection do // miodzio
Już nawet tym bym się od biedy zadowolił…
@furious programming:
Od dawna gierki sobie generowały tekstury czcionek z *.ttf ;)
https://nehe.gamedev.net/tutorial/freetype_fonts_in_opengl/24001/