MODULE: ImGi 0.4.2 (GUIs with Script and Overlays)

Started by eri0o, Mon 15/02/2021 00:30:05

Previous topic - Next topic

eri0o

ImGi version 0.4.2


Get Latest Release imgi.scm | GitHub Repo | Demo Windows | Demo Linux | Download project .zip

AGS Script Module for Immediate Gui, uses script Overlays to render the interface.

This is beta quality, I predict Overlays will enter into fashion in one of the next seasons so this module is being written for when the time comes.  8-)



Usage
Spoiler

Code: ags
function repeatedly_execute() // In Room Script, use room_RepExec()
{
  ImGi.Begin();

  if(ImGi.BeginWindow("Hello World", 32, 32, 130, 60, eImGi_Opt_AlignCenter | eImGi_Opt_NoClose))
  {
    // makes following rows to have two columns of width 60 and 70, and let height be default
    ImGi.LayoutRow2(60, 70);

    ImGi.Label("A Label:");
    if(ImGi.Button("A Button!"))
    {
      player.Say("You clicked the button!");
    }

    ImGi.EndWindow();
  }

  ImGi.End();
}
[close]

Script API
Spoiler

ImGi entire API uses static functions and attributes

Basic

ImGi.Begin
Code: ags
void ImGi.Begin()

Call only once per frame and make sure to call End() after.

ImGi.End
Code: ags
void ImGi.End()

Call only once per frame, after Begin() is called.



Layout System

ImGi uses a row based layout system. Each row can have a number of columns (up to 16 columns), each with it's own width, and also a height.
Controls then will be placed in this cell. If you don't change how rows are, it's assumed to keep same LayoutRow.

If you don't specify a height, it will use your style size and padding. Places elements relative to the bottom. Controls may make height bigger than what you set.
If your column width is 0, it will your style size and padding. A negative width will try to place that element at that distance relative from the rights.

Some special Controls can force the width of it's specific column, ignoring the positive values you set, this can be useful if you don't know before hand their sizes.

ImGi.LayoutRow1
ImGi.LayoutRow2
ImGi.LayoutRow3
ImGi.LayoutRow4
Code: ags
void ImGi.LayoutRow1(int width, int height = 0)
void ImGi.LayoutRow2(int w1, int w2, int height = 0)
void ImGi.LayoutRow3(int w1, int w2, int w3, int height = 0)
void ImGi.LayoutRow4(int w1, int w2, int w3, int w4, int height = 0)

Functions to configure the next LayoutRow to use, from a single column (LayoutRow1) up to four columns (LayoutRow4). Use these if you know you want either of these number of columns.
You can optionally specify a height.

ImGi.LayoutRow
Code: ags
void ImGi.LayoutRow(int count, int widths[], int height = 0)

Pass an array of widths with count elements to configure the columns in a row. You can optionally specify a height.

This is useful if you are reading an array of things or have 5 or more columns in your layout. The maximum number of widths is 16.
Code: ags
int row[];

row = new int[2];
row[0] = 60; // set a predefined column width size per element in row
row[1] = 70; // this is the width of other column
ImGi.LayoutRow(2 /*n columns*/, row); // rows after this line have such column config


ImGi.LayoutBeginColumn
ImGi.LayoutEndColumn
Code: ags
void ImGi.LayoutBeginColumn()
void ImGi.LayoutEndColumn()

Allows subdividing a cell in a row in more rows and columns. You start the column with ImGi.LayoutBeginColumn(), and it's void, so ALWAYS call LayoutEndColumn() after (you don't check it's return value because it's void!).




Window

A window can be created by a BeginWindow and if this is successful (returns any non false value), it has to call EndWindow to specify where it logically ends.
All controls must exist within a window. An example of normal usage is below:

Code: ags
if(ImGi.BeginWindow("My First Window!", 32, 32, 130, 60))
{
  ImGi.Text("Hi!"); // your controls are here ...
  ImGi.EndWindow();
}



ImGi.BeginWindow
Code: ags
ImGi_Res ImGi.BeginWindow(String title, int x, int y, int width, int height, ImGi_Opt opt = 0)

Creates a window, make sure to call a matching EndWindow() if this method return is not false.

ImGi.EndWindow
Code: ags
void ImGi.EndWindow()

