Dziwna rzecz z tym czasem, niby jest a ciągle go brak. Niektórzy mówią, że wcale nie istnieje, że jest tylko abstrakcyjną wielkością umożliwiającą obliczenia fizyczne, coś jak zero w matematyce, które jest zerem, czyli brakiem czegoś, a jednak przewija się na każdym kroku.
Inni znowu twierdzą, że jest tylko tu i teraz, bez przyszłości ani przeszłości. Ale ci najdziwniejsi mówią, że nie tylko przeszłość ma wpływ na przyszłość ale także, że to co się jeszcze nie wydarzyło na to co już się stało…

Niestety nie rozstrzygniemy tego tutaj, możemy natomiast mierzyć to co wszyscy nazywamy upływem czasu, bez względu na to czy istnieje czy nie. W związku z tym napiszemy sobie skrypt, który będzie nam rysował wektorowy zegar, będzie to lepsza wersja systemowego Clocka.

Ktoś może zapytać – po kiego skoro już jest? Do głowy przychodzi mi co najmniej kilka powodów. Po pierwsze, będę mógł sobie go odpalić na którymkolwiek systemie, czy windzie, czy mac osie, a nawet andku i na iosie. Po drugie dodam kilka rzeczy których nie ma oryginał. Poza tym czuję jakiś dziwny sentyment do tego „brzydala”. Kiedyś nawet zrobiłem sobie wersję „namacalną” i od tej pory zawsze mierzy mi upływ czasu stojąc na półce powyżej.
W tej części będzie sporo nowych rzeczy, ale najpierw chciałem podziękować teh_KaiNowi za pomoc, bo bez niego by się nie udało. Dotarcie tu gdzie jestem z budową tego zegara to była ciężka przeprawa, bo gdy już myślałem że mam wszystko i będzie już z górki to zawsze pojawiała się jakaś niby pierdoła, którą musiałem jakoś poprawić albo obejść, używając swoich (wciąż nader skromnych) umiejętności w pisaniu skryptów Hollywoodowych. No ale jakoś się udało, ufff.

Niestety ze względu na to, że „Amiga NG” będzie teraz drukowana, to w tej części skupię się jedynie na stworzeniu samego zegara, bez dodatków, które sobie wymyśliłem. Jedyną nowością będzie to, że zegar nie będzie miał tła w postaci okienka, nie będzie tam nic. Chciałem sprawdzić czy możliwe w Hollywood zrobić taki zegarek, jakie choćby są w pakiecie Enhancera dla AmigaOS 4.1.

Zrzut 1

No dobra, żeby zrobić takie przeźroczyste tło, musimy zastosować pewien trik, którego nauczył mnie jPV – autor różnych programów zaczynających się na RNO -comics, -anim, Desktop widgets i pewnie wielu innych. Ponieważ Hollywood potrzebuje tła żeby móc cokolwiek wyświetlić i określić rozmiar okna, musimy sobie stworzyć najpierw tarczę zegara, która będzie naszym tłem i zarazem określi jego rozmiary, dzięki temu możemy pominąć atrybuty width i height komendy preprocesora @DISPLAY.

Nie będę tutaj opisywał skryptu linia po linii, bo część rzeczy jest już Wam znana, a poza tym starałem się dokładnie wszystko skomentować w kodzie; opiszę pokrótce nowe funkcje i (ich) atrybuty.

Nowe atrybuty funkcji preprocesora @DISPLAY

Hidden = True
Powoduje że okno naszego programu nie pojawi się do póki nie użyjemy funkcji OpenDisplay(ID). Przyczyną dla której używamy Hidden jest fakt, że najpierw nasz program sam musi stworzyć swoje tło i dopiero wtedy będziemy mieli co pokazywać.

NoModeSwitch = True uniemożliwia przełączanie programu z trybu okienkowego na pełnoekranowy. W końcu ma to być zegarek na blacie WB.

ScaleMode = #SCALEMODE_AUTO
Automatyczny wybór sposobu skalowania zawartości okna, drugą opcją która mamy to #SCALEMODE_LAYER. Podobno daje lepszą jakość skalowania, aczkolwiek nie jestem w stanie tego stwierdzić, bo gdy użyłem tej opcji to przy zmianie wielkości okna następuje zawieszenie systemu. Podobnie wygląda sytuacja gdy pominiemy ten atrybut, bo SCALEMODE_AUTO to ustawienie domyślne, więc można by go pominąć.
Jest jeszcze opcja #SCALEMODE_NONE, którą ustawiamy gdy nie chcemy skalować naszego okna.

