furious programming
2017-10-12 01:21

Co jakiś czas ktoś – celowo lub przy okazji – pyta się na forum, czy referencja w Pascalu to wskaźnik, czy nie wskaźnik. Uściślijmy więc.

Tak, referencja instancji klasy to wskaźnik, co prawda traktowany jest w odmienny sposób, jednak to nie zmienia faktu bycia wskaźnikiem. Aby sprawdzić to w praktyce, wystarczy referencję zrzutować na pointer, a ten następnie z powrotem na referencję konkretnej klasy.

Przykład testowej aplikacji poniżej:

uses
  Classes;
var
  ListObj: TStringList;
  ListPtr: Pointer;
begin
  ListObj := TStringList.Create();
  try
    ListObj.Add('free pascal');
 
    ListPtr := Pointer(ListObj);      // przerobienie referencji na wskaźnik
    ListObj := TStringList(ListPtr);  // przerobienie wskaźnika na referencję
 
    Write(ListObj.Text);
  finally
    ListObj.Free();
  end;
end.

Dwa rzutowania i wszystko jasne. Miszung ze wskazaniami nie sprawi, że w linijce z wyświetleniem zawartości listy dostaniemy wyjątek.

Gdzieniegdzie, raczej w starych kodach pisanych np. w Delphi 7, można spotkać konwersję referencji na zwykłą liczbę, w celu jej przesłania w parametrze razem z komunikatem.

Druga sprawa – czasem ktoś wpada na pomysł, aby wykorzystać zwykłą procedurę jako zdarzenie jakiegoś obiektu. I też pojawiają się różne zdania na temat tego, czy da się, czy jednak się nie da. Otóż, bezpośrednio nie da się – zmienna zdarzeniowa nie jest pointerem, więc nie można do niej przypisać adresu zwykłej procedury. A czym jest? Nie jest to jeden wskaźnik, a dwa – jeden przechowuje adres kodu do wykonania, a drugi wskazuje na dodatkowe dane.

Aby móc użyć zwykłej procedury jako zdarzenia, należy przypisać adres tej procedury do pierwszego wskaźnika. A co z drugim, tym na dane? Cóż, można w nim przesłać adres czegokolwiek – wyślijmy więc referencję obiektu, aby dopełnić pierwszą część tego wpisu. ;)

Prosty przykład:

uses
  Classes;
 
  procedure OnListChange(ASender: TObject);  // zwykła procedura, zgodna z TNotifyEvent
  var
    List: TStringList absolute ASender;
  begin
    Write(List.Text);
  end;
 
var
  List: TStringList;
  Method: TMethod;  // TMethod reprezentuje wymaganą strukturę wskaźników
begin
  List := TStringList.Create();
  try
    Method.Code := @OnListChange;  // przypisanie adresu zwykłej procedury
    Method.Data := List;           // przypisanie referencji (ta podana zostanie w parametrze ASender)
 
    List.OnChange := TNotifyEvent(Method);  // ustawienie przygotowanego zdarzenia (rzutowanie konieczne)
    List.Add('free pascal');
  finally
    List.Free();
  end;
end.

Po wywołaniu metody Add, wartość jej parametru zostanie dodana do listy, po czym instancja odpali zdarzenie OnChange, o ile zostało ustawione. My to zrobiliśmy, więc zostanie wykonana procedurka OnListChange – na ekranie konsoli pojawi się zawartość listy, czyli w tym przypadku napis free pascal.

#free-pascal #lazarus #delphi

Azarien

Referencja to referencja. To czy referencja jest samym tylko wskaźnikiem, czy jakimś opakowanym, czy czymś więcej - to szczegół implementacyjny.

furious programming

Owszem, jednak aby wiedzieć z czym ma się do czynienia i co niestandardowego można z referencjami zrobić, warto zagłębić się w temat i podłubać. W razie kolejnego ciekawskiego, będę mógł podlinkować ten wpis, zamiast znów tłumaczyć to samo. :P

furious programming
2017-09-19 19:55

Generyki we Free Pascalu coraz bardziej zaczynają mnie denerwować – straszanie toporny jest ten mechanizm… Pierwsza wkurzająca rzecz to sposób używania zmiennych, przechowujących referencje do list generycznych.


Dla przykładu – chcemy użyć listy generycznej do przechowywania instancji poniższej klasy:

type
  TEntry = class(TObject)
  {..}
  public
    Data: Integer;
  end;

Teraz deklaracja zmiennej dla listy. W stylu Delphi – ale z użyciem dostępnego typu kontenera – było by to tak jak poniżej, ale wyrzuci błąd kompilacji (treść w komentarzu):

var
  Entries: TFPGObjectList<TEntry>;  // Error: Generics without specialization cannot be used as a type for a variable

No dobrze, dodajmy magiczne słówko spezialize:

var
  Entries: spezialize TFPGObjectList<TEntry>;

Taki kod zostanie poprawnie skompilowany, jednak nie mam zielonego pojęcia skąd mam teraz wydłubać konstruktor, aby utworzyć instancję takiej listy. Nie mam określonego typu danych (jawnej klasy), więc trzeba by jakiejś magii, może ze słówkiem specialize, może z generic, a może z czymś innym, jednak różne konfiguracje zawodzą.