Has to be called each time a BeginWindow is successful once all elements inside the window are listed

ImGi.OpenWindow
Code: ags
void ImGi.OpenWindow(String title)

If a window of matching title is closed, it opens again.

ImGi.Close
Code: ags
void ImGi.Close()

Closes what is the current scope (Window, Popup, ...). Don't call it outside of a Window, a Popup, ...

ImGi.BeginPopup
ImGi.EndPopup
ImGi.OpenPopup
Code: ags
ImGi_Res ImGi.BeginPopup(String title)
void ImGi.EndPopup()
void OpenPopup(String name)


Popups are like windows, but you can't move or resize them, and they have no headers. They open where the mouse is when calling OpenPopup by default.
Popups open on top of everything, and they close if you click outside of them. Clicks outside of the popup that hit no window will be forwarded to the rest of your game to handle.

ImGi.BeginPanel
ImGi.EndPanel
Code: ags
ImGi_Res ImGi.BeginPanel(String name, ImGi_Opt opt = 0)
void ImGi.EndPanel()


If you need a scrollable area inside a window that is not the window itself, you can use panels! A panel has to be inside of a window, it will use the LayoutRow cell size for it's size. If it returns successful, you have to call EndPanel() after.
Code: ags
if(ImGi.BeginPanel("Pan")){
  ImGi.Text("Hi panel!"); // your controls are here ...
  ImGi.EndPanel();
}





Controls

Controls are things you can place inside a window. Controls cannot exist outside of windows.

Every controls takes a string as label, this string can't be empty and can't match the label of other control. The exception is if the control can take an icon or graphic, then if you pass null as the string and the control has an icon, it will use the icon if possible. Each window or similar will be a new scope, used to compose this identification, so two different windows can have controls with matching labels.

ImGi comes with a limited number of icons, whenever you can provide an icon as a parameter, you can alternatively pass a sprite number, and in this case it will use that sprite instead of the icon. The default icons use negative numbers, so they don't conflict with your sprite ID.

ImGi.Empty
Code: ags
void ImGi.Empty()

This control does nothing and is invisible. Use it when you don't want to place anything in cell to advance the layout.

ImGi.Label
Code: ags
void ImGi.Label(String label)



This control is a Label containing the specified text. It has no interaction.

ImGi.Text
Code: ags
void ImGi.Text(String text)



This control is a Multiline Label for visualization only. It has no interaction.

ImGi.TextBox
Code: ags
String ImGi.TextBox(String label, String buf, int bufsz, ImGi_Result* res = 0, ImGi_Opt opt = 0)

This control is an editable TextBox. Click on it to give focus and enter the text input with the keyboard. Enter exits focus.

The character limit is defined in bufsz. This function will return the buf String modified, just assign it to the same String so it's content can be updated.

ImGi.Button
Code: ags
ImGi_Res ImGi.Button(String label, ImGi_Icon icon = 0, ImGi_Opt opt = eImGi_Opt_AlignCenter)



This control is a Button. When clicked, it will return a value different than false.

ImGi.ButtonImage
Code: ags
ImGi_Res ImGi.ButtonImage(String label, int graphic_normal, int graphic_over, int graphic_pressed, ImGi_Opt opt = 0)


Pass a sprite for the Button normal state, one for when mouse is over, and a graphic for when it's clicked. You can set label null if it's the only button in the window with same graphics.

ImGi.CheckBox
Code: ags
ImGi_Res ImGi.CheckBox(String label, CheckBoxState* chkst, ImGi_Icon icon = eImGi_Icon_Check)



This control is a CheckBox. It doesn't store state, so make sure to pass it's state. You optionally pass a different icon to it.

ImGi.Number
ImGi.NumberI
Code: ags
ImGi_Res ImGi.Number(String label, ImGi_Real* value, float step = 0, String format = 0, ImGi_Opt opt = 0)
ImGi_Res ImGi.NumberI(String label, ImGi_Int* value, int step = 0, String format = 0, ImGi_Opt opt = 0)



This control shows a Number, set step to allow quick mouse drag adjustments. Holding shift and clicking it allows entering input with the keyboard.

You can pass a format string similar to the one used with String.Format to specify how the number should be rendered. It's a float, so make sure to use either "%f" or "%g".

