Mage's Modular Programming Demo - Widgets
Preface.
What if making excellent menu systems was as easy as a single command, and you also had access to the code?
Several weeks ago I posted about my desire to expand on previous examples of reusable generic functions. I mentioned that a core set of functions could be created, highly polished that allow new programs to be written with excellent complex features and little to no work needed because of reusable code. Well it turns out that I've been working on this for a few weeks. Previously I released a Bitmap Font framework called BFont.
https://forum.thegamecreators.com/thread/207584. I have used this as a foundation and expanded the work fixing bugs, adding features, and most importantly adding GUI Widgets like lists and buttons. I have long had issues when writing programs where I would need to list information like file names, or have buttons, or other controls. These things always need to be rewritten, they were never consistent, took a lot of time, looked terrible, and offered minimal functionality. What if I could create a 100% functional button with one command? Or a list? This demo is just that. It is a series of functions that allow people to create Buttons, Button Lists, Graphical Image Buttons, Dialogs, Lists, Radio Buttons, Check-boxes, and Textboxes all with one command. Everything is very polished and featured, ties into the Bitmap Font system, and is accessible and expandable since the source is right there. I have decided to release this as a demo presentation in part as a way for me to push me to polish this framework to a high level.
I also mentioned the difficulty of handling text input. I have long used some reusable functions to accept text input, where functionality was minimal. You could see characters you could type, backspace was supported, and only one of the enter buttons was supported. For this demo I have gone all out. The textbox widget in this demo attempts to replicate most of the features of a windows textbox. There is mouse support, both enter buttons are supported, you can seek and edit the middle of text, home/del/end keys are supported and most impressively autocomplete has been implemented. I mentioned this should be possible when I talked about this several weeks ago. Here it has been implemented. If the feature is active then the textbox will break sentences apart and remember words, and store those words in a dictionary file. When you type the textbox will offer autocomplete suggestions.
I feel this work will put to rest some longstanding pains I have had making apps like editors and menus.
I will attach a downloadable demo with the full code and media (the bitmap font uses images).
Lists - Problem solved. Permanently.
I started with making a fully functional list widget. You can see that there is full mouse support. The scroll and selection is completely there. Also an important detail is that through this entire demo mouse clicking activates on release not press. This makes all of the controls very nice to use. Have you ever tried to list a bunch of stuff on the screen only to have them scroll off the screen? Does it look ugly? Wierd controls to select stuff? Long confusing code? This fixes that. You can basically setup a list with one command and there's a bunch of simple controls like in visual basic. You can also easily sort and search the list with one command.
Dialog Boxes and Bitmap Fonts.
I released a bitmap font system BFont. This is MFont, and updated expanded version. This solves a major problem with most other font systems. Professional games don't install windows fonts, and don't use them. MFont allows you to ensure in a simple way text looks the same on all computers. It also gives you some extra control and conveniences. The dialog box in this shot is a single widget with 2 buttons built in. This is useful for an Ok/Cancel dialog or something like that.
Radio Buttons, Check Boxes, Button Lists and Color Themes.
It's probably a good idea to have some more robust controls than buttons. So I implemented Button Lists which are buttons where identical sized buttons can be easily added below on the screen. This is good for menus. Then I added radio buttons which are a list of buttons where only one of the buttons is allowed to be active at a time. Finally I implemented check boxes which look like radio buttons but there's no rule about one being active at a time. All of these use consistent styles and controls. They are all single command creation and use. Also seen here is color theme support. All of the controls and control text have a common color system. This enables very quick and easy changing of the entire color theme.
Text Boxes and Autocomplete.
While I am pleased at doing a good job with lists, I am also very pleased with this work. So the textbox has an editing cursor and mouse support. You can click to place the cursor or use arrow keys including Home/End. This is a big deal, I've always had to delete a whole sentence if I was typing something and made a mistake. Both enter keys are supported and Del/Backspace are too. You'll also see here that the textbox has an autocomplete feature. Basically if the feature is active and if you have a dictionary.txt file in the main directory of your program, the textbox will suggest words from that dictionary file. When you type the ghosted suggestion will appear. If you press spacebar it will autocomplete. If you press the delete key the autocomplete will disappear in case you need to type a space instead of autocomplete.
CPU Friendly Framerate Limiter.
This framework and the demos all use a cpu friendly framerate limiter. This allows the program to run capped at 60 fps (or other rates) and the program will only use 1% of the cpu instead of 100% at all times no matter what. This will allow apps to save battery life, and keeps fans quieter. Basically the framework wraps SYNC in a function where this extra functionality can be tacked on. This is also done since I plan on adding in easier controls for controlling multiple cameras and the SYNC MASK command which is always a pain to deal with. Also I have a crash prevention system which is not included in this framework since it would make things confusing. It's better to include the wrapper now rather than convert every program for it later. So I decided to throw in the SYNC command wrapper with CPU friendly frame limiter. I think this will help people greatly.
This is based on this previous tip:
https://forum.thegamecreators.com/thread/202812
Extra Details...
This issue was resolved in update 3.
The bitmap font system was fast but not as fast as regular screen text. So for MFont I explored using Image Kit but there were technical limitations that made using it problematic. Instead I wrote a caching system where text that is written to the screen a lot (consider your game is probably drawing 30 or 60 frames per second) the text would be converted into an image and merely pasted automatically on the fly. I found that this approach along with the necessary overhead was only 10-20% faster than using sprites for each letter. Additionally the initial conversion process to turn a recolored, scaled, u/v adjusted, 8bit transparent, string of sprites into a single image was too slow to be useful in most situations. The caching effect would generate a single nearly 500ms lag spike. While a single lag spike like this might be excusable in some situations, it kills the program if lots of changing text is on the screen. This functionality is still included in the demo. You can activate/deactivate this by setting mfont_fastdrawenabled to 1 or 0. I wanted it to work and be a higher profile inclusion but its not suitable for prime time.
The code for converting a recolored, scaled, u/v adjusted, 8bit transparent, string of sprites into a single image is shown here:
`Converts a string of pasted bitmap text sprites into an equivilant image that can be pasted as an opimization.
`Works by pasting a version of the text on white and black background in a hidden bitmap layer, then combining the two images into one.
`The combine method will cause the resulting image to have all of the intended 8bit transparency and color.
`Stores resulting image in fast draw system or returns zero index as abort code.
Function MFont_FastDraw_CreateImg(mIndex, textstring$)
`Get the text width
textWidth = MFont_GetTextWidth(textstring$)
If textWidth = 0 Then ExitFunction 0
`Get a free bitmap if it exists.
For i = 1 to 31
If Bitmap Exist(i) = 0 then bitmapNum = i
Next i
`Fail if no available bitmaps
If bitmapNum = 0 Then ExitFunction 0
`Store current bitmap so it can be restored
currentBitmap = Current Bitmap()
`Create a new bitmap width of the text and double the height of the text.
Create Bitmap bitmapNum, textWidth, MFont_Size * 2
Set Current Bitmap bitmapNum
`Create a white box as a background for the top half of the bitmap.
A2FillBox 0, 0, textWidth, MFont_Size - 1, RGB(255,255,255)
`Draw the font twice. Over white and black background.
MFont_FastDraw_Text(textstring$)
`Grab images of the same text over black and white backgrounds.
wImg = MFont_GetFreeObj(0)
Get Image wImg, 0, 0, textWidth, MFont_Size - 1, 3
bImg = MFont_GetFreeObj(0)
Get Image bImg, 0, MFont_Size, textWidth, MFont_Size * 2 - 1, 3
`combine the images into one image.
`The resulting image will have all intended transparency baked in. Result replaces the black gackground image.
If MFont_FastDraw_Combine(bImg, wImg) = 0
`in the event at there is a failure, delete the black image so it doesn't end up drawn to screen.
MFont_DeleteImage(bImg)
mIndex = 0
EndIf
`Delete the white background image. Nolonger needed.
MFont_DeleteImage(wImg)
`Record image index into optimization system.
if mIndex > 0 Then mfont_fastdrawimg(mIndex) = bImg
`Restore the bitmap settings to the original
Delete Bitmap bitmapNum
Set Current Bitmap currentBitmap
MFont_Reset()
EndFunction mIndex
`This function takes two versions of the same image with black and white backgrounds and generates the same image with a transparent background.
`All 8bit transparency is properly generated. The resulting image will replace the black background image.
function MFont_FastDraw_Combine(bImg, wImg)
`Exit if one of the images doesnt exist
If bImg < 1 then ExitFunction 0
If Image Exist(bImg) = 0 then ExitFunction 0
If wImg < 1 then ExitFunction 0
If Image Exist(wImg) = 0 then ExitFunction 0
`Find unused memblocks
Memblock1 = 1
repeat
inc Memblock1
until memblock exist(Memblock1) = 0
Memblock2 = 2
repeat
inc Memblock2
until memblock exist(Memblock2) = 0
`Create 2 Memblocks from black image and white image.
make memblock from image Memblock1, bImg
Width = memblock dword(Memblock1,0)
Height = memblock dword(Memblock1,4)
Depth = memblock dword(Memblock1,8)
make memblock from image Memblock2, wImg
Width2 = memblock dword(Memblock2,0)
Height2 = memblock dword(Memblock2,4)
Depth2 = memblock dword(Memblock2,8)
`Exit if one of the images has an illegal dimension.
If Width = 0 then ExitFunction 0
If Width2 = 0 then ExitFunction 0
If Height = 0 then ExitFunction 0
If Height2 = 0 then ExitFunction 0
If Depth <> 32 then ExitFunction 0
If Depth2 <> 32 then ExitFunction 0
If Width <> Width2 then ExitFunction 0
If Height <> Height2 then ExitFunction 0
`Nested Loops: Run Through every pixel, generating the final render, overwriting the black image memblock.
Position = 12 `skip header
mCounter = 1
for y = 1 to Height `For: Cycle pixel rows.
for x = 1 to Width`For: Cycle pixel columns.
`Retrieve the ABGR values (RGB with Alpha, in a wierd order) from the Memblocked Images.
Alpha = memblock byte(Memblock1, Position+3)
Blue = memblock byte(Memblock1,Position)
Green = memblock byte(Memblock1,Position+1)
Red = memblock byte(Memblock1,Position+2)
oAlpha = memblock byte(Memblock2, Position+3)
oBlue = memblock byte(Memblock2,Position)
oGreen = memblock byte(Memblock2,Position+1)
oRed = memblock byte(Memblock2,Position+2)
`IF Block: Find the ABGR Value for the Final Render pixel. Optimizes for Completely Transparent, Completely Opaque, and Parital Transparency pixels.
`:A pixel (x,y) from Black Image and White Image is compared to determine what the Final Render Pixel is.
If Red + Green + Blue = 0 and oRed + oGreen + oBlue = 765 `IF: The pixel is empty. Record Empty Pixel. (Black Image Pixel is RGB(0,0,0) and White Image Pixel is RGB(255,255,255))
newCol = (0 << 24) + (0 << 16) + (0 << 8) + 0
Else `Else: Pixel is not empty.
If Red = oRed and Blue = oBlue and Green = oGreen `IF: The pixel is 100% Opaque. Record the Pixel. (Both black image and white image pixels are identical)
newCol = (Alpha << 24) + (Red << 16) + (Green << 8) + Blue `//argb
Else `Else: Pixel is not 100% opaque and not empty. Oh no math... figure out what the heck its RGBA values are.
`Obtain the alpha value
RealAlpha# = 256.0 + Red - oRed
If RealAlpha# = 0 Then RealAlpha# = 0
If RealAlpha# > 255 Then RealAlpha# = 255
`If Block: Use the Alpha value to calculate the RGB values from the Black Image Pixel.
If RealAlpha# = 0 `Error Trap: Division by 0
RealRed = 0
RealGreen = 0
RealBlue = 0
Else
RealRed = Int(256.0 * Red / RealAlpha#)
RealGreen = Int(256.0 * Green / RealAlpha#)
RealBlue = Int(256.0 * Blue / RealAlpha#)
EndIf
`Record the ABGR semi-transparent pixel for final render.
newCol = (Int(RealAlpha#) << 24) + (RealRed << 16) + (RealGreen << 8) + RealBlue `//argb
EndIf `Pixel is Opaque or Translucent
EndIf `Pixel is Empty
`Write the Final Render Pixel, overwriting the existing Black Image Pixel.
write memblock Dword Memblock1,Position, newCol
inc Position,4
next x
next y
`Overwrite Black Image with the Final Render Image.
make image from memblock bImg,Memblock1
`Delete the memblocks
delete memblock Memblock1
delete memblock Memblock2
EndFunction 1
Based on my previous post here:
https://forum.thegamecreators.com/thread/184514
Notable Exclusions.
Issue resolved in update 3.
Issue resolved in update 4.
I probably should have included a progress bar. Shameful, it would only take about an hour to program it in as a widget. I might update with it. I also plan to make a File Load and File Save widget that is a "Meta" widget. So creating a file load widget automatically creates a list for folders, a list for files, some buttons, and handles the functionality. Then when the user finally picks a file, the entire "Meta" widget activates (in the same sense a button would when pressed), so the function drawing the file load widget detects the activation and can then grab the picked file, delete the widget, and move on to actually loading the file. So the entire file loading process becomes 1 command and fairly trivial. No need to concern yourself with a menu system or navigating folders. Since its a 1 command draw, you can even add in extra functionality like previewing what ever the file load widget is currently selecting before it "activates" with the users picked file. Anyway that might be an update. I will be doing this.
Requirements.
1. Dark Basic Professional - Latest Version
https://forum.thegamecreators.com/thread/180294
2. Advanced 2D Plugin
https://forum.thegamecreators.com/thread/179096
Feel free to comment, complain, dissect, suggest, ask, and talk.
Working Demo Source Code - Attached to this post!
Previous modular code discussion:
https://forum.thegamecreators.com/thread/220626
Interested in converting other fonts into this system? Here's the tool:
https://forum.thegamecreators.com/thread/189225
See my previous tip here...
https://forum.thegamecreators.com/thread/207584
Update 3 - Oct 25 2017 - Adds Progress Bars, Fast Draw, Image Text, Camera Mask Management, Frame Limiter Controls.
Update 4 - Oct 27 2017 - Adds File Load Dialogs, File Save Dialogs, Basic Panels, and an example for Graphical Buttons.
Update 5 - Oct 31 2017 - Adds Unlimited widgets and list sizes, help documentation, debug panel, and bug fixes.
Update 6 - Nov 15 2017 - Adds Multi-Line Text/TextBoxes, Drop Down Lists, Bitmap Font Patches, Bug fixes.
Update 6.1 - Nov 20 2017 - Bug fixes. Patches to Button Lists, Debug Menu, Textbox caption changing, help documentation.
Update 6.2 - Dec 4 2017 - Bug fixes. Mouse right click activate when released now works correctly.
Update 6.3 - Feb 9 2018 - Mouse middle click added, enhanced multiple font support, tweaked auto-complete min 5 characters or more.
Update 6.4 - Mar 4 2018 - Enhanced File Load/Save dialogs with folders/drives support. Fixed related bugs.
Update 6.5 - April 7 2018 - Added support for inline color codes for text.
Update 6.5.2 - July 6 2018 - Bug Fixes. Added support for removing entries from lists.
Update 6.5.4 - October 5 2018 - Bug Fixes. Added support for text links.
Update 6.5.5 - Jan 7 2021 - Fixed Dictionary Hard limit Crash. Bug Fixes with List sorting. Bug Fixes with MFont Character Limit Calc and Inline Color.
Update 6.5.6 - Jan 21 2021 - Bug fix with list scroll bar that required mouse to remain within the bar. Other minor bug fixes.
Update 6.5.7 - Feb 14 2022 - Bug fix with deleted textboxes not deleting entirely. Added Condense function to Lists.
Update 6.5.8 - Feb 15 2022 - Added SetRaw function to progress bars. Sets progress, skips animation. Fixed crash on multiple panels created.
Update 6.5.9 - Mar 2 2022 - Added MImgButton to documentation, and corrected some errors. Added Initialization Loading screen.
Update 6.7.2 - Sept 20 2022 - Added tree lists and inline text icons, corrected crash errors with various controls, updated documentation.
Update 6.7.3 - Sept 22 2022 - Fixed bug with progress bar animations. More accurate, fluid, less jumpy.
Update 6.7.4 - July 17 2023 - Fixed bug with File Save dialog not selecting a file.
Update 6.7.5 - May 22 2024 - Added Color Palette, fixed bug where dictionary would overflow and crash.