Pewnym obejściem jest po prostu zadeklarowanie osobnego typu, z którego będzie możliwe wywołanie konstruktora:

type
  TEntries = specialize TFPGObjectList<TEntry>;
var
  Entries: TEntries;
begin
  Entries := TEntries.Create();
  {..}

No i fajnie – kod się kompiluje, listę da się utworzyć. Po problemie? Nie… :]


Tak utworzona klasa TEntries co prawda potrafi już przechowywać obiekty klasy TEntry, jednak dostęp do nich jest nieco utrudniony. Załóżmy, że chcemy uzyskać dostęp do pierwszego obiektu listy i np. wpisać dane do zmiennej TEntry.Data. Odpowiedni zapis wygląda tak:

Entries[0].Data := $FF; // lub Entries.Items[0].Data := $FF;

Kod jest poprawny, kompiluje się, działa. Gdzie jest problem? W domyślnej właściwości Items. W klasie TFPGObjectList zdefiniowana jest w taki sposób, że zwraca lub modyfikuje T, czyli nie wiadomo co:

property Items[Index: Integer]: T read Get write Put; default;

Kompilator nie widzi problemu i najwyraźniej podczas kompilacji pod to T podstawia sobie klasę, której obiekty moja lista przechowuje. Jednak mechanizm kompletowania kodu gubi się – po klepnięciu kropki po nawiasach z indeksem elementu, powinno pojawić się okienko completion box i coś podpowiedzieć, ale wyrzuca błąd:

Entries[0].  // Error: illegal qualifier . found

Zapewne mechanizm ten dalej widzi typ elementu jako T (no bo tak jest zdefiniowany w klasie bazowej), więc nie może nic podpowiedzieć, a że pisanie kodu bez funkcji kompletowania jest niewygodne, dlatego też trzeba to naprawić. Znów małe obejście – można nadpisać właściwość Items, konkretyzując typ na jakim ma operować:

type
  TEntries = class(spezialize TFPGObjectList<TEntry>)
  public
    property Items[AIndex: Integer]: TEntry read Get write Put; default;
  end;

Na szczęście można skorzystać z istniejących metod pełniących rolę akcesora i mutatora, czyli metod Get i Put, a całość ustawić jako właściwość domyślną, całkowicie przykrywając poprzedniczkę. Plus jest też taki, że w okienku do kompletowania kodu będzie sugerowało naszą (skonkretyzowaną) właściwość Items, a do tej bazowej nie będzie dawać dostępu.

Nazwa takiej właściwości może być inna – nie musi to być akurat Items.


No, w tym momencie da się mieć własną listę generyczną, da się utworzyć jej instancję, kod będzie się kompilował i kompletowanie kodu nie będzie się dławić. Mam nadzieję, że w przyszłości coś się w tej materii zmieni, bo nie jest to zbyt wygodne w obsłudze. Już za kilka tygodni opublikowana zostanie wersja 1.8 środowiska – zobaczymy co nowego się pojawi.

#free-pascal #lazarus

Azarien

Jednak mechanizm kompletowania kodu gubi się – no to jest już problem samego IDE, nie kompilatora… generyki w Delphi może i są lepsze, ale w FPC były pierwsze. A w {$MODE DELPHI} nie działają te delphiowe?

furious programming

