Simulating custom properties and events on GUIs

Started by SupSuper, Mon 16/04/2007 22:42:28

Previous topic - Next topic

SupSuper

I'm writing up a module that does... top secret stuff with the GUIs. But since I can't add custom properties or events for them, my concept to simulate them is to have:
- my own Struct to hold all the custom properties.
- an Array of said Struct, the size of AGS_MAX_GUIS, to "link" with the actual GUIs.
- a While loop on Rep_Ex that goes through said array, checks all the custom properties and reacts appropriately.

The catch here is that having a While loop running AGS_MAX_GUIS times every game tick is... slow, to say the least. Any suggestions?
Programmer looking for work

GarageGothic

Looping through an array of 50 GUIs shouldn't be very heavy on the CPU, unless you check/change A LOT of different variables for every entry. The secrecy about the actual intent of the function doesn't make it easier to come with suggestions.

Is it really necessary to run through every single GUI every frame? Wouldn't it be enough to check the ones visible, or ones marked as "dirty" by any function changing the properties?

strazer

Why would you need to check the GUI's properties repeatedly? Without knowing what you want to do exactly, the obvious suggestion seems to be to run the check only when needed, in the event functions that can cause the property to change, say a mouse click on the GUI or something. You can put the check/react code in a separate function for easy access.

And if you really need rep_ex, you don't need to iterate to the theoretical maximum number of GUIs, just the ones that actually exist. So only count up to Game.GUICount (see manual).

monkey0506

#3
Game.GUICount

You seem to have the Properties bit worked out so I won't touch that....but regarding the "can't add...events for [the GUIs]"....you could put something like this in your module script:

Code: ags
void MyModuleGUIClick(GUI* theGUI, GUIControl* control) {
  if (theGUI == null) return; // if control is null, run as a click on GUI background
  if (theGUI == gMyGUI1) {
    if (control.ID == 1) { /* blah */ }
    // etc.
    }
  }

function on_event(EventType event, int data) {
  if (event == eEventGUIMouseDown) {
    MyModuleGUIClick(gui[data], GUIControl.GetAtScreenXY());
    }
  }


Essentially the same as interface_click except I've updated it to use a GUI* and GUIControl* as parameters instead of integers...seeing as script modules aren't allowed to use interface_click...this works the same way.

[EDIT:]

I've modified the function to work with null control parameters, so you can use it for clicks on the GUI background as well as clicks on GUI controls.

SupSuper

Fine, I'll flesh out my concept a little more. My custom events involve the cursor, things like OnMouseOver or OnMouseOut. So, for example: I have a function that sets some kind of hover effect on a GUI. So, on rep_ex, I check whichever GUI the mouse is over. Then I check if that GUI has a hover effect set by (looping through the struct), and if so, react appropriately.
Or, in pseudo-code:
Code: ags

struct Stuff {
  bool Hover;
}

Stuff lotsa_stuff[AGS_MAX_GUIS];

HoverOn(int i)
{
  Stuff[i].Hover = true;
}

rep_ex()
{
  GUI *theGui = GUI.GetAtScreenXY(mouse.x, mouse.y);
  
  int i = 0;
  while (i < AGS_MAX_GUIS)
  {
    if (lotsa_stuff[i].Hover && theGui.ID == i)
      // do hover stuff
    i++;
  }
}


As you can see, I was using the array's index as the GUI's ID, though I suppose this is grossly inefficient. Suggestions?
Programmer looking for work

GarageGothic

#5
Why not just:

Code: ags
rep_ex()
{
  GUI *theGui = GUI.GetAtScreenXY(mouse.x, mouse.y);
  if (lotsa_stuff[theGui.ID].Hover) {
      // do hover stuff
    }
}


?

Khris

Hehe. scnr

And btw, shouldn't it be:
Code: ags
HoverOn(int i)
{
  lotsa_stuff[i].Hover = true;
}

monkey0506

#7
Would something along these lines be more efficient perhaps?

Code: ags
struct Stuff {
  GUI* mouseOverGUI;
  GUI* prevMouseOverGUI;
  };

Stuff hover_stuff;

function repeatedly_execute() {
  GUI* gat = GUI.GetAtScreenXY();
  if (((gat != null) && (hover_stuff.mouseOverGUI == gat)) || ((gat == null) && (hover_stuff.mouseOverGUI == null))) return;
  hover_stuff.prevMouseOverGUI = hover_stuff.mouseOverGUI;
  hover_stuff.mouseOverGUI = gat;
  // UNDO hover stuff (i.e., OnMouseOut) to hover_stuff.prevMouseOverGUI (IFF (if and only if) prevMouseOverGUI is NOT NULL)
  // DO hover stuff (i.e., OnMouseOver) to hover_stuff.mouseOverGUI (IFF mouseOverGUI is NOT NULL)
  }


You could even set up something like this:

Code: ags
enum MyTopSekritEventType {
  eEventGUIMouseOver = 100,
  eEventGUIMouseLeave = 101
  // etc.
  }

void MyTopSekritEvents(MyTopSekritEventType topSekritEvent, int data) {
  if (topSekritEvent == eEventGUIMouseOver) {
    make_character_explode(player); // WTF??? that IS TOP-SEKRIT!!!
    }
  if (topSekritEvent == eEventGUIMouseLeave) {
    if (data == gMygui.ID) make_universe_implode();
    }
  }


You could then call that function inside of rep_ex:

Code: ags
// code from above
// UNDO hover stuff:
  if (hover_stuff.prevMouseOverGUI != null) MyTopSekritEvents(eEventGUIMouseLeave, hover_stuff.prevMouseOverGUI.ID);