Sizeable = True
Włącza skalowanie okna, domyślnie możemy uchwycić dolny prawy róg, podobnie jak w okienkach systemowych. Ale w naszym wypadku to niemożliwe, bo okienko naszego zegara jest w pełni przeźroczyste, co powoduje, że nie możemy go kliknąć.

SizeRegion
Na początku jedna uwaga, oszczędzi Wam rwania włosów z głowy, jak było w moim wypadku, a mianowicie atrybut ten oczekuje listy tabeli, nawet jeśli chcemy mieć tylko jeden obszar za który możemy przeciągać nasze okno, musimy zamknąć nasze tagi podwójnymi nawiasami klamrowymi {{ }}.

Atrybut ten oczekuje następujących wartości: SizeRegion={{Type, X, Y, Width, Height}}
jako Type podajemy #BOX, jedyny akceptowany rodzaj, nie ma tu żadnego wyboru. X i Y to współrzędne gdzie ma się zacząć obszar rozciągania, a Width i Height to jego szerokość i wysokość.

To wszystkie nowe rzeczy jeśli chodzi o preprocesor.

Następna funkcja to EscapeQuit(True), powoduje zakończenie działania programu przez naciśnięcie klawisza ESCape.
Zanim przejdę dalej, wspomnę tylko, że jeśli chcecie możecie zassać kolorowanie składni dla edytora Annotate, plik jest dostępny w tym samym miejscu gdzie reszta plików szkółki.

Następny element skryptu to zmienne globalne, które zadeklarowałem żeby móc ich używać w kilku miejscach, jednocześnie nadając im nazwy które będą znaczące. Ogólnie nie zalecane jest używanie zmiennych globalnych, lepiej używać lokalnych, bo są „lżejsze”, ale nasz skrypt nie będzie bardzo wymagający więc tym razem możemy sobie na nie pozwolić.

z_Radius = 225
z_CX = 250
z_CY = 250

Pierwsza zmienna określa odległość punktów minut i godzin od środka zegara, dwie pozostałe wyznaczają jego środek. Przedrostki „z_” – jak zmienna – wymyśliłem sobie sam żeby łatwiej mi było odnajdywać je w kodzie; żeby dodatkowo sobie ułatwić życie, dodałem ich kolorowanie (Zrzut 2).

Zrzut 2

Teraz w końcu narysujmy tarczę zegara. Jak już wspomniałem, zegar będzie wektorowy, nie użyjemy żadnych plików zewnętrznych z grafiką, wszystko stworzymy w skrypcie.
Żeby móc stworzyć jakikolwiek obiekt wektorowy musimy zdefiniować najpierw rodzaj jego wypełnienia (albo jego brak, czyli same linie krawędzi), możemy też określić czy chcemy wygładzanie, albo by nasze figury rzucały cień. Służą do tego funkcje z biblioteki DRAW (Draw library).

