Overlapping GUI Buttons

Started by Baron, Wed 27/08/2014 05:05:19

Previous topic - Next topic

Baron

Greetings Tech Experts,
     I've got this GUI with overlapping buttons.  Everything looks great, but the buttons aren't square so in order to make them "fit" properly they have to overlap each other (at least, the empty box that's a part of the sprite but isn't visible overlaps).  Functionally everything works fine, except that the invisible portion of the highest (z-order) button seems to be intercepting clicks meant for the graphic visible underneath.  This behaviour is unlike other AGS elements such as objects, where even if blank portions overlap it is the visible one that interprets the click.  This is only a problem around the edges of my buttons where the overlap occurs, but I foresee it being frustrating for users.  Is there a simple workaround for this or am I going to have to use my ...*sigh*... brain?

AnasAbdin

You could disable all the buttons except for the current top button :-\

Crimson Wizard

#2
Oh that... hehehe.

I once tried to write a pixel-perfect detection for GUI controls, then found that there's no ZOrder property available in script, so you can't really know which order to check them in.

I was driven crazy... and then I wrote this:

Code: ags

/***************************************************************************/
//
// !!!!!!!!   WARNING   !!!!!!!!
// Due certain difficulties (lack of necessary commands in AGS Script)
// this function is now implemented as a very ugly workaround.
//
// [[ OLD COMMENT, DON'T REMEMBER IF FOLLOWING IS TRUE --
//    ( TESTS REQUIRED ) ]]
// One of the consequences is that after this function is called the
// gui controls may end up in different z-order. :'(
//
/***************************************************************************/
GUIControl *PixelPerfectGetGUIControl(GUI *parent, int x, int y, bool test_clickable)
{
  if (parent.ControlCount == 0)
    return null; // GUI has no controls
  if (x < parent.X || x >= parent.X + parent.Width || y < parent.Y || y >= parent.Y + parent.Height)
    return null; // clicked outside GUI
  
  // Unfortunately there's no way to know GUIControl's ZOrder in AGS 3.2.1.
  // Therefore I have to do this ugly workaround.
  GUIControl *zordered[] = new GUIControl[parent.ControlCount];
  int ctrl_index = -1;
  GUIControl *first_found = null;
  GUIControl *found_ctrl = null;
  GUIControl *cur_ctrl = null;
  Button *cur_btn;
  DynamicSprite *test_spr;
  DrawingSurface *test_ds;
  
  // What is basically going on here:
  // 1. Pick the control found at screen's x/y;
  // 2. Test control's graphic to ensure the touched pixel is non-transparent
  // 3a. If there's solid pixel under x/y, choose the control.
  // 3b. Otherwise send control down the Z-Order and repeat until there are untested controls
  // The controls are remembered in pointer array, in the order of their original Z-order, so
  // that we could restore their positions later.
  bool exit_loop = false;
  while (found_ctrl == null && !exit_loop)
  {
    // if we have a previous control, send it to the bottom
    if (cur_ctrl)
      cur_ctrl.SendToBack();
      
    cur_ctrl = GUIControl.GetAtScreenXY(x, y);
    if (cur_ctrl == null)
      exit_loop = true; // no controls at this point
    if (first_found == null)
      first_found = cur_ctrl; // remember the first control we found
    else if (first_found == cur_ctrl)
      exit_loop = true; // reached the first control again: no more controls
      
    if (!exit_loop && cur_ctrl.OwningGUI == parent)
    {    
      ctrl_index++;
      zordered[ctrl_index] = cur_ctrl; // remember control in our z-order array
    
      if (!test_clickable || cur_ctrl.Clickable)
      {
        // Get control's graphic and test clicked pixel
        test_ds = null;
        cur_btn = cur_ctrl.AsButton;
        if (cur_btn)
        {
          test_spr = DynamicSprite.CreateFromExistingSprite(cur_btn.Graphic, true);
          test_ds = test_spr.GetDrawingSurface();
        }
        else
        {          
          // TODO: other controls? maybe test background color?
        }
        
        if (test_ds)
        {
          if (test_ds.GetPixel(x - cur_ctrl.X + parent.X, y - cur_ctrl.Y + parent.Y) != COLOR_TRANSPARENT)
          {
            // solid pixel found, select this control
            found_ctrl = cur_ctrl;
          }
          test_ds.Release();
          test_spr.Delete();
        }
        else
        {
          found_ctrl = cur_ctrl;
        }
      }
    }
    
    //Display("found %d, cur %d, first %d", found_ctrl, cur_ctrl, first_found);
  }
  
  // Restore the controls Z-order, walking them backwards
  while (ctrl_index >= 0)
  {
    zordered[ctrl_index].BringToFront();
    ctrl_index--;
  }
  
  return found_ctrl;
}


It is unfinished, as you may see, I made it work only for buttons and, for some reason, for sliders.

It is commented, but in two words: it takes topmost control's graphic and checks if pixel is transparent. If it is, then it sends control to the back z-order level, and takes the next, until non-transparent pixel is found.
After search is finished, it tries to restore the z-order.
The comment sais that it may leave controls in different z-order, but unfortunately I don't remember if that's really true and how high chances are that this happens. Maybe this comment was left from older version of the code.

EDIT: removed slider part, and removed struct part from function name.
EDIT2: added few more comments just in case.

Monsieur OUXX

CW's solution seems good and not too CPU-consuming as far as AGS goes.
 

Baron

Wow, this seems to be just what I'm after.  A huge thanks, CW!  ;-D

Crimson Wizard

BTW, I finally implemented GUIControl.ZOrder property in 3.3.1 branch, so this code will be much simplier if moved to next 3.3.1 release ;) (no need to hack existing z-order).

SMF spam blocked by CleanTalk