Using structs, managed structs and arrays for a UI

Started by bauldur_rises, Sun 28/08/2022 07:25:18

Previous topic - Next topic

bauldur_rises

Hey, I'm very much a newbie at all this, so I'm trying to get my head around a lot of things and sometimes I have difficulty understanding the manual.

My objective is to have two buttons at the bottom of the screen, pushing one will open some slots above it, and similar with the second.  These are like two different inventories, so you can press a button on each slot.




Each of these rectangles is its own Gui with button.

So, what I'm currently trying to do something like this:

Code: ags
 // Header

struct Slot_Structure{
   //various variables
  int NumberOfSlots;

};

managed struct Slot{
  //various variables
  int xpos;  
  int ypos;   //to determine where it is on screen
  GUI* slotgui;

  import static Slot*[] NewArray (int count);
};


// script


Slot_Structure blue;
Slot_Structure red;

static Slot*[] Slot::NewArray (int count)
{
  Slot* slot[];
  count++;  // adding one slot so that slot[0] can refer to the bottom controlling button
  slot = new Slot[count];
  for (int i=0; i<count;i++) { slot[i] = new Slot; }
  return slot;
}


function game_start()
{
  blue.NumberOfSlots = 4;
  red.NumberOfSlots = 8;

  for (int i=0; i<= blue.NumberOfSlots; i++)
   {
       //assign each slot a GUI
   }

 for //same thing for red  - this could probably be its own extender function
}



I doubt this works, but my ultimate goal is to be able to refer to something like blue.slot[3].slotgui in order to do things to the third slot's gui (making it visible or not, etc).  I'm not sure how to connect the struct and the managed struct, and how to handle the dynamic array in the process.

Is anyone able to offer some guidance?

Snarky

Thanks for adding the picture, that's helpful.

Before getting into the weeds with the code, I wonder if there's a good reason why each of the rectangle slots needs to be its own GUI. It seems to me that it would be easier to make the whole thing (i.e. the whole red grid and the whole blue grid, respectively) as one GUI with multiple elements (Buttons). You could potentially even use Inventory Windows.

Another important question is which parts are static and which are dynamic. For example, is the max number of slots available in each inventory dynamic? Is the layout? It might help if you get a bit more concrete about what these menus/slots are and what they're meant to contain (both conceptually and visually).

What I would try to do, if possible, is use built-in AGS components for all the visual parts and display logic, without any need for the SlotStructure or Slot structs. Then for whatever is in these slots you would have some data model, and some kind of reference/table that links the slot with the underlying data item. My impression of this code is that you're mixing up the presentation layer with the data layer, and it's going to confuse things.

bauldur_rises

Thanks for replying.

From playing around earlier, it seemed to me that if I used a single GUI with a bunch of buttons it would interfere with detecting what was under the mouse for other stuff I was doing, even if the background was transparent.  I am also displaying text at the bottom of the screen from a property attached to hotspots, objects and so on, but having the full GUI would prevent the script from reading whether there was a hotspot/object underneath - presumably detecting a GUI instead.

I also wasn't sure if the tween functions would work with buttons for moving the slot around.  This is why I didn't use an Inventory Window - my intent was to have the slots slide into place when you open or close the selection.

The blue menu is meant to let you choose from available characters, while the red is to select from the selected character's inventory.

With the blue menu, my intent was that if you only had 2 characters available, only 2 slots would appear above it, while if you had say 5 characters available, then only 4 slots would be displayed, but some arrows would appear allow the player to scroll through the list.  Similarly for the inventory list, except the highest number of visible slots would be 8.

Forgive my ignorance, but I'm not familiar with some of the terms you're using.  Am I understanding you correctly that you are suggesting using GUIs and GUIControls visually, and then create a struct for containing data related to the given GUIControl?  So I may have 'gBlueCharacterMenu', with 5 buttons, and then have an array of structs  (eg Slot* blueslots[5]), and use that array to place variables in?  like:

Code: ags
// No SlotStructure struct
struct Slot {
  int Whatever_Data_Is_In_The_Slot;
  // etc etc
  GUIControl* thiscontrol;   //So I can issues visual commands to the right button - like to move it from one position to another
}


or more like:

Code: ags
//again, no SlotStructure struct
struct CharacterSlot {
  Character* slotCharacter [4];
  int xstart [4];
  int ystart[4];
  int xend[4];
  //etc etc
}


Or am I misunderstanding what you're saying entirely?

Snarky

Quote from: bauldur_rises on Sun 28/08/2022 14:34:19
From playing around earlier, it seemed to me that if I used a single GUI with a bunch of buttons it would interfere with detecting what was under the mouse for other stuff I was doing, even if the background was transparent.  I am also displaying text at the bottom of the screen from a property attached to hotspots, objects and so on, but having the full GUI would prevent the script from reading whether there was a hotspot/object underneath - presumably detecting a GUI instead.

Yeah, the GUI will capture clicks and will be detected as what's under the mouse. But then, the items in the grid will do that as well. So do you mean that you want to be able to click at and see the names of hotspots and objects just in the gutters between the grid items? That seems weird to me: if I've brought up a menu, I expect the whole area it covers (even if parts are transparent) to only respond to menu-actions.