@Azarien: w trybie DELPHI działa (https://ideone.com/XiOBmR), jednak to nie jest dla mnie rozwiązanie – chcę korzystać z trybu OBJFPC wszędzie. A przecież nie będę go zmieniał wszędzie tam, gdzie używam generyków.

furious programming
2017-09-13 21:00

Kolejna ciekawostka ze świata Lazarusa – czy wiedziałeś, że edytor kodu wyposażony jest w funkcję zaznaczania kolumnowego (jak w cmd) oraz w tzw. multi-caret? A no jest – zaznaczanie kolumnowe jest od dawna, a obsługa wielu kursorów od wersji 1.6 środowiska.


Standardowe (liniowe) zaznaczanie kodu to po prostu wciśnięcie LPM i przesuwanie myszą. Zaznaczanie kolumnowe aktywuje się poprzez wciśnięcie i trzymanie lewego klawisza Alt i tak jak wyżej – przesuwania myszą. Obsługa z poziomu klawiatury to kombinacja Shift+Alt i strzałki.


Multi-caret – aby dodać kolejny kursor do edytora, należy wcisnąć kombinację Shift+Ctrl i trzymając te klawisze, klikać w docelowe miejsca (w których chcemy mieć dodatkowe kursory). Po puszczeniu ww. klawiszy możemy coś dopisywać, nadpisywać lub kasować. Aby deaktywować dodatkowe kursory, wystarczy kliknąć w inne miejsce gołym LPM.

Więcej na temat tego pluginu można wyczytać tutaj – SynEdit MultiCaret plugin.

#lazarus

furious programming
2017-09-12 21:39

Poprzedni mój wpis na temat bazgrołów graficznych z poziomu kodu nawet się spodobał, więc postanowiłem umieścić nowy – w podobnej tematyce. Dziś pokażę sposób renderowania obrazu z otoczką. Sposób ten wykorzystuję we własnych kontrolkach – zwykłych przyciskach oraz w przyciskach zakładek. Również daje się "motywować", więc kolor obrazu i tła jak najbardziej możliwe są do określenia.

O ile malowanie tekstu z obramowaniem było małym oszustwem (wielokrotnym malowaniem ciemnego tekstu pod spodem), to w przypadku grafiki już tak łatwo nie będzie. Co prawda można – podobnie jak wcześniej – wiele razy namalować obrazek pod spodem, imitując tym samym otoczkę, jednak na pewno nie będzie to sposób efektywny. Dlatego też trzeba podejść do tematu nieco od innej strony.

Jednym z ciekawszych rozwiązań jest wielowarstwowe renderowanie. Gotowy obraz będzie wynikiem wypełnienia tła, przemalowania w locie grafiki wzorca, jego malowania na płótnie, oraz finalnie namalowania drugiej grafiki, zawierającej półprzezroczystą otoczkę.


Etap 1. Przygotowanie grafik

Najpierw należy się zająć grafikami. Pierwszy krok to przygotowanie grafiki wzorca. Aby móc w pełni obsługiwać kolorowe tła, wzorzec musi obsługiwać kanał alfa, dlatego też przygotujmy sobie 32-bitowy obraz PNG. Do jego stworzenia skorzystam z programu Inkscape. Wybierzmy obraz – ja skorzystam z loga 4p (bez tytułu):

0.png

W dowolnym programie do obróbki grafiki rastrowej (np. Paint.NET), przyciemniam ją maksymalnie:

1.png

Teraz otwieram Inkscape i przeciągając plik z obrazem, upuszczam go na dokumencie:

2.png

Aby stworzyć wektorowy kształt na jego podstawie, wystarczy obraz zaznaczyć i skorzystać z narzędzia Path\Trace Bitmap…. Domyślne ustawienia wystarczą – klikamy Ok i mamy wektorową odbitkę:

3.png

Obraz można było wcześniej przyciemnić, ale też można to zrobić w Inkscape – jak kto woli. Teraz czas na otoczkę – zaznaczamy obraz i korzystamy z opcji Path\Linked Offset. Rozciągamy obramowanie na zadaną grubość:

4.png

Obramowanie jest koloru czarnego, z ustawionym kanałem alfa na 85 – po to, aby otoczka zawsze przyciemniała kolor tła (w przeciwnym razie była by bezużyteczna). Kolejny krok to już eksport grafik do docelowych plików – najpierw wzorzec (wypełnienie). W tym celu ustawiam otoczkę na całkowicie przezroczystą i eksportuję całość do pliku:

5.png

Teraz otoczka – najpierw należy ją oddzielić od wypełnienia. Zaznaczamy ją, przywracamy poprzedni stan kanału alfa (tutaj: wartość 85) i używamy opcji Path\Combine – otoczka stanie się osobnym obiektem. Teraz zaznaczamy oba obiekty i korzystamy z opcji Path\Exclusion. W wyniku tych działań otrzymamy samo dziurawe obramowanie (dla podglądu rozsunąłem obiekty):

6.png

Aby wyeksportować samą otoczkę, można albo usunąć główny obiekt (ten czarny), albo ustawić mu maksymalną przezroczystość. Po wszystkim eksportujemy dziada do drugiego pliku:

7.png

W efekcie mamy dwa pliki – jeden z czarnym wzorcem, a drugi z samą przyciemniającą otoczką (oba w tym samym rozmiarze):

8.png


Etap 2: Programowanie

Obrazki są gotowe, więc czas na kod. Co prawda można już oba namalować (jeden nad drugim) jednak mamy tylko jeden kolor – czarny. Potrzebujemy więc metody, która najpierw pomaluje grafikę wzorca na zadany kolor, a dopiero potem namaluje ją na docelowym płótnie. Wydaje się skomplikowane, no bo jak wypełnić kolorem grafikę o niewiadomych kształtach, w dodatku z antialiasingiem? Nic prostszego – pomalować każdy jej piksel. ;)

Ale nie tak prędko – pomalować należy każdy piksel grafiki, nie ruszając kanału alfa. Dlatego też skorzystamy z poczciwego ScanLine i dobierzemy się do składowych każdego piksela. Do tego celu przydadzą się odpowiednie typy danych (ich deklaracja może nie być konieczna):

type
  TRGBTriple = packed record
    B, G, R: UInt8;
  end;
 
type
  TRGBQuadRec = packed record
    B, G, R, A: UInt8;
  end;
 
type
  PRGBQuadArr = ^TRGBQuadArr;
  TRGBQuadArr = packed array [0 .. MaxInt div SizeOf(TRGBQuadRec) - 1] of TRGBQuadRec;

Do właściwego namalowania wzorca – tak jak poprzednio – użyjemy helpera dla klasy TCanvas:

type
  TCanvasHelper = class helper for TCanvas
  public
    procedure DrawGraphicPattern(AX, AY: Integer; APattern: TPortableNetworkGraphic; AColor: TColor);
  end;
 
  procedure TCanvasHelper.DrawGraphicPattern(AX, AY: Integer; APattern: TPortableNetworkGraphic; AColor: TColor);
  var
    LLine: PRGBQuadArr;
    LLineIdx, LPixelIdx: Integer;
    LColor: TRGBTriple;
  begin
    RedGreenBlue(AColor, LColor.R, LColor.G, LColor.B);
    APattern.BeginUpdate();
 
    for LLineIdx := 0 to APattern.Height - 1 do
    begin
      LLine := APattern.ScanLine[LLineIdx];
 
      for LPixelIdx := 0 to APattern.Width - 1 do
      begin
        LLine^[LPixelIdx].R := LColor.R;
        LLine^[LPixelIdx].G := LColor.G;
        LLine^[LPixelIdx].B := LColor.B;
      end;
    end;
 
    APattern.EndUpdate();
    Self.Draw(AX, AY, APattern);
  end;

Zapewne nasuwa się pytanie – dlaczego operuję bezpośrednio na APattern, nie używając grafiki pomocniczej?

Odpowiedź jest prosta – bo mogę. Dopóki nie zmienia się wartości kanału alfa, obraz może być wielokrotnie przemalowywany na dowolne kolory i nie będzie to miało żadnych negatywnych skutków. Drugi plus jest taki, że brak kopiowania obrazu przyspieszy proces renderowania.

Przykładowe wywołanie metody (malowanie tła pomijam – jest takie samo jak w poprzednim wpisie, oliwkowe):

//FPattern: TPortableNetworkGraphic;
Canvas.DrawGraphicPattern(PosX, PosY, FPattern, clWhite);

Rezultat:

9.png

Teraz pozostaje jeszcze otoczka. Tu nie trzeba żadnych dodatków – standardowa metoda Canvas.Draw wystarczy. Małe przypomnienie – skoro obrazy są tego samego rozmiaru, muszą być namalowane w tej samej pozycji, aby obramowanie pasowało do wzorca. Po namalowaniu otoczki mamy wszystko gotowe:

10.png

Tu podany jest sposób działający niezależnie – najpierw malowany jest pattern w zadanym kolorze (jedna metoda), a następnie malowana jest otoczka (druga metoda). Nic nie stoi na przeszkodzie, aby napisać jedną metodę, która wykonywać będzie obie te czynności. Wystarczy drugi parametr typu TPortableNetworkGraphic:

procedure DrawGraphicPattern(AX, AY: Integer; APattern, AOutline: TPortableNetworkGraphic; AColor: TColor);

oraz kod malujący otoczkę, umieszczony na końcu:

  Self.Draw(AX, AY, APattern);
  Self.Draw(AX, AY, AOutline);
end;

Sposób ten nie jest wrażliwy na kolor tła, więc można używać dowolnych kolorów, bez ingerencji w grafiki czy kod renderujący:

11.png


PS: Do ikonki z otoczką najlepiej będzie pasować tekst z otoczką. :]

12.png

#free-pascal #lazarus

czysteskarpety

panie ale to roboty jest, jednak zostanę przy png :)

furious programming

@kate87: jak będę miał coś ciekawego do pokazania to na pewno o tym napiszę. :]

@czysteskarpety: ale z czym? Wbrew pozorom, przygotowanie tych dwóch grafik zajmuje mi – komuś niezbyt ogarniającemu grafikę – dosłownie dwie minuty. Natomiast kod metody malującej pisze się raz – też nie dłużej niż pięć minut.

W swoim projekcie wykorzystuję taką metodę, jednak w ciekawszy sposób. Przycisk posiada nie dwie, a pięć grafik (w postaci zgrupowanych właściwości), w kolejności są to:

  • Normal – dla normalnego stanu,
  • Hover – po najechaniu myszą,
  • Inactive – grafika przeznaczona dla specjalnego trybu, w którym formularz wyświetlany jest w odcieniach szarości,
  • Disabled – dla kontrolki zablokowanej (Enabled na False),
  • Pattern – wzorzec (przeznaczenie jak we wpisie wyżej).

Do tego jest dodatkowa właściwość – ImageCombined typu Boolean – której stan określa sposób renderowania obrazu przycisku.

Jeśli ustawiona jest na False, kontrolka maluje jedynie tło oraz odpowiedni obrazek (któryś z czterech pierwszych, w zależności od stanu). A jeśli jest na True to wypełnia tło, maluje Pattern w zadanym kolorze (jaśniejszym niż tło lub białym, w zależności od ”hover”, a także w kolorze lub odcieniach szarości, w zależności od właściwości InterfaceActive), a następnie maluje któryś z pierwszych czterech obrazów, w których należy umieścić przyciemniające obramowanie – mocno przyciemniające dla Normal i Hover, a słabiej dla Inactive i Disabled.

Dzięki temu na odblokowanym formularzu przycisk malowany jest w kolorach, a na zablokowanym w odcieniach szarości (wszystkie pozostałe kontrolki również). I wygląda to wyśmienicie. :]

furious programming
2017-09-08 02:43

Ostatnio dość dużo dłubię w grafice – i przy jej tworzeniu, i przy programowaniu.

Jedną z takich dłubanin było malowanie tekstu z obramowaniem. Co prawda istnieje natywny dla systemu sposób takiego dekorowania tekstu (BeginPath i EndPath), jednak ma on pewne ograniczenia. Niewątpliwie jednym z najbardziej denerwujących jest brak wsparcia antialiasingu. Szkoda, bo dociąganie kobylastej biblioteki graficznej (lub kopiowanie z niej tego co interesujące) tylko po to, by namalować kilka napisów, nie brzmi zbyt rozsądnie.