NumberI is the same control but for integer (int) numbers, it's not the same control just wrapped, so it's format string default is "%d".

ImGi.Slider
ImGi.SliderI
Code: ags
ImGi_Res ImGi.Slider(String label, ImGi_Real* value, float low, float high, float step = 0,  String format = 0, ImGi_Opt opt = 0)
ImGi_Res ImGi.SliderI(String label, ImGi_Int* value, int low, int high, int step = 0, String format = 0, ImGi_Opt opt = 0)



This control is a Slider. You can adjust it manually with the mouse or you can hold shift and click to specify a value with the keyboard.

You can pass a format string similar to the one used with String.Format to specify how the number should be rendered. It's a float, so make sure to use either "%f" or "%g".

SliderI is the same control but for integer (int) numbers, it's not the same control just wrapped, so it's format string default is "%d".



Options

Some controls and other elements can take options. Below is the list of available options. If you wish to pass two or more options, you can combine them with the bitfield or operator |

Code: ags
ImGi.Button("centered text and non-interactive",0, eImGi_Opt_AlignCenter | eImGi_Opt_NoInteract)



eImGi_Opt_AlignCenterThe header of a window or the control will have text aligned to center.
eImGi_Opt_AlignRightThe header of a window or the control will have text aligned to right.
eImGi_Opt_NoInteractDisables interaction with the control.
eImGi_Opt_NoFrameIf the control or window has any frame, it's not drawn.
eImGi_Opt_NoResizeYou can't resize the window by click-dragging it's bottom right corner.
eImGi_Opt_NoScrollWindow has no scrollbars.
eImGi_Opt_NoCloseWindow has no close button.
eImGi_Opt_NoTitleWindow has to title bar.
eImGi_Opt_HoldFocusControls with this option will require clicking on a different control to remove focus. Default of some controls.
eImGi_Opt_AutoSizeMakes the window resize to fit content.
eImGi_Opt_PopUpCloses the container when clicking out of it. This is used by default in Popus.
eImGi_Opt_ClosedMakes the container start closed by default.
eImGi_Opt_ExpandedThese are for tree elements, which are not implemented yet.




Utilities

ImGi.SetFocusLastControl
Code: ags
void ImGi.SetFocusLastControl()

Places the focus on what is the last control. Some controls behave differently when focused - like Number and TextBox. Focus is only reggarding input, but won't scroll or move things at center.




Style and Design customization

ImGi.Style
Code: ags
ImGi_Style* ImGi.Style

Holds the Current Style for ImGi.

[close]

License

This code is licensed with MIT LICENSE. The code on this module is based on rxi's Microui, which is also MIT licensed, referenced in the license, this port though has many changes - lots of new bugs too.

Crimson Wizard

To clarify, this is basically an alternate GUI system made with AGS own drawing on overlays; does not requires any plugins etc?

eri0o

Quote from: Crimson Wizard on Mon 15/02/2021 04:21:33
To clarify, this is basically an alternate GUI system made with AGS own drawing on overlays; does not requires any plugins etc?

Pure AGS Script, it's a single .scm file too.  :-D

No plugins, just import the imgi.scm in your Game project and use it!

Spoiler
The demo game can be run and experimented with, to help make sense of the module's API.

For now I plan to try to just work on removing bugs and once this works best, then I will work a bit on performance, and then I will start adding features.

The code is very much based on original Microui code from rxi since it was made with the idea people would not use as is but instead use as a base or understand and port to uncommon platforms.  :)
[close]

eri0o


Spoiler

Version 0.2.0 released, fixed bugs:

  • More than one window now supported!
  • Scrollbars are working.
  • Fixed size windows supported.
  • Manually resizable windows supported.
  • Properly adjust to the game resolution.
Previously window was always auto-adjusting to content, now this requires a flag for AutoResize.

Version 0.2.1 released, fixed bugs:

  • Prevents clicks from passing through ImGi windows.
  • Mouse wheel for scrolling now works

Version 0.2.2 released, fixed bugs:

  • Text input working.
  • Default font is 0, you can set a different one in ImGi.Style.
  • Render now does frame skipping, useful for high resolution games.
[close]

eri0o