But sure, if that's what you want, it's probably easiest to have each item on a separate GUI. (I think there is a way to peek at what is below a GUI, but I don't remember off the top of my head how to do it.)

Quote from: bauldur_rises on Sun 28/08/2022 14:34:19
I also wasn't sure if the tween functions would work with buttons for moving the slot around.  This is why I didn't use an Inventory Window - my intent was to have the slots slide into place when you open or close the selection.

You absolutely can Tween buttons. (Tweening GUI elements is even the example shown on the module thread.) But not items in inventory windows, no (except for very basic scrolling).

Quote from: bauldur_rises on Sun 28/08/2022 14:34:19
The blue menu is meant to let you choose from available characters, while the red is to select from the selected character's inventory.

With the blue menu, my intent was that if you only had 2 characters available, only 2 slots would appear above it, while if you had say 5 characters available, then only 4 slots would be displayed, but some arrows would appear allow the player to scroll through the list.  Similarly for the inventory list, except the highest number of visible slots would be 8.

Forgive my ignorance, but I'm not familiar with some of the terms you're using.  Am I understanding you correctly that you are suggesting using GUIs and GUIControls visually, and then create a struct for containing data related to the given GUIControl?  So I may have 'gBlueCharacterMenu', with 5 buttons, and then have an array of structs  (eg Slot* blueslots[5]), and use that array to place variables in?

Well, I probably wouldn't use a Slot or CharacterSlot struct at all. For one thing, you don't need the x,y info, since the GUIs or Buttons already have all of that.

Instead, I would just have an array of the Buttons or GUIs (depending on how you end up implementing it) that we use as the Character slots:

Code: ags
GUI* characterMenuSlot[4];


Alternatively, if you create the GUIs systematically, you could just know that it's GUI 5-8 (for example) that are the character slots. In that case, I typically define some constants and use the gui[] array:

Code: ags
#define GUI_CHAR_SLOT_START 5
#define GUI_CHAR_SLOT_END 9

  // Example of use
  for(int i=GUI_CHAR_SLOT_START; i<GUI_CHAR_SLOT_END; i++)
  {
    // Set the display of this slot
    gui[i].Controls[0].AsButton.Graphic = ... ;
  }


With that set up, I might then have another array to keep track of which Characters we have in our menu.

Code: ags
Character* characterMenu[CHARACTER_MENU_MAX];
int characterMenuCount; // How many characters are currently in our menu
int characterMenuSlotStart; // Which characterMenu index the first character slot is displaying


And that's about it. Now when you click on one of the slots, you just check the GUI.ID, and from there you work out which slot it is (using the characterMenuSlot array or some simple arithmetic with GUI_CHAR_SLOT_START), and from there in turn which character it is (using characterMenu and characterMenuSlotStart). And similarly to display the menu.

Now, you might say that you could just as well put all of this inside of a struct. Sure, you can. In that case it ought to be called something like CharacterMenu. But since you only want to have one CharacterMenu and the data is minimal, it's not really a big benefit right now. Putting it into its own script module is more immediately useful. You can always refactor it later. (Probably into something like AnimatedMenu or GridMenu that you would use for both the character and inventory menus: it wouldn't be a whole lot of data they have in common, but perhaps a fair amount of code.)

You would do something similar for the inventory menu, except that you don't need an equivalent of the characterMenu array: just use the character's actual inventory.

(Edit: slightly better variable names)

Laura Hunt

Quote from: Snarky on Sun 28/08/2022 17:58:12
But sure, if that's what you want, it's probably easiest to have each item on a separate GUI. (I think there is a way to peek at what is below a GUI, but I don't remember off the top of my head how to do it.)

The way I do it is simply to check in rep-exec what room object (or hotspot) is at the mouse coordinates, so it doesn't matter whether there's a GUI on top or not.

Code: ags
function room_RepExec() {
  Object*o = Object.GetAtScreenXY(mouse.x, mouse.y);
  if (o == null) // do something like for example clearing the object's name/description from the display
  else // do something else such as displaying the name/description of the object
}


This of course creates the opposite problem: if you don't want objects/hotspots to react to the mouse moving over them when you have a GUI open, you need to add a condition for this not to happen if certain GUI(s) is/are visible.

bauldur_rises

Thank you guys, this is very helpful.

I'm currently going for something like this:

Code: ags
enum Slot_Type {eCharacter,  eInventory};

int Char_Slot_Count;

struct Slot {
    GUIControl* slotgui;
    
    Slot_Type type;
    int contained;
    
    //other stuff
};


This seems to work so far.

I still like the idea of using a struct since it makes it easier for me to keep track of what int etc goes with what.  I plan to do animations and stuff and so rather than having a bunch of individual arrays, my mind deals better with an array of structs with all the relevant info.

My ambition was to make a system where I could more or less create make as many of these kind of menus as I'd like with just altering some variables, a bit like a module. But that may be a little beyond my grasp at the moment.


QuoteThe way I do it is simply to check in rep-exec what room object (or hotspot) is at the mouse coordinates, so it doesn't matter whether there's a GUI on top or not.

You've saved me!  I was using GetLocationType(mouse.x, mouse.y) to return the type and go from there, which wasn't working with a fullscreen GUI, but going (Whatever).GetAtScreenXY works!  Thank you.

SMF spam blocked by CleanTalk