Rozwiązanie znalazłem metodą prób i błędów, i choć nietypowe, może nieco dziwne, to zdaje egzamin. Chodzi o to, aby malować tekst bez tła. Przypisać kolor obramowania do Font.Color i namalować cztery razy docelowy tekst – odpowiednio o piksel wyżej i w lewo, wyżej i w prawo, niżej w lewo i niżej w prawo. Po tym wszystkim przywrócić ustawiony wcześniej kolor i namalować docelowy tekst w zadanej pozycji (już bez przesunięcia).


Załóżmy, że chcemy namalować na oliwkowym tle biały tekst z ciemniejszym obramowaniem. Najpierw wypełniamy obszar pod spodem standardową metodą, np. Canvas.FillRect:

Canvas.Brush.Color := $0034765E;
Canvas.FillRect(ClientRect);

Co da poniższy efekt:

0.png

Następnie ustawiamy właściwość Font:

Canvas.Font.Name := 'Ubuntu';
Canvas.Font.Size := 10;
Canvas.Font.Color := clWhite;
Canvas.Font.Style := [fsBold];

wpisujemy kolor obramowania do właściwości Pen (można go przekazać w parametrze – jak kto woli):

Canvas.Pen.Color := $00245241;

i na koniec wołamy metodę malującą tekst z obramowaniem:

Canvas.DrawTextOutlined(PosX, PosY, '4programmers.net');

Metoda działa w dwóch krokach – najpierw maluje cztery napisy z przesunięciem, tworząc ciemniejsze wypełnienie:

1.png

a na koniec maluje tekst w zadanym kolorze na wierzchu. Gotowy efekt poniżej:

2.png

Tekst w powiększeniu wygląda następująco:

3.png

Teraz kwestia kodu – w klasie TCanvas nie istnieje metoda DrawTextOutlined – stworzyłem dla niej helper. Cały kod niżej:

type
  TCanvasHelper = class helper for TCanvas
  public
    procedure DrawTextOutlined(AX, AY: Integer; const AText: String);
  end;
 
  procedure TCanvasHelper.DrawTextOutlined(AX, AY: Integer; const AText: String);
  var
    LOldBrushStyle: TBrushStyle;
    LOldFontColor: TColor;
  begin
    LOldBrushStyle := Self.Brush.Style;
    LOldFontColor := Self.Font.Color;
 
    Self.Brush.Style := bsClear;
    Self.Font.Color := Self.Pen.Color;
 
    Self.TextOut(AX - 1, AY - 1, AText);
    Self.TextOut(AX + 1, AY - 1, AText);
    Self.TextOut(AX - 1, AY + 1, AText);
    Self.TextOut(AX + 1, AY + 1, AText);
 
    Self.Font.Color := LOldFontColor;
    Self.TextOut(AX, AY, AText);
 
    Self.Brush.Style := LOldBrushStyle;
  end;

Zaletą jest to, że w ten sposób można namalować tekst zarówno o braku dodatkowych atrybutów, jak również tekst wytłuszczony, pochylony, podkreślony czy przekreślony. Drugą zaletą jest automatyczne dostosowanie się do systemowych ustawień antialiasingu – jeśli usługa rozmywania tekstu jest wyłączona to obramowanie będzie twarde, ale w dalszym ciągu widoczne.

Niestety, ale metoda ta ma też wady – nie nadaje się do malowania grubszego obramowania (przesunięcia większego niż 1px), co przy większych literach jest raczej konieczne. O ile ogólnie jest to możliwe, to wiązałoby się z malowaniem tekstu dla obramowania o wiele więcej razy, co może negatywnie odbić się na wydajności renderowania. Mimo wszystko do prostych zastosowań wystarczy – mnie wystarczyło.


W bardzo podobny sposób możliwe jest też malowanie tekstu z cieniem pod spodem. Różnica polega na tym, że tekst cienia wystarczy namalować raz, zamiast cztery razy. To jak daleko ma się znajdować cień i jak duży ma być, można zdefiniować w dodatkowych parametrach. Idąc za ciosem, niżej przykład metody malującej taki tekst:

type
  TCanvasHelper = class helper for TCanvas
  public
    procedure DrawTextShadowed(AX, AY, AOffsetX, AOffsetY: Integer; const AText: String);
  end;
 
  procedure TCanvasHelper.DrawTextShadowed(AX, AY, AOffsetX, AOffsetY: Integer; const AText: String);
  var
    LOldBrushStyle: TBrushStyle;
    LOldFontColor: TColor;
  begin
    LOldBrushStyle := Self.Brush.Style;
    LOldFontColor := Self.Font.Color;
 
    Self.Brush.Style := bsClear;
 
    Self.Font.Color := Self.Pen.Color;
    Self.TextOut(AX + AOffsetX, AY + AOffsetY, AText);
 
    Self.Font.Color := LOldFontColor;
    Self.TextOut(AX, AY, AText);
 
    Self.Brush.Style := LOldBrushStyle;
  end;

I znów – ustawiamy font oraz kolor dodatkowy dla cienia (przykładowo tak jak wcześniej) i wołamy metodę:

Canvas.DrawTextShadowed(PosX, PosY, 2, 2, '4programmers.net');

Efekt działania metody poniżej:

4.png

Co w powiększeniu przedstawia się następująco:

5.png

To nie wszystko – przykład zawiera cień o takim samym rozmiarze jak tekst właściwy, jednak nic nie stoi na przeszkodzie, aby cień był większy lub mniejszy, sprawiając wrażenie głębi (im wizualnie głębiej ma być cień, tym większy i z większym offsetem powinien być malowany).

Można się tym bawić bez końca, więc na cieniu zakończmy ten wpis. ;)

#free-pascal #lazarus

Azarien

szybkie góglanie wykazuje, że ludzie nie mają zbyt pochlebnego zdania na temat wydajności CopyRect, zwłaszcza w połączeniu z przezroczystością. jeśli to na tym tracisz wydajność, spróbowałbym czegoś innego np. funkcji WinApi AlphaBlend (nie wiem jak z jej wydajnością) albo czegoś o czym wiadomo że jest wydajne (Direct3D, OpenGL, czy choćby starego DirectDraw)

furious programming

Porzuciłem ten moduł właśnie ze względu na konieczne modyfikacje i optymalizacje – za dużo z tym roboty, a funkcjonalność zbyt mała. Na pewno CopyRect nie jest najlepszym rozwiązaniem, jednak zależało mi na tym, aby nie babrać się nadmiernie w winapi i nie dociągać dodatkowych bibliotek.

Teraz to nawet nie mam na czym sprawdzić tego AlphaBlend – pliki fontów usunąłem dawno temu, łącznie z konsolowym narzędziem do ich generowania. Pozostanie więc ciekawostką i przestrogą, aby nie być „mądrzejszym od telewizora”.

furious programming
2017-08-16 23:21

Wpis na temat ciekawostek ze świata Lazarusa.

Czy wiedziałeś, że składnia Free Pascal posiada wbudowany operator potęgowania? Operator ten opisuje fraza ** i można go bez problemu przeciążyć. Dla zwykłych intów zapis funkcji dokonującej potęgowania może wyglądać np. tak (bez używania zawartości modułu Math):

{$INLINE ON}
 
operator **(const ABase, AExponent: Integer): Integer; inline;
begin
  Result := Trunc(Exp(Ln(ABase) * AExponent));
end;

Zdefiniowanie funkcji dla tego operatora umożliwi potęgowanie w poniższy sposób:

Number := 2 ** 16;  // w zmiennej wygląduje liczba 65536

I teraz WTF – póki co operator ten nie posiada przyporządkowanej domyślnej funkcji z biblioteki standardowej, więc aby móc go użyć do obliczeń, należy samemu ten operator przeciążyć… Wsparcie tego operatora zapewne wprowadzono stosunkowo niedawno i obstawiam, że zapomnieli o uzupełnieniu stdliba. :D

PS: Biorę jeszcze pod uwagę celowość braku takich funkcji, jednak nie wiem co tym celem miałoby być.

#Lazarus

furious programming

Dla zainteresowanych tematyką Lazarusa dodam, że już niebawem pojawi się nowa wersja środowiska (o numerze 1.8) oraz kolejna odsłona FPC (numer 3.0.4). Release candidate można pobrać z repo i potestować.

furious programming
2017-08-13 23:12

Dziś obchodzę jubileusz rekompilacji Lazarusa po raz 512, w celu zaktualizowania w środowisku swojej paczki komponentów dla bieżącego projektu. Dobrze, że rekompilacja całego IDE jest wymagana tylko wtedy, gdy wprowadzone zmiany dotyczą designera – w przeciwnym razie, numer rebuilda sięgnąłby spokojnie 2048. :D

#Lazarus

Spine

Jak Ty to liczyłeś :D ? Automatycznie jakoś, czy notowałeś?

furious programming

Numer builda zwiększam ręcznie (to dwa kliknięcia) przy każdej reinstalacji paczki. Dzięki temu np. wiem, że rekompilacja środowiska zabrała mi z życia łącznie już ponad cztery godziny (licząc pół minuty na każdą). :]

furious programming
2017-04-06 23:02

Ostatnio zaktualizowałem sobie Lazarusa z wersji 1.6.2 (FPC 3.0.0) do najnowszej wersji 1.6.4 (FPC 3.0.2) i dostałem pierdylion błędów podczas próby rekompilacji swojego dość dużego projektu. Po wielu godzinach dłubania, niestety porzuciłem to IDE i wróciłem do poprzedniej wersji środowiska - w niej wszystko kompilowało się bezbłędnie. Dziś postanowiłem znów spróbować, albowiem zależy mi na najnowszej wersji biblioteki standardowej i komponentów. Nie żeby tym razem było inaczej - po zainstalowaniu środowiska, projekt nadal nie daje się skompilować. Jednak tym razem postanowiłem nie poddawać się i zmusić cały kod do poprawnego kompilowania.


Problem nr 1 - własne helpery dla typów prostych