New CI system working for automating building the demo and the module! Improvements in 0.3.0

  • Prevents crashes for lack of Overlays, uses a Single Overlay render
  • Adds formating option in Number and Slider component (updated in top post)
  • Fixed bug that was rendering the screen twice, improving performance!

lorenzo

As usual, you make the most amazing stuff! This will be incredibly useful for debugging games.
I'll give it a try tomorrow!

eri0o

Awesome Lorenzo! I really am looking for input reggarding the script API if it makes sense or not. Also if you have things in mind for doing in terms of GUI, this module is major fun to code, so just shoot and I can see what can be done.  8-)

This GUI performance is not great yet but may be not prohibitively slow, depending a lot on what is done in it, the game resolution and so and so... But the code has a LOT of areas for performance improvement, but in my first pass of code, I went for the easier to understand and modify, so I do plan to make it smoother.

lorenzo

I tinkered a bit with the demo project that you provided. It's so cool and slick, great work!

The documentation on the thread is very well made and modifying the demo game helped me to understand more how the module works. I want to try a few different things in the next few days.

One question that is probably very dumb. Once you have created a window, is there a way to close/hide it? I know there is ImGi.OpenWindow, but I couldn't find a command to close it. I'm probably missing something very obvious... (laugh)

Anyway, this is really good. I can't wait to experiment with it more during the weekend!

eri0o

I think I had not yet added a ImGi.Close(), but if you don't pass the option for it not have a close button, it should have a close button on top. Next version will have ImGi.Close() that can close the context of whatever you are in - for now these are only windows.

Adding things in the API is super easy, and creating and adding new controls is fairly easy too, so if you need things, just ask for them! I find it fun to code it too!  (laugh)

At first I am trying to purposely not add a ton of things because I thought the documentation would scare people away.

I am right now doing a bit of work to improve performance, maybe I can finish until the end of today  :wink: My roadmap is at some point to allow texturing the GUI, something like, use these sprites for borders and stuff, so that it would be somewhat usable in a game too.

Oh, I am not sure I am making good choices in the API, so if something feels wrong, or you don't like, I can try to improve. For instance, I am not sure the slider should return if did change or not, or like, simply the value - and then not having that weird ImGi_Real or ImGi_Int pointer passed to it, but at the same time using it I can separate my data from my GUI better...

lorenzo

Thanks for the answer! I found it the option you mentioned: eImGi_Opt_NoClose. Man, do I feel dumb! (laugh)
I was modifying the demo, adding buttons, etc., so I have probably missed a lot of stuff. I hope I'll be less sleepy during the weekend to give it a more thorough look to the module.

Adding more options to customize the GUI to make it look nicer with sprites etc. would make the module even better for use in-game. The style editor in the demo is already quite good too!

For changes in the API, I wouldn't know, as I'm very dumb when it comes to coding. I just use things the way they're (if I even manage to do that :P) and try to adapt myself... (laugh)
But if I do find something, I'll let you know, thank you!

eri0o



Hey! I haven't been able to work on documentation yet, but I do plan to do soon! I managed to get performance improvements, things should run a bit smoother! (there's a lot to do there still)

Version 0.4.0 comes with:

  • ImGi.ButtonImage(String label, int graphic_normal, int graphic_over, int graphic_pressed), this is the more traditional AGS button where you have a sprite for each button state.
  • Checkbox now can receive a custom image to use.
  • You can also pass an image in a regular ImGi.Button in the slot for icons, and it will be used when rendering the button!
  • You can also use ImGi.Close() to close the window you are in.
  • Added ImGi.LayoutRow1 to ImGi.LayoutRow4, which should make easier to configure layouts with 1, 2, 3 or 4 columns of different widths.
  • Generally better performance, but a lot has been rewritten, any bugs please report!

lorenzo

#11
That looks awesome!

EDIT: Played a bit with the new features, it's great! I think you could use this module not only for debugging purposes but also to create some cool puzzles involving computer interfaces. Very impressive!

eri0o

I added more documentation on the first topic above, specially on the new things!

The whole thing right now is being drawn on a single dynamic sprite, so theoretically you COULD shove it in a room object. Also I need to figure out how to explain and ease the creation of styles, because the idea is you can like ImGi.Button, ImGi.Style, ImGi.Button, like, change things like padding, color and stuff have different styles in controls in the same window if one wants.