SetFillStyle(#FILLCOLOR) powoduje użycie koloru do wypełnienia, nie definiuje jaki ma to być kolor i czy ma być przeźroczysty. To określamy w innym miejscu.
SetFillStyle(#FILLNONE, 1) powoduje rysowanie jedynie krawędzi wektorów linią o grubości jednego piksela. Możemy jeszcze użyć #FILLGRADIENT i #FILLTEXTURE, ale nie będę ich tu opisywał bo nie będą potrzebne, możecie sami poczytać w manualu.

SetFormStyle(#ANTIALIAS) wygładza krawędzie, do wyboru mamy jeszcze:
SetFormStyle(#SHADOW) – cienie,
SetFormStyle(#EDGE) – dodaje obwódkę wokół wektora
SetFormStyle(#NORMAL) – przywracanie ustawienia surowego, czyli bez wygładzania, cienia i obwódki.
Możemy użyć kilku jednocześnie, ale musimy to zrobić za każdym razem przywołując funkcję SetFormStyle, to znaczy nie możemy napisać:
SetFormStyle(#ANTIALIAS, #EDGE).

Jak już określiliśmy jak mają wyglądać nasze wektory to zacznijmy je rysować. Żeby stworzyć jakikolwiek wektor musimy przydzielić mu najpierw ścieżkę, służy do tego funkcja:

StartPath(1)

Przyjmuje ona tylko jeden argument i jest to identyfikator jaki mu nadamy. Jak już mamy „pojemnik” na nasz wektor to możemy zacząć dodawać do niego krzywe. W naszym wypadku to będzie okręg, stworzymy go za pomocą funkcji:

AddCircleToPath(1, z_CX, z_CY, 250)

Funkcja to wymaga czterech argumentów, pierwszy to IDentyfikator ścieżki w jakiej chcemy stworzyć wektora, w tym wypadku 1, drugi i trzeci to współrzędne środka okręgu, u nas to z_CX i z_CY czyli 250 i 250 pikseli. Czwarty argument to promień, w tym wypadku 250 pikseli.
Jak już skończyliśmy dodawać krzywe do ścieżki to musimy ją zamknąć, w innym wypadku jeśli zaczęlibyśmy dodawać nowe punkty zostałyby one połączone z krzywą zdefiniowaną poprzednio, i mielibyśmy brzydką linie łączącą je ze sobą. Służy do tego funkcja:

ClosePath(ID)

Kilka słów na temat jak system ścieżek i krzywych (wektorów) działa.
Musimy pamiętać, że o ile nie użyjemy odpowiednich funkcji, to wszystko będzie rysowane od punktu 0,0 czyli od górnego lewego rogu. Dlatego też podaliśmy środek naszego okręgu jako z_CX i z_CY, w innym wypadku mielibyśmy widoczną jedynie dolną prawą ćwiartkę widoczną. Poza tym jeśli zdefiniujecie kilka krzywych w obrębie jednej ścieżki to wszystkie będą miały taki sam kolor, nie da się inaczej niż tworząc nową ścieżkę używając StartPath().

Ponad to rysując nowe krzywe w obrębie tej samej ścieżki będą one się zaczynały w punkcie gdzie skończyła się poprzednia, podając współrzędne nowego wektora będą one wskazywały na punkt ogólny naszego okna programu, a nie relatywnie do ostatniego punktu poprzedniej krzywej, chyba że użyjemy funkcji zaczynających się na Rel, są takie trzy: RelCurveTo, RelLineTo i RelMoveTo. Wtedy możemy podawać odległość względem ostatniego punktu.
Na przykład, ostatnia krzywa skończyła się w punkcie 100,100, teraz chcemy narysować linię która skończy się w 150, 100, użyjemy funkcji RelLineTo(1, 50, 0),
gdzie 1 to identyfikator ścieżki, a 50 i 0 to pięćdziesiąt pikseli na osi X, czyli w prawo i zero pikseli na osi Y, czyli ani w dół ani w górę. Gdybyśmy użyli funkcji LineTo(), to linia zostałaby narysowana nie od 100,100 do 150,100, tylko do 50,0.

Ok, jak już mamy naszego pierwszego wektora, to trzeba go jakoś narysować na ekranie naszej apki. Jako że w naszym wypadku ma to być tło to musimy najpierw przekształcić krzywą w pędzel używając funkcji:

PathToBrush(ID, {{ID, Color, X, Y}}

Jako pierwszy argument oczekuje ona podania identyfikatora pędzla do którego krzywa albo krzywe są przypisywane, stąd podwójne nawiasy klamrowe, Color to oczywiście kolor krzywej, w tym wypadku możemy podać inny kolor dla każdego wektora przypisywanego do pędzla. Co więcej styl wypełnienia i wygładzanie, cień itd. mogą być różne bo są określane w trakcie tworzenia danej krzywej. Nasza funkcja wygląda tak:

Obramowanie = PathToBrush(Nil, {{ID=1, Color=$FFFFFF}} )

No dobra, ale zapytacie o co kaman z tym Obramowaniem i Nilem? Jest to system automatycznego przypisywania identyfikatora, poprzez podanie stałej wbudowanej Nil; sama wartość identyfikatora jest określana przez Hollywood, my tylko podajemy nazwę pod którą ma być zapisana jego wartość, tu „Obramowanie”.
Ten sposób może być używany wszędzie gdzie zajdzie taka potrzeba. Bezpośrednie podawanie wartości ID ogranicza nas do liczb, czasami ciężko się połapać co jest co, w takim wypadku przychodzi z pomocą automatyczne przypisywanie ID.
Dzięki temu możemy nadać identyfikatorowi jakąś znaczącą dla nas nazwę i używać jej podobnie jak zmiennych.

ID=1 to identyfikator ścieżki jaką przypisujemy do pędzla, i na koniec chcemy żeby nasz wektor miał kolor biały. Color=$FFFFFF. Współrzędne X i Y pomijamy.
Dodam tylko jeszcze że mimo zamiany wektora na pędzel, wciąż pozostanie on wektorem, czyli obracanie, czy zmiana rozmiaru nie będzie wpływać na jego jakość.

Cały ten zabieg z zamianą potrzebny nam był, żeby użyć wspomnianego wyżej triku, umożliwiającego stworzenie zegarka bez ramek okna i tła. Tło takie nie musi mieć żadnego regularnego kształtu, będzie wyglądać tak jak pędzel (brush) po zastosowaniu następnej funkcji, którą jest:

BrushToBGPic(BrushID, BGPicID)

Jako pierwszy argument podajemy identyfikator pędzla, u nas to będzie „Obramowanie”, a jako drugi identyfikator tła, u nas „1”. Następnie użyjemy funkcji do wyświetlania obrazka z tłem:

DisplayBGPic(ID {X, Y})

ID jak się domyślacie to identyfikator obrazka, a X i Y to współrzędne ekranu na którym nasz program będzie uruchamiany. Możemy użyć liczb albo stałych wbudowanych, takich jak #TOP, #RIGHT, #CENTER itp. Poza liczbami i wbudowanymi, możemy też użyć specjalnej stałej #KEEPPOSITION, przydaje się ona gdy zmieniamy tło i chcemy żeby nasza apka pozostała w tym samym miejscu na ekranie, bo w przeciwnym razie została by przesunięta na domyślną pozycję czyli środek.

Teraz otwieramy ekran apki stosując funkcję

OpenDisplay(1)

jedyny argument jaki przekazujemy do tej funkcji to identyfikator. Funkcja ta powoduje że ekran naszego programu staje się widoczny, Ta Da, i stała się magia. O innych argumentach które możemy przekazać do tej funkcji możecie poczytać w manualu, jest ich całkiem sporo. (Zrzut 3).

Zrzut 3

Mamy już tarczę zegara, teraz przyszedł czas na następne części naszego czasomierza.
Zajmiemy się punktami godzin i minut – romby i kreseczki. Samo tworzenie obiektów wektorowych, to pikuś, schody się zaczynają w miejscu gdy chcemy je automagicznie wyrysować tak żeby użyć tylko jednej pętli dla obydwu, i żeby jeszcze znalazły się
na odpowiednich miejscach. Tu z pomocą przyszedł mi teh_Kain, z resztą nie tylko tu.
Należy pamiętać że wcześniej ustawione styl wypełnienia i styl formy #FILLCOLOR i #ANTIALIAS wciąż obowiązują, dopóki ich nie zmienimy. W naszym wypadku nie ma takiej potrzeby dlatego zostawiamy jak jest.

Zaczynamy od rozpoczęcia nowej ścieżki, identyfikator może być ten sam co poprzednio, bo ścieżka tarczy zegara jest już zamknięta. Identyfikator może a nawet musi mieć inną wartość jeśli mamy więcej niż jedną ścieżkę to wtedy musimy podać inny żeby program wiedział gdzie chcemy dodać nowe wierzchołki naszych wektorów. Ustawiamy punkt z którego zaczniemy kreślić naszą krzywą na 0,0, tak na wszelki wypadek. Służy do tego funkcja:

MoveTo(1, 0, 0)

Pierwszy argument to identyfikator ścieżki a dwa zera to współrzędne X i Y.
Teraz narysujemy pierwszą linię rombu, który będzie punktem godzin. Użyjemy:

LineTo(1, 8, 20)

Tak jak poprzednio, 1 to identyfikator ścieżki, a 8 i 20 to współrzędne X i Y do których prowadzi nasza linia, oczywiście swój początek ma na 0, 0 dzięki MoveTo().
Następnie użyjemy funkcji bardzo podobnej do LineTo(), jedyna różnica jest taka że nie rysuje linii do podanych współrzędnych, tylko względnie od ostatniego wierzchołka, czyli rysujemy linię od 8, 20 (ostatni wierzchołek poprzedniego wektora) do 0, 40 (który teraz jest punktem od którego zaczniemy rysować dalej)

RelLineTo(1, -8, 20)

Jak widać nazwa funkcji różni się jedynie przedrostkiem „Rel”, od relative, przyjmuje takie same argumenty, ale zachowuje się inaczej niż LineTo(). Jest to wygodne bo możemy odnieść się względem ostatniego punktu, a nie wyliczać wszystko względem ogólnego układu współrzędnych naszego okna.
Dwa polecenia RelLineTo więcej i „zamkniemy” nasz romb przez ClosePath(1).

Następnie przypisujemy nasze cztery proste tworzące romb do identyfikatora pędzla „Romb” jednocześnie nadając mu odpowiedni kolor, w tym wypadku szary.

Romb = PathToBrush(Nil, {{ID=1, Color=$707070}})

Pamiętajcie o podwójnych nawiasach klamrowych, nawet jeśli przypisujecie brushowi jedną ścieżkę.
Teraz ze względu na udokumentowane aczkolwiek niechciane przeze mnie zachowanie Hollywooda musimy zmienić styl wypełnienia:

SetFillStyle(#FILLNONE, 1)

FillNone nie wypełnia powierzchni a jedynie krawędzie ścieżek, linią o podanej w drugim argumencie grubości, u nas 1. Dodam tylko że gdybyśmy nie zmienili stylu wypełnienia, nasza pojedyncza linia nie zostanie narysowana jeśli SetFormStyle będzie ustawiony na #ANTIALIAS, bez niego linia będzie widoczna. Ma to miejsce jedynie w przypadku jednej linii, gdy są dwie lub więcej wszystko jest ok.
Szczerze pisząc nie wiem co autor Hollywooda miał na myśli tak to konstruując…
Dobra, dla mnie to bug, a jego czerstwe tłumaczenie nie ma dla mnie sensu :P.

Nie będę opisywał procesu kreślenia jednej linii, przejdę od razu do mięsa, czyli pętli liczącej współrzędne pozycji kresek i rombów i rysującej je w odpowiednich miejscach na tarczy zegara. Zadanie byłoby dużo łatwiejsze gdyby można przesunąć punkt uchwytu obiektu poza jego obręb. Niestety argumenty AnchorX i Y przyjmują jedynie wartości po między 0 i 1. Nic z tym nie mogłem zrobić przez co wszystko się skomplikowało. Nie ma tego złego i dzięki temu nauczyłem się czegoś nowego.

Przechodząc do rzeczy. Użyłem pętli While – Wend bo jej konstrukcja najlepiej mi odpowiadała (W żadnym wypadku nie twierdzę, że skrypt jest zoptymalizowany, jeżeli ktoś ma uwagi czy rady jak ulepszyć algorytmy zawarte w moim kodzie to dajcie znać). Ponad to wewnątrz pętli While musiałem użyć
instrukcji warunkowej If – ElseIf – Else – EndIf. Pętla oblicza wszystkie punkty godzi i minut a instrukcja warunkowa IF decyduje czy ma zostać narysowany
Romb godzin czy Kreska minut.

Zacznijmy lekcję matmy. Jako że mamy sześćdziesiąt minut w godzinie i tyle jest na tarczy, to cały okrąg podzieliłem przez 60, jak wiadomo pełny okrąg ma
360 stopni, więc dzieląc go przez 60 wychodzi nam jeden symbol Kreski albo Romba co 6 stopni. Teraz mając jedynie współrzędne środka okręgu z_CX=250 na osi X i z_CY 250 na osi Y i długość promienia okręgu na którym mają być rysowane punkty z_Radius=225, użyłem trygonometrii żeby obliczyć gdzie każdy punkt ma się znajdować.

Wygląda to tak:

i=0
While i < 60
i=i+1
z_angle = i*2*#Pi/60
i=0, zeruje wartość zmiennej i. While i < 60 to początek pętli gdzie sprawdzamy czy zmienna „i” nie jest większa od 60, jeśli jest mniejsza to dodajemy do niej 1 – i=i+1. Następnie zmiennej z_angle przypisujemy wynik działania i*2*#Pi/60. W pierwszym przebiegu pętli wynik będzie następujący:
1*2Pi/60 => 1*360/60 => 6, w drugim 2*360/60=>12, a w ostatnim 60*360/60 =>360.

Tu kilka uwag. Po pierwsze #Pi to stała Hollywood i oczywiście symbolizuje liczbę Pi.
Po drugie zwróćcie uwagę czy dzielenie jest przy użyciu znaku „/”(forward slash), jest bowiem różnica po między / i \. Wynikiem dzielenia przez / jest liczba zmiennoprzecinkowa, natomiast przy dzieleniu przez „\” (backslash) dostajemy jedynie wartość całkowitą z dzielenia, miejsca po przecinku są obcinane.

X = z_CX + z_Radius * Cos(z_angle)
Y = z_CY + z_Radius * Sin(z_angle)

Policzywszy kąt i mając daną długość promienia, możemy użyć funkcji trygonometrycznych do obliczenia współrzędnych punktu na krawędzi okręgu. Użyjemy do tego funkcji Cos(z_angle) i Sin(z_angle), wynikiem jest wartość w Radianach, którą trzeba zamienić na stopnie funkcją Deg()jednocześnie przypisując wynik zmiennej z_rotation

z_rotation = Deg(z_angle)

Nie będę się zagłębiał w matematyczny aspekt bo to musicie sami rozgryźć, a poza tym istnieje duże ryzyko, że coś pokręcę i będzie wstyd.

Założenie jest takie:
Jeśli kąt w danym przebiegu pętli różny jest od 30 stopni to rysuj Kreskę
Jeśli nie to rysuj Romb

Prościej być nie może. Musimy to jedynie zapisać używając funkcji Hollywood.
Wartość 30 bierze nam się z dzielenia 360 (pełen okrąg) przez 12 (ilość godzin).
Ale jak określić czy wartość kąta to 30, 60 czy 150 stopni, nie pisząc dla każdej wartości po między 60 i 360 stopni instrukcji warunkowej ElseIf? Możemy to obliczyć sprawdzając czy reszta z dzielenia przez 30 da nam 0. Bo 60/30=2 czyli nie ma reszty.
W tym celu użyjemy operatora %, który jako wynik podaje wartość reszty z dzielenia. Na przykład 7%3 da nam 1, 5%3 da nam 2.

Niestety nie miałem tak łatwo bo okazało się, że reszta z dzielenia w punktach gdzie mają być godziny częściej równa jest 29 niż 0. Udało mi się pokonać i tą trudność stosując operator logiczny AND.
Operator <> znaczy „różny od”.O tych operatorach jak i wielu innych poczytacie w manualu.

Nasz blok warunkowy wygląda tak:

If (z_rotation%30)<>0 AND (z_rotation%30)<>29 ; jeśli reszta z dzielenia różna (<>0) od 0 i (<>29) od 29
DisplayBrush(Kreska, X, Y, {Rotate=270-z_rotation, AnchorX=0.5, AnchorY=1, Shadow=True, ShadowColor=$707070, ShadowSize=0})
Else
DisplayBrush(Romb , X, Y, {Rotate=270-z_rotation, AnchorX=0.45, AnchorY=0.5, Shadow=True, ShadowColor=$707070, ShadowSize=0})
EndIf

Wend – oznaczenie końca pętli While – Wend.

Teraz wyjaśnię co się dzieje w Funkcji rysującej DisplayBrush(). Pierwszy argument to identyfikator pędzla (brusha) do wyświetlenia, dwa następne to współrzędne na osiach X i Y. Teraz mamy {Tabelę} z listą argumentów. Pełna lista wiecie gdzie jest, tak, w manualu ;p. Pierwszy element tabeli Rotate=270-z_rotation to wartość w stopniach o jaką ma być obrócony pędzel wokół uchwytu (Anchor), wartość ta liczona jest w pętli trochę wyżej. 270 to stopnie o jakie musiałem obrócić każdy z pędzli bo były pod złym kątem. Pozycje AnchorX i Y zmieniają punkt uchwytu i obrotu obiektu.

Shadow=True to włączenie cienia, ShadowColor chyba nie muszę tłumaczyć, ale już ShadowSize muszę, bo to nie jak wskazuje nazwa rozmiar cienia a jego odległość od rzucającego go obiektu, w tym wypadku pędzla. Ja ustawiłem na 0 bo nie chodziło mi o cień, a o efekt poświaty wokół pędzla (Zrzut 4).

Zrzut 4

Teraz tworzymy wskazówki, postępujemy podobnie jak w wypadku Kresek i Rombów, nie będę tego opisywał, bo nie ma tam nic nowego. Jedynie co wymaga wyjaśnienia to funkcja FreePath(1). Powoduje ona usunięcie z pamięci nie potrzebnej nam już ścieżki.

Teraz nadszedł czas na zajęcie się czasem, a jaśniej pisząc wyciągniemy sobie wartość godzin, minut i sekund z systemowego zegara funkcjami GetTime() i MidStr() i przypiszemy je odpowiednim zmiennym.

z_czas = GetTime(True)

z_czas = GetTime(True) z_czas to zmienna która przechowa systemowy czas, podanie True spowoduje, że czas będzie zawierał także sekundy, w innym wypadku są to jedynie godziny i minuty. Format w jakim przekazywana jest godzina wygląda tak: HH:MM:SS.
Teraz żeby zmiennym z_godz, z_min i z_sek przypisać wartości odpowiednio HH, MM i SS musimy użyć funkcji operującej na ciągach znaków MidStr(). Jako jej pierwszy argument przekazujemy zmienną pod która jest nasz ciąg znaków, u nas to jest z_czas gdzie przechowujemy HH:MM:SS, drugi argument to pozycja znaku w ciągu od którego chcemy zacząć kopiowanie znaków, jeśli jest to pierwszy znak to podajemy 0, a trzeci argument funkcji to ilość znaków jakie nas interesują. W przypadku godziny mamy:

z_godz = MidStr(z_czas, 0, 2) – ze zmiennej z_czas skopiuj dwa znaki zaczynając od pierwszego i wklej je do zmiennej z_godz.

z_godz = MidStr(z_czas, 0, 2)
z_min = MidStr(z_czas, 3, 2)
z_sek = MidStr(z_czas, 6, 2)
Teraz musimy te wartości zamienić na kąty pod jakimi mają być rysowane wskazówki naszego zegara.

W przypadku sekund sprawa jest prosta, mnożymy wartość zmiennej z_sek razy sześć, to sześć to kąt co ile są rysowane Kreski minut na tarczy zegara. Musimy jeszcze tylko pomnożyć tą wartość razy minus jeden bo w przeciwnym wypadku wskazówki będą nam chodziły „pod prąd”.

z_sek_angle = (z_sek*6)*-1
W przypadku minut postępujemy podobnie, z tym że musimy podać kąt pośredni po między sąsiadującymi minutami, w innym wypadku wskazówka się nie poruszy dopóki nie nastąpi zmiana minuty na następną, zwiększamy jakby rozdzielczość ruchu wskazówki. W wypadku wskazówki sekund ruch jest skokowy, bo funkcja GetTime() nie umożliwia odczytu milisekund z systemu. Można by samemu to policzyć, aczkolwiek czas i tak byłby niedokładny w stosunku do systemowego, bo byłby zaokrąglany do sekundy w której uruchamiamy nasz program. Zwróćcie uwagę, że wartość zmiennej zwiększającej „rozdzielczość” ruchu minutowego ramienia, to jest obliczona jedną linie wyżej wartość kąta sekundnika, a nie sekundy odczytane funkcją GetTime().

z_min_angle = ((z_min*6)*-1)+ z_sek_angle/60

Z godzinami jest bardzo podobnie, z tym że jest ich pięć razy mniej niż minut na tarczy więc mnożymy godzinę przez trzydzieści zamiast sześciu. Poza tym także w tym wypadku używamy wyliczoną wartość kąta wskazówki minutowej i dzielimy ją przez ilość godzin na tarczy zegara, czyli dwanaście.

z_godz_angle = ((z_godz*30)*-1)+ z_min_angle/12

Teraz już tylko pozostało wyświetlić to co udało nam się policzyć. Używamy do tego tej samej funkcji co w przypadku innych elementów zegara, czyli DisplayBrush.
Składnia jest identyczna, oczywiście poza identyfikatorem pędzli i kąta obrotu (Zrzut 5).

Zrzut 5

Jak pewnie zauważyliście nasz zegar „nie chodzi”. Żeby tchnąć w niego życie napiszemy sobie funkcję prywatną, zwaną też funkcją użytkownika. Według zaleceń jej nazwa powinna rozpoczynać się przedrostkiem p_ żeby odróżnić ją od funkcji wbudowanych i uniknąć konfliktu w razie gdyby funkcja o takiej nazwie została dodana przez autora
Hollywood w przyszłości.

Żeby poinformować Hollywood, że chcemy napisać własną funkcję, definicję jej zamykamy po między Function i EndFunction

A oto jak będzie wyglądać nasza funkcja:

Pierwszy krok to usunięcie wskazówek zegara, które są wyświetlone za sprawą trzech rozkazów DisplayBrush znajdujących się poza naszą funkcją. Funkcja nasza będzie przyzywana raz po upływie każdej sekundy, przez co gdyby wskazówki nie zostały narysowane przed wywołaniem naszej prywatnej funkcji, to po uruchomieniu naszego programu zegar nie miałby wskazówek przez jedną sekundę.

Undo(#Brush, WskazMala)
Undo(#Brush, WskazDuza)
Undo(#Brush, WskazSek)

Funkcja Undo() usuwa obiekt graficzny z ekranu, jako pierwszy argument przekazujemy typ obiektu do usunięcia, używamy do tego specjalnych stałych, w naszym wypadku jest to #BRUSH. Drugi argument to identyfikator obiektu. Trzeci to poziom zagnieżdżenia.
Dla przykładu gdy mamy wyświetlone trzy te same obiekty i chcemy usunąć tylko ostatni, to podajemy tu wartość 1 (domyślna), każdy poprzedni obiekt ma wartość o jeden większą, czyli gdybyśmy chcieli usunąć pierwszy narysowany z trzech widocznych obiektów to podajemy wartość 3.

Drugi krok, to ponowne odczytanie czasu systemowego i przeliczenie go na odpowiednie kąty dla naszych wskazówek.

z_czas = GetTime(True)
z_godz = MidStr(z_czas, 0, 2)
z_min = MidStr(z_czas, 3, 2)
z_sek = MidStr(z_czas, 6, 2)

z_sek_angle = (z_sek*6)*-1
z_min_angle = ((z_min*6)*-1)+ z_sek_angle/60
z_godz_angle = ((z_godz*30)*-1)+ z_min_an gle/12

Trzeci i ostatni krok to wyświetlenie ich. I tak w kółko.

Może dziwić takie rozwiązanie ale takie zastosowałem bo lepszego w tej chwili nie znam.
Nasza funkcja prywatna będzie wywoływana w odstępach określonych w funkcji SetInterval(), która natomiast będzie wywoływana przez funkcję WaitEvent() która to znajduje się w nieskończonej pętli Repeat – Forever.

SetInterval(1, p_RuchWskazowek, 1000)

Przekazujemy do tej funkcji trzy argumenty, pierwszy to identyfikator interwału, czyli odstępu czasu, drugi to funkcja jaka będzie wywoływana co podaną jako trzeci argument ilość milisekund.

SetInterval() nie będzie działać bez użycia WaitEvent(), która jest bardzo ważną funkcją w Hollywood, bez niej funkcja przekazana w SetInterval nie będzie wywoływana.
WaitEvent() ma tą zaletę, że jest wykonywana co jakiś czas, czyli raz na ilość milisekund podanych w funkcji SetInterval(). Dzięki temu możemy ograniczyć szybkość wykonywania się głównej pętli naszego programu do pożądanej ilości klatek na sekundę bez względu na szybkość maszyny na której nasza apka jest uruchamiana. Bez niej program wykonywany byłby z maksymalną możliwą szybkością, pożerając tym samym sto procent czasu procesora.
Żeby funkcja WaitEvent() była wywoływana tak długo jak działa nasz program, umieszczamy ją w nieskończonej pętli:

Repeat
WaitEvent()
Forever

W naszym wypadku nie przekazujemy żadnych argumentów do tej funkcji. WaitEvent sama się zatroszczy o wywołanie funkcji podanej jako argument SetInterval (Zrzut 6).

Zrzut 6

I tym sposobem dotarliśmy do końca trzeciego odcinka bojów z Ostrokrzewem, zegar działa choć sporo mu brakuje, resztą rzeczy zajmę się w następnych odcinkach, bo to jeszcze nie koniec, a wręcz dopiero początek.

Nie wiem na ile możliwości Hollywood i moje zdolności do jego nauki mi pozwolą osiągnąć, bo musicie pamiętać, że ten cykl to swoisty wykres prezentujący krzywą pozyskiwania wiedzy i doświadczenia w posługiwaniu się nim. Zdaję sobie sprawę, że jak do tej pory szału nie ma. Zegarek i to w dodatku kopia systemowego raczej jest mało porywający, ale od czegoś trzeba zacząć. Na wodotryski przyjdzie czas, miejmy nadzieję ;).

Pliki ze skryptem i wykonywalne plus aplet, są do ściągnięcia z tego samego miejsca co poprzednio:

www.czernik.me/hollywood.html

’ferin’ – Amiga NG (2) 1/2018

—> do spisu artykułów

Dodaj komentarz