Introduction

formLESS is a GUI framework that provides a set of useful and common "form components" that will help you build a rich user interface for your game.

What sets formLESS apart from other similar resources is that it doesn't even try to draw the GUI for you, instead it provides all the functionality and logic you need and lets you build on top of it, allowing you focus on how to present your interface.
This is possible because formLESS components are thought to be attached to GUI elements, and are therefore kept separate from their representation, meaning that you create your own GUI objects, define how they will look, and attach a component to them to provide the required functionality. This way you don't need to modify nor even look at how component objects work, you just embed an use their values in your objects.

All of the above of course implies that you still code the graphical part of the user interface, but it also means that you are given more freedom on how to do it, and don't have to modify someone else work or deal with cluttered skinning systems.

Install

1. Open GameMaker: Studio, log in to the Marketplace and navigate to the Library tab. From here you can download the resource into your project.
2. Next, go into the extensions folder in the resource tree, double click the formLESS extension and select the "Import resources" tab.
3. To include formLESS into an existing project, import the contents of the "fl" folders in sprites, scripts and objects. If you want to look at a sample implementation, import all.

Form controllers

The first thing to do to start using formLESS in one of your game rooms is to add a form controller. This is done by creating a new object, assigning obj_fl_controller to it as its parent and putting it in your room. Do not use obj_fl_controller directly, the idea is that you create a form controller for every room that requires it.

Form controllers handle the focus and z-index of the room components and are therefore required for everything else to work properly. They also allow you to specify some behaviour for the components present in the current room. This is done by ovverriding the following events:

alarm0: if you want to focus/blur/modify a component at room start, you will need to do it here, since the creation order of the various components possibily now known in advance.

event user 0: this event is called whenever a button or tab component is pressed. The component that called the event can be referred in this event as "other". In short, in this event you specify all the actions of your buttons/tabs in the current room. Here's an example implementation:

switch(other.name) { //Get the name of the component that called this event
    case "button_1":
        //button 1 has been pressed
    break;
    case: "button_2":
        //button 2 has been pressed
    break;
    case: "tab_1":
        //tab 1 has been pressed
    break;
}

other events: if you add a create, room end or destroy event, be sure to call event_inherited() somewhere.

In your form controller, you also inherit a variable called components that holds an automatically updated ds_list of components present in you room, sorted by depth.

Components

Components represent the form fields you can attach to your objects to provide the required functionality.
All components have a related fl_add_(component_name) script that has to be called, with its specific component arguments, inside the UI object you want it attached to.

All components have a set of common attributes you can access after you have created an instance (attributes marked with * should be considered read only):

name: the name of the specific component instance. You should avoid giving the same name to two components in the same room. The name is used by some script to identify the component instance.
value: the value of the field. Depending on the type of component, it can be a real, string or array (see specific component reference)
x *: x position of the component in the room
y *: y position of the component in the room
rel_x *: x position relative to the instance the component is attached to
rel_y *: y position relative to the instance the component is attached to
width: width of the component
height: height of the component
disabled: if true, the component is considered disabled. E.G: textfields become reaonly, buttons no longer execute their action on click.
parent*: The instance the component is attached to

Moreover, components have a set of read only attributes telling their current state:

on_focus *: true when the component received focus in the current step
on_blur *: true when the component loses focus in the current step
on_change *: true when the value of the component has changed in the current step
on_press *: true if the component has been pressed in the current step
on_release *: true if the component has been released in the current step
has_focus *: true if the component has focus
pressing *: true if the component is currently being pressed
hover *: true if the mouse if hovering the component

Note that the on_release and pressing attributes are true when the component has focus and the mouse is released / pressed in that step, they don't require the mouse to be hovering the component.

Button

Buttons are a simple component that execute an action when pressed.

When buttons are pressed, event_user(0) of the form controller is called in order to determine the action to be performed (see form controllers section above)

Custom attributes:

None

Creation:

Returns a button component instance

fl_add_button(name,rel_x,rel_y,width,height,[value]);

Checkbox

Checkboxes are components that hold a true / false checked status.

Custom attributes:

checked: true of false, determines the current checkbox state.

Creation:

Returns a checkbox component instance

fl_add_checkbox(name,rel_x,rel_y,width,height,[value],[checked]);

List

Lists are simple components that reference a ds_list of options and allow to cycle through them. Note that the ds_list of options is only referenced by the component, it will not be destroyed automatically nor altered in any way by the component itself.

The value of the component represents the value at the current position in the list.

Custom attributes:

options: The ds_list holding the values. If you change its values during execution, be sure to call fl_field_refresh() afterwards.
index: The current index of the referenced element in the list. It is automatically kept between 0 and ds_list_size(options)-1 at every refresh

Creation:

Returns a list component instance

fl_add_radio(name,rel_x,rel_y,width,height,[options],[selected_option_index]);

Radio button

Radio buttons, like checkboxes, hold a checked / unchecked status, but are usually linked in groups where only one radio button in the same group can be checked at one time.

Custom attributes:

checked: true of false, determines the current radio button state.
group: A string holding the group name the radio button belongs to.

Creation:

Returns a radio button component instance

fl_add_radio(name,rel_x,rel_y,width,height,[value],[group],[checked]);

Scrollbar

Scrollbar components can be horizontal or vertical, and always hold a value between 0 and 1, representing the current position of the scrollbar handle.

The scrollbar handle size (also a value between 0 and 1) can be updated dynamically by changing the size variable, in order to be adapted to the total size of the content you want to be scrollable.

Custom attributes:

horizontal *: true of false, determines the disposition of the scrollbar. Can not be changed after creation.
size: The size of the scrollbar handle, as a number between 0 and 1, with respect to the total size of the scrollbar. You can change the size at any moment, but you will need to refresh the component with fl_field_refresh() afterwards.
handle *: Hold the instance id of the handle sub-component (useful to draw the bar handle using its x and y coordinates).

Creation:

Returns a scrollbar component instance

fl_add_scrollbar(name,rel_x,rel_y,width,height,[start_value],[horizontal],[handle_size]);

Slider

Sliders can either be vertical or horizontal, an allow to select a real value between a defined range.

Sliders are by default empty, but can have any number of knobs attached to them that will work independently, created using fl_slider_add_knob().

The value of a slider is always a 1D array of values, with size equal to the total number of knobs attached to it.

Custom attributes:

knobs *: An array holding the knob instances associated with the slider.
min_value: The smallest value of the slider
max_value: The largest value of the slider
step: If set to anything different that -1, the knobs associated with the slider will "jump" from one value to the other by the amount specified, instead of sliding smoothly. vertical *: Hold the slider disposition. Can not be altered after creation.

Creation:

Returns a slider component instance

fl_add_slider(name,rel_x,rel_y,width,height,[min_value],[max_value],[step],[vertical]);

Slider knobs

Slider knobs are subcomponents that can be attached to sliders at any time.

Knobs x and y values are considered to be their center point. This is usually the expected behaviour, but sometimes depending on how components are drawn you want to constrain the range of movement of a knob; this can be done by applying an offset. An offset tells the knob that its movement is limited (relative to the slider position) within 0+offset and slider_width-offset.

Custom attributes:

index *: The index of the knob in the slider knobs array, and also the index of its value in the slider value array.
offset: Offset applied to constrain the knob movement

Creation:

Returns a slider knob component instance

fl_slider_add_knob(slider,default_value,width,height,[offset]);

Tab

Tabs are basically buttons that will hold their state when pressed, and can be grouped allowing one and only one tab per group to be active at the same time.

Like buttons, tabs will call event user 0 of the form controller when pressed in order to perform an action. Pressing a tab however will change the component state to active/inactive until pressed again. If a tab becomes active, all other tabs belonging to the same group will become inactive.

Custom attributes:

active *: true of false, tells the current state of the tab. If you want to change that programmatically, use fl_field_activate()
group: The group the tab belongs to (as string).

Creation:

Returns a tab component instance

fl_add_tab(name,rel_x,rel_y,width,height,[value],[group],[active]);

Textfield

Textfields allow users to input data as string. Their behaviour is based keyboard input, but the value can also be changed programmatically. A textfield allows a number of actions on its content from the user, like selecting a part its value, copy, paste, cut, moving the cursor with the keyboard or by clicking on the text, and allows the value to exceed the total width of the textfield by masking the extra characters.

Moreover, it is also possible to limit the total number of characters the textfield can hold or mask the visible value with "*" characters (useful for passwords).

Keep in mind that if you change any of the specific properties of a textfield programmatically, you may need to cal fl_field_refresh() to recompute cursor position / visible value etc...
Also, when using a custom font, you will need to pass that as well in order to allow the component to compute the string width accordingly.

Custom attributes:

font: the font that will be used to draw the textfield value (used only to determine strin width, it doesn't actually draw anything with it).
visible_value: The actual part of the value that is visible with respect to the size of the textfield
limit: Maximum number of characters allowed in the field
masked: If true, replaces the visible characters with * .
cur: The position of the cursor in the value (with 0 being before the first letter, and string_length(value) being after the last letter)
cur_x *: The x position of the cursor in the visible string, relative to component x
cur_visible *: The cursor by default goes from visible to invisible and vice versa every half second. You can use this when drawing the cursor.
cur_speed: Determines both the how fast the cur_visible variable will change its value, and how fast the cursor moves when using the arrow keys
start_char *: If the width of the value is larger than the size of the textfield, start_char will hold the starting character to display in the visible substring.
end_char *: As above, this tells the last character of the substring.
sel: When selecting, this variable holds the starting character of the selection
sel_x: The x position of the selection start.

Creation:

Returns a textfield component instance

fl_add_textfield(name,rel_x,rel_y,width,height,[default_value],[font],[limit],[masked]);

Function reference

Other than the scripts listed above for creating components, formLESS has a number of scripts that help interacting with components programmatically.

fl_field_activate(field name or id)

Executes the activation action for the specified component. This only applies to buttons, tabs, checkboxes and radio buttons, and has the same effect as clicking on them.

Example fl_field_activate("save_button");
fl_field_blur(field name or id)

Removes focus from the specified field.

Example fl_field_blur("username");
fl_field_find(field_name)

Return the instance of the field having the specified name.

Example var password_field = fl_field_find("password"); //finds the field with name "password"
password_field.masked = false; //removes character masking, showing the password in clear
fl_field_refresh(password_field);//Refresh the field to apply changes
fl_field_focus(field name or id)

Adds focus to the specified field, blurring the currently focused element.

Example fl_field_focus("username");
fl_field_refresh(field name or id)

Refreshed the specified field values and variables. This has to be called whenever you change some critical component attributes that require other things to adjust (e.g: textfield, slider or scrollbar values, or the options of a list component).

Example var username_field = fl_field_find("username"); //finds the textfield with name "username"
username_field.value = ""; //Clears it value
fl_field_refresh(username_field);//Refresh the field to apply changes

5. Credits

Contact me on the GMC forums or by email at simoneguerra<at>ekalia.com