Edit: I also need a word to express things that can hold controls - like a Window can hold controls, a Popup too, and I am adding others soon. AGS uses the word GUI but I think it may be confusing if I use the same...

Edit2: Added Version 0.4.1, it packs additionally:

  • Added ImGi.BeginPanel and ImGi.EndPanel, which can be used to create scrollable areas in a cell in any row in a window.
  • Added ImGi.SetFocusLastControl, this is the first of a small series of utility functions that will make easier to add extra behaviors in the gui.
  • Minor internal adjustments of things I missed in 0.4.0.   :=

Edit3: Added Version 0.4.2, it packs additionally:

  • Added ImGi.LayoutBeginColumn() and ImGi.LayoutEndColumn(), so you can subdivide a cell in the row in more rows and columns!
  • Added ImGi.Empty(), a control that does nothing and is invisible, it allows leaving empty cells in the layout.
  • Demo has a wrong room until I figure how to explain layouts better.

This enables doing more interesting layouts:



Code: ags
ImGi.LayoutRow3(50, 100, 50); // rows will have 3 columns, of width 50, 100 and 50.

ImGi.Image(2030);                  // in this row, our first cell has this image

ImGi.LayoutBeginColumn();               // second cell in this row will have different things going on inside
ImGi.LayoutRow2(50, 50);                // let's divide in two columns
  ImGi.Empty();     ImGi.Image(2107);   //first sub-row
  ImGi.Image(2111); ImGi.Image(2115);   //second sub-row
ImGi.LayoutEndColumn();                 // end the cell

ImGi.Image(2031);                  // back to the row, our third cell has other image


It's still like doing a GUI using pixely Excel tables but it works alright.

Monsieur OUXX

Wow this was buried in the forums but this needs to stay up!
 

eri0o

Thanks @Monsieur OUXX, it's currently using it's own software renderer (in script), but I have an update soon with an optional hardware accelerated renderer (using Z ordered overlays from 3.6.X) that I will push soon - it was actually the original renderer since I started doing this with ags4 in mind and then retrofit for 3.5.X.

Unfortunately I haven't had any clue yet on how to provide something similar to nine slice theming (in a way the API is kept nice). But at least the hardware acceleration should improve drawing times.

Kastchey

I just wanted to give this a quick bump and say thank you for that module, eri0o. It can probably be useful enough in traditional adventure games and for non-adventures made with AGS, it's pure gold. Fantastic job!

One thing I noticed is that the module seems to have a hard limit of 35 windows, or at least when I hit that number, it wouldn't display the next window until I removed a random earlier one.
And no, I don't actually need more than 35 windows :) I noticed it during my first attempt when I was learning the ropes and produced a number of test windows to see how they work.

eri0o

Uhm, I don't remember this hard limit, but I can look into it. Just got the computer working in my new place. :)

I have a non-finished version of this module cutting down some stuff, like scrolling, so that I can leverage what exists of Overlays to make it render less slowly - I think you could notice this module can take quite a hit in performance if you have lots of sliders at the same time.

Kastchey

#17
It may have possibly something to do with an overlays limit in AGS rather than you code? I don't know if there is an actual limit to overlays, just guessing.

Anyway kudos for your work again. Can you tell if the new version will be backwards compatible with the projects that already use ImGi? If so, I'll be more than happy to test it in my current WiP.

Edit: Ah, I remembered one more thing I noticed. If there are two buttons with exactly the same text within the same window, neither will appear. There will be blank rows displayed instead. I don't know if this is resolvable or not, but I thought I'd mention it :)

eri0o

about the buttons stuff, basically it uses the button text and hashes it, and uses this as the name of the button, as a reference, internally. You can try adding spaces at the end I think, it may work, if they HAVE to have the same name. Buttons also support sprite icons too, if you may switch your text for an image - but I think the sprite number is then used as ID instead, so matching sprites probably would cause the same collision problem...

Baguettator

Very impressive work ! I just didn't understand how to script the button when it's clicked ? How can I make it doing something ?

EDIT : and as it uses overlays, is it possible to Resize dynamically the whole window with its buttons inside ?

SMF spam blocked by CleanTalk