// DO hover stuff:
  if (hover_stuff.mouseOverGUI != null) MyTopSekritEvents(eEventGUIMouseOver, hover_stuff.mouseOverGUI.ID);

SupSuper

I'm sorry, but you completely lost me with that last post. I've got a good mind to solve this myself.

As for the previous, they make me feel "d'oh", though in the future I might still need to check for events that I can't check that easily, but thanks anyway.
Programmer looking for work

monkey0506

Code: ags
struct Stuff {
  GUI* mouseOverGUI;
  GUI* prevMouseOverGUI;
  };

Stuff hover_stuff;

function repeatedly_execute() {
  GUI* gat = GUI.GetAtScreenXY();
  if (((gat != null) && (hover_stuff.mouseOverGUI == gat)) || ((gat == null) && (hover_stuff.mouseOverGUI == null))) return;
  hover_stuff.prevMouseOverGUI = hover_stuff.mouseOverGUI;
  hover_stuff.mouseOverGUI = gat;
  if (hover_stuff.prevMouseOverGUI != null) {
    // on-mouse-out for PREVMOUSEOVERGUI
    }
  if (hover_stuff.mouseOverGUI != null) {
    // on-mouse-over for MOUSEOVERGUI
    }
  }


I've modified the end somewhat. And now I'll try to explain it. I've had to make some assumptions about the extent and purpose of what you're trying to do, so forgive me if I've only just made the first three letters of myself....poor, poor mumptions.

Anyway...:o....I'm guessing you're only using this "Stuff" struct to determine whether the mouse is hovering over a certain GUI. I'm also guessing that you only want the mouse to be able to hover over one GUI at a time. If you want to perform actions on all the GUIs the mouse is hovering over you would have to take a different approach (even different compared to the way you're currently doing it, not just the way I'm doing it).

With that said, I've written the "Stuff" struct like this:

Code: ags
struct Stuff {
  GUI* mouseOverGUI;
  GUI* prevMouseOverGUI;
  };

Stuff hover_stuff;


hover_stuff.mouseOverGUI I will use to store whichever GUI the mouse is currently hovering over. hover_stuff.prevMouseOverGUI will store the GUI the mouse was hovering over during the last game loop (if it has changed since the last game loop).

In repeatedly_execute, I create a GUI pointer, gat which I use to hold the value returned by GUI.GetAtScreenXY(). The next statement is pretty complex, so I'll break it up. What I want to do is check if the GUI the mouse is currently over, gat, is the same as hover_stuff.mouseOverGUI. If they are the same, then I don't need to do anything to the GUIs (unless you want to do something every game loop, such as an animation, in which case this line should be omitted):

Code: ags
if (((gat != null) && (hover_stuff.mouseOverGUI == gat)) || ((gat == null) && (hover_stuff.mouseOverGUI == null))) return;


This can be broken into these two parts:

Code: ags
if ((gat != null) && (hover_stuff.mouseOverGUI == gat))

OR

if ((gat == null) && (hover_stuff.mouseOverGUI == null))


Only one of the two needs to be true. The first part checks if both pointers point to the same GUI. In AGS you can't currently compare any pointer to a null pointer (and we don't know if gat might be null). So first I check that gat isn't null. If it is then we will have to run the second part to check if they are both null. If it isn't null, we can then compare the two to see if they are the same.

The second part simply checks if the two pointers are BOTH null. If neither part of the check returns true, then the script will continue to execute repeatedly_execute (i.e., performing the "hover stuff"), otherwise (if either part returns true) it will cease execution of repeatedly_execute.

Assuming you're still with me and I haven't completely obliterated your brain, I'll continue trying to explain my script:

Code: ags
  hover_stuff.prevMouseOverGUI = hover_stuff.mouseOverGUI;
  hover_stuff.mouseOverGUI = gat;


If you have moved the mouse over a different GUI than the mouse was over during the last game loop, this simply switches out the variables. hover_stuff.prevMouseOverGUI will now hold the GUI the mouse was over during the last loop, and hover_stuff.mouseOverGUI will hold the GUI the mouse is currently over.

The part I've added is this:

Code: ags
  if (hover_stuff.prevMouseOverGUI != null) {
    // on-mouse-out for PREVMOUSEOVERGUI
    }
  if (hover_stuff.mouseOverGUI != null) {
    // on-mouse-over for MOUSEOVERGUI
    }


Both of these statements check that one of our two variables is not null. If the first, hover_stuff.prevMouseOverGUI, is non-null then we can say, "the user has moved the mouse off of the GUI it was over during the last loop!" and can run the "on-mouse-out" interactions for that GUI.

If the second, hover_stuff.mouseOverGUI is non-null then we can say, "this is the GUI the mouse is currently over!" and run the "on-mouse-over" interactions for that GUI.

Hopefully I haven't just institutionalized you.... :=

SupSuper

Nah, it makes more sense now, though it's not exactly what I needed, so I just mix and matched with my own code.

In matters of efficiency, I just rewrote the struct to hold pointers to the relevant GUI Controls, so that I only add entries as needed and don't need to use the index, though in return I still have to use lots of while loops, since AGS doesn't support dynamic arrays or any array functions. But hey, it works without affecting speed. :P

I also found out the main cause of the slowdowns is because AGS doesn't run perfectly on Vista. :( (dunno if it's because of the drivers or the OS)

And so you stop mocking the whole "top secret" bit, I'll explain. I'm writing a module completely focused on GUIs. Since GUIs always go unappreciated, it's time to give them the respect they deserve! Currently its supports having Popup GUIs on any side of the screen and buttons with Normal, MouseIn and MouseOut animations. A lot more is planned, such as MouseDown and MouseUp animations, transition effects, new controls, z-sorting, and more!
Programmer looking for work

SMF spam blocked by CleanTalk