From c9f1d7c8ca3f2cb6df672c9da58235b4a2ff2651 Mon Sep 17 00:00:00 2001 From: Fabio Date: Sat, 3 May 2025 10:00:37 +0200 Subject: [PATCH] Added Helpers library --- Examples/Helpers/HL_Convert_BytesTo.hws | 24 + Examples/Helpers/HL_Examples.hws | 1152 +++++++++++++++ Examples/Helpers/HL_ParseRunArgs.hws | 22 + Examples/Helpers/HL_Safe.hws | 18 + Examples/Helpers/HL_SizeString.hws | 40 + Examples/Helpers/HL_Value2Perc.hws | 19 + Examples/Helpers/HL_WaitForAction.hws | 33 + Helpers.hws | 1711 +++++++++++++++++++++++ Helpers_license.txt | 21 + Helpers_readme.md | 1200 ++++++++++++++++ images/Helpers.jpg | Bin 0 -> 56086 bytes 11 files changed, 4240 insertions(+) create mode 100644 Examples/Helpers/HL_Convert_BytesTo.hws create mode 100644 Examples/Helpers/HL_Examples.hws create mode 100644 Examples/Helpers/HL_ParseRunArgs.hws create mode 100644 Examples/Helpers/HL_Safe.hws create mode 100644 Examples/Helpers/HL_SizeString.hws create mode 100644 Examples/Helpers/HL_Value2Perc.hws create mode 100644 Examples/Helpers/HL_WaitForAction.hws create mode 100644 Helpers.hws create mode 100644 Helpers_license.txt create mode 100644 Helpers_readme.md create mode 100644 images/Helpers.jpg diff --git a/Examples/Helpers/HL_Convert_BytesTo.hws b/Examples/Helpers/HL_Convert_BytesTo.hws new file mode 100644 index 0000000..59f15c9 --- /dev/null +++ b/Examples/Helpers/HL_Convert_BytesTo.hws @@ -0,0 +1,24 @@ +/* +************************************************** +*** Helpers.hws Example : HL.Convert.BytesTo() *** +************************************************** +*/ + +@INCLUDE "../../+Includes.hws" +@INCLUDE #INC_HELPERS + +NPrint("HL.Convert.BytesTo Example") +NPrint("--------------------------\n") + +Local bytes = 24589798800 +Local decimals = 2 + +NPrint("Converting " .. bytes .. " bytes to") +NPrint(" Kb : " .. HL.Convert.BytesTo(bytes, #HL_KILOBYTES, decimals)) +NPrint(" Mb : " .. HL.Convert.BytesTo(bytes, #HL_MEGABYTES, decimals)) +NPrint(" Gb : " .. HL.Convert.BytesTo(bytes, #HL_GIGABYTES, decimals)) +NPrint(" Tb : " .. HL.Convert.BytesTo(bytes, #HL_TERABYTES, decimals)) +NPrint(" Auto : " .. HL.Convert.BytesTo(bytes, #HL_AUTO, decimals)) + +NPrint("\n--- Left mouse button to quit ---") +WaitLeftMouse() diff --git a/Examples/Helpers/HL_Examples.hws b/Examples/Helpers/HL_Examples.hws new file mode 100644 index 0000000..e990dfb --- /dev/null +++ b/Examples/Helpers/HL_Examples.hws @@ -0,0 +1,1152 @@ +/* +**************************** +*** Helpers.hws Examples *** +**************************** +* NOTES +* In this single source you can look at the usage most of the +* features provided by the Helpers library. To give a nicer look to +* the examples I've used the example.Render() function where I've +* defined some local functions used to render the informations on the +* screen. +********************************************************************* +*/ + +; Includes the Helpers library, you may need to remap the following path +; to point to where you have copied the Helpers library. +@INCLUDE "../../+Includes.hws" +@INCLUDE #INC_HELPERS + +; Here is a table where we will save all our variables. +Global example = {} + +; Since I want to show the examples in a nice screen, the following function +; will define some local functions to render the screen, it also use these +; functions to draw an empty screen. The local functions will be returned +; so that our single examples can access them to update their screen. +Function example.Render(title, description) + ; A simple function that prepares a nice screen to run the prodived + ; tests & examples. + + ; Setup the screen size + Local screen_width, screen_height = 800, 600 + SetDisplayAttributes({ Width = screen_width, Height = screen_height }) + + ; Setup colors & variables + Local colBG = $444444 + Local colTitle = { bg = $338800, fg = $FFFF00 } + Local colBody = { bg = $555555, fg = $FFFFFF } + Local shadowSize = 3 + Local borders = 8 + Local text_borders = 4 + Local titleHeight = 24 + Local bodyHeight = 500 + + ; Calculate the 3 areas sizes and positions: title, body, description + Local titleArea = { x = borders, y = borders, w = screen_width-borders*2, h = titleHeight } + Local bodyArea = { x = borders, y = titleArea.y+titleArea.h+borders, w = titleArea.w, h = bodyHeight } + Local descArea = { x = borders, y = bodyArea.y+bodyArea.h+borders, w = bodyArea.w, h = screen_height-shadowSize-(bodyArea.y+bodyArea.h+borders*2) } + + ; Define some utility functions to draw the screen, this is the table where we are + ; goin to define the rendering functions and that will be returned to the caller. + Local utilities = {} + + ; Draw the backgorund + SetFillStyle(#FILLCOLOR) + SetFormStyle(#ANTIALIAS) + SetFormStyle(#SHADOW, ARGB(128, $000000), shadowSize, #SHDWSOUTHEAST) + Box(0, 0, screen_width, screen_height, colBG) + + ; Defines the function that renders the screen's Title + utilities.drawTitle = + Function(title) + SetFont(#SANS, titleHeight-4) + SetFontStyle(#ANTIALIAS+#BOLD) + Box(titleArea.x, titleArea.y, titleArea.w, titleArea.h, colTitle.bg, { RoundLevel = 20 }) + TextOut(#CENTER, titleArea.y+titleArea.h/2, title, { align = #CENTER, anchorX = 0.5, anchorY = 0.5, color = colTitle.fg }) + EndFunction + ; Renders the screen's Title + utilities.drawTitle(title) + + ; Define the function that renders the screen's Body + utilities.drawBody = + Function() + SetFillStyle(#FILLCOLOR) + SetFormStyle(#ANTIALIAS) + SetFormStyle(#SHADOW, ARGB(128, $000000), shadowSize, #SHDWSOUTHEAST) + Box(bodyArea.x, bodyArea.y, bodyArea.w, bodyArea.h, colBody.bg, { RoundLevel = 3 }) + EndFunction + ; Renders the screen's Body + utilities.drawBody() + + ; Defines the function that renders the screen's description at the bottom + utilities.drawDescription = + Function(description) + SetFillStyle(#FILLCOLOR) + SetFormStyle(#ANTIALIAS) + SetFormStyle(#SHADOW, ARGB(128, $000000), shadowSize, #SHDWSOUTHEAST) + + SetFont(#SANS, titleHeight*0.70) + SetFontStyle(#ANTIALIAS) + Box(descArea.x, descArea.y, descArea.w, descArea.h, colBody.bg, { RoundLevel = 20 }) + TextOut(descArea.x+descArea.w/2, descArea.y+descArea.h/2, description, { align = #CENTER, anchorX = 0.5, anchorY = 0.5, color = colBody.fg, wordwrap = descArea.w-text_borders*2 }) + EndFunction + ; Renders the screen's description + utilities.drawDescription(description) + + ; Defines a generic Print to help us to print informations in the + ; screen's body. + utilities.print = + Function(text, x, y) + ; If x or y is not provided set them at the top/left of the + ; screen's body. + If IsNil(y) Then y = bodyArea.y+text_borders + If IsNil(x) Then x = bodyArea.x+text_borders + + ; Usable width (total width - left and right borders) + Local usable_width = bodyArea.w - text_borders*2 + ; Check how many lines will be used + Local textWidth = TextWidth(text) + ; Calculate how many lines will be used + Local usedLines = Round(textWidth/usable_Width + 0.5) + ; Text height + Local h = TextHeight("|") + ; Check if the text will be printed outside the bottom area + Local lastY = y + h*usedLines + + If lastY > bodyArea.y + bodyArea.h - h + ; If the text will go out of the screen asks the user to + ; hit the left mouse button to continue so the screen will be + ; cleared. + TextOut(x, y, "::: Left Mouse Key to continue... :::", { Color = $00FFFF }) + WaitLeftMouse() + utilities.drawBody() + y = bodyArea.y + text_borders + lastY = y+h*usedlines + EndIf + + ; Renders the text + TextOut(x, y, text, { WordWrap = usable_width }) + y = lastY + + ; Returns the x & y positions for the next print + Return(x, y) + EndFunction + + ; We also need a function able to draw a simple menu and wait for the user + ; input. + utilities.drawMenu = + Function(menuDefinition) + Repeat + ; Clear the body + utilities.drawBody() + + SetFont(#SANS, titleHeight*0.80) + SetFontStyle(#ANTIALIAS) + + ; Defines 2 item columns positions + Local column1 = bodyArea.x + text_borders + 20 + Local column2 = bodyArea.x + bodyArea.w/2 + text_borders + Local y = bodyArea.y + text_borders + Local h = TextHeight("|") + Local yellow = "[color=$FFFF00]" + Local white = "[color=$FFFFFF]" + + ; No width check is performed here to simplify the code! :) + Local c = column1 + For i = 0 To ListItems(menu)-1 + TextOut(c-TextWidth(ToString(i)), y, yellow .. i) + TextOut(c+8, y, white .. menuDefinition[i].name) + ; Swap the active column + If c = column1 + c = column2 + Else + y = y + h + c = column1 + EndIf + + Next + + ; At the bottom of the area ask for the user input + Local y = bodyArea.y + bodyArea.h - h*2 + Local message = "[b]Type your choice and hit enter :[/b]" + TextOut(column1+16, y, message, { Color = $66FF88 }) + + ; Wait for the user's input + Locate(column1+16+TextWidth(message), y) + Local choice = InKeyStr(#NUMERICAL, Nil, False, True) + + ; Check the input + If choice <> "" + choice = ToNumber(choice) + If HaveItem(menuDefinition, choice) + ; Menu item exists, execute the example function + menuDefinition[choice].func() + EndIf + EndIf + + Forever + EndFunction + + ; Returns the bodyArea, the text_borders and the utilities table where all + ; our rendering functions are stored. + Return(bodyArea, text_borders, utilities) + +EndFunction + +;========================= +;=== EXAMPLE FUNCTIONS === +;========================= +Function example.TEST_AmperConversion() + ; --------------------------------------------------------------------------- + ; Function used to test the ampersands conversion into UTF8 encoded characters. + ; --------------------------------------------------------------------------- + Local fontName = "tahoma" + Local fontSize = 20 + Local title = "TEST : Ampersands Conversion to UTF8" + + Local bodyArea, text_borders, utilities = + example.Render(title, + "This program tests the function [b]HL.Convert.HTML2Hollywood()[/b] " .. + "to check if ampersands are handled correctly. " .. UpperStr(fontName) .. " font is " .. + "used for this test, change it if it is not installed in your system or install it. Very useful to render basic HTML pages!") + + Local x, y = bodyArea.x+text_borders, bodyArea.y+text_borders + + SetFont(fontName, fontSize) + SetFontColor($DDDDDD) + SetFontStyle(#ANTIALIAS) + + Local page, count = 1, 0 + + ; The following code will iterate through all html amper symbols + ; and will print them on the screen along with their unicode + ; character. + For i, v In Pairs(HL.HTML_Amper) + ; The symbol is converted into the corresponding unicode + ; character tha is stored in the variable. + ; HL.Convert.HTML2Hollywood() is able to convert others symbols + ; like bold tag (), it is illustrated in another + ; example. + count = count + 1 + Local decoded = HL.Convert.HTML2Hollywood(i) + decoded = ReplaceStr(decoded, "[", "[[") + decoded = ReplaceStr(decoded, "]", "]]") + decoded = i .. "[color=$FFFF00]" .. decoded + + TextOut(x, y, decoded, { Encoding = #ENCODING_UTF8 }) + x = x + TextWidth(decoded)+12-TextWidth("[color=$FFFF00]") + + ; Check if we have to go to a new line + If x > bodyArea.x+bodyArea.w-text_borders*2 - 150 + ; Move to the next line and reset the x coordinate + y = y + fontSize + x = bodyArea.x + text_borders + + ; Check if we have reached the bottom of the area + If y > bodyArea.y+bodyArea.h-fontSize*2-text_borders + ; Print a message before resetting the screen and wait + ; for the user to hit the left mouse button + SetFont(#SANS, fontSize) + SetFontStyle(#ANTIALIAS+#BOLD) + TextOut(x, y+fontSize/2, "=== Left Mouse Click to continue ===", { Color = $00FFFF }) + WaitLeftMouse() + + ; Reset the screen and increase the page number that will be printed + ; in the title. + page = page + 1 + utilities.drawTitle(title .. " (page " .. page .. ")") + utilities.drawBody() + y = bodyArea.y+text_borders + + ; Restore the font we are using to print the amper and unicode pairs + SetFont(fontName, fontSize) + SetFontColor($DDDDDD) + SetFontStyle(#ANTIALIAS) + EndIf + EndIf + Next + + y = y + 30 + x, y = utilities.print("Decoded [color=$FFFF00][b]" .. count .. "[/b][/color] ampersand symbols", Nil, y) + x, y = utilities.print("[color=$FFFF00]Click [b]Left Mouse Button[/b] to continue...", Nil, y) + + WaitLeftMouse() + +EndFunction + +;============================================================================== + +Function example.TEST_WaitForAction() + ; --------------------------------------------------------------------------- + ; Test HL.WaitForAction() function + ; + ; HL.WaitForAction() arguments + ; key Monitored key. + ; delay Delay in milliseconds for the detection loop, every + ; wait a call to will be made. + ; callback Function called every loop check with the + ; parameter. If the callback function returns TRUE the + ; wait loop will be interrupted immediatly. + ; timeout Timeout in milliseconds. + ; to_callback Function called if the timeout is reached, the function + ; will be called with parameter. + ; userData Custom user data parameter passed to the callback + ; function. + ; --------------------------------------------------------------------------- + Local fontName = #SANS + Local fontSize = 18 + Local title = "TEST : WaitForAction()" + + Local bodyArea, text_borders, utilities = + example.Render(title, + "This program tests the function [b]HL.WaitForAction()[/b], it also " .. + "shows how this function can be used to create animated wait-for-action " .. + "screens.") + + SetFont(fontName, fontSize) + SetFontColor($DDDDDD) + SetFontStyle(#ANTIALIAS) + + ; Timer animation position + Local c = { 650, 270 } + Local angle = 0 + + SetFormStyle(#ANTIALIAS) + + ; This callback function will be called every time the input is checked, + ; in this example it is called to update a timer. + Local callback = Function(userdata, elapsed) + SetFillStyle(#FILLCOLOR) + SetFormStyle(#NORMAL) + SetFormStyle(#ANTIALIAS) + Local component = (angle+1)/361*255 + Arc(c[0], c[1], 50, 50, 0, angle, RGB(0, component, component)) + Local t = 10-Int(elapsed/100)/10 + Box(c[0]+25, c[1]-20, 50, 20, $cc0000) + TextOut(c[0]+40, c[1]-20, FormatStr("%.1f", t)) + SetFillStyle(#FILLNONE, 3) + Circle(c[0], c[1], 50, $cc0000) + angle = angle + 1 + If angle > 360 Then angle = angle - 360 + EndFunction + + ; This callback function will be called when the timeout is reached + Local timeout = + Function() + SystemRequest("ALERT!", "Timeout reached!", "I see", #REQICON_INFORMATION) + EndFunction + + ; Set some shortcuts + Local bullet = "[color=$FF9900]•[/color] " + Local white = "[color=$FFFFFF]" + Local yellow = "[color=$FFFF00]" + + ; Print infos to the screen + Local x, y = utilities.print("This test program will wait for the user input and will listen to the following events :") + x, y = utilities.print(bullet .. "The 'a' keypress", x, y) + x, y = utilities.print(bullet .. "The left mouse button press", x, y) + x, y = utilities.print(bullet .. "Any joystick button", x, y) + x, y = utilities.print("The animation is updated every 5 milliseconds and a timeout of 10 seconds will be set, " .. + "this means that if the user take no actions for 10 seconds the timeout callback function " .. + "will be called.", x, y) + y = y + 10 + x, y = utilities.print("This function is useful to halt the program execution waiting for the user input but still " .. + "to show animations or run background tasks. Very useful to show some informative text and " .. + "to let the program continue its flow.", x, y) + + y = y + 10 + x, y = utilities.print(yellow .. "Test will start after you click the left mouse button.", x, y) + WaitLeftMouse() + + y = y + 10 + x, y = utilities.print(yellow .. "Starting the test now...", x, y) + + ; Here is the utility command, the routine will wait for 10 seconds, + ; every 5 milliseconds the routine 'callback' will be called to + ; update a timer. The 'timeout' callback will be called if the + ; 10 seconds timeout is reached. + ; The program flow will continue if the user press he specified key + ; (in this case the 'a' key) or if the user pushes the left mouse + ; button, or if the timeout is reached. + Local result = HL.WaitForAction("a", 5, callback, 1000*10, timeout) + + y = y + 10 + If result + x, y = utilities.print(yellow .. "Nice, you have taken an action before the timeout!", x, y) + Else + x, y = utilities.print(yellow .. "Timeout occurred!", x, y) + EndIf + + y = y + 10 + x, y = utilities.print(yellow .. "Finished,", x, y) + + y = y + 10 + x, y = utilities.print(white .. "Click [b]Left Mouse Button[/b] to quit.", x, y) + + Wait(1, #SECONDS) + WaitLeftMouse() +EndFunction + +;============================================================================== + +Function example.TEST_StringFuncs() + ; The following functions will be tested: + ; HL.CutBetweenLimits() + ; HL.GetBetweenLimits() + ; HL.CutStringLeft() + ; HL.CutStringRight() + ; HL.SizeString + ; --------------------------------------------------------------------------- + Local fontName = #SANS + Local fontSize = 18 + Local title = "TEST : String Manipulation Functions" + + Local bodyArea, text_borders, utilities = + example.Render(title, + "This program tests the provided string manipulation functions.") + + SetFont(fontName, fontSize) + SetFontColor($DDDDDD) + SetFontStyle(#ANTIALIAS) + + ; Set some shortcuts + Local bullet = "[color=$FF9900]•[/color] " + Local white = "[color=$FFFFFF]" + Local yellow = "[color=$FFFF00]" + + ; Set a test string and print it to the screen + Local s1 = "Hello, this is a test string with some real Hollywood\n" .. + "tags [color=$ff0000]like this[/color] and some invented and\n" .. + "not interpreted by Hollywood {link}like this{/link} and" .. + "{link}this{/link}" + Local x, y = utilities.print(yellow .. "We are going to make a test on the following string:") + x, y = utilities.print(s1, x, y) + + ;::: HL.CutBetweenLimits() ::: + y = y + 50 + x, y = utilities.print(bullet .. yellow .. "[b]HL.CutBetweenLimits()[/b]", x, y) + x, y = utilities.print(" Cut all occurencies between two substrings, including the substring.", x, y) + + y = y + 8 + x, y = utilities.print("Trying to cut between [b]{link}[/b] and [b]{/link}[/b] substrings...", x, y) + text, removed = HL.CutBetweenLimits(s1, "{link}", "{/link}") + y = y + 8 + x, y = utilities.print(yellow .. "Returned text (notice the removed text from the source string):", x, y) + x, y = utilities.print(text, x, y) + y = y + 40 + x, y = utilities.print(yellow .. "Removed text:", x, y) + For i, v In Pairs(removed) Do x, y = utilities.print(" " .. bullet .. v, x, y) + + y = y + 20 + x, y = utilities.print("As you can see all [b]{link}anytext{/link}[/b] tags has been removed and returned in the result table.", x, y) + x, y = utilities.print("This function is very useful to parse custom tags.", x, y) + + y = y + 20 + x, y = utilities.print(yellow .. "Click [b]Left Mouse Button[/b] to continue.", x, y) + + WaitLeftMouse + + + ;::: HL.GetBetweenLimits() ::: + utilities.drawBody() + + ; Test string + Local s1 = "Hello, this is a test string with some real Hollywood\n" .. + "tags [color=$ff0000]like this[/color] and some invented and\n" .. + "not interpreted by Hollywood {link}like this{/link} and" .. + "{link}this{/link}" + Local x, y = utilities.print(yellow .. "We are going to make a test on the following string:") + x, y = utilities.print(s1, x, y) + + y = y + 50 + x, y = utilities.print(bullet .. yellow .. "[b]HL.GetBetweenLimits()[/b]", x, y) + x, y = utilities.print(" Returns all occurencies between two substrings, without the substrings, also returns the found text positions.", x, y) + y = y + 8 + x, y = utilities.print("Trying to get between [b]{link}[/b] and [b]{/link}[/b] substrings...", x, y) + text, positions = HL.GetBetweenLimits(s1, "{link}", "{/link}") + y = y + 8 + x, y = utilities.print(yellow .. "Returned text (notice the returned text without the tags):", x, y) + For i, v In Pairs(text) Do x, y = utilities.print(" " .. bullet .. "Position " .. i .. " : " .. v, x, y) + y = y + 15 + x, y = utilities.print(yellow .. "Positions (start and end positions for each text found including the tags):", x, y) + For i, v In Pairs(positions) + x, y = utilities.print(" " .. bullet .. "Positions " .. i, x, y) + For l, k In Pairs(v) Do x, y = utilities.print(" " .. bullet .. l .. " : " .. k, x, y) + Next + + y = y + 10 + x, y = utilities.print("This function is very useful to extract part from text, for example from html pages.", x, y) + + y = y + 20 + x, y = utilities.print(yellow .. "Click [b]Left Mouse Button[/b] to continue.", x, y) + + WaitLeftMouse + + + ;::: HL.CutStringLeft(), HL.CutStringRight() ::: + utilities.drawBody() + + ; Test string + Local s1 = "This is a test string long enough to execute our tests" + Local x, y = utilities.print(yellow .. "We are going to make a test on the following string:") + x, y = utilities.print(s1, x, y) + + y = y + 20 + x, y = utilities.print(bullet .. yellow .. "[b]HL.CutStringLeft()[/b]", x, y) + x, y = utilities.print(bullet .. yellow .. "[b]HL.CutStringRight()[/b]", x, y) + + x, y = utilities.print(" Cut the string to the left/right if its length is higher than a specified value.", x, y) + x, y = utilities.print(" To show that the string has been trunkated a triple dot (...) will be used.", x, y) + + SetFont(#MONOSPACE, fontSize) + SetFontColor($DDDDDD) + SetFontStyle(#ANTIALIAS) + y = y + 10 + x, y = utilities.print("[u]COLUMNS COUNT [/u]", x, y) + x, y = utilities.print(" 11111111112", x, y) + x, y = utilities.print("12345678901234567890", x, y) + x, y = utilities.print("--------------------", x, y) + x, y = utilities.print(yellow .. HL.CutStringLeft(s1, 17) .. white .. " HL.CutStringLeft()", x, y) + x, y = utilities.print(yellow .. HL.CutStringRight(s1, 17) .. white .. " HL.CutStringRight()", x, y) + x, y = utilities.print("--------------------", x, y) + + SetFont(fontName, fontSize) + SetFontColor($DDDDDD) + SetFontStyle(#ANTIALIAS) + y = y + 10 + x, y = utilities.print("Using these functions it's easy to constrict text in columns or in a delimited space.", x, y) + + y = y + 20 + x, y = utilities.print(yellow .. "Click [b]Left Mouse Button[/b] to continue.", x, y) + + WaitLeftMouse + + + ;::: HL.SizeString() ::: + utilities.drawBody() + + ; Test string + Local s1 = "This is a test string long enough to execute our tests" + Local s2 = "Short string" + Local x, y = utilities.print(yellow .. "We are going to make a test on the following strings:") + x, y = utilities.print(bullet .. s1, x, y) + x, y = utilities.print(bullet .. s2, x, y) + + y = y + 20 + x, y = utilities.print(bullet .. yellow .. "[b]HL.SizeString()[/b]", x, y) + x, y = utilities.print(" Adjust the string size to the specified length, if the string lenght is lower than the", x, y) + x, y = utilities.print(" specified length, blank spaces will be added to the end, if the string length si higher", x, y) + x, y = utilities.print(" than the specified length the string will be trunkated and a triple dot will be added.", x, y) + + SetFont(#MONOSPACE, fontSize) + SetFontColor($DDDDDD) + SetFontStyle(#ANTIALIAS) + y = y + 10 + x, y = utilities.print("[u]COLUMNS COUNT [/u]", x, y) + x, y = utilities.print(" 11111111112", x, y) + x, y = utilities.print("12345678901234567890", x, y) + x, y = utilities.print("--------------------", x, y) + x, y = utilities.print(yellow .. HL.SizeString(s1, 20) .. white .. "<- ends here" .. " Long string", x, y) + x, y = utilities.print(yellow .. HL.SizeString(s2, 20) .. white .. "<- ends here" .. " Short string", x, y) + x, y = utilities.print("--------------------", x, y) + + SetFont(fontName, fontSize) + SetFontColor($DDDDDD) + SetFontStyle(#ANTIALIAS) + y = y + 10 + x, y = utilities.print("Using these functions it's easy to constrict text in columns or in a delimited space.", x, y) + + y = y + 20 + x, y = utilities.print(yellow .. "Click [b]Left Mouse Button[/b] to quit.", x, y) + + WaitLeftMouse + + +EndFunction + +;============================================================================== + +Function example.TEST_Convert4TextOut() + ; --------------------------------------------------------------------------- + ; Test Convert.ForTextOut() + ; --------------------------------------------------------------------------- + Local fontName = #SANS + Local fontSize = 18 + Local title = "TEST : Convert for TextOut()..." + + Local bodyArea, text_borders, utilities = + example.Render(title, + "[b]HL.Convert.ForTextOut()[/b] is very handy to avoid errors with [b]TextOut()[/b] " .. + "when inside the string to print there are square brackets not part of the standard HW tags. " .. + "This function escape those square brackets leaving untouched all HW tags.") + + SetFont(fontName, fontSize) + SetFontColor($DDDDDD) + SetFontStyle(#ANTIALIAS) + + Local bullet = "[color=$FF9900]•[/color] " + Local white = "[color=$FFFFFF]" + Local yellow = "[color=$FFFF00]" + + Local x, y = utilities.print(white .. "Here is the source string, as you can see there are Hollywood tags " .. + "and text inside square brackets, those, if not escaped, cause " .. + "crashes because Hollywood does not recognize them :") + y = y + 10 + + ; Source string + Local source = "Color [color=#RED]RED[/color], Color [COLOR=#YELLOW]YELLOW[/COLOR], Style [b]BOLD[/b], text inside square [brackets]." + + ; Escape all square brackets to print the string to the screen + Local escaped = ReplaceStr(source, "[", "[[") + escaped = ReplaceStr(escaped, "]", "]]") + + ; Print it to the screen + x, y = utilities.print(escaped, x, y) + + y = y + 10 + x, y = utilities.print(white .. "If we use text out with this string the part [i][[brackets]][/i] will cause an error, let's use " .. + "[color=$FF8800]HL.Convert.ForText()[/color] function instead...", x, y) + ; Let's use the conversion function to avoid an error because of the + ; ...text inside square [brackets] <-- this cause the error + Local converted = HL.Convert.ForTextOut(source) + + ; Print it with no fear! + y = y + 10 + x, y = utilities.print(converted, x, y) + + y = y + 10 + x, y = utilities.print(white .. "As you can see only the unrecognized tags have been escaped!", x, y) + + y = y + 50 + x, y = utilities.print(yellow .. "Click [b]Left Mouse Button[/b] to continue.", x, y) + + WaitLeftMouse() + +EndFunction + +;============================================================================== + +Function example.TEST_HTML2Hollywood() + ; --------------------------------------------------------------------------- + ; Test Convert.HTML2Hollywood() + ; --------------------------------------------------------------------------- + Local fontName = #SANS + Local fontSize = 18 + Local title = "TEST : Convert for HTML to Hollywood..." + + Local bodyArea, text_borders, utilities = + example.Render(title, + "[b]HL.Convert.HTML2Hollywood()[/b] allow you to convert and render to the screen very simple " .. + "HTML pages.") + + SetFont(fontName, fontSize) + SetFontColor($DDDDDD) + SetFontStyle(#ANTIALIAS) + + Local bullet = "[color=$FF9900]•[/color] " + Local white = "[color=$FFFFFF]" + Local yellow = "[color=$FFFF00]" + + Local x, y = utilities.print(white .. "[b]HL.Convert.HTML2Hollywood()[/b] converts simple HTML pages to printable text " .. + "using [b]TextOut()[/b], here is a list of all supported entities :") + y = y + 10 + x, y = utilities.print(bullet .. "Slashes, double slashes and Tabs", x, y) + x, y = utilities.print(bullet .. "HTML tags (
,
, , , etc...)", x, y) + x, y = utilities.print(bullet .. "HTML ampersands (761 supported)", x, y) + x, y = utilities.print(bullet .. "Lists (ordered and unordered)", x, y) + x, y = utilities.print(bullet .. "Escaped unicodes (with \\uXXXX notation)", x, y) + x, y = utilities.print(yellow .. "This function does not yet handle basic tags like , <HEAD>, <BODY>, etc...", x, y) + + y = y + 10 + x, y = utilities.print(white .. "Here is a simple HTML page source we are going to convert :", x, y) + + ; Source string + Local source = +[[<em>⇒ Unordered list</em> +<ul> + <li>Coffee</li> + <li>Tea</li> + <li>Milk</li> +</ul> + +<b>⇒ Ordered HTML List</b> +<ol> + <li>Coffee</li> + <li>Tea</li> + <li>Milk</li> +</ol> +]] + + ; Print the source string + x, y = utilities.print(source, x, y) + + ; Click to continue + y = y + 200 + x, y = utilities.print(yellow .. "Click to see how it is converted and rendered using [color=#white]tahoma font[/color]...", x, y) + + WaitLeftMouse() + + ; Let's convert the source + SetFont("tahoma", fontSize) + SetFontColor($DDDDDD) + SetFontStyle(#ANTIALIAS) + + Local converted = HL.Convert.HTML2Hollywood(source) + utilities.drawBody() + x, y = utilities.print(yellow .. "Here is the result :") + + y = y + 10 + x, y = utilities.print(converted, x, y) + + y = y + 200 + x, y = utilities.print(yellow .. "Click [b]Left Mouse Button[/b] to continue.", x, y) + + WaitLeftMouse() + +EndFunction + +;============================================================================== + +Function example.TEST_BufferedStrings() + ; --------------------------------------------------------------------------- + ; Test BufferedString Object + ; --------------------------------------------------------------------------- + ; Setup some variables to handle the output + Local fontName = #SANS + Local fontSize = 18 + Local title = "TEST : Buffered Strings" + + Local bodyArea, text_borders, utilities = + example.Render(title, + "This program show how buffered strings objects work, and how much you " .. + "can improve code execution speed using this object if you have to concatenate " .. + "strings character by character. You will be surprised!") + + SetFont(fontName, fontSize) + SetFontColor($DDDDDD) + SetFontStyle(#ANTIALIAS) + + Local bullet = "[color=$FF9900]•[/color] " + Local white = "[color=$FFFFFF]" + Local yellow = "[color=$FFFF00]" + x, y = utilities.print(bullet .. "Building a 250.000 characters string, one char at a time, be patient...") + + ; Concatenate 250000 characters, one character at a time + Local s = "" + StartTimer(1) + For Local i = 1 To 250000 Do s = s .. Chr(Rnd(15)+65) + x, y = utilities.print(" Elapsed : " .. white .. GetTimer(1) .. "ms", x, y) + x, y = utilities.print(" String Len : " .. white .. StrLen(s) .. " characters", x, y) + y = y + 8 + + ; Concatenate 250000 characters using BuffereStrings object + x, y = utilities.print(bullet .. "Building the same 250000 characters string using [b]BufferedString[/b] object...", x, y) + Local so = HL.BufferedString:New() + StartTimer(1) + For Local i = 1 To 250000 Do so:AddChar(Chr(Rnd(15)+65)) + x, y = utilities.print(" Elapsed : " .. yellow .. GetTimer(1) .. "ms", x, y) + x, y = utilities.print(" String Len : " .. yellow .. StrLen(s) .. " characters", x, y) + y = y + 8 + + ; Preparing the BufferedString for in-middle readings + x, y = utilities.print(bullet .. "Preparing the BufferedString for read...", x, y) + StartTimer(1) + so:PrepareForRead() + x, y = utilities.print(" Elapsed : " .. yellow .. GetTimer(1) .. "ms", x, y) + y = y + 8 + s = so:Get() + + ; Read in-middle strings using MidStr() + x, y = utilities.print(bullet .. "Reading using MidStr(), 20.000 times...", x, y) + StartTimer(1) + For Local i = 0 To 20000 + Local a = MidStr(s, 350+i, 400+i) + Next + x, y = utilities.print(" Elapsed : " .. white .. GetTimer(1) .. "ms", x, y) + y = y + 8 + + ; Read in-middle strings using BufferedString's :Read() method + x, y = utilities.print(bullet .. "Reading using :Read() method, 20.000 times...", x, y) + StartTimer(1) + For Local i = 0 To 20000 + Local a = so:Read(350+i, 400+i) + Next + x, y = utilities.print(" Elapsed : " .. yellow .. GetTimer(1) .. "ms", x, y) + y = y + 8 + + ; Check if in-middle string reading is correct + x, y = utilities.print(bullet .. "Checking random reads to check if :Read() method is exactly the same as MidStr() x20.000 times...", x, y) + For Local i = 0 To 20000 + Local rstart, rlen = Rnd(20000), Rnd(2000) + Local mstring1 = MidStr(s, rstart, rlen) + Local mstring2 = so:Read(rstart, rlen) + If mstring1 <> mstring2 + DebugPrint(i .. " >> ERROR! start : " .. rstart .. ", len : " .. rlen) + DebugPrint(mstring1) + DebugPrint(mstring2) + EndIf + Next + + ; Finish the test + so = Nil + y = y + 16 + x, y = utilities.print(yellow .. "BufferedString object are much faster!", x, y) + y = y + 16 + + x, y = utilities.print(white .. "Click [b]Left Mouse Button[/b] to quit.", x, y) + + WaitLeftMouse() +EndFunction + +;============================================================================== + +Function example.TEST_Colors() + ; --------------------------------------------------------------------------- + ; Test Color object + ; --------------------------------------------------------------------------- + Local fontName = #SANS + Local fontSize = 18 + Local title = "TEST : Color Object" + + Local bodyArea, text_borders, utilities = + example.Render(title, + "This program show how color objects can be used. They are very useful if you have basic colors " .. + "and want to build variations, darker or brighter colors based on them.") + + SetFont(fontName, fontSize) + SetFontColor($DDDDDD) + SetFontStyle(#ANTIALIAS) + + Local bullet = "[color=$FF9900]•[/color] " + Local white = "[color=$FFFFFF]" + Local yellow = "[color=$FFFF00]" + + ; Creation methods + x, y = utilities.print(bullet .. "Testing color creation methods...") + Local color1 = HL.Color:New({ r = 125, g = 100, b = 255 }) + Local color2 = HL.Color:New() + color2:fromValue($22FF88) + Local color3 = HL.Color:New() + color3:fromARGB(100, 0, 255, 255) + + ; Conversion methods + x, y = utilities.print(" Color 1 : " .. "[color =" .. color1:toARGBValue() .. "]" .. color1:toARGBValue(), x, y) + x, y = utilities.print(" Color 2 : " .. "[color =" .. color2:toARGBValue() .. "]" .. color2:toARGBValue(), x, y) + x, y = utilities.print(" Color 3 : " .. "[color =" .. color3:toARGBValue() .. "]" .. color3:toARGBValue(), x, y) + + ; :Darken() and :Brighten() methods + color1:Darken(160) + x, y = utilities.print(bullet .. "Darkening color 1 by 160 : " .. "[color =" .. color1:toARGBValue() .. "]" .. color1:toARGBValue(), x, y) + + color2:Brighten(140) + x, y = utilities.print(bullet .. "Brightening color 2 by 140 : " .. "[color =" .. color2:toARGBValue() .. "]" .. color2:toARGBValue(), x, y) + + ; Make shades using :Darken() & :Brighten() + x, y = utilities.print(bullet .. "Building 2 shaded boxes using line, :Darken() and :Brighten() methods... ", x, y) + For Local i = 200 To 400 + ; Box 1 + color3:Darken(1) + Line(600, i-150, 770, i-150, color3:toRGBValue()) + + ; Box 2 + color1:Brighten(1) + Line(600, i+65, 770, i+65, color1:toRGBValue()) + Next + + ; Finish the test + color1, color2, color3 = Nil, Nil, Nil + + y = y + 16 + x, y = utilities.print(yellow .. "Finished.", x, y) + y = y + 16 + + x, y = utilities.print(white .. "Click [b]Left Mouse Button[/b] to quit.", x, y) + + WaitLeftMouse() +EndFunction + +;============================================================================== + +Function example.TEST_ConvertBytes() + ; --------------------------------------------------------------------------- + ; Test ConvertBytes + ; --------------------------------------------------------------------------- + Local fontName = #SANS + Local fontSize = 18 + Local title = "TEST : Convert Bytes To..." + + Local bodyArea, text_borders, utilities = + example.Render(title, + "This program shows how you can use the function [b]HL.Convert.BytesTo()[/b] to " .. + "convert bytes to Kilobytes, Megabytes, Gigabytes, etc...") + + SetFont(fontName, fontSize) + SetFontColor($DDDDDD) + SetFontStyle(#ANTIALIAS) + + Local bullet = "[color=$FF9900]•[/color] " + Local white = "[color=$FFFFFF]" + Local yellow = "[color=$FFFF00]" + + Local x, y = utilities.print(white .. "Generating and converting some random values, all convertions will have 3 decimals (this value can be configured)...") + y = y + 10 + + Local decimals = 3 + For Local i = 0 To 4 + value = Int(RndF()*(10^(7+Rnd(3)))) + x, y = utilities.print(bullet .. "Bytes to convert : " .. yellow .. value, x, y) + x, y = utilities.print(" " .. bullet .. HL.Convert.BytesTo(value, #HL_KILOBYTES, decimals) .. "Kb " .. bullet .. HL.Convert.BytesTo(value, #HL_MEGABYTES, decimals) .. "Mb", x, y) + x, y = utilities.print(" " .. bullet .. HL.Convert.BytesTo(value, #HL_GIGABYTES, decimals) .. "Gb " .. bullet .. HL.Convert.BytesTo(value, #HL_TERABYTES, decimals) .. "Tb", x, y) + x, y = utilities.print(" " .. bullet .. "Aute scale: " .. HL.Convert.BytesTo(value, #HL_AUTO, decimals), x, y) + y = y + 5 + Next + + x, y = utilities.print(yellow .. "Finished.", x, y) + x, y = utilities.print(white .. "Click [b]Left Mouse Button[/b] to continue.", x, y) + + WaitLeftMouse() + +EndFunction + +;============================================================================== + +Function example.TEST_GetRandomNames() + ; --------------------------------------------------------------------------- + ; Test GetRandomNames + ; --------------------------------------------------------------------------- + Local fontName = #SANS + Local fontSize = 18 + Local title = "TEST : GetRndName()..." + + Local bodyArea, text_borders, utilities = + example.Render(title, + "If you need to generate unique names to name your objects you can use the " .. + "function [b]HL.GetRndName()[/b], it will keep track of already generated " .. + "strings to avoid duplicates.") + + SetFont(fontName, fontSize) + SetFontColor($DDDDDD) + SetFontStyle(#ANTIALIAS) + + Local bullet = "[color=$FF9900]•[/color] " + Local white = "[color=$FFFFFF]" + Local yellow = "[color=$FFFF00]" + + Local x, y = utilities.print(white .. "Generating random names...") + y = y + 10 + + For Local i = 0 To 18 + b, a = utilities.print(bullet .. white .. HL.GetRndName(), x, y) + x, a = utilities.print(bullet .. white .. HL.GetRndName(), x+200, y) + x, a = utilities.print(bullet .. white .. HL.GetRndName(), x+200, y) + x, y = utilities.print(bullet .. white .. HL.GetRndName(), x+200, y) + x = b + Next + + y = y + 10 + x, y = utilities.print(yellow .. "Finished.", x, y) + x, y = utilities.print(white .. "Click [b]Left Mouse Button[/b] to continue.", x, y) + + WaitLeftMouse() + +EndFunction + +;============================================================================== + +Function example.TEST_Value2Perc() + ; --------------------------------------------------------------------------- + ; Test Value2Perc + ; --------------------------------------------------------------------------- + Local fontName = #SANS + Local fontSize = 18 + Local title = "TEST : Value2Perc()..." + + Local bodyArea, text_borders, utilities = + example.Render(title, + "Do you need to convert a specific value into a percentual within a range? " .. + "the function [b]HL.Value2Perc()[/b] is for you!") + + SetFont(fontName, fontSize) + SetFontColor($DDDDDD) + SetFontStyle(#ANTIALIAS) + + Local bullet = "[color=$FF9900]•[/color] " + Local white = "[color=$FFFFFF]" + Local yellow = "[color=$FFFF00]" + + Local x, y = utilities.print(white .. "Here are some examples :") + y = y + 10 + + SetFont(#MONOSPACE, fontSize) + SetFontColor($DDDDDD) + SetFontStyle(#ANTIALIAS) + + x, y = utilities.print(bullet .. "Range ( 1: 100), Value = 89, % = " .. yellow .. HL.Value2Perc({ 1, 100 }, 89)*100 .. "%", x, y) + x, y = utilities.print(bullet .. "Range ( 21: 120), Value = 109, % = " .. yellow .. HL.Value2Perc({ 21, 120 }, 109)*100 .. "%", x, y) + x, y = utilities.print(bullet .. "Range ( 40:1500), Value = 91, % = " .. yellow .. HL.Value2Perc({ 40, 1500 }, 91)*100 .. "%", x, y) + x, y = utilities.print(bullet .. "Range ( 5: 70), Value = 104, % = " .. yellow .. HL.Value2Perc({ 5, 70 }, 104)*100 .. "%", x, y) + + SetFont(fontName, fontSize) + SetFontColor($DDDDDD) + SetFontStyle(#ANTIALIAS) + + y = y + 10 + x, y = utilities.print(yellow .. "Finished.", x, y) + x, y = utilities.print(white .. "Click [b]Left Mouse Button[/b] to continue.", x, y) + + WaitLeftMouse() + +EndFunction + +;============================================================================== + +Function example.TEST_GetRndColor() + ; --------------------------------------------------------------------------- + ; Test GetRndColor() + ; --------------------------------------------------------------------------- + Local fontName = #SANS + Local fontSize = 18 + Local title = "TEST : GetRndColor()..." + + Local bodyArea, text_borders, utilities = + example.Render(title, + "The function [b]HL.GetRndColor()[/b] returns a random color " .. + "with an optional random alpha to obtain random colors with transparency.") + + SetFont(fontName, fontSize) + SetFontColor($DDDDDD) + SetFontStyle(#ANTIALIAS) + + Local bullet = "[color=$FF9900]•[/color] " + Local white = "[color=$FFFFFF]" + Local yellow = "[color=$FFFF00]" + + Local x, y = utilities.print(white .. "Here are some boxes using random colors with transparency :") + y = y + 10 + + SetFillStyle(#FILLCOLOR) + SetFormStyle(#NORMAL) + + For Local i = 0 To 200 + ; Compute random positions and random sizes + Local x, y = Rnd(500) + bodyArea.x + 50, Rnd(300) + bodyArea.y + 50 + Local w, h = Rnd(180) + bodyArea.x + 50, Rnd(50) + bodyArea.y + 10 + Box(x, y, w, h, HL.GetRndColor(True)) + Wait(25, #MILLISECONDS) + Next + + SetFont(fontName, fontSize) + SetFontColor($DDDDDD) + SetFontStyle(#ANTIALIAS) + + y = bodyArea.y + bodyArea.h - 60 + x, y = utilities.print(yellow .. "Finished.", x, y) + x, y = utilities.print(white .. "Click [b]Left Mouse Button[/b] to continue.", x, y) + + WaitLeftMouse() + +EndFunction + +;============================================================================== + +Function example.TEST_CheckKeyboard() + ; --------------------------------------------------------------------------- + ; Test CheckKeyboard + ; --------------------------------------------------------------------------- + Local fontName = #SANS + Local fontSize = 18 + Local title = "TEST : CheckKeyboard()..." + + Local bodyArea, text_borders, utilities = + example.Render(title, + "This function waits for a keypress from a list of allowed keys. It can check " .. + "and exit immediatly or it can wait for a valid keypress, also it can " .. + "wait until the pressed key is released before returning.") + + SetFont(fontName, fontSize) + SetFontColor($DDDDDD) + SetFontStyle(#ANTIALIAS) + + Local bullet = "[color=$FF9900]•[/color] " + Local white = "[color=$FFFFFF]" + Local yellow = "[color=$FFFF00]" + + Local x, y = utilities.print(white .. "Here are some tests :") + y = y + 10 + + x, y = utilities.print(bullet .. "Wait for one of the following key to be pressed : a, t, W, Left Shift ", x, y) + Local pressedKey = HL.Input.CheckKeyboard({ "a", "t", "W", "LSHIFT" }, True, False) + x, y = utilities.print(" You have pressed : " .. yellow .. pressedKey, x, y) + + x, y = utilities.print(bullet .. "Wait for one of the following key to be pressed : b, c, R, Right Shift ", x, y) + x, y = utilities.print(" (Waits until the key is released)", x, y) + Local pressedKey = HL.Input.CheckKeyboard({ "b", "c", "R", "RSHIFT" }, True, True) + x, y = utilities.print(" You have pressed : " .. yellow .. pressedKey, x, y) + + x, y = utilities.print(bullet .. "Same as the first example, but using a loop :", x, y) + Local pressedKey = "" + While pressedKey = "" + pressedKey = HL.Input.CheckKeyboard({ "a", "t", "W", "LSHIFT" }, False, False) + Circle(x+600, y, 10, GetRandomColor()) + Wait(10, #MILLISECONDS) ; <= Do not overload the CPU + Wend + x, y = utilities.print(" You have pressed : " .. yellow .. pressedKey, x, y) + + + y = y + 10 + x, y = utilities.print(yellow .. "Finished.", x, y) + x, y = utilities.print(white .. "Click [b]Left Mouse Button[/b] to continue.", x, y) + + WaitLeftMouse() + +EndFunction + +;HL.TEST_AmperConversion() +;HL.TEST_WaitForAction() +;HL.TEST_StringFuncs() +;HL.TEST_BufferedStrings() +;HL.TEST_Colors() +; DA FARE : HL.Convert.BytesTo() +; DA FARE : HL.Convert.ForTextOut() +; DA FARE : HL.GetRndName(), HL.GetRndColor() +; DA FARE : HL.Input.CheckJoystick(), HL.Input.CheckKeyboard() +; DA FARE : HL.LineHook.Enable() / .Disable() +; DA FARE : HL.Value2Perc() +; SISTEMARE UTILITIES.PRINT PER FARGLI SUPPORTARE IL WORDWRAP, CON LA DIMENSIONE TOTALE DELLA STRINGA +; RIESCO A CAPIRE QUANTE RIGHE MI OCCUPA IL TESTO WORDWRAPPATO. + + + + + + + + + + + + + + + +; Let's define a MENU +Global menu = + { [0] = { name = "Amper Conversion", func = example.Test_AmperConversion }, + [1] = { name = "Wait For Action", func = example.Test_WaitForAction }, + [2] = { name = "String Functions", func = example.Test_StringFuncs }, + [3] = { name = "Buffered Strings", func = example.Test_BufferedStrings }, + [4] = { name = "Color Object", func = example.Test_Colors }, + [5] = { name = "Convert Bytes to...", func = example.Test_ConvertBytes }, + [6] = { name = "Get Random Name", func = example.Test_GetRandomNames }, + [7] = { name = "Value 2 Perc", func = example.Test_Value2Perc }, + [8] = { name = "Get Random Color", func = example.Test_GetRndColor }, + [9] = { name = "Check Keyboard", func = example.TEST_CheckKeyboard }, + [10] = { name = "Convert for TextOut", func = example.TEST_Convert4TextOut }, + [11] = { name = "HTML 2 Hollywood", func = example.TEST_HTML2Hollywood }, + [12] = { name = "[color=$88FF00][b]QUIT[/b][/color]", func = Function() End EndFunction } + } + +Local body, txtBord, utils = example.Render("HELPERS LIBRARY EXAMPLES", "Type the example number and hit ENTER to execute it.") +utils.drawMenu(menu) + +WaitLeftMouse() \ No newline at end of file diff --git a/Examples/Helpers/HL_ParseRunArgs.hws b/Examples/Helpers/HL_ParseRunArgs.hws new file mode 100644 index 0000000..9080efe --- /dev/null +++ b/Examples/Helpers/HL_ParseRunArgs.hws @@ -0,0 +1,22 @@ +/* +*********************************************** +*** Helpers.hws Example : HL.ParseRunArgs() *** +*********************************************** +************************************************************************ +*** THIS EXAMPLE MUST BE COMPILED AND EXECUTED USING THE COMMANDLINE *** +*** WITH SOME Argument - value PAIRS *** +*** Example: prompt>compiled.exe -param1 first -param2 second *** +************************************************************************ +*/ + +@INCLUDE "../../+Includes.hws" +@INCLUDE #INC_HELPERS + +NPrint("HL.ParseRunArgs Example") +NPrint("---------------------\n") + +Local Arguments = HL.ParseRunArgs(False) +ForEach(Arguments, NPrint) + +NPrint("\n--- Left mouse button to quit ---") +WaitLeftMouse() diff --git a/Examples/Helpers/HL_Safe.hws b/Examples/Helpers/HL_Safe.hws new file mode 100644 index 0000000..2e29418 --- /dev/null +++ b/Examples/Helpers/HL_Safe.hws @@ -0,0 +1,18 @@ +/* +*************************************** +*** Helpers.hws Example : HL.Safe() *** +*************************************** +*/ + +@INCLUDE "../../+Includes.hws" +@INCLUDE #INC_HELPERS + +NPrint("HL.Safe Example") +NPrint("---------------\n") + +Local Uninitialized = Nil +Local initialized = "Hello!" +NPrint(HL.Safe(Uninitialized) .. " - " .. HL.Safe(Initialized)) + +NPrint("\n--- Left mouse button to quit ---") +WaitLeftMouse() diff --git a/Examples/Helpers/HL_SizeString.hws b/Examples/Helpers/HL_SizeString.hws new file mode 100644 index 0000000..0d0b8f8 --- /dev/null +++ b/Examples/Helpers/HL_SizeString.hws @@ -0,0 +1,40 @@ +/* +********************************************* +*** Helpers.hws Example : HL.SizeString() *** +********************************************* +*/ + +@INCLUDE "../../+Includes.hws" +@INCLUDE #INC_HELPERS + +NPrint("HL.SizeString Example") +NPrint("---------------------\n") + +; Suppose you have 40 character screen and a table with <n> entries with the +; fields <name> and <surnames>, you want to print a table with these +; informations: + +; Initialize the table with some items +Local people = + { { name = "Clark", surname = "Kent" }, + { name = "Peter", surname = "Parker" }, + { name = "Bruce", surname = "Wayne" } } + +; Now we will print a nice table to the screen with the first column of 14 +; characters and the second one of 22 (plus 3 separators): + +; Header +NPrint("|" .. HL.SizeString("NAME", 14) .. "|" .. HL.SizeString("SURNAME", 22) .. "|") + +; Contents +For Local i = 0 To 2 + NPrint("|" .. HL.SizeString(people[i].name, 14) .. + "|" .. HL.SizeString(people[i].surname, 22) .. "|") +Next + +; Exemple with string exceeding the available space +NPrint("|" .. HL.SizeString("This is an extremely long line!", 14) .. + "|" .. HL.SizeString("Another line, see how it works?", 22) .. "|") + +NPrint("\n--- Left mouse button to quit ---") +WaitLeftMouse() diff --git a/Examples/Helpers/HL_Value2Perc.hws b/Examples/Helpers/HL_Value2Perc.hws new file mode 100644 index 0000000..acd7674 --- /dev/null +++ b/Examples/Helpers/HL_Value2Perc.hws @@ -0,0 +1,19 @@ +/* +********************************************* +*** Helpers.hws Example : HL.Value2Perc() *** +********************************************* +*/ + +@INCLUDE "../../+Includes.hws" +@INCLUDE #INC_HELPERS + +NPrint("HL.Value2Perc Example") +NPrint("---------------------\n") + +NPrint("1) Range=( 1: 100), Value= 89, Converted=" .. HL.Value2Perc({ 1, 100}, 89)) +NPrint("2) Range=(21: 120), Value=109, Converted=" .. HL.Value2Perc({21, 120}, 109)) +NPrint("3) Range=(40:1500), Value= 91, Converted=" .. HL.Value2Perc({40, 1500}, 91)) +NPrint("4) Range=( 5: 70), Value=104, Converted=" .. HL.Value2Perc({ 5, 70}, 104)) + +NPrint("\n--- Left mouse button to quit ---") +WaitLeftMouse() diff --git a/Examples/Helpers/HL_WaitForAction.hws b/Examples/Helpers/HL_WaitForAction.hws new file mode 100644 index 0000000..7576bbe --- /dev/null +++ b/Examples/Helpers/HL_WaitForAction.hws @@ -0,0 +1,33 @@ +/* +************************************************ +*** Helpers.hws Example : HL.WaitForAction() *** +************************************************ +*/ + +@INCLUDE "../../+Includes.hws" +@INCLUDE #INC_HELPERS + +Function update_callback() + If IsKeyDown("SPACE") + Return(True) + Else + Return(False) + EndIf + +EndFunction + +Function timeout_callback() + NPrint("TIMED OUT!!!") + +EndFunction + +NPrint("HL.WaitForAction Example") +NPrint("------------------------\n") + +NPrint("Hold down the 'p' key or the left mouse button, timeout is 10 seconds...") +NPrint("SPACE key is used to break the wait loop.") +HL.WaitForAction("p", 25, update_callback, 10000, timeout_callback, Nil) + +NPrint("\n--- Left mouse button to quit ---") +Wait(50) +WaitLeftMouse() diff --git a/Helpers.hws b/Helpers.hws new file mode 100644 index 0000000..83bf56a --- /dev/null +++ b/Helpers.hws @@ -0,0 +1,1711 @@ + /******************************************************************** + * EASING LIBRARY * + * Author : Fabio Falcucci (Allanon) * + * License : MIT * + * Version : 1.7 * + * Release : 03/05/2025 * + * Dependancies : - * + * * + * PayPal Support hijoe@tin.it * + * Support me on Patreon! https://www.patreon.com/Allanon71 * + * Bitcoin https://coindrop.to/allanon * + * * + * Github repo (leaving) https://github.com/Allanon71 * + * Gitea repo (updated) https://gitea.it/allanon/HollywoodLibs * + * ------------------------------------------------------------------ + */ + + /******************************************************************** + * Helper library is an include file for Hollywood that adds several + * common task functions. + * + * This include objects and methods to handle efficiently strings, + * colors, some basic functions to handle HTML code and many more. + * ------------------------------------------------------------------ + +CONTENTS +======== +:: BUFFERED STRING OBJECT :: +HL.BufferedString:AddChar() +HL.BufferedString:AddString() +HL.BufferedString:Get() +HL.BufferedString:New() +HL.BufferedString:PrepareForRead() +HL.BufferedString:Read() +HL.BufferedString:Set() + +:: COLOR OBJECT :: +HL.Color:Brighten() +HL.Color:Clone() +HL.Color:Darken() +HL.Color:New() +HL.Color:fromARGB() +HL.Color:fromValue() +HL.Color:toARGB() +HL.Color:toRGB() +HL.GetRndColor() + +:: STRINGS :: +HL.Capitalize() +HL.CutBetweenLimits() +HL.CutStringLeft() +HL.CutStringRight() +HL.GetBetweenLimits() +HL.GetReversedDateTime() +HL.GetRndName() +HL.SizeString() + +:: NUMBERS :: +HL.RoundBig() +HL.Value2Perc() +HL.FormatNum(v, i, d) + +:: CONVERSIONS :: +HL.Convert.BytesTo() +HL.Convert.ForTextout() +HL.Convert.HTML2Hollywood() +HL.Convert.HTMLAmper2UTF8() +HL.Convert.HTMLTag2HollywoodTag() +HL.Convert.Unicode2UTF8() + +:: INPUT :: +HL.Input.CheckJoystick() +HL.Input.CheckKeyboard() +HL.WaitForAction() + +:: MISC :: +HL.IsNil() +HL.IsNotNil() +HL.LineHook.Enable() +HL.LineHook.Disable() +HL.NBWait() +HL.ParseRunArgs() +HL.RestartApp() + +---- CHANGE LOG ------------------------------------------------ +03/05/2025 + FIX : Fixed HL.NBWait(), the timer used was never stopped. +19/09/2020 + ADD : HL.RoundBig() now accept a third parameter to round up or down +03/09/2020 + ADD : HL.FormatNum(v, i, d) +07/08/2020 + ADD : HL.RestartApp() +04/07/2020 + ADD : HL.Capitalize(txt) +17/06/2020 + ADD : HL.NBWait(ms) +06/05/2020 + ADD : HL.RoundBig(value, decimals) +23/02/2019 + ADD : Added 761 Ampersands symbols +12.02/2019 + FIX : Fixed a bug in HL.Convert.HTMLAmper2UTF8() function +02.06.2018 + NEW : HL.BufferedString:PrepareForRead() + NEW : HL.BufferedString:Read() +07.04.2017 + NEW : HL.GetBetweenLimits(txt, sLimit, eLimit, caseSense) + Added <caseSense> for the tags search (default = False) + FIX : HL.CutBetweenLimits(txt, sLimit, eLimit, tags) Fixed +16.02.2017 + NEW : #HL_AUTO Added to HL.Convert.BytesTo() +20.12.2016 + NEW : Added HL.LineHook.Enable() / .Disable() to allow nested + uses of this function. +13.12.2016 + FIX : Fixed a bug in HL.WaitForAction(), when a joystick was detected + the timeout will never be reached. +*********************************************************************/ + +;---[ MAIN DATA STRUCTURES ]--------------------------------------------------- +Global HL = {} + HL.Version = "1.7" + HL.Build = "07.05.2020" + HL.Convert = {} + HL.RndNames = {} + HL.HTML_Tags = { + ["<div>"] = "\n", + ["</div>"] = "", + ["<br>"] = "\n", + ["<b>"] = "[b]", + ["</b>"] = "[/b]", + ["<i>"] = "[i]", + ["</i>"] = "[/i]", + ["<u>"] = "[u]", + ["</u>"] = "[/u]", + ["<em>"] = "[b]", + ["</em>"] = "[/b]", + ["<small>"] = "", + ["</small>"] = "", + ["<strong>"] = "[u]", + ["</strong>"] = "[/u]", + ["<sub>"] = "", + ["</sub>"] = "", + ["<sup>"] = "", + ["</sup>"] = "", + ["<ins>"] = "", + ["</ins>"] = "", + ["<del>"] = "", + ["</del>"] = "", + ["<mark>"] = "[b][u]", + ["</mark>"] = "[/u][/b]", + ["<p>"] = "", + ["</p>"] = "", + ["<span>"] = "", + ["</span>"] = "" } + + HL.HTML_Amper = { + ; https://www.toptal.com/designers/htmlarrows/arrows/ + ; ::: ARROWS ::: + ["←"] = "2190", ["↑"] = "2191", ["→"] = "2192", + ["↓"] = "2193", ["↔"] = "2194", ["↕"] = "2196", + ["↖"] = "2197", ["↗"] = "2197", ["↘"] = "2198", + ["↙"] = "2199", ["↚"] = "219a", ["↛"] = "219b", + ["&larrw;"] = "219c", ["↝"] = "219d", ["↞"] = "219e", + ["↟"] = "219f", ["↠"] = "21a0", ["↡"] = "21a1", + ["↢"] = "21a2", ["↣"] = "21a3", ["↤"] = "21a4", + ["↥"] = "21a5", ["&mapstoright;"] = "21a6", ["↧"] = "21a7", + ["↩"] = "21a9", ["↪"] = "21aa", ["↫"] = "21ab", + ["↬"] = "21ac", ["↭"] = "21ad", ["↮"] = "21ae", + ["↰"] = "21b0", ["&rhs;"] = "21b1", ["↲"] = "21b2", + ["↳"] = "21b3", ["↵"] = "21b5", ["↶"] = "21b6", + ["↷"] = "21b7", ["↺"] = "21ba", ["&ararr;"] = "21bb", + ["↼"] = "21bc", ["↽"] = "21bd", ["↾"] = "21be", + ["↿"] = "21bf", ["⇀"] = "21c0", ["⇁"] = "21c1", + ["⇂"] = "21c2", ["⇃"] = "21c3", ["⇄"] = "21c4", + ["⇅"] = "21c5", ["⇆"] = "21c6", ["⇇"] = "21c7", + ["⇈"] = "21c8", ["⇉"] = "21c9", ["⇊"] = "21ca", + ["⇋"] = "21cb", ["⇌"] = "21cc", ["⇍"] = "21cd", + ["⇎"] = "21ce", ["⇏"] = "21cf", ["⇐"] = "21d0", + ["⇑"] = "21d1", ["⇒"] = "21d2", ["⇓"] = "21d3", + ["⇔"] = "21d4", ["⇕"] = "21d5", ["⇖"] = "21d6", + ["⇗"] = "21d7", ["⇘"] = "21d8", ["⇙"] = "21d9", + ["⇚"] = "21da", ["⇛"] = "21db", ["&ziglarr;"] = "21dc", + ["⇝"] = "21dd", ["⇤"] = "21e4", ["⇥"] = "21e5", + ["⇵"] = "21f5", ["⇽"] = "21fd", ["⇾"] = "21fe", + ["⇿"] = "21ff", ["⟵"] = "27f5", ["⟶"] = "27f6", + ["⟷"] = "27f7", ["⟸"] = "27f8", ["⟹"] = "27f9", + ["⟺"] = "27fa", ["⟼"] = "27fc", ["⟿"]="27ff", + ["⤂"] = "2902", ["⤃"] = "2903", ["⤄"] = "2904", + ["⤅"] = "2905", ["⤌"] = "290c", ["⤍"] = "290d", + ["⤎"] = "290e", ["⤏"] = "290f", ["⤐"] = "2910", + ["⤑"]="2911", ["⤒"]="2912",["⤓"]="2913", + ["⤖"] = "2916", ["⤙"] = "2919", ["⤚"] = "291a", + ["⤛"] = "291b", ["⤜"] = "291c", ["⤝"] = "291d", + ["⤞"] = "291e", ["⤟"]= "291f", ["⤠"]= "2920", + ["⤣"] = "2923", ["⤤"] = "2924", ["⤥"] = "2925", + ["⤦"] = "2926", ["⤧"] = "2927", ["⤨"] = "2928", + ["⤩"] = "2929", ["⤪"] = "292a", ["⤳"] = "2933", + ["⤵"]= "2935", ["⤶"] = "2936", ["⤷"] = "2937", + ["⤸"]= "2938", ["⤹"] = "2939", ["⤼"]= "293c", + ["⤽"]= "293d", ["⥅"] = "2945", ["⥈"]= "2948", + ["⥉"]="2949", ["⥊"]="294a", ["⥋"]="294b", + ["⥎"]="294e", ["⥏"]="294f", ["⥐"]="2950", + ["⥑"]="2951", ["⥒"]="2952", ["⥓"]="2953", + ["⥔"]="2954", ["⥕"]="2955", ["⥖"]="2956", + ["⥗"]="2957", ["⥘"]="2958", ["⥙"]="2959", + ["⥚"]="295a", ["⥛"]="295b", ["⥜"]="295c", + ["⥝"]="295d", ["⥞"]="295e", ["⥟"]="295f", + ["⥠"]="2960", ["⥡"]="2961", ["⥢"]="2962", + ["⥣"] = "2963", ["⥤"] = "2964", ["⥥"] = "2965", + ["⥦"]= "2966", ["⥧"]= "2967", ["⥨"] = "2968", + ["⥩"] = "2969",["⥪"] = "296a", ["⥫"] = "296b", + ["⥬"] = "296c", ["⥭"] = "296d", ["⥮"] = "296e", + ["⥯"] = "296f", ["⥰"] = "2970", ["⥱"] = "2971", + ["⥲"] = "2972", ["⥳"] = "2973", ["⥴"] = "2974", + ["⥵"] = "2975", ["⥶"] = "2976", ["⥸"] = "2978", + ["⥹"] = "2979", ["⥻"] = "297b", ["⥼"] = "297c", + ["⥽"] = "297d", ["⥾"] = "297e", ["⥿"] = "297f", + + ; ::: CURRENCIES ::: + ["$"] = "0024", ["¢"] = "00a2", ["£"] = "00a3", ["€"] = "00ac", + ["¥"] = "00a5", ["¤"] = "00a4", + + ; ::: LETTERS ::: + ["À"] = "00c0", ["Á"] = "00c1", ["Â"] = "00c2", ["Ã"] = "00c3", + ["Ä"] = "00c4", ["Å"] = "00c5", ["Æ"] = "00c6", ["Ç"] = "00c7", + ["È"] = "00c8", ["É"] = "00c9", ["Ê"] = "00ca", ["Ë"] = "00cb", + ["&Lgrave;"] = "00cc", ["Ĺ"] = "00cd", ["&Lcirc;"] = "00ce", ["&Luml;"] = "00cf", + ["Ð"] = "00d0", ["Ñ"] = "00d1", ["Ò"] = "00d2", ["Ó"] = "00d3", + ["Ô"] = "00d4", ["Õ"] = "00d5", ["Ö"] = "00d6", ["Ø"] = "00d7", + ["Ù"] = "00d9", ["Ú"] = "00da", ["Û"] = "00db", ["Ü"] = "00dc", + ["Ý"] = "00dd", ["Þ"] = "00de", ["ß"] = "00df", ["à"] = "00e0", + ["á"] = "00e1", ["â"] = "00e2", ["ã"] = "00e3", ["ä"] = "00e4", + ["å"] = "00e5", ["æ"] = "00e6", ["ç"] = "00e7", ["è"] = "00e8", + ["é"] = "00e9", ["ê"] = "00ea", ["ë"] = "00eb", ["ì"] = "00ec", + ["í"] = "00ed", ["î"] = "00ee", ["ï"] = "00ef", ["ð"] = "00f0", + ["ñ"] = "00f1", ["ò"] = "00f2", ["ó"] = "00f3", ["ô"] = "00f4", + ["õ"] = "00f5", ["ö"] = "00f6", ["ø"] = "00f7", ["ù"] = "00f8", + ["Ú"] = "00da", ["Û"] = "00db", ["Ü"] = "00dc", ["Ý"] = "00dd", + ["Þ"] = "00de", ["ß"] = "00df", ["à"] = "00e0", ["á"] = "00e1", + ["â"] = "00e2", ["ã"] = "00e3", ["ä"] = "00e4", ["å"] = "00e5", + ["æ"] = "00e6", ["ç"] = "00e7", ["è"] = "00e8", ["é"] = "00e9", + ["ê"] = "00ea", ["ë"] = "00eb", ["ì"] = "00ec", ["í"] = "00ed", + ["î"] = "00ee", ["ï"] = "00ef", ["ð"] = "00f0", ["ñ"] = "00f1", + ["ò"] = "00f2", ["ó"] = "00f3", ["ô"] = "00f4", ["õ"] = "00f5", + ["ö"] = "00f6", ["ø"] = "00f7", ["ù"] = "00f9", ["ú"] = "00fa", + ["û"] = "00fb", ["ü"] = "00fc", ["ý"] = "00fd", ["þ"] = "00fe", + ["ÿ"] = "00ff", ["Ā"] = "0100", ["ā"] = "0101", ["Ă"] = "0102", + ["ă"] = "0103", ["Ą"] = "0104", ["ą"] = "0105", ["Ć"] = "0106", + ["ć"] = "0107", ["Ĉ"] = "0108", ["ĉ"] = "0109", ["Ċ"] = "010a", + ["ċ"] = "010b", ["Č"] = "010c", ["č"] = "010d", ["Ď"] = "010e", + ["ď"] = "010f", ["Đ"] = "0110", ["đ"] = "0111", ["Ē"] = "0112", + ["ē"] = "0113", ["Ė"] = "0116", ["ė"] = "0117", ["Ę"] = "0118", + ["ę"] = "0119", ["Ě"] = "011a", ["ě"] = "011b", ["Ĝ"] = "011c", + ["ĝ"] = "011d", ["Ğ"] = "011e", ["ğ"] = "011f", ["Ġ"] = "0120", + ["ġ"] = "0121", ["Ģ"] = "0122", ["Ĥ"] = "0124", ["ĥ"] = "0125", + ["Ħ"] = "0126", ["ħ"] = "0127", ["Ĩ"] = "0128", ["ĩ"] = "0129", + ["Ī"] = "012a", ["ī"] = "012b", ["Į"] = "012e", ["į"] = "012f", + ["İ"] = "0130", ["ı"] = "0131", ["IJ"] = "0132", ["ij"] = "0133", + ["Ĵ"] = "0134", ["ĵ"] = "0135", ["Ķ"] = "0136", ["ķ"] = "0137", + ["ĸ"] = "0138", ["Ĺ"] = "0139", ["ĺ"] = "013a", ["Ļ"] = "013b", + ["ļ"] = "013c", ["Ľ"] = "013d", ["ľ"] = "013e", ["Ŀ"] = "013f", + ["ŀ"] = "0140", ["Ł"] = "0141", ["ł"] = "0142", ["Ń"] = "0143", + ["ń"] = "0144", ["Ņ"] = "0145", ["ņ"] = "0146", ["Ň"] = "0147", + ["ň"] = "0148", ["ʼn"] = "0149", ["Ŋ"] = "014a", ["ŋ"] = "014b", + ["Ō"] = "014c", ["ō"] = "014d", ["Ő"] = "0150", ["ő"] = "0151", + ["Œ"] = "0152", ["œ"] = "0153", ["Ŕ"] = "0154", ["ŕ"] = "0155", + ["Ŗ"] = "0156", ["ŗ"] = "0157", ["Ř"] = "0158", ["ř"] = "0159", + ["Ś"] = "015a", ["ś"] = "015b", ["Ŝ"] = "015c", ["ŝ"] = "015d", + ["Ş"] = "015e", ["ş"] = "015f", ["Š"] = "0160", ["š"] = "0161", + ["Ţ"] = "0162", ["ţ"] = "0163", ["Ť"] = "0164", ["ť"] = "0165", + ["Ŧ"] = "0166", ["ŧ"] = "0167", ["Ũ"] = "0168", ["ũ"] = "0169", + ["Ū"] = "016a", ["ū"] = "016b", ["Ŭ"] = "016c", ["ŭ"] = "016d", + ["Ů"] = "016e", ["ů"] = "016f", ["Ű"] = "0170", ["ű"] = "0171", + ["Ų"] = "0172", ["ų"] = "0173", ["Ŵ"] = "0174", ["ŵ"] = "0175", + ["Ŷ"] = "0176", ["ŷ"] = "0177", ["Ÿ"] = "0178", ["Ź"] = "0179", + ["ź"] = "017a", ["Ż"] = "017b", ["ż"] = "017c", ["Ž"] = "017d", + ["ž"] = "017e", ["̑"] = "0311", + + ; ::: MATH ::: + ["+"] = "002b", ["−"] = "2212", ["×"] = "00d7", ["÷"] = "00f7", + ["="] = "003d", ["≠"] = "2260", ["±"] = "00b1", ["¬"] = "00ac", + ["<"] = "003c", [">"] = "003e", ["°"] = "00b0", ["¹"] = "00b9", + ["²"] = "00b2", ["³"] = "00b3", ["ƒ"] = "0192", ["%"] = "0025", + ["‰"] = "0089", ["‱"] = "2031", ["∀"] = "2200", ["∁"] = "2201", + ["∂"] = "2202", ["∃"] = "2203", ["∄"] = "2204", ["∅"] = "2205", + ["∇"] = "2207", ["∈"] = "2208", ["∉"] = "2209", ["∋"] = "220b", + ["∌"] = "220c", ["∏"] = "220f", ["∐"] = "2210", ["∑"] = "2211", + ["∓"] = "2213", ["∔"] = "2214", ["∖"] = "2216", ["∗"] = "2217", + ["∘"] = "2218", ["√"] = "221a", ["∝"] = "221d", ["∞"] = "221e", + ["∟"] = "221f", ["∠"] = "2220", ["∡"] = "2221", ["∢"] = "2222", + ["∣"] = "2223", ["∤"] = "2224", ["∥"] = "2225", ["∦"] = "2226", + ["∧"] = "2227", ["∨"] = "2228", ["∩"] = "2229", ["∪"] = "222a", + ["∫"] = "222b", ["∬"] = "222c", ["∭"] = "222d", ["∮"] = "222e", + ["∯"] = "222f", ["∰"] = "2230", ["∱"] = "2231", ["∲"] = "2232", + ["∳"] = "2233", ["∴"] = "2234", ["∵"] = "2235", ["∶"] = "2236", + ["∷"] = "2237", ["∸"] = "2238", ["∺"] = "223a", ["∻"] = "223b", + ["∼"] = "223c", ["∽"] = "223d", ["∾"] = "223e", ["∿"] = "223f", + ["≀"] = "2240", ["≁"] = "2241", ["≂"] = "2242", ["≃"] = "2243", + ["≄"] = "2244", ["≅"] = "2245", ["≆"] = "2246", ["≇"] = "2247", + ["≈"] = "2248", ["≉"] = "2249", ["≊"] = "224a", ["≋"] = "224b", + ["≌"] = "224c", ["≍"] = "224d", ["≎"] = "224e", ["≏"] = "224f", + ["≐"] = "2250", ["≑"] = "2251", ["≒"] = "2252", ["≓"] = "2253", + ["≔"] = "2254", ["≕"] = "2255", ["≖"] = "2256", ["≗"] = "2257", + ["≙"] = "2259", ["≚"] = "225a", ["≜"] = "225c", ["≟"] = "225f", + ["≡"] = "2261", ["≢"] = "2262", ["≤"] = "2264", ["≥"] = "2265", + ["≦"] = "2266", ["≧"] = "2267", ["≨"] = "2268", ["≩"] = "2269", + ["≪"] = "226a", ["≫"] = "226b", ["≬"] = "226c", ["≭"] = "226d", + ["≮"] = "226e", ["≯"] = "226f", ["≰"] = "2270", ["≱"] = "2271", + ["≲"] = "2272", ["≳"] = "2273", ["≴"] = "2274", ["≵"] = "2275", + ["≶"] = "2276", ["≷"] = "2277", ["≸"] = "2278", ["≹"] = "2279", + ["≺"] = "227a", ["≻"] = "227b", ["≼"] = "227c", ["≽"] = "227d", + ["≾"] = "227e", ["≿"] = "227f", ["⊀"] = "2280", ["⊁"] = "2281", + ["⊂"] = "2282", ["⊃"] = "2283", ["⊄"] = "2284", ["⊅"] = "2285", + ["⊆"] = "2286", ["⊇"] = "2287", ["⊈"] = "2288", ["⊉"] = "2289", + ["⊊"] = "228a", ["⊋"] = "228b", ["⊍"] = "228d", ["⊎"] = "228e", + ["⊏"] = "228f", ["⊐"] = "2290", ["⊑"] = "2291", ["⊒"] = "2292", + ["⊓"] = "2293", ["⊔"] = "2294", ["⊕"] = "2295", ["⊖"] = "2296", + ["⊗"] = "2297", ["⊘"] = "2298", ["⊙"] = "2299", ["⊚"] = "229a", + ["⊛"] = "229b", ["⊝"] = "229d", ["⊞"] = "229e", ["⊟"] = "229f", + ["⊠"] = "22a0", ["⊡"] = "22a1", ["⊢"] = "22a2", ["⊣"] = "22a3", + ["⊤"] = "22a4", ["⊥"] = "22a5", ["⊧"] = "22a7", ["⊨"] = "22a8", + ["⊩"] = "22a9", ["⊪"] = "22aa", ["⊫"] = "22ab", ["⊬"] = "22ac", + ["⊭"] = "22ad", ["⊮"] = "22ae", ["⊯"] = "22af", ["⊰"] = "22b0", + ["⊲"] = "22b2", ["⊳"] = "22b3", ["⊴"] = "22b4", ["⊵"] = "22d5", + ["⊶"] = "22b6", ["⊷"] = "22b7", ["⊸"] = "22b8", ["⊹"] = "22b9", + ["⊺"] = "22ba", ["⊻"] = "22bb", ["⊽"] = "22bd", ["⊾"] = "22be", + ["⊿"] = "22bf", ["⋀"] = "22c0", ["⋁"] = "22c1", ["⋂"] = "22c2", + ["⋃"] = "22c3", ["⋄"] = "22c4", ["⋅"] = "22c5", ["⋆"] = "22c6", + ["⋇"] = "22c7", ["⋈"] = "22c8", ["⋉"] = "22c9", ["⋊"] = "22ca", + ["⋋"] = "22cb", ["⋌"] = "22cc", ["⋍"] = "22cd", ["⋎"] = "22ce", + ["⋏"] = "22cf", ["⋐"] = "22d0", ["⋑"] = "22d1", ["⋒"] = "22d2", + ["⋓"] = "22d3", ["⋔"] = "22d4", ["⋕"] = "22d5", ["⋖"] = "22d6", + ["⋗"] = "22d7", ["⋘"] = "22d8", ["⋙"] = "22d9", ["⋚"] = "22da", + ["⋛"] = "22db", ["⋞"] = "22de", ["⋟"] = "22df", ["⋠"] = "22e0", + ["⋡"] = "22e1", ["⋢"] = "22e2", ["⋣"] = "22e3", ["⋦"] = "22e6", + ["⋧"] = "22e7", ["⋨"] = "22e8", ["⋩"] = "22e9", ["⋪"] = "22ea", + ["⋫"] = "22eb", ["⋬"] = "22ec", ["⋭"] = "22ed", ["⋮"] = "22ee", + ["⋯"] = "22ef", ["⋰"] = "22f0", ["⋱"] = "22f1", ["⋲"] = "22f2", + ["⋳"] = "22f3", ["⋴"] = "22f4", ["⋵"] = "22f5", ["⋶"] = "22f6", + ["⋷"] = "22f7", ["⋹"] = "22f9", ["⋺"] = "22fa", ["⋻"] = "22fb", + ["⋼"] = "22fc", ["⋽"] = "22fd", ["⋾"] = "22fe", + + ;::: NUMBERS ::: + ["¼"] = "00bc", ["½"] = "00bd", ["¾"] = "00be", ["⅓"] = "2153", + ["⅔"] = "2154", ["⅕"] = "2155", ["⅖"] = "2156", ["⅗"] = "2157", + ["⅘"] = "2158", ["⅙"] = "2159", ["⅚"] = "215a", ["⅛"] = "215b", + ["⅜"] = "215c", ["⅝"] = "215d", ["⅞"] = "215e", + + ;::: PUNCTUATION ::: + ["!"] = "0021", ["""] = "0022", ["#"] = "0023", ["%"] = "0025", + ["&"] = "0026", ["'"] = "0027", ["("] = "0028", [")"] = "0029", + ["*"] = "002a", [","] = "002c", ["."] = "002e", ["/"] = "002f", + [":"] = "003a", [";"] = "003b", ["?"] = "003f", ["@"] = "0040", + ["["] = "005b", ["\"] = "005c", ["]"] = "005d", ["&Hat"] = "005e", + ["_"] = "005f", ["`"] = "0060", ["{"] = "007b", ["|"] = "007c", + ["}"] = "007d", ["˜"] = "007e", [" "] = "00a0", ["¡"] = "00a1", + ["¦"] = "00a6", ["§"] = "00a7", ["¨"] = "00a8", ["©"] = "00a9", + ["ª"] = "00aa", ["«"] = "00ab", ["¬"] = "00ac", ["­"] = "00ad", + ["®"] = "00ae", ["¯"] = "00af", ["²"] = "00b2", ["³"] = "00b3", + ["´"] = "00b4", ["µ"] = "00b5", ["¶"] = "00b6", ["·"] = "00b7", + ["¸"] = "00b8", ["¹"] = "00b9", ["º"] = "00ba", ["»"] = "00bb", + ["¿"] = "00bf", ["‐"] = "2010", ["–"] = "2013", ["—"] = "2014", + ["―"] = "2015", ["‖"] = "2016", ["‘"] = "2018", ["’"] = "2019", + ["‚"] = "201a", ["“"] = "201c", ["”"] = "201d", ["„"] = "201e", + ["†"] = "2020", ["‡"] = "2021", ["•"] = "2022", ["‥"] = "2025", + ["…"] = "2026", ["‰"] = "2030", ["‱"] = "2031", ["′"] = "2032", + ["″"] = "2033", ["‴"] = "2034", ["‵"] = "2035", ["‹"] = "2039", + ["›"] = "203a", ["‾"] = "203e", ["⁁"] = "2041", ["⁃"] = "2043", + ["⁄"] = "2044", ["⁏"] = "204f", ["⁗"] = "2057", ["™"] = "2122", + + ;::: SYMBOLS ::: + ["ℂ"] = "2102", ["℅"] = "2105", ["ℊ"] = "210a", ["ℋ"] = "210b", + ["ℌ"] = "210c", ["ℍ"] = "210d", ["ℎ"] = "210e", ["ℏ"] = "210f", + ["ℐ"] = "2110", ["ℑ"] = "2111", ["ℒ"] = "2112", ["ℓ"] = "2113", + ["ℕ"] = "2115", ["№"] = "2116", ["℗"] = "2117", ["℘"] = "2118", + ["ℙ"] = "2119", ["ℚ"] = "211a", ["ℛ"] = "211b", ["ℜ"] = "211c", + ["ℝ"] = "211d", ["℞"] = "211e", ["ℤ"] = "2124", ["℧"] = "2127", + ["ℨ"] = "2128", ["℩"] = "2129", ["ℬ"] = "212c", ["ℭ"] = "212d", + ["ℯ"] = "212f", ["ℰ"] = "2130", ["ℱ"] = "2131", ["ℳ"] = "2133", + ["ℴ"] = "2134", ["ℵ"] = "2135", ["ℶ"] = "2136", ["ℷ"] = "2137", + ["ℸ"] = "2138", ["ⅅ"] = "2145", ["ⅆ"] = "2146", ["ⅇ"] = "2147", + ["ⅈ"] = "2148", ["★"] = "2605", ["☆"] = "2606", ["☎"] = "260e", + ["♀"] = "2640", ["♂"] = "2642", ["♠"] = "2660", ["♣"] = "2663", + ["♥"] = "2665", ["♦"] = "2666", ["♪"] = "266a", ["♭"] = "266d", + ["♮"] = "266e", ["♯"] = "266f", ["✓"] = "2713", ["✗"] = "2717", + ["✠"] = "2720", ["✶"] = "2736", ["❘"] = "2758", ["❲"] = "2772", + ["❳"] = "2773", + + } + +;---[ CONSTANTS ]-------------------------------------------------------------- +Const #HL_KILOBYTES = 1 +Const #HL_MEGABYTES = 2 +Const #HL_GIGABYTES = 3 +Const #HL_TERABYTES = 4 +Const #HL_AUTO = 9 + +;---[ FUNCTIONS ]-------------------------------------------------------------- + +Function HL.Capitalize(t) + If StrLen(t) = 1 + Return(UpperStr(t)) + EndIf + + Local f = UpperStr(LeftStr(t, 1)) + Local l = LowerStr(RightStr(t, StrLen(t)-1)) + Return(f..l) +EndFunction + +Function HL.NBWait(ms) + ; Non blocking wait + Local tid = StartTimer(Nil) + While GetTimer(tid) <= ms + CheckEvent() + Wait(5, #MILLISECONDS) + Wend + StopTimer(tid) +EndFunction + +Function HL.IsNil(value) ; OBSOLETE +/****************************************************************************** +result = HL.IsNil(value) + +** OBSOLETE : Use native IsNil() instead. *** +Check <value> and returns TRUE if it is NIL, otherwise returns FALSE. +--------------------------------------------------------------------- +INPUT + value => Value to check against NIL. + +OUTPUT + result => TRUE if 'value' is NIL otherwise FALSE. +******************************************************************************/ + If GetType(value) = #NIL + Return(True) + Else + Return(False) + EndIf + +EndFunction + +Function HL.RestartApp() +/******************************************************************** +HL.RestartApp() + +Used to restart the current HW running application. + +INPUT + - +OUTPUT + - +NOTES + Tested only on Windows systems, the hws extension must be associated + with Hollywood.exe to work with scripts. +********************************************************************/ + ; Retrive plaform informations + Local v = GetVersion() + If v.Platform <> "Win32" + DebugPrompt("At this time only Windows systems are supported!") + Return() + + EndIf + + ; Retrieve the current app name + Local type, exeName = GetProgramInfo() + Local dq = "\"" + + Switch type + Case #PRGTYPE_SCRIPT + ; Restart the script + Local scriptFull = exeName + Run(dq .. scriptFull .. dq) + + Case #PRGTYPE_PROGRAM + ; Restart the executable + Local programFull = exeName + Run(dq .. programFull .. dq) + + Default + ; Applets are not supported + + EndSwitch + +EndFunction + +Function HL.RoundBig(value, decimals, roundDown) +/******************************************************************** +rounded = HL.RoundBug(value, decimals) + +Round a value to the given decimals, this function is needed to avoid +errors like this: + DebugPrint(Int(40268.33971372/100000)*100000) + > -21474.83648 + +INPUT + value : value to convert + decimals : how many decimals you want + roundDown = To round down (default is roundUp) +OUTPUT + rounded : rounded value with the specified decimals +********************************************************************/ + Local rounded = Val(value) + Local sign = IIf(rounded<0, -1, 1) + Local multiplier = 10^decimals + Local intPart = Int(rounded) + + If IsNil(roundDown) Then RoundDown = False + Local vRound = IIf(roundDown, -0.5, 0.5) + + rounded = rounded-intPart + + rounded = Cast(Abs(rounded)*multiplier+vRound, False, #INTEGER) + rounded = intPart + (rounded)/(multiplier*sign) + + Return(rounded) + +EndFunction +/* +Local v = "9511.262466530" +DebugPrint("VALUE:", FormatStr("%.10f", v)) +DebugPrint("RESUL:", FormatStr("%.10f", HL.RoundBig(v, 8))) +DebugPrint(" 0.123456789") +DebugPrompt("?") +*/ + +HL.LineHook = {} +HL.LineHook.Counter = 0 + +Function HL.LineHook.Disable() +/****************************************************************************** +HL.LineHook.Disable() + +Disable the line hook respecting the nested usage. +******************************************************************************/ + HL.LineHook.Counter = HL.LineHook.Counter - 1 + + If HL.LineHook.Counter <= 0 + HL.LineHook.Counter = 0 + DisableLineHook() + EndIf + +EndFunction + +Function HL.LineHook.Enable() +/****************************************************************************** +HL.LineHook.Enable() + +Disable the line hook respecting the nested usage. +******************************************************************************/ + HL.LineHook.Counter = HL.LineHook.Counter + 1 + + If HL.LineHook.Counter = 1 + EnableLineHook() + EndIf + +EndFunction + +Function HL.IsNotNil(value) ; OBSOLETE +/****************************************************************************** +result = HL.IsNotNil(value) + +** OBSOLETE : Use Not(IsNil()) instead ** +Check <value> and returns FALSE if it is NIL, otherwise returns TRUE +--------------------------------------------------------------------- +INPUT + value => Value to check against NIL. +OUTPUT + result => FALSE if 'value' is NIL otherwise TRUE. +******************************************************************************/ + If GetType(value) = #NIL + Return(False) + Else + Return(True) + EndIf + +EndFunction + +Function HL.Convert.BytesTo(bytes, target, decimals) +/****************************************************************************** +converted = HL.Convert.BytesTo(bytes, target, decimals) + +Convertes the bytes specified in 'bytes' into the 'target' unit, the +result will have 'decimals' decimal positions. +--------------------------------------------------------------------- +INPUT + bytes => The bytes value to convert + target => The conversion target, can be one of the following constants : + -> #HL_KILOBYTES + -> #HL_MEGABYTES + -> #HL_GIGABYTES + -> #HL_TERABYTES + -> #HL_AUTO Automatic conversion + decimals => How many decimals the results should have (default = 0) +OUTPUT + converted => The value after the conversion. +NOTE + If you use #HL_AUTO you will get a string as result with the appropriate + suffix. +******************************************************************************/ + Local result, sfx = bytes, "" + + If IsNil(decimals) Then decimals = 0 + + If target = #HL_AUTO + If bytes/1099511627776 > 1 + target = #HL_TERABYTES + sfx = "Tb" + ElseIf bytes/1073741824 > 1 + target = #HL_GIGABYTES + sfx = "Gb" + ElseIf bytes/1048576 > 1 + target = #HL_MEGABYTES + sfx = "Mb" + ElseIf bytes/1024 > 1 + target = #HL_KILOBYTES + sfx = "Kb" + Else + sfx = "b" + EndIf + EndIf + + Switch target + Case #HL_KILOBYTES + result = bytes/1024 + Case #HL_MEGABYTES + result = bytes/1048576 + Case #HL_GIGABYTES + result = bytes/1073741824 + Case #HL_TERABYTES + result = bytes/1099511627776 + Case #HL_AUTO + result = bytes + + Default + Return(result) + + EndSwitch + + Local result = ToNumber(FormatStr("%." .. decimals .. "f", result)) + If sfx <> "" Then result = ToString(result) .. sfx + + Return(result) + +EndFunction + +Function HL.GetRndName() +/****************************************************************************** +name = HL.GetRndName() + +Use this function to obtain a unique random name anytime you need it. +--------------------------------------------------------------------- +OUTPUT + name => A string with an unique random name. +NOTES + All returned random names are stored in the HL.RandomNames table to avoid + reusing and make them unique, if you wish to reset the assigned names for + some reasons (for example for low memory conditions) just reset this table + but be aware that already assigned names could be reused. +******************************************************************************/ + Local rnd_name = PadNum(Rnd(999999), 6) + + While GetType(RawGet(HL.RndNames, rnd_name)) <> #NIL + rnd_name = PadNum(Rnd(999999), 6) + Wend + + HL.RndNames[rnd_name] = 1 + + Return("rnd" .. rnd_name) + +EndFunction + +Function HL.ParseRunArgs(CaseSensitive) +/****************************************************************************** +parsedArgs = HL.ParseRunArgs(caseSensitive) + +Parse command line arguments and return a table indexed with the argument +key and with the value sets with the argument's parameter. +The switch 'caseSensitive' defaults to TRUE, set it to FALSE to parse +arguments and parameters without case distinctions. +--------------------------------------------------------------------- +INPUT + caseSensitive => TRUE to parse case sensitive arguments and parameters. +OUTPUT + parsedArgs => A table indexed with the command line arguments and + with values sets as the parameter values. +NOTE + Keep in mind that all value will be returned as strings, it's up to + you to convert values in the needed format. + If 'caseSensitive' is TRUE all arguments will be lowercase. + This command is really usefull to test argument passed with the command + line simply testing the resulting table keys. +******************************************************************************/ + If HL.IsNil(caseSensitive) Then caseSensitive = False + + Local Result = {} + Local Args, ArgsCount = GetCommandLine() + + Local index, value = NextItem(Args) + While GetType(index) <> #NIL + If Not(caseSensitive) + Result[value.arg] = value.param + Else + Result[LowerStr(value.arg)] = LowerStr(value.param) + EndIf + + index, value = NextItem(Args, index) + Wend + + Return(Result) + +EndFunction + +Function HL.Safe(value) ; OBSOLETE +/******************************************************************************* +result = HL.Safe(value) + +** OBSOLETE : Use ToString() instead ** +Returns the string 'NIL' if 'value' is NIL, otherwise returns 'value' without +any changes. +This function is usefull to concatenate strings without worring about errors +caused by concatenating uninitialized variables. +--------------------------------------------------------------------- +INPUT + value => Value to process. +OUTPUT + result => 'value' or the string 'NIL' +*******************************************************************************/ + If HL.IsNil(value) + Return("NIL") + Else + Return(value) + EndIf + +EndFunction + +Function HL.SizeString(txt, size) +/******************************************************************************* +processed = HL.SizeString(txt, size) + +This function is usefull to process strings and to produce easily aligned text +columns, the string 'txt' will be processed and adapted to the 'size' length by +adding spaces or by removing exceeding characters and adding '...' to the end to +indicate that the string is not finished. +--------------------------------------------------------------------- +INPUT + txt => Text to process. + size => Text length including the triple dots (if needed) or the additional + spaces. +OUTPUT + processed => Processed text with the exact length equal to 'size'. +*******************************************************************************/ + Local l = StrLen(txt) + + If l > size Then Return(LeftStr(txt, size-3) .. "...") + If l = size Then Return(txt) + + Return(txt .. RepeatStr(" ", size-l)) + +EndFunction + +Function HL.Value2Perc(Range, Value) +/******************************************************************************* +percentual = HL.Value2Perc(range, value) + +Convert the given 'value' to a percentual value, the percentual will be calculated +using the table 'range' that must have the following fields: + [0] Minimum value in the range + [1] Maximum value in the range +If 'value' is out of range it will get Minimum or Maximum depending on the +boundary it exceeds. +--------------------------------------------------------------------- +INPUT + range => A table with at least two entries at index 0 and 1: + [0] : Lower range limit + [1] : Higher range limit + value => Value we need to convert into a percentual notation. +OUTPUT + percentual => Converted value ranging from 0 to 1. +******************************************************************************/ + If value < Range[0] + value = range[0] + ElseIf value > Range[1] + value = Range[1] + EndIf + + ; 100:x = Total:value + + If (Range[1] - Range[0]) = 0 Then Return(0) + Return((Value-Range[0])/(Range[1] - Range[0])) + +EndFunction + +Function HL.WaitForAction(key, delay, callback, timeout, timeout_callback, userdata) +/******************************************************************************* +HL.WaitForAction(key, delay, callback, timeout, to_callback, userData) + +This function is used to wait for an user action, the action can be a key press +defined by 'key', the left mouse button press or any button of any connected +joystick. It's possible to define a timeout to let the program continue without +user action. This routine provides callback functions to run an animation or to +accomplish other tasks during the wait or to call a specific function if the +timeout occurs. + +** TODO : Add timeout & current time to the 'callback' function ** +--------------------------------------------------------------------- +INPUT + key => Monitored key, or ANY for any key. + delay => Delay in milliseconds for the detection loop, every wait a call + to 'callback' will be made. + callback => Function called every loop check with the <userData> parameter. + If the callback function returns TRUE the wait loop will be + interrupted immediatly. + timeout => Timeout in milliseconds. + to_callback => Function called if the timeout is reached, the function will + be called with 'userData' parameter. + userData => Custom user data parameter passed to the callback function. +OUTPUT + result => False if a timeout occurred, otherwise True +******************************************************************************/ + Local t = StartTimer(Nil) + Local looping = True + Local result = False + Local jc = CountJoysticks() + + While looping + Wait(delay, #MILLISECONDS) + + ; Every check execute the callback function + If HL.IsNotNil(callback) + If (callback(userdata, GetTimer(t))) Then looping = False + EndIf + + ; User action? + If IsLeftMouse() Or IsKeyDown(key) + looping = False + result = True + ElseIf jc > 0 + For Local i = 0 To jc-1 + If JoyFire(i) + looping = False + result = True + Break() + EndIf + Next + EndIf + + ; Timeout occurred? + If HL.IsNotNil(timeout) + If GetTimer(t) > timeout + looping = False + If HL.IsNotNil(timeout_callback) + timeout_callback(userdata) + result = False + EndIf + EndIf + EndIf + + Wend + + StopTimer(t) + + Return(result) + +EndFunction + +Function HL.CutStringLeft(text, maxLen) +/******************************************************************************* +cutted = HL.CutStringLeft(text, maxLen) + +Cut the given 'text' to the left if the string length is greater the 'maxLen' +and prefix the string with the triple point (...) to indicate that the string +has been shortened. +--------------------------------------------------------------------- +INPUT + text => The text to process + maxLen => The max length the text should have excluding the triple point +OUTPUT + cutted => The processed text +******************************************************************************/ + If StrLen(text) > maxlen + Return("..." .. RightStr(text, maxlen)) + EndIf + + Return(text) + +EndFunction + +Function HL.CutStringRight(text, maxLen) +/******************************************************************************* +cutted = HL.CutStringRight(text, maxLen) + +Cut the given string to the right if the string length is greater the <maxLen> +and add the triple point (...) suffix to indicate that the string has been shortened. +--------------------------------------------------------------------- +INPUT + text => The text to process + maxLen => The max length the text should have excluding the triple point + +OUTPUT + cutted => The processed text +******************************************************************************/ + If StrLen(text) > maxlen + Return(LeftStr(text, maxlen) .. "...") + EndIf + + Return(text) + +EndFunction + +Function HL.GetBetweenLimits(txt, sLimit, eLimit, caseSense) +/******************************************************************************* +HL.GetBetweenLimits(txt, sLimit, eLimit, casesense) + +Get the text between the limiters. +--------------------------------------------------------------------- +INPUT + txt => Text to process + sLimit => Starting limiter string + eLimit => Ending limiter string + casesense => True to make it case sensitive (default=False) +OUTPUT + ttext => Results table, each entry is a string composed by the + starting limiter, the text between and and ending limiter. + tpos => Positions of the tags in the original string. + -> start_pos + -> end_pos +*******************************************************************************/ + Local p, sl, el = 0, StrLen(sLimit), StrLen(eLimit) + Local results, tpos = {}, {} + + If IsNil(casesense) Then casesense = False + + Local p1 = FindStr(txt, sLimit, casesense) + While p1 <> -1 + Local p2 = FindStr(txt, eLimit, casesense, p1) + If p2 = -1 Then Break() + Local t = MidStr(txt, p1+sl, p2-p1-sl) + InsertItem(results, t) + InsertItem(tpos, { start_pos = p1, end_pos = p2 + el }) + p = p2 + el + p1 = FindStr(txt, sLimit, casesense, p) + Wend + + Return(results, tpos) + +EndFunction + +Function HL.CutBetweenLimits(txt, sLimit, eLimit, tags) +/******************************************************************************* +HL.CutBetweenLimits(txt, sLimit, eLimit, tags) + +Removes all text between the given limiter strings, limiter included. +--------------------------------------------------------------------- +INPUT + txt => Text to process + sLimit => Starting limiter string + eLimit => Ending limiter string + tags => Where to add removed text (optional) +OUTPUT + text => Processed text + removed => A table with all removed strings, if the 'tags' table has been + provided the strings will be added to it otherwise a new table + will be returned. Each entry is a string composed by the starting + limiter, the text between and and ending limiter. +*******************************************************************************/ + Local removed = {} + If Not(IsNil(tags)) Then removed = tags + Local p1 = FindStr(txt, sLimit, False) + While p1 <> -1 + Local p2 = FindStr(txt, eLimit, False, p1) + If p2 = -1 Then Break() + InsertItem(removed, MidStr(txt, p1, p2-p1+StrLen(eLimit))) + txt = UnmidStr(txt, p1, p2-p1+StrLen(eLimit)) + p1 = FindStr(txt, sLimit) + Wend + Return(txt, removed) + +EndFunction + +Function HL.Convert.Unicode2UTF8(value) +/******************************************************************************* +converted = HL.Convert.Unicode2UTF8(value) + +Encode 'value', an hexadecimal string WITHOUT any prefixes ($, 0x), to the +corresponding UTF8 string. +--------------------------------------------------------------------- +INPUT + value => The hexadecimal value to convert without any prefix +OUTPUT + converted => The encoded UTF8 string +NOTES + Additional informations here : http://www.czyborra.com/utf/ +******************************************************************************/ + Local v = ToNumber(value) + + If v < $80 + Return(Chr(v)) + ElseIf v < $0800 + Local v1 = $c0 | v >> 6 + Local v2 = $80 | v & $3f + Return(Chr(v1) .. Chr(v2)) + ElseIf v < $10000 + Local v1 = $e0 | v >> 12 + Local v2 = $80 | v >> 6 & $3f + Local v3 = $80 | v & $3f + Return(Chr(v1) .. Chr(v2) .. Chr(v3)) + ElseIf v < $200000 + Local v1 = $f0 | v >> 18 + Local v2 = $80 | v >> 12 & $3f + Local v3 = $80 | v >> 6 & $3f + Local v4 = $80 & $3f + Return(Chr(v1) .. Chr(v2) .. Chr(v3) .. Chr(v4)) + EndIf + +EndFunction + +Function HL.Convert.HTMLTag2HollywoodTag(html_tag) +/******************************************************************************* +converted = HL.Convert.HTMLTag2HollywoodTag(html_tag) + +Convert the given 'html_tag' into stardard text Hollywood's tags where possible. +--------------------------------------------------------------------- +INPUT + html_tag => The HTML tag to convert +OUTPUT + converted => The converted tag +NOTES + The table 'HL.HTML_Tags' holds all supported conversions. +******************************************************************************/ + If GetType(RawGet(HL.HTML_Tags, html_tag)) <> #NIL + Return(HL.HTML_Tags[html_tag]) + Else + Return(html_tag) + EndIf + +EndFunction + + +Function HL.Convert.HTMLAmper2UTF8(html_amper) +/******************************************************************************* +converted = HL.Convert.HTMLAmper2UTF8(html_amper) + +Convert the given 'html_amper' into an UTF8 encoded string +--------------------------------------------------------------------- +INPUT + html_amper => The HTML amper to convert +OUTPUT + converted => The encoded text +NOTES + The table 'HL.HTML_Amper' holds all supported conversions. +******************************************************************************/ + If GetType(RawGet(HL.HTML_Amper, html_amper)) <> #NIL + Local utf_8 = HL.HTML_Amper[html_amper] + Local t = "" + For i = 1 To StrLen(utf_8) Step 4 + t = t .. Chr("$" .. MidStr(utf_8, i-1, 4)) + Next + Return(t) + EndIf + +EndFunction + +Function HL.Convert.ForTextOut(text) +/******************************************************************************* +converted = HL.Convert.ForTextOut(text) + +Escape square brackes not part of Hollywood tags to avoid errors with TextOut() +-------------------------------------------------------------------------------- +INPUT + text => Text to be converted +OUTPUT + converted => Converted text +*******************************************************************************/ + Local temp = text + Local pos, l = 0, StrLen(text) + Local found = True + + ;DebugPrint("CONVERT FOR TEXTOUT (Helpers) -> \n" .. text .. "\n\n" .. StrLen(text)) + + ; Remove fixed tags + + Local tags = { "[b]", "[/b]", + "[u]", "[/u]", + "[i]", "[/i]", + "[color=", "[/color", + "[edge=", "[/edge", + "[shadow=", "[/shadow" } + ; Use a temporary string to identify all HW tags, when they are + ; found replaces the commands with a _ + For Local tn = 0 To ListItems(tags)-1 + Local pos = 0 + Local p = FindStr(temp, tags[tn], False, pos) + While p <> -1 + Local e = FindStr(temp, "]", False, p) + If e <> -1 + For Local k = p To e + temp = LeftStr(temp, k) .. "_" .. RightStr(temp, l-k-1) + Next + pos = e-1 + EndIf + + p = FindStr(temp, tags[tn], False, pos) + Wend + Next + + ; At this point the temporary tring is cleared from all the HW tags, we need + ; to find the remaining square brackets and escape them on the original string. + For Local i = l-1 To 0 Step -1 + If MidStr(temp, i, 1) = "[" + text = LeftStr(text, i) .. "[" .. RightStr(text, StrLen(text)-i) + ElseIf MidStr(temp, i, 1) = "]" + text = LeftStr(text, i) .. "]" .. RightStr(text, StrLen(text)-i) + EndIf + Next + + Return(text) + +EndFunction + +Function HL.Convert.HTML2Hollywood(text) +/******************************************************************************* +converted = HL.Convert.HTML2Hollywood(text) + +Convert the given HTML 'text' into a text formatted with Hollywood tags. +--------------------------------------------------------------------- +INPUT + text => The HTML text to convert +OUTPUT + converted => The converted text +NOTES + Currently handled HTML entities are: + - Slashes, Double-Slashes, Tabs + - HTML Tags (see HL.HTML_Tags table) + - HTML Ampersands (see HL.HTML_Amper table) + - Ordered lists + - Unordered lists +******************************************************************************/ + ; Remove Unneeded Escapes + text = ReplaceStr(text, "\\/", "/") ; Slash + text = ReplaceStr(text, "\\" .. Chr(34), Chr(34)) ; Double quotes + text = ReplaceStr(text, "\\t", "\t") ; Tab + + ; Handle HTML Tags + Local pos1 = FindStr(text, "<", False) + While pos1 <> -1 + Local pos2 = FindStr(text, ">", False, pos1) + If pos2 <> -1 + ; Possible HTML Tag found + Local l = StrLen(text) + Local HTML_tag = MidStr(text, pos1, pos2-pos1+1) + Local holly_tag = HL.Convert.HTMLTag2HollywoodTag(HTML_Tag) + If holly_tag = HTML_tag + pos1 = pos1 + StrLen(HTML_tag) + Else + ;DebugPrint("HTML TAG : ", HTML_Tag, " @ ", pos1, " --> " .. "-|" .. holly_tag .. "|-") + text = LeftStr(text, pos1) .. holly_tag .. RightStr(text, l-pos2-1) + EndIf + Else + ; Unmatching <> + Break + EndIf + pos1 = FindStr(text, "<", False, pos1) + Wend + + ; Handle HTML Ampersands + Local pos1 = FindStr(text, "&", False) + While pos1 <> -1 + Local pos2 = FindStr(text, ";", False, pos1) + If pos2 <> -1 + ; Possible HTML Tag found + Local l = StrLen(text) + Local HTML_amper = MidStr(text, pos1, pos2-pos1+1) + ;DebugPrint("AMPER : ", HTML_Amper) + Local utf_8 = HL.Convert.HTMLAmper2UTF8(HTML_amper) + ;DebugPrint("Encoded with : ", utf_8) + ;TextOut(100, 100, "-> " .. utf_8 .. " <-", { Encoding = #ENCODING_UTF8 }) + ;WaitLeftMouse() + ;Local utf_8 = HL.Convert.Unicode2UTF8(unicode) + ;DebugPrint("HTML Amper : ", HTML_amper, " @ ", pos1, " --> " .. "-|" .. unicode .. "|->|"..utf_8.."|-") + text = LeftStr(text, pos1) .. utf_8 .. RightStr(text, l-pos2-1) + ; text = LeftStr(text, pos1) .. holly_tag .. RightStr(text, l-pos2-1) + Else + pos1 = pos1 + 1 + EndIf + pos1 = FindStr(text, "&", False, pos1) + Wend + + ; Handle Escaped Unicodes + Local pos = FindStr(text, "\\u", False) + While pos <> -1 + Local unicode = MidStr(text, pos+2, 4) + Local l = StrLen(text) + unicode = ToNumber("$" .. unicode) + Local utf_8 = HL.Convert.Unicode2UTF8(unicode) + text = LeftStr(text, pos) .. utf_8 .. RightStr(text, l-pos-6) + pos = FindStr(text, "\\u", False, pos) + Wend + + ; handle Ordered Lists + Local p1 = 0 + While True + Local p1 = FindStr(text, "<ol>", False) + If p1 = -1 Then Break + + Local p2 = FindStr(text, "</ol>", False, p1) + Local starting = LeftStr(text, p1) + Local substring = MidStr(text, p1, p2-p1+5) + Local ending = RightStr(text, StrLen(text)-p2-5) + substring = ReplaceStr(substring, "<ol>", "") + substring = ReplaceStr(substring, "</ol>", "") + substring = ReplaceStr(substring, "</li>", "") + Local cnt, pos = 1, 0 + While True + Local p = FindStr(substring, "<li>", False, pos) + If p = -1 Then Break + substring = LeftStr(substring, p) .. ToString(cnt) .. ". " .. RightStr(substring, StrLen(substring)-4-p) + cnt = cnt + 1 + pos = p + Wend + text = starting .. substring .. ending + + Wend + + ; Handle unordered Lists + Local p1 = 0 + While True + Local p1 = FindStr(text, "<ul>", False) + If p1 = -1 Then Break + + Local p2 = FindStr(text, "</ul>", False, p1) + Local starting = LeftStr(text, p1) + Local substring = MidStr(text, p1, p2-p1+5) + Local ending = RightStr(text, StrLen(text)-p2-5) + substring = ReplaceStr(substring, "<ul>", "") + substring = ReplaceStr(substring, "</ul>", "") + substring = ReplaceStr(substring, "</li>", "") + Local cnt, pos = 1, 0 + While True + Local p = FindStr(substring, "<li>", False, pos) + If p = -1 Then Break + substring = LeftStr(substring, p) .. "* " .. RightStr(substring, StrLen(substring)-4-p) + cnt = cnt + 1 + pos = p + Wend + text = starting .. substring .. ending + + Wend + + Return(text) + +EndFunction + + +/******************************************************************************* + CLASS BufferedString + This class is used to speed up string concatenation. + *******************************************************************************/ + HL.BufferedString = { } + HL.BufferedString.dest = "" + HL.BufferedString.temp = "" + HL.BufferedString.len = 400 + HL.BufferedString.tlen = 0 + HL.BufferedString.chunks = {} + +Function HL.BufferedString:PrepareForRead() + ;DebugPrint("Preparing text chunks...") + self.chunks = {} + Local data = self.dest .. self.temp + Local blocks = StrLen(data)/self.len + Local chunks = Int(blocks) + If blocks <> chunks Then chunks = chunks + 1 + + ;DebugPrint("Blocks = " .. blocks) + ;DebugPrint("Chunks = " .. chunks) + For i = 0 To chunks + ;DebugPrint("Setting Chunk " .. i .. " (" .. i*self.len .. ", " .. self.len .. ")") + self.chunks[i] = MidStr(data, i*self.len, self.len) + Next +EndFunction + +Function HL.BufferedString:Read(start, lenght) + If lenght = 0 Then Return("") + Local start_chunk = Int(start/self.len) + Local end_chunk = Int((start+lenght)/self.len) + ;Local start_pos = start_chunk*self.len + start + ;Local end_pos = end_chunk*self.len + start + lenght + ;DebugPrint(":Read(" .. start .. ", " .. lenght .. ")") + If start_chunk = end_chunk + ;DebugPrint(" Read in the same chunk") + Local start_pos = Mod(start, self.len) + ;DebugPrint(" Chunk : " .. start_chunk .. ", " .. start_pos .. ", " .. lenght) + Return(MidStr(self.chunks[start_chunk], start_pos, lenght)) + Else + ;DebugPrint(" Read in multiple chunks") + ;DebugPrint(" Chunks : " .. start_chunk .. " - " .. end_chunk) + Local start_pos = start - start_chunk*self.len + Local l = self.len-start_pos + ;DebugPrint(" On chunk " .. start_chunk .. " : Start = " .. start_pos .. ", Lenght = " .. l) + Local t = MidStr(self.chunks[start_chunk], start_pos, l) + lenght = lenght - l + For Local i = start_chunk+1 To end_chunk-1 + ;DebugPrint(" Adding content of chunk " .. i) + t = t .. self.chunks[i] + lenght = lenght - self.len + Next + ;DebugPrint(" On chunk " .. end_chunk .. " : Start = 0, Lenght = " .. lenght) + t = t .. MidStr(self.chunks[end_chunk], 0, lenght) + Return(t) + EndIf +EndFunction + +Function HL.BufferedString:New() +/******************************************************************************* +BSObject = HL.BufferedString:New() + +Create a new buffered string object. +--------------------------------------------------------------------- +OUTPUT + BSObject => The new created object. +--------------------------------------------------------------------- +NOTE + String concatenation in Hollywood is somewhat slow so I've implemented this + class for speed up concatenation where there is the need to concatenate + character by character, for example while decoding or encoding data. This + trick speed up a lot this operation, in my tests I was able to process strings + with single character concatenation at around 18 Kb/s, using this system I was + able to reach 680 Kb/s. +******************************************************************************/ + Return(CopyTable(self)) + +EndFunction + +Function HL.BufferedString:AddChar(char) +/******************************************************************************* +HL.BufferedString:AddChar(char) + +Add the character 'char' to the buffered string object. +--------------------------------------------------------------------- +INPUT + char => The character string to add to the buffered string object +******************************************************************************/ + self.tlen = self.tlen + 1 + self.temp = self.temp .. char + If self.tlen = self.len + self.dest = self.dest .. self.temp + self.temp = "" + self.tlen = 0 + EndIf + +EndFunction + +Function HL.BufferedString:Set(string) + self.dest = string + self.temp = "" + self.tlen = 0 +EndFunction + +Function HL.BufferedString:AddString(string) + self.dest = self.dest .. self.temp .. string + self.temp = "" + self.tlen = 0 +EndFunction + +Function HL.BufferedString:Get() +/******************************************************************************* +HL.BufferedString:Get() + +Returns the current contents of the buffered string object. +--------------------------------------------------------------------- +OUTPUT + contents => Current buffered string contents. +******************************************************************************/ + Return(self.dest .. self.temp) + + EndFunction + + +/******************************************************************************* + CLASS Color + This class is used to manage colors. + *******************************************************************************/ +HL.Color = { } +HL.Color.r = 0 +HL.Color.g = 0 +HL.Color.b = 0 +HL.Color.a = 0 + +Function HL.Color:New(params, alpha) +/****************************************************************************** +ColorObj = HL.Color:New(params, alpha) + +Creates a new color object +--------------------------------------------------------------------- +INPUT + params => An optional table with one or more members as follow: + r : Red component + g : Green component + b : Blue component + a : Transparency level + Or a single RGB color value, followed by an optional alpha value. +OUTPUT + ColorObj => A new color object +*******************************************************************************/ + Local obj = CopyTable(self) + If Not(IsNil(params)) + If GetType(params) = #TABLE + If RawGet(params, "r") <> Nil Then obj.r = params.r + If RawGet(params, "g") <> Nil Then obj.g = params.g + If RawGet(params, "b") <> Nil Then obj.b = params.b + If RawGet(params, "a") <> Nil Then obj.a = params.a + Else + If IsNil(alpha) Then alpha = 0 + obj.r = Red(params) + obj.g = Green(params) + obj.b = Blue(params) + obj.a = alpha + EndIf + EndIf + + Return(obj) +EndFunction + +Function HL.Color:Clone() +/****************************************************************************** +ColorObj = HL.Color:Clone() + +Clone the color to a new object +--------------------------------------------------------------------- +OUTPUT + ColorObj => The clones color object +******************************************************************************/ + Return(CopyTable(self)) +EndFunction + +Function HL.Color:fromValue(value) +/****************************************************************************** +HL.Color:fromValue(value) + +Load the color object with the given color value +--------------------------------------------------------------------- +INPUT + value => The color value to load into the object +******************************************************************************/ + Local value = ToNumber(value) + self.r = Red(value) + self.g = Green(value) + self.b = Blue(value) + self.a = Shr(value, 24) +EndFunction + +Function HL.Color:fromARGB(a, r, g, b) +/****************************************************************************** +HL.Color:fromARGB(a, r, g, b) + +Load the color object with the given A, R, G, B components +--------------------------------------------------------------------- +INPUT + a => Alpha component + r => Red component + g => Green component + b => Blue component +******************************************************************************/ + self.r = r + self.g = g + self.b = b + self.a = a +EndFunction + +Function HL.Color:toRGBValue() +/****************************************************************************** +value = HL.Color:toRGBValue() + +Returns the RGB color value +--------------------------------------------------------------------- +OUTPUT + value => RGB color value stored in the color object +******************************************************************************/ + Return(RGB(self.r, self.g, self.b)) +EndFunction + +Function HL.Color:toARGBValue() +/****************************************************************************** +value = HL.Color:toARGBValue() + +Returns the ARGB color value +--------------------------------------------------------------------- +OUTPUT + value => ARGB color value stored in the color object +******************************************************************************/ + Return(ARGB(self.a, RGB(self.r, self.g, self.b))) +EndFunction + +Function HL.Color:Darken(delta) +/****************************************************************************** +HL.Color:Darken(delta) + +Make the current color darker by a given factor represented by 'delta' +--------------------------------------------------------------------- +INPUT + delta => Amount in units to subtract from all current color's components +******************************************************************************/ + self.r = self.r - delta + If self.r < 0 Then self.r = 0 + self.g = self.g - delta + If self.g < 0 Then self.g = 0 + self.b = self.b - delta + If self.b < 0 Then self.b = 0 + + If self.r > 255 Then self.r = 255 + If self.g > 255 Then self.g = 255 + If self.b > 255 Then self.b = 255 + +EndFunction + +Function HL.Color:Brighten(delta) +/****************************************************************************** +HL.Color:Brighten(delta) + +Make the current color brighten by a given factor represented by 'delta' +--------------------------------------------------------------------- +INPUT + delta => Amount in units to add to all current color's components +******************************************************************************/ + self.r = self.r + delta + If self.r > 255 Then self.r = 255 + self.g = self.g + delta + If self.g > 255 Then self.g = 255 + self.b = self.b + delta + If self.b > 255 Then self.b = 255 + + If self.r < 0 Then self.r = 0 + If self.g < 0 Then self.g = 0 + If self.b < 0 Then self.b = 0 + +EndFunction + +Function HL.GetRndColor(alpha) +/****************************************************************************** +color = HL.GetRndColor(alpha) + +Returns a random color, eventually with a random alpha transparency value. +--------------------------------------------------------------------- +INPUT + alpha => Set to TRUE if you want to get a random color with random + transparency. +******************************************************************************/ + Local a = 0 + ;-- old implementation -- + ; Local r, g, b = Rnd(255), Rnd(255), Rnd(255) + Local rc = GetRandomColor() + + If alpha Then a = Rnd(255) + + ; Return(ARGB(a, RGB(r, g, b))) + Return(ARGB(a, rc)) + +EndFunction + + + +HL.Input = {} + +Function HL.Input.CheckKeyboard(keyList, waitKey, waitRelease) +/****************************************************************************** +key = HL.Input.CheckKeyboard(keyList, waitKey, waitRelease) + +Wait for a key press from a list of allowed keys. +--------------------------------------------------------------------- +INPUT + keyList => A table of all allowed keys, you can set it to 'ANY' to detect + keypresses coming from any keys. + waitKey => TRUE to wait until a key is pressed + FALSE to make a scan and return immediatly + waitRelease => TRUE to wait until a pressed key is released + FALSE to return immediatly as soon as the key is pressed +OUTPUT + key => A string representing the pressed key or an empty string if no key + has been pressed (this last case is valid only if you have set + waitKey = FALSE). +******************************************************************************/ + + If GetType(keyList) = #TABLE + If ListItems(keyList)=0 Then Return("") + ElseIf GetType(keyList) = #STRING + If keyList = "ANY" + ; Build characters table + keyList = {} + For i = 1 To 255 + If (i > 32 And i < 127) + InsertItem(keyList, Chr(i)) + EndIf + Next + InsertItem(keyList, "SPACE") + InsertItem(keyList, "BACKSPACE") + InsertItem(keyList, "ENTER") + InsertItem(keyList, "RETURN") + InsertItem(keyList, "ESC") + EndIf + EndIf + + If IsNil(waitKey) Then waitKey = False + If IsNil(waitRelease) Then waitRelease = True + + Repeat + For Local i = 0 To ListItems(keyList)-1 + If IsKeyDown(keyList[i]) + If waitRelease + While IsKeyDown(keyList[i]) + Wait(10, #MILLISECONDS) + Wend + EndIf + Return(keyList[i]) + EndIf + Next + + Wait(10, #MILLISECONDS) + Until waitKey = False + + Return("") + +EndFunction + +Function HL.GetReversedDateTime() + ; Useful for attach the reversed date to files + Local d = GetDate(False) + Local t = GetTime(True) + + Local day = LeftStr(d, 2) + Local mon = MidStr(d, 3, 2) + Local yea = MidStr(d, 6, 4) + Local tim = ReplaceStr(t, ":", "") + + Local s = yea .. mon .. day .. "-" .. tim + Return(s) +EndFunction + +Function HL.Input.CheckJoystick(waitKey, waitRelease) +/****************************************************************************** +dir, fire = HL.Input.CheckJoystick(waitKey, waitRelease) + +Wait for a joystick button press or for any direction detected. +--------------------------------------------------------------------- +INPUT + waitKey => TRUE to wait until a button/direction is triggered + FALSE to make a scan and return immediately + waitRelease => TRUE to wait until a pressed button/direction is released + FALSE to return immediatly as soon as the button/direction + is pressed. +OUTPUT + dir => The numeric code of the direction detected or -1 if no direction was + detected. + fire => The numeric code of the button pressed or -1 if no button was + detected. +******************************************************************************/ + + If IsNil(waitKey) Then waitKey = False + If IsNil(waitRelease) Then waitRelease = True + + Repeat + ; Parse all available joysticks + Local joyCount = CountJoysticks() + For j = 0 To joyCount-1 + ; Check directions + If JoyDir(j) <> #JOYNODIR + Local jDir = JoyDir(j) + If waitRelease + While JoyDir(j) = jDir + Wait(10, #MILLISECONDS) + Wend + EndIf + Return(jDir, -1) + EndIf + + ; Check buttons + If JoyFire(j, 0) <> 0 + Local jFire = JoyFire(j, 0) + If waitRelease + While JoyFire(j, 0) = jFire + Wait(10, #MILLISECONDS) + Wend + EndIf + Return(-1, jFire) + EndIf + + Next + + Wait(10, #MILLISECONDS) + Until waitKey = False + + Return(-1, -1) + +EndFunction + +Function HL.FormatNum(v, i, d) + ; Format the number <v> with <i> integer positions and <d> decimal positions + ; Return the formatted string + ; ------------------------------------------------------- + Local iv = Int(v) ; Integer part + Local dv = v-iv ; Decimal part + + Local is = ToString(iv) + Local ds = UnRightStr(FormatStr("%." .. d .. "f", dv), 2) + + While StrLen(is) < i + is = " " .. is + Wend + + Return(is .. "." .. ds) + +EndFunction diff --git a/Helpers_license.txt b/Helpers_license.txt new file mode 100644 index 0000000..a9c26c0 --- /dev/null +++ b/Helpers_license.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017-2025 Fabio Falcucci + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Helpers_readme.md b/Helpers_readme.md new file mode 100644 index 0000000..502ddaa --- /dev/null +++ b/Helpers_readme.md @@ -0,0 +1,1200 @@ +# HELPERS LIBRARY + +```plain +Author : Fabio Falcucci (Allanon) +License : MIT (see Helpers_license.txt) +Version : 1.7 +Release : 07.05.2020 +Dependancies : - + +PayPal Support : hijoe@tin.it +Patreon Support : https://www.patreon.com/Allanon71 + +Repositories + - GitHub : https://github.com/Allanon71 (old versions) + - Gitea : https://gitea.it/allanon/HollywoodLibs + +Contacts + - email : fabio.falcucci@alice.it + - Mastodon : @allanon@mastodon.uno + - Links : https://linksta.cc/@Allanon + - WebSite : https://a-mc.biz +``` +--- + +## WHAT IS + +[![HELPERS Lib: Test program in action](images/Helpers.jpg)]() + +Helper library is an include file for Hollywood that adds several common task utility functions. + +This includes objects and methods to handle efficiently strings, colors, some basic functions to handle HTML code and many more usuful stuff. + +The image shows the main menu of the program used to test & shows the most important library functions. + +--- +## INTRODUCTION + +Even if Hollywood is a fully featured programming language, sometimes, you need functions that do a recurring job or help you with common tasks: I gathered all of these functions into this library. + +At some point, the inbuilt string functions, I noticed low performances on very long strings, so I started to investigate and I've found a way to increase the string handling speed by a huge factor so I built an object and some methods to deal with strings and speedup string handling. + +--- + +## EXAMPLES + +- [HL_WaitForAction](Examples/Helpers/HL_WaitForAction.hws) : Show how to use this function. +- [HL_Value2Perc](Examples/Helpers/HL_Value2Perc.hws) : Simple example that shows how to use this function. +- [HL_SizeString](Examples/Helpers/HL_SizeString.hws) : Shows how to format data into columns using this function. +- [HL_Safe](Examples/Helpers/HL_Safe.hws) : Shows how to get rid of errors caused by trying to handle Nil values and uninitialized variables. +- [HL_ParseRunArgs](Examples/Helpers/HL_ParseRunArgs.hws) : Easily parse arguments passed running the program from the command line (this program needs to be compiled as stated in the example source). +- [HL_Convert_BytesTo](Examples/Helpers/HL_Convert_BytesTo.hws) : Shows how to use this function. +- [HL_Examples](Examples/Helpers/HL_Examples.hws) : A collection of additional examples (this proigram is the one showed in the above screenshot). + +## INDEX + +- [Buffered String Object](#buffered_string_object) : To gain a huge improvements when dealing with string manipulations. +- [Color Object](#color_object) : To handle colors easily +- [String Functions](#strings) : Strings utility functions +- [Numbers Functions](#numbers) : Numebers utility functions +- [Conversion Functions](#conversions) : Functions to convert data, HTML tags, Unicode, ... +- [Input Functions](#input) : Functions to handle common input routines +- [Misc Functions](#misc) : Other utility functions + +--- + +<a id="buffered_string_object"></a> +## Buffered String Object + +This object and its methods are used to take advange by the buffered strings I implemented to achieve an **huge speed boost** in string handling functions. If your application has a lot of string manipulation you'd better use this object since the speed improvement is really impressive! + +As said, string concatenation in Hollywood is somewhat slow so I've implemented this class for speed up concatenations where there is the need to concatenate data, especially character by character, for example while decoding or encoding data. + +This trick speed up a lot this operation, in my tests I was able to process strings with single character concatenation at around **18 Kb/s**, using this system I was able to reach **680 Kb/s** ! + +### Data Structure + +Each object have the following structure : + +```plain +HL.BufferedString = + { dest = "", ; Output buffer + temp = "", ; Temporary buffer + len = 400, ; Temporary buffer length in characters (default= 400) + tlen = 0, ; Current temporary buffer length + chunks = { } ; String chunks + } +``` + +You can adjust the **temporary buffer length** (`.len` field) by overwriting this field in your objects or in the root object `HL.BufferedString`, in the latter case, which is a global setting, all successive object creations will have the buffer length you have specified. + +### Methods + +- [HL.BufferedString:AddChar()](#hl_bs_addchar) +- [HL.BufferedString:AddString()](#hl_bs_addstring) +- [HL.BufferedString:Get()](#hl_bs_get) +- [HL.BufferedString:New()](#hl_bs_new) +- [HL.BufferedString:PrepareForRead()](#hl_bs_prepareforread) +- [HL.BufferedString:Read()](#hl_bs_read) +- [HL.BufferedString:Set()](#hl_bs_set) + +--- + +<a id="color_object"></a> +## Color Object + +This object is used to handle colors, you can use its methods to manipulate colors easily. + +### Data Structure + +Each object have the following structure : + +```plain +HL.Color = + { r = 0, ; Red component (default = 0) + g = 0, ; Green component (default = 0) + b = 0, ; Blue component (default = 0) + a = 0 ; Alpha component (default = 0 -> full opaque) + } +``` + +Please note that all color components, including the alpha channel, must be within the range 0-255. + +### Methods + +- [HL.Color:Brighten(delta)](#hl_col_brighten) +- [HL.Color:Clone()](#hl_col_clone) +- [HL.Color:Darken(delta)](#hl_col_darken) +- [HL.Color:fromARGB(a, r, g, b)](#hl_col_fromargb) +- [HL.Color:fromValue(value)](#hl_col_fromvalue) +- [HL.Color:new(params, alpha)](#hl_col_new) +- [HL.Color:toARGBValue()](#hl_col_toargbvalue) +- [HL.Color:toRGBValue()](#hl_col_torgbvalue) + +--- + +<a id="strings"></a> +## Strings + +There is a set of utility functions dedicated to handling the strings. + +- [HL.Capitalize(t)](#hl_str_capitalize) +- [HL.CutBetweenLimits(txt, sLimit, eLimit, tags)](#hl_str_cutbetweenlimits) +- [HL.CutStringLeft(text, maxLen)](#hl_str_cutstringleft) +- [HL.CutStringRight(text, maxLen)](#hl_str_cutstringright) +- [HL.GetBetweenLimits(txt, sLimit, eLimit)](#hl_str_getbetweenlimits) +- [HL.GetReversedDateTime()](#hl_str_getreverseddatetime) +- [HL.GetRndName()](#hl_str_getrndname) +- [HL.Safe(value)](#hl_str_safe) +- [HL.SizeString(txt, size)](#hl_str_sizestring) + +--- + +<a id="numbers"></a> +## Numbers + +There is also some functions to deal with numbers. + +- [HL.FormatNum(value, ipos, dpos)](#hl_num_formatnum) +- [HL.RoundBig(value, decimals, roundDown)](#hl_num_roundbig) + +--- + +<a id="conversions"></a> +## Conversions + +Functions to convert values from an unit to another. + +- [HL.Convert.BytesTo(bytes, target, decimals)](#hl_conv_bytesto) +- [HL.Convert.ForTextOut(text)](#hl_conv_fortextout) +- [HL.Convert.HTML2Hollywood(text)](#hl_conv_html2hollywood) +- [HL.Convert.HTMLAmper2UTF8(html_amper)](#hl_conv_htmlamper2utf8) +- [HL.Convert.HTMLTag2HollywoodTag(html_tag)](#hl_conv_htmltag2hollywoodtag) +- [HL.Convert.Unicode2UTF8(value)](#hl_conv_unicode2utf8) + +--- + +<a id="input"></a> +## Input + +Some functions to handle common input routines. + +- [HL.WaitForAction(key, delay, callback, timeout, timeout_callback, userdata)](#hl_input_waitforaction) +- [HL.Input.CheckJoystick(waitKey, waitRelease)](#hl_input_checkjoystick) +- [HL.Input.CheckKeyboard(keyList, waitKey, waitRelease)](#hl_input_checkkeyboard) + +--- + +<a id="misc"></a> +## Misc + +There are also some functions that does not fit in the ohter categories. + +- [HL.IsNil(value)](#hl_misc_isnil) +- [HL.IsNotNil(value)](#hl_misc_isnotnil) +- [HL.GetRndColor(alpha)](#hl_misc_getrndcolor) +- [HL.LineHook.Enable()](#hl_misc_linehook_enable) +- [HL.LineHook.Disable()](#hl_misc_linehook_disable) +- [HL.NBWait(ms)](#hl_misc_nbwait) +- [HL.ParseRunArgs(CaseSensitive)](#hl_misc_parserunargs) +- [HL.RestartApp()](#hl_misc_restartapp) +- [HL.Value2Perc(Range, Value)](#hl_misc_value2perc) + +--- + +# DOCUMENTATION + +--- + +# Buffered Strings + +--- + +<a id="hl_bs_addchar"></a> +## HL.BufferedString:AddChar(char) + +Adds the character `char` to a buffered string object. + +**SYNOPSIS** + +`BufferedStringObject:AddChar(char)` + +**INPUT** + +- `char` : (STR) A string with a single character to add at the end of the buffered string. + +--- + +<a id="hl_bs_addstring"></a> +## HL.BufferedString:AddString(string) + +Adds the string `string` to a buffered string object. + +**SYNOPSIS** + +`BufferedStringObject:AddString(string)` + +**INPUT** + +- `string` : (STR) The string to add at the end of the buffered string. + +--- + +<a id="hl_bs_get"></a> +## HL.BufferedString:Get() + +Returns the content of the buffered string. + +**SYNOPSIS** + +`content = BufferedStringObject:Get()` + +**OUTPUT** + +- `content` : (STR) The string stored in the buffered string. + +--- + +<a id="hl_bs_new"></a> +## HL.BufferedString:new() + +Creates a new, empty, buffered string object. + +**SYNOPSIS** + +`BufferedStringObject = HL.BufferedString:New()` + +**OUTPUT** + +- `BufferedStringObject` : (TABLE) A new and empty buffered string object. + +--- + +<a id="hl_bs_prepareforread"></a> +## HL.BufferedString:prepareForRead() + +Whenever you need to read in the middle of the buffered string with the `:Read()` method you need to call this method to prepare the buffered string to be read. + +**SYNOPSIS** + +`BufferedStringObject:prepareForRead()` + +--- + +<a id="hl_bs_read"></a> +## HL.BufferedString:read(start, length) + +This method allow you to read in the middle of the buffered string starting from character number `start`, this method will return a string of length equal to the specified `length`. + +**SYNOPSIS** + +`contents = BufferedStringObject:read(start, length)` + +**INPUT** + + - `start` : (NUM) Start position of the string to return. + - `length` : (NUM) How many characters must be returned. + +**OUTPUT** + + - `content` : (STR) The extracted string. + +--- + +<a id="hl_bs_set"></a> +## HL.BufferedString:set(string) + +Sets the buffered string object with the specified `string`. + +**SYNOPSIS** + +`BufferedStringObject:set(string)` + +**INPUT**add + + - `string` : (STR) The string to store in the buffered string object. + +--- + +# Colors + +--- + +<a id="hl_col_brighten"></a> +## HL.Color:Brighten(delta) + +Make the current color brighten by a given factor represented by `delta`. + +**SYNOPSIS** + +`ColorObject:Brighten(delta)` + +**INPUT** + +- `delta` : (NUM) Amount in units to add to all current color's components (excluding the alpha channel). + +--- + +<a id="hl_col_clone"></a> +## HL.Color:Clone() + +Clones the color object and returns a new object. + +**SYNOPSIS** + +`newColorObject = ColorObject:Clone()` + +--- + +<a id="hl_col_darken"></a> +## HL.Color:Darken(delta) + +Make the current color darker by a given factor represented by `delta`. + +**SYNOPSIS** + +`ColorObject:Darken(delta)` + +**INPUT** + +- `delta` : (NUM) Amount in units to subtract to all current color's components (excluding the alpha channel). + +--- + +<a id="hl_col_fromargb"></a> +## HL.Color:fromARGB(a, r, g, b) + +Load the color object with the given `a`, `r`, `g` and `b` color components. + +**SYNOPSIS** + +`ColorObject:fromARGB(a, r, g, b)` + +**INPUT** + +- `a` : (NUM) Transparency value +- `r` : (NUM) Red component +- `g` : (NUM) Green component +- `b` : (NUM) Blue component + +--- + +<a id="hl_col_fromvalue"></a> +## HL.Color:fromValue(value) + +Load the color object with the given color `value`. + +**SYNOPSIS** + +`ColorObject:fromValue(value)` + +**INPUT** + +- `value` : (NUM) A 32bit color value. + +--- + +<a id="hl_col_new"></a> +## HL.Color:new(params, alpha) + +Creates a new color object. + +**SYNOPSIS** + +`ColorObject = HL.Color:new(params, alpha)` + +**INPUT** + +- `params` : (TABLE) It's an optional table with one or more members as follow: +- - `r` : (NUM) Red component +- - `g` : (NUM) Green component +- - `b` : (NUM) Blue component +- - `a` : (NUM) Transparency level + +**OR** + +- `params` : (NUM) An RGB color value +- `alpha` : (NUM) Optional alpha channel value. + +**OUTPUT** + +- `ColorObject` : (TABLE) A new color object + +--- + +<a id="hl_col_toargbvalue"></a> +## HL.Color:toARGBValue() + +Returns the ARGB color value. + +**SYNOPSIS** + +`colorValue = ColorObject:toARGBValue()` + +**OUTPUT** + +- `colorValue` : (NUM) A 32bit color value. + +--- + +--- + +<a id="hl_col_torgbvalue"></a> +## HL.Color:toRGBValue() + +Returns the RGB color value, without the alpha component. + +**SYNOPSIS** + +`colorValue = ColorObject:toRGBValue()` + +**OUTPUT** + +- `colorValue` : (NUM) A 24bit color value. + +--- + +# Strings + +--- + +<a id="hl_str_capitalize"></a> +## HL.Capitalize(t) + +Returns the string `t` with the first letter uppercase and all the remaining string in lowercase style. + +**SYNOPSIS** + +`result = HL.Capitalize(t)` + +**INPUT** + +- `t` : (STR) The string to process. + +**OUTPUT** + +- `result` : (STR) The processed string. + +--- + +<a id="hl_str_cutbetweenlimits"></a> +## HL.CutBetweenLimits(txt, sLimit, eLimit, tags) + +Removes all text between the given limiter strings, limiters included. + +**SYNOPSIS** + +`text, removed = HL.CutBetweenLimits(txt, sLimit, eLimit, tags)` + +**INPUT** + +- `txt` : (STR) Text to process +- `sLimit` : (STR) Starting limiter string +- `eLimit` : (STR) Ending limiter string +- `tags` : (TABLE)(OPT) Where to add removed text. + +**OUTPUT** + +- `text` : (STR) Processed text. +- `removed` : (TABLE) A table with all the removed strings, if the `tags` table has been provided the strings will be added to it otherwise a new table will be returned. +Each entry is a string composed by the starting limiter (`sLimiter`), the text between the limiters and the ending limiter (`eLimiter`). + +--- + +<a id="hl_str_cutstringleft"></a> + +## HL.CutStringLeft(text, maxLen) + +Cut the given `text` to the left if the string length is greater then `maxLen` and add a triple point prefix (`…`) to the string to indicate that the string has been shortened. + +**SYNOPSIS** + +`cutted = HL.CutStringLeft(text, maxLen)` + +**INPUT** + +- `text` : (STR) The text to process +- `maxLen` : (NUM) The final length the text should have excluding the triple point prefix. + +**OUTPUT** + +- `cutted` : (STR) The processed text. + +--- + +<a id="hl_str_cutstringright"></a> +## HL.CutStringRight(text, maxLen) + +Cut the given `text` to the right if the string length is greater then `maxLen` and add a triple point suffix (`…`) to the string to indicate that the string has been shortened. + +**SYNOPSIS** + +`cutted = HL.CutStringRight(text, maxLen)` + +**INPUT** + +- `text` : (STR) The text to process +- `maxLen` : (NUM) The final length the text should have excluding the triple point prefix. + +**OUTPUT** + +- `cutted` : (STR) The processed text. + +--- + +<a id="hl_str_getbetweenlimits"></a> + +## HL.GetBetweenLimits(txt, sLimit, eLimit) + +Get the text between the limiters. + +**SYNOPSIS** + +`ttext = HL.GetBetweenLimits(txt, sLimit, eLimit)` + +**INPUT** + +- `txt` : (STR) Text to process +- `sLimit` : (STR) Starting limiter string +- `eLimit` : (STR) Ending limiter string + +**OUTPUT** + +- `ttext` : (TABLE) Results table: each entry is a string composed by the starting limiter (`sLimiter`), the text between the limiters and the ending limiter (`eLimiter`). + +--- + +<a id="hl_str_getreverseddatetime"></a> +## HL.GetReversedDateTime() + +Returns the date and time in reversed order, useful for filenames that need to be sorted (for example log files). + +**SYNOPSIS** + +`txt = HL.GetReversedDateTime()` + +**OUTPUT** + +- `txt` : (STR) Current date and time with `yyyymmdd-hhmmss` format. + +--- + +<a id="hl_str_getrndname"></a> +## HL.GetRndName() + +Use this function to obtain a unique random name anytime you need it. + +**SYNOPSIS** + +`name = HL.GetRndName()` + +**OUTPUT** + +- `name` : (STR) A string with an unique random name. + +**NOTES** + +All returned random names are stored in the `HL.RandomNames` table to avoid +reusing and make them unique, if you wish to reset the assigned names for +some reasons (for example for low memory conditions) you only need to reset this table (with `HL.RandomNames = {}`). + +--- + +<a id="hl_str_safe"></a> +## HL.Safe(value) + +Returns the string `NIL` if `value` is Nil, otherwise returns `value` without +any change. +This function is usefull to concatenate strings without worring about errors caused by concatenating uninitialized variables. + +**This functions is OBSOLETE**, use `ToString()` instead. + +**SYNOPSIS** + +`result = HL.Safe(value)` + +**INPUT** + +- `value` : (ANY) Value to process. + +**OUTPUT** + +- `result` : (ANY) The `value` or the string `NIL`. + +--- + +<a id="hl_str_sizestring"></a> +## HL.SizeString(txt, size) + +This function is usefull to process strings and to easily produce aligned text columns. +The string `txt` will be processed and adapted to the given `size` length by adding spaces or by removing exceeding characters and adding `…` to the end to indicate that the string has been trunkated. + +**SYNOPSIS** + +`processed = HL.SizeString(txt, size)` + +**INPUT** + +- `txt` : (STR) Text to process. +- `size` : (UM) Target text length including the triple dots (if needed) or the additional spaces. + +**OUTPUT** + +- `processed` : (STR) Processed text with the exact length equal to `size`. + +**EXAMPLE** + +```plain +NPrint("HL.SizeString Example") +NPrint("---------------------\n") +; Suppose you have 40 character screen and a table with 'n' entries with the +; fields 'name' and 'surnames' and you want to print a table with these +; informations aligned into columns. + +; Initialize the table with some items +Local people = + { { name = "Clark", surname = "Kent" }, + { name = "Peter", surname = "Parker" }, + { name = "Bruce", surname = "Wayne" } } + +; Now we will print a nice table to the screen with the first column with a +; width of 14 characters, the second one with a width of 22 (plus 3 +; column separators): + +; Header +NPrint("|" .. HL.SizeString("NAME", 14) .. "|" .. HL.SizeString("SURNAME", 22) .. "|") + +; Contents +For Local i = 0 To 2 + NPrint("|" .. HL.SizeString(people[i].name, 14) .. + "|" .. HL.SizeString(people[i].surname, 22) .. "|") +Next + +; Exemple with string exceeding the available space +NPrint("|" .. HL.SizeString("This is an extremely long line!", 14) .. + "|" .. HL.SizeString("Another line, see how it works?", 22) .. "|") + +NPrint("\n--- Left mouse button to quit ---") +WaitLeftMouse() +``` + +--- + +# Numbers + +--- + +<a id="hl_num_formatnum"></a> +## HL.FormatNum(value, ipos, dpos) + +Returns a string with the formatted `value` with `ipos` integer positions and `dpos` decimal positions. Extremely useful to print aligned numbers in columns. + +**SYNOPSIS** + +`result = HL.FormatNum(value, ipos, dpos)` + +**INPUT** + +- `value` : (NUM) The number to format. +- `ipos` : (NUM) How many integer positions the result must have. +- `dpos` : (NUM) How many decimal positions the result must have. + +**OUTPUT** + +- `result` : (STR) The processed value. + +**NOTES** + +Note that if that the integer part will be filled with spaces to the left and the decimal part will be filled with zeros to the right. + +--- + +<a id="hl_num_roundbig"></a> +## HL.RoundBig(value, decimals, roundDown) + +Rounds `value` to the given decimals. + +This function is needed to avoid errors like this: + +```plain +DebugPrint(Int(40268.33971372/100000)*100000) +> -21474.83648 +``` + +**SYNOPSIS** + +`rounded = HL.RoundBug(value, decimals, roundDown)` + +**INPUT** + +- `value` : (NUM) The value to process +- `decimals` : (NUM) How many decimals you want +- `roundDown` : (BOOL) `True` to round down (default is `False` which performs a round up). + +**OUTPUT** + +- `rounded` : (NUM) The rounded value with the specified decimals. + +--- + +# Conversions + +--- + +<a id="hl_conv_bytesto"></a> +## HL.Convert.BytesTo(bytes, target, decimals) + +Convertes the bytes specified in `bytes` into the `target` unit, the result will have `decimals` decimal positions. + +**SYNOPSIS** + +`converted = HL.Convert.BytesTo(bytes, target, decimals)` + +** INPUT ** + +- `bytes` : (NUM) The bytes value to convert +- `target` : (NUM) The conversion target, can be one of the following constants: +`#HL_KILOBYTES`, `#HL_MEGABYTES`, `#HL_GIGABYTES`, `#HL_TERABYTES`, `#HL_AUTO`. +- `decimals` : (NUM) How many decimals the results should have (default = 0) + +**OUTPUT** + +- `converted` : (STR) The value after the conversion. + +**NOTES** + +If you use `#HL_AUTO` the `bytes` value will be converted to the most appropriate format and a suffix will be added to the resulting string. + +**EXAMPLE** + +```plain +Local bytes = 24589798800 +Local decimals = 2 + +NPrint("Converting " .. bytes .. " bytes to") +NPrint(" Kb : " .. HL.Convert.BytesTo(bytes, #HL_KILOBYTES, decimals)) +NPrint(" Mb : " .. HL.Convert.BytesTo(bytes, #HL_MEGABYTES, decimals)) +NPrint(" Gb : " .. HL.Convert.BytesTo(bytes, #HL_GIGABYTES, decimals)) +NPrint(" Tb : " .. HL.Convert.BytesTo(bytes, #HL_TERABYTES, decimals)) +NPrint(" Auto : " .. HL.Convert.BytesTo(bytes, #HL_AUTO, decimals)) +``` + +--- + +<a id="hl_conv_fortextout"></a> +## HL.Convert.ForTextOut(text) + +Escape square brackes (only if not part of Hollywood tags system) to avoid errors with the native command `TextOut()` function. + +**SYNOPSIS** + +`converted = HL.Convert.ForTextOut(text)` + +**INPUT** + +- `text` : (STR) Text to process + +**OUTPUT** + +- `converted` : (STR) Converted text. + +--- + +<a id="hl_conv_html2hollywood"></a> +## HL.Convert.HTML2Hollywood(text) + +Convert the given HTML `text` into a text formatted with Hollywood tags. + +**SYNOPSIS** + +`converted = HL.Convert.HTML2Hollywood(text)` + +**INPUT** + +- `text` : (STR) The HTML text to convert into an Hollywood compatible format. + +**OUTPUT** + +- `converted` : (STR) The converted text. + +**NOTES** + +Currently handled HTML entities are: + +- Slashes +- Double-Slashes +- Tabs +- HTML Tags (see `HL.HTML_Tags` table) +- HTML Ampersands (see `HL.HTML_Amper` table) +- Ordered lists +- Unordered lists + +--- + +<a id="hl_conv_htmlamper2utf8"></a> +## HL.Convert.HTMLAmper2UTF8(html_amper) + +Convert the given `html_amper` into an UTF8 encoded string. + +**SYNOPSIS** + +`converted = HL.Convert.HTMLAmper2UTF8(html_amper)` + +**INPUT** + +- `html_amper` : (STR) The HTML amper to convert. + +**OUTPUT** + +- `converted` : (STR) The encoded text. + +**NOTES** + +The table `HL.HTML_Amper` holds all supported conversions. + +--- + +<a id="hl_conv_htmltag2hollywoodtag"></a> +## HL.Convert.HTMLTag2HollywoodTag(html_tag) + +Convert the given `html_tag` into an Hollywood stardard text's tags where possible. + +**SYNOPSIS** + +`converted = HL.Convert.HTMLTag2HollywoodTag(html_tag)` + +**INPUT** + +- `html_tag` : (STR) The HTML tag to convert. + +**OUTPUT** + +- `converted` : (STR) The converted tag. + +**NOTES** + +The table `HL.HTML_Tags` holds all supported conversions. + +--- + +<a id="hl_conv_unicode2utf8"></a> +## HL.Convert.Unicode2UTF8(value) + +Encode `value`, an hexadecimal string **WITHOUT** any prefixes (`$`, `0x`), to the corresponding UTF8 string. + +**SYNOPSIS** + +`converted = HL.Convert.Unicode2UTF8(value)` + +**INPUT** + +- `value` : (NUM) The hexadecimal value to convert without any prefix. + +**OUTPUT** + +- `converted` : (STR) The encoded UTF8 string + +**NOTES + +Additional informations here : [http://www.czyborra.com/utf/](http://www.czyborra.com/utf/) + +--- + +# Input + +--- + +<a id="hl_input_waitforaction"></a> +## HL.WaitForAction(key, delay, callback, timeout, timeout_callback, userdata) + +This function is used to wait for an user action, the action can be a key press +defined by `key` character, the left mouse button press or any button of any connected joystick. + +It's possible to define a timeout to let the program continue without any user action. + +This routine supports callback functions to run an animation during the wait time or to accomplish other tasks, or to call a specific function if the timeout occurs. + +**SYNOPSIS** + +`result = HL.WaitForAction(key, delay, callback, timeout, timeout_callback, userdata)` + +**INPUT** + +- `key` : (STR) Monitored key, or the string `ANY` for any key press. +- `delay` : (NUM) Delay in milliseconds for the detection loop, every time the delay is passed a call to `callback` function will be made. +- `callback` : (FUNC) Function called at every loop, it's called with the `userData` parameter. If the callback function returns `True` the wait loop will be interrupted immediatly (usefull to cancel the wait loop by the user). +- `timeout` : (NUM) Timeout in milliseconds. +- `to_callback` : (FUNC) Function called if the timeout is reached, the function will be called with the `userData` parameter. +- `userData` : (ANY) Custom user data that is passed to the callback function. + +**OUTPUT** + +- `result` : (BOOL) `False` if a timeout occurred, otherwise `True`. + +--- + +<a id="hl_input_checkjoystick"></a> +## HL.Input.CheckJoystick(waitKey, waitRelease) + +Wait for a joystick button press or for any direction detected. + +**SYNOPSIS** + +`dir, fire = HL.Input.CheckJoystick(waitKey, waitRelease)` + +**INPUT** + +- `waitKey` : (BOOL) `True` to wait until a button/direction is triggered, `False` to execute a single scan and return immediately. +- `waitRelease` : (BOOL) `True` to wait until a pressed button/direction is released, `False` to return immediatly as soon as the button/direction is pressed. + +**OUTPUT** + +- `dir` : (NUM) The numeric code of the direction detected or -1 if no direction has been detected. +- `fire` : (NUM) The numeric code of the button pressed or -1 if no button has been detected. + +--- + +<a id="hl_input_checkkeyboard"></a> +## HL.Input.CheckKeyboard(keyList, waitKey, waitRelease) + +Wait for a key press from a list of allowed keys. + +**SYNOPSIS** + +`key = HL.Input.CheckKeyboard(keyList, waitKey, waitRelease)` + +**INPUT** + +- `keyList` : (TABLE) A table of all allowed keys, you can set it to the string `ANY` to detect any key press. +- `waitKey` : (BOOL) `True` to wait until a key is pressed, `False` to make a single scan and return immediatly. +- `waitRelease` : (BOOL) `True` to wait until a pressed key is released, `False` to return immediatly as soon as the key is pressed. + +**OUTPUT** + +- `key` : (STR) A string representing the pressed key or an empty string if no key has been pressed (this last case is valid only if you have set `waitKey = False`). + +--- + +# Misc + +--- + +<a id="hl_misc_isnil"></a> +## HL.IsNil(value) + +**This functions is OBSOLETE**, use `IsNil()` instead. + +Check `value` and returns `True` if it is `Nil`, otherwise returns `False`. + +**SYNOPSIS** + +`result = HL.IsNil(value)` + +**INPUT** + +- `value` : (ANY) Value to check against `Nil`. + +**OUTPUT** + +- `result` : `True` if `value` is `Nil` otherwise `False`. + +**NOTES** + +This function was developed when the native `IsNil()` function wasn't yet implemented. + +--- + +<a id="hl_misc_isnotnil"></a> +## HL.IsNotNil(value) + +**This functions is OBSOLETE**, use `Not(IsNil())` instead. + +Check `value` and returns `False` if it is `Nil`, otherwise returns `True`. + +**SYNOPSIS** + +`result = HL.IsNotNil(value)` + +**INPUT** + +- `value` : (ANY) Value to check against `Nil`. + +**OUTPUT** + +- `result` : `False` if `value` is `Nil` otherwise `True`. + +**NOTES** + +This function was developed when the native `IsNil()` function wasn't yet implemented. + +--- + +<a id="hl_misc_linehook_enable"></a> +## HL.LineHook.Enable() + +This function should be used in conjuction with `HL.LineHook.Disable()` and its +purpose is to remember `LineHook()` status especially when called several times +by nested routines or recursive routines. +It executes the native command `EnableLineHook()` and increase the usage counter so that it counter part `HL.LineHool.Disable()` will be effectively executed when the counter reaches 0. + +--- + +<a id="hl_misc_linehook_disable"></a> +## HL.LineHook.Disable() + +This function should be used in conjuction with `HL.LineHook.Enable()` and its +purpose is to remember `LineHook()` status especially when called several times +by nested routines or recursive routines. +It executes the native command `DisableLineHook()` only when the counter of `HL.LineHook.Enable()` is equal to 0. If the counter is not 0 then it decrease the counter by one instead. + +--- + +<a id="hl_misc_parserunargs"></a> +## HL.ParseRunArgs(CaseSensitive) + +Parses the command line arguments and returns a table indexed with the argument key and with the value set with the argument's parameter. + +The switch `caseSensitive` defaults to `True`, set it to `False` to parse arguments and parameters without casing distinctions. + +**SYNOPSIS** + +`parsedArgs = HL.ParseRunArgs(caseSensitive)` + +**INPUT** + +- `caseSensitive` : (BOOL) `True` to parse case sensitive arguments and parameters, if you set this parameter to `False` all arguments and parameters will be converted to lower case characters. + +**OUTPUT** + +- `parsedArgs` : (TABLE) A table indexed with the command line arguments and with values sets as the parameter values. + +**NOTE** + +Keep in mind that all values will be returned always as strings, it's up to you to convert values in the needed/expected format. + +This command is really usefull to test arguments passed using the command line, you only need to check the resulting table keys with the supported arguments and act accordingly. + +--- + +<a id="hl_misc_value2perc"></a> +## HL.Value2Perc(range, value) + +Convert the given `value` to a percentual value. + +The percentual is calculated using the table `range` that must have 2 entries, like this: + +- Index `[0]` : Range's minimum value +- Index `[1]` : Range's maximum value + +If `value` is out of range it will get minimum or maximum value depending on which boundary it exceeds. + +**SYNOPSIS** + +`percentual = HL.Value2Perc(range, value)` + +**INPUT** + +- `range` : (TABLE) A table with at least two entries at index 0 and 1 used to specify the minimum and maximum `value`'s range. +- `value` : (NUM) Value we need to convert into a percentual value. + +**OUTPUT** + +- `percentual` : (NUM) Converted value ranging from 0 to 1. + +**EXAMPLE** + +```plain +NPrint("HL.Value2Perc Example") +NPrint("---------------------\n") + +NPrint("1) Range=( 1: 100), Value= 89, Converted=" .. HL.Value2Perc({ 1, 100}, 89)) +NPrint("2) Range=(21: 120), Value=109, Converted=" .. HL.Value2Perc({21, 120}, 109)) +NPrint("3) Range=(40:1500), Value= 91, Converted=" .. HL.Value2Perc({40, 1500}, 91)) +NPrint("4) Range=( 5: 70), Value=104, Converted=" .. HL.Value2Perc({ 5, 70}, 104)) + +NPrint("\n--- Left mouse button to quit ---") +WaitLeftMouse() +``` + +--- + +<a id="hl_misc_nbwait"></a> +## HL.NBWait(ms) + +This function is used to pause the program for the time specified in `ms` (milliseconds). While the program is paused the internal loop will performs event checking allowing you to pause the program without pausing anything driven by events handlers. The internal loop performs an event check every 5 milliseconds. + +`NBWait` stands for non-blocking wait. + +**SYNOPSIS** + +`HL.BNWait(ms)` + +**INPUT** + +- `ms` : (NUM) Timeto wait, expressed in milliseconds. + +--- + +<a id="hl_misc_restartapp"></a> +## HL.RestartApp() + +Used to restart the current Hollywood running application. + +**NOTES** + +Tested only on Windows systems, the `.hws` extension must be associated with `Hollywood.exe` to let it work also with scripts. + +--- + +<a id="hl_misc_getrndcolor"></a> +## HL.GetRndColor(alpha) + +Returns a random color, eventually with a random alpha transparency value. + +**SYNOPSIS** + +`color = HL.GetRndColor(alpha)` + +**INPUT** + +- `alpha` : (BOOL) Set to `True` if you want to get a random color with random transparency. + +**OUTPUT** + +- `color` : (NUM) The generated random color value. + +--- + +# CHANGE LOG + +- `02/05/2025` +- - Written a proper documentation +- - Fixed `HL.NBWait()`: the used timer was never stopped. +- `19/09/2020` +- - `HL.RoundBig()` now accept a third parameter to round up or down +- `03/09/2020` +- - Added `HL.FormatNum(v, i, d)` +- `07/08/2020` +- - Added `HL.RestartApp()` +- `04/07/2020` +- - Added `HL.Capitalize(txt)` +- `17/06/2020` +- - Added `HL.NBWait(ms)` +- `06/05/2020` +- - Added `HL.RoundBig(value, decimals)` +- `23/02/2019` +- - Added 761 Ampersand symbols +- `12.02/2019` +- - Fixed a bug in the `HL.Convert.HTMLAmper2UTF8()` function +- `02.06.2018` +- - Added `HL.BufferedString:PrepareForRead()` +- - Added `HL.BufferedString:Read()` +- `07.04.2017` +- - In `HL.GetBetweenLimits(txt, sLimit, eLimit, caseSense)` added `caseSense` for the tags search (default = False) +- - Fixed a bug in `HL.CutBetweenLimits(txt, sLimit, eLimit, tags)` +- `16.02.2017` +- - Added `#HL_AUTO` in `HL.Convert.BytesTo()` +- `20.12.2016` +- - Added `HL.LineHook.Enable()` and `HL.LineHook.Disable()` to allow nested uses of this function. +- `13.12.2016` +- - Fixed a bug in `HL.WaitForAction()`, when a joystick was detected the timeout will never be triggered. diff --git a/images/Helpers.jpg b/images/Helpers.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b93bbfe1da86c06e1034f393059a0e0b6f9349ad GIT binary patch literal 56086 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 literal 0 HcmV?d00001