#Setup
- install Minimacy, as described in the [Setup section](page://2000.100.100). Build Minimacy or use the Release distribution.
- clone the demo repository:
>git clone https://github.com/ambermind/airport-display.git
We assume now that:
- [minimacy] is the full path of the Minimacy directory, it contains at least these subfolders: ./bin ./rom ./programs
- [minimacyBinary] is the full path of Minimacy binary with UI (minimacy.exe on Windows, minimacyX11GL on Unix, minimacyMacX11GL on MacOS).
- [airport-display] is the full path of this example on your disk
Then, you may start this example with:
>[minimacyBinary] [airport-display]/boot.mcy
Alternately, you may replace the content of [minimacy]/programs with the content of [airport-display]. It will overwrite 'boot.mcy'. You should create a dedicated copy of Minimacy in order to do so.
Then, you have nothing more to do to build iOS, Android or MacOS app. See [Building an App (MacOS, iOS, Android)](page://2000.300.100)
#Usage
Once started, a window should open with this content:
[
](html)
This demonstration retrieves the schedule of next flights at CDG (Charles-de-Gaulle airport near Paris, France) from an internet website, then:
- display the schedule, page after page
- refresh the list of next flights every minute
#Implementation
The considered airport is defined by its 3-letters code. You may change it as you wish.
const AIRPORT="cdg";;
This demo gets the schedule from: [https://www.avionio.com/widget/en/cdg/departures](https://www.avionio.com/widget/en/cdg/departures)
This url is a html which includes a timestamp. In order to get more than 1 page, you need to add ts and page parameters to the url (?ts=*&page=*).
So the logic is the following:
- get the first page
- extract the timestamp
- loop on the next pages providing the timestamp and the page number starting from 1.
We implement a function 'avionioParse' which get the url with [[httpGet]], then parse the html code with [[htmlParse]], then extract the list of flights.
Each flight is a tuple [hour, destination, flight, company, estimated].
The function also extracts the timestamp and the title and returns a tuple [title, ts, listItems] where ts is the timestamp and listItems is the list of flights tuples.
The parsing greatly depends on the html page structure. It must be adapted when the origin website has changed.
fun avionioParse(suffix)=
let echoLn strConcat(URL, suffix) -> url in
let httpGet(url) -> data in
let htmlParse(data, 0, false) -> [xml, _] in
let head(xmlFilterChilds(xml, "body")) -> xml in
let head(xmlFilterChilds(xml, "main")) -> xml in
let strTrim(xmlGetContent(head(xmlFilterChilds(xml, "h1")))) -> title in
let head(xmlFilterChilds(xml, "table")) -> xml in
let head(xmlFilterChilds(xml, "tbody")) -> xml in
let xmlFilterChilds(xml, "tr") -> listItems in
let head(xmlFilterChilds(head(listItems), "td")) -> xmlNav in
let head(xmlFilterChilds(xmlNav, "a")) -> xmlNav in
let xmlGetAttribute(xmlNav, "href") -> href in
let head(strSplit("&", head(tail(strSplit("?ts=", href))))) -> ts in
let listFilter(listItems, lambda(xml) = strContains(xmlGetAttribute(xml, "class"), "tt-row")) -> listItems in
let listMap(listItems, lambda(xml) =
let xmlFilterChilds(xml, "td") -> columns in
let listMap(columns, lambda(xml) = strTrim(xmlGetContent(xml))) -> columns in
let columns -> hour::_::_::destination::flight::company::estimated::_ in
[hour, destination, flight, company, estimated]
) -> listItems in (
echoLn strFormat("nbItems= *", listLength(listItems));
[title, ts, listItems]
);;
This function use the [[httpGet]] function which is synchronous: the function halts until the reply is received.
Then we can implement 'avionioLoad' which get the firstpage and then loops to get a total of NB_SERVER_PAGES pages.
Items are concatenated and stored in the global variable FlightList. The title is also stored in the global variable BoardTitle.
fun avionioLoad ()=
let avionioParse(nil) -> [title, ts, listItems] in (
for i=1;i [_, _, listItemsNext] in
set listItems=listConcat(listItems, listItemsNext);
if listItems<>nil then (
set FlightList=listItems;
set BoardTitle=title
);
onTimeout(1000*REFRESH_DATA_PERIOD, #avionioLoad)
);;
Finaly we set a timer of REFRESH_DATA_PERIOD seconds. The the timer fires, it calls avionioLoad again, and therefore refresh the list of flights.
The 'run' function calls 'avionLoad' to get a first list of flights. It starts the UI with the BoardTitle, then calls 'flightDraw' to display de flights.
fun main ()=
avionioLoad();
uiStart(0, 0, WIDTH, HEIGHT, UI_RESIZE, BoardTitle);
flightDraw(0);;
We first need a Font to draw the text.
const FontText= fontFromBitmap(bitmapFromPng(load("rsc/fonts/Arial_32_256.png")));;
We load such a font from a bitmap you will found in ./minimacy/rom/rsc/fonts/Arial_32_256.png
Fonction composition could be read like this:
- first we [[load]] the file
- we assume it is a png file and call [[bitmapFromPng]] to create a bitmap from it
- finally we call [[fontFromBitmap]] to make a font from it.
More information about these bitmaps in [core.2d.font](page://ref.core.2d.font) and more specificaly in [[fontFromBitmap]]. You may create your own font bitmaps using [FontMaker](https://minimacy.net/book/#/font_maker).
Then, we define a recursive function to draw one flight at a specific vertical coordinate 'y' and recursively draw the next flights from 'y+ROW_HEIGHT'.
fun _flightDrawItem(listItems, y)=
if listItems<>nil && y+ROW_HEIGHT <= HEIGHT-MARGIN_Y then
let head(listItems) -> [hour, destination, flight, company, estimated] in (
bitmapTextU8(uiBuffer(), MARGIN_X, y, 0, hour, FontText, COLOR_TEXT, nil);
bitmapTextU8(uiBuffer(), MARGIN_X+80, y, 0, destination, FontText, COLOR_TEXT, nil);
bitmapTextU8(uiBuffer(), WIDTH/2-140, y, 0, flight, FontText, COLOR_TEXT, nil);
bitmapTextU8(uiBuffer(), WIDTH/2, y, 0, company, FontText, COLOR_TEXT, nil);
bitmapTextU8(uiBuffer(), WIDTH-MARGIN_X, y, ALIGN_RIGHT, estimated, FontText, COLOR_TEXT, nil);
bitmapLine(uiBuffer(), 0, y+ROW_HEIGHT-5, WIDTH, y+ROW_HEIGHT-5, COLOR_SEPARATOR, nil);
_flightDrawItem(tail(listItems), y+ROW_HEIGHT)
);;
We stop the recursion when listItems is empty or when the 'y' coordinate is outside the screen minus a margin MARGIN_Y.
Else we extract the flight tuple and use [[bitmapTextU8]] to draw texts. We also draw an horizontal line with [[bitmapLine]] to separate each line.
Colors are provided as 32 bits ARGB integers.
The last function draws a page of flights and set a timer to draw the next page.
The function is called with a parameter 'skip' which indicates the index of the first flight to display, therefore the page to display. It is first called with skip=0.
We first compute the number of flights per page in 'nbItemsPerPage'.
Then we "update" the skip value, so that if it is bigger than the number of flights, it resets to zero and displays the first page again.
fun flightDraw(skip)=
let FlightList -> listItems in
let listLength(listItems) -> nbItems in
let (HEIGHT-MARGIN_Y*2)/ROW_HEIGHT -> nbItemsPerPage in
let if skip skip in
(
echoLn strFormat("display flights */*", skip, nbItems);
bitmapErase(uiBuffer(), COLOR_BACKGROUND);
_flightDrawItem(listSkip(listItems, skip), MARGIN_Y);
bitmapTextU8(uiBuffer(), WIDTH-MARGIN_X, HEIGHT-2, ALIGN_RIGHT|ALIGN_BOTTOM, BoardTitle, FontText, COLOR_TITLE, nil);
uiUpdate();
onTimeout(1000*NEXT_PAGE_PERIOD, lambda ()= flightDraw(skip+nbItemsPerPage))
);;
Then the drawing itself:
- [[bitmapErase]] on [[uiBuffer]] to reset the screen buffer with the specified color.
- '_flightDrawItem' to display the page of flights, starting at y=MARGIN_Y
- [[bitmapTextU8]] to display the title in the bottom right
- [[uiUpdate]] to send the screen buffer [[uiBuffer]] to the screen
Finally we call [[onTimeout]] to call itself ('flightDraw') after NEXT_PAGE_PERIOD seconds, with the next flights (skipping skip+nbItemsPerPage).