W owym projekcie, posiadam paczkę z kilkunastoma komponentami. Znajdują się w niej również moduły ogólnego przeznaczenia - z zestawem stałych, typów, procedur i funkcji, z których korzystają wszystkie komponenty. I mam sobie moduł o nazwie HomeHelpers, w którym to zdefiniowanych jest naście helperów dla typów prostych, struktur i klas. Moduł ten kompiluje się bez zająknięcia, jednak błędy lecą we wszystkich modułach, w których komponenty korzystają z metod tychże helperów. Przy każdym wywołaniu metody dowolnego mojego helpera, kompilator wyrzuca błąd illegal qualifier. To bardzo dziwne, bo moduł z helperami jest na liście uses.

Okazuje się, że twórcy środowiska postanowili wzbogacić bibliotekę standardową o solidną kupę helperów właśnie dla typów prostych. Znajdują się one w module SysUtils, a dokładniej w includowanym pliku syshelph.inc. Pomyślałem, że skoro są już helpery do typów, do których sam je dorobiłem, to system moich nie bierze pod uwagę. I tak faktycznie było - na liście uses, moduł HomeHelpers był wcześniej od SysUtils, więc kompilator wybierał helpery z tego drugiego. Wystarczyło zmienić kolejność modułów, tak aby mój był po SysUtils i kod elegancko się kompiluje.

Ale to nie wszystko - mogę teraz korzystać z metod własnych helperów, jednak te wbudowane nie są dostępne. Powód jest prosty - wcześniej nie istniały wbudowane helpery do typów prostych, więc moje nie miały z czego dziedziczyć (tak, dziedziczenie również obowiązuje i w tym przypadku). Teraz bazowe helpery istnieją, więc moje muszą dziedziczyć z tych wbudowanych, aby można było korzystać z zawartości moich i wbudowanych helperów. Tak więc należało po prostu zmodyfikować nagłówki własnych.


Problem nr 2 - drzewka TreeStructInfo w komponentach

Drugim, dość solidnym WTF były błędy związane z ładowaniem zawartości plików .lfm do pamięci, zarówno podczas otwierania projektu w środowisku, jak i podczas rozruchu skompilowanej aplikacji. Niektóre komponenty z mojej paczki używają drzew TreeStructInfo do przechowywania drzewek danych. Głównym użytkownikiem ich jest kontrolka klasy TFormatLabel, która używa takich drzewek do ustawień hintów (czas oczekiwania na pojawienie się dymku, czas do jego schowania, tytuł, treść itd.) dla linków. No i jest problem - poczas ładowania drzewka z zasobów, dostawałem wyjątek o treści end tree line not found. Wyjątek ten generuje moja biblioteka do obsługi tych plików. Wygląda na to, że drzewo, które komponent otrzymuje do załadowania, jest niekompletne.

Komponent ten posiada upublicznioną właściwość, za pomocą której możliwe jest stworzenie drzewka za pośrednictwem okienka Inspektora Obiektów. W jego konstruktorze tworzony jest obiekt klasy TStringList, który takie drzewko przechowuje. W klasie tej znajduje się też metoda uzupełniająca tę listę o puste drzewko, aby parser TreeStructInfo nigdy nie dostał pustej listy. Wygląda ona dość niepozornie:

procedure TFormatLabel.SetDefaultLinkTipsData();
begin
  FLinksTipsData.Clear();
  FLinksTipsData.AddStrings(['treestructinfo "2.0"', 'end tree']);
end;

Nic nadzwyczajnego - wyczyszczenie bieżącej zawartości listy i dodanie dwóch linijek z zapisem pustego drzewa. Po wykonaniu tego kodu, klasa TStringList wywołuje swoje zdarzenie OnChange, pod które podpiętą mam swoją metodę - dzięki temu komponent wie, kiedy drzewko z właściwości zostało zmodyfikowane i może sobie przebudować mapkę ustawień hintów. Ale nawet ten kod powoduje wyjątek end tree line not found, choć jak byk linijka zakańczająca ciało drzewa jest dodawana. Swoją metodę podłączoną pod zdarzenie TStringList.OnChange nieco zmodyfikowałem, aby móc podglądnąć zawartość drzewka (w oknie Watches rzecz jasna), jakie dostaje klasa budująca z niego mapkę. Ku mojemu zdziwieniu, faktycznie drzewo nie zawiera linii end tree, więc parser prawidłowo rzuca wyjątek o jej braku.

I tu niespodzianka. W poprzedniej wersji środowiska i jego bibliotek, powyższy kod był na swój sposób atomowy - generował tylko jedno wywołanie OnChange, po wywołaniu metody AddStrings. W najnowszej wersji, AddStrings woła OnChange po dodaniu każdej linii z osobna. Pierwsze wywołanie tego zdarzenia wykonywane jest po dodaniu linijki treestructinfo "2.0" - klasa budująca mapkę dostaje niekompletne drzewo, co rozkłada parser, czego wynikiem jest nawet brak możliwości pokazania formularza w designerze, już nie mówiąc o odpaleniu skompilowanej aplikacji.

Rozwiązanie znów banalne - objąć ten nieszczęsny kod metodami blokującymi, czyli BeginUpdate i EndUpdate. Po kompilacji wszystko działa w jak najlepszym porządku. Skoro wcześniej działało, a teraz nie działa, twórcy musieli coś pozmieniać w klasie TStrings, że działa inaczej. Nieważne co zmienili - najważniejsze, że i ten problem udało się rozwiązać.


Mam sporego pecha, jeśli chodzi o trzecią wersję FPC... Kiedy zaktualizowałem kompilator z wersji bodajże 2.6.4 do 3.0.0, rozwaliło mi bibliotekę do TreeStructInfo - zmieniono kodowanie łańcuchów typu UTF8String, przez co musiałem wszystkie wymienić na zwykłe String (równoważne z AnsiString). Teraz update do 3.0.2 i znów rozwałka - tym razem helpery i listy w kontrolkach.

Strach pomyśleć co będzie przy kolejnej wersji :]

#lazarus #fpc

Azarien

Kurczę, takie zmiany to się robi przy przeskoku z 3.0 na 4.0 na przykład, a nie z 3.0.0 na 3.0.2 :-(

furious programming

@Azarien: dobrze że znalazłem wbudowane helpery, bo tak to bym pewnie twierdził, że kompilator zepsuli. Choć tak szczerze mówiąc, znalazłem je zupełnie przez przypadek - przy poprzedniej próbie nie zdawałem sobie sprawy z ich istnienia. Miałem trochę szczęścia w nieszczęściu.

Wczoraj próbowałem poszukać informacji na temat nowości w wersji 3.0.2, ale nic nie znalazłem na ten temat. Jedyne co o helperach dla typów prostych znalazłem to informację, że od wersji 3.0.0 w ogóle da się je tworzyć, jednak nic na temat dodania takowych do biblioteki standardowej.

furious programming
2016-05-08 18:39

Jak niektórzy już wiedzą z dedykowanego wątku, w poprzednich kilku dniach wprowadzonych zostało kilka zmian i ulepszeń w projekcie TreeStructInfo; Zmiany te dotyczą przede wszystkim funkcjonalności formatu - od tej pory linkowanie zewnętrznych plików i budowanie w ten sposób wieloplikowych systemów konfiguracyjnych nie będzie możliwe; Linkowanie jest procesem kłopotliwym, zagmatwanym i bardzo ciężkim do zabezpieczenia, dlatego też nie będzie już dłużej wspierane;

Z racji tak znaczącej zmiany, specyfikacja formatu została poprawiona, a także nieco odświeżona, aby była bardziej czytelna i zrozumiała; Zmiany dotknęły także biblioteki do obsługi plików konfiguracyjnych, co stało się bodźcem do opublikowania kolejnej wersji API, oznaczonej wersją 2.0 beta 3; Nowa wersja biblioteki możliwa jest do pobrania zarówno ze strony projektu, jak i z repozytorium w GitHub; Modyfikacja kodu źródłowego biblioteki przyczyniło się także do uzupełnienia dokumentacji API oraz tutorialu; Przy okazji Google sprawił mi niespodziankę i zgrupował wyniki w wyszukiwarce;

Wiem, że projekt formatu TreeStructInfo jest póki co mocno hermetyczny, jednak mam nadzieję, że ciągły rozwój projektu zachęci innych do przygotowania API w innych językach; Korzystając ze wskazówek użytkowników doszedłem do ostatecznej formy, dzięki czemu format w końcu nie jest zależny ani od języków programowania, ani od platform programowych; Funkcjonalność formatu w wersji 2.0 nie będzie już modyfikowana i nie ma już takiej potrzeby;

Tak więc w końcu jestem w pełni zadowolony z samego formatu i będę mógł się teraz skupić na wydaniu stabilnej wersji biblioteki.

#treestructinfo #lazarus

furious programming
2016-04-27 16:31

Ten nowy silnik unikodowy w FPC 3.0.0 rzekomo lepszy (jeszcze nie zdążyłem się przekonać), jednak potrafi nastręczyć kłopotów... Wszystko za sprawą nowej składni dla deklaracji typów łańcuchów znaków; Alias UTF8String wcześniej posiadał taką deklarację:

type UTF8String = type AnsiString;

a teraz taką:

type UTF8String = type AnsiString(CP_UTF8);

Jak widać przyjmuje w nawiasach dodatkowy pseudo-argument, określający kodowanie; I teraz jeżeli w projekcie używało się tego typu, wszelkie literały zawierające polskie znaki diakrytyczne (i pewnie inne kilkubajtowe) będą nieprawidłowo widziane przez funkcję UTF8CharacterToUnicode; Dzięki temu mój formatowalny labelek szlag trafił :)

Rozwiązaniem jest oczywiście nieużywanie tego aliasu, a skorzystanie z poczciwego typu String; Główny projekt już przeportowany, teraz pozostało zaktualizować bibliotekę TreeStructInfo.

#lazarus #fpc

Azarien

@furious programming: Właśnie sprawdziłem jak to działa na najnowszym Lazarusie (prosto z repo) z najnowszym FPC (takoż). Póki używa się samego LCL, to wystarczy zwykłe string i działa po prostu. A do funkcji WinAPI takich jak MessageBoxW wystarczy rzutowanie pwidechar(unicodestring(napis)). I podejrzewam, że takie rozwiązanie jest też odporne na wersję Lazarusa.

furious programming

@Azarien: To chyba będzie najbezpieczniejsze rozwiązanie, bez konieczności używania kilku typów danych dla łańcuchów.