Skip to main content
GameDev.net gamedev.net
🔒 Locked

GUI picking backends

Started by Ng Jun 15, 2011 at 3:13 PM 3 replies 2.4k views
Original Post
Ng
Ng
Hello,


Even the most simple GUI system needs a way of "picking" primitives - by that I mean algorithms and processes responsible for determining which parts of the GUI are being manipulated by the user. Are there any "classic" solutions to this?

For example, I could model everything as boxes (windows, buttons, widgets in general, would have bounding volumes), and ray cast into the "GUI-scene" to determine if a mouse click hit something. Taking a scrollbar as an example, I would need to know, specifically, if I hit the actual scrolling control or some other part of the widget - so maybe there's a bounding box for the entire scrollbar and another, contained box, representing the "scroll button"

But what about more arbitrary graphical representations for widgets? A round button can still use the bounding volume, but you're obviously not getting a perfect fit - *and* you might not even *need* that kind of precision. Still, I wonder if it's plausible to represent "pickable" controls and areas using bitmaps, with pixel-perfect collision detection - not really for precision, but rather for more flexibility or ease of defining those areas by artists and designers. We might use an artist created bitmap as input for a pre-processor that maps "pickable" widget parts by pixel color or alpha values, and feed that to the GUI picker.

Also, a hierarchical organization for GUI items seems like a win here, because we can filter unnecessary picking tests faster than say, using a flat list of widgets and testing them all against a pick event.

I'm not sure if I'm being too vague or not, but I've never really found any decent references on this specific part of a GUI system. Or is this one of those "in-the-end-everyone-ends-up-with-a-custom-solution" problems?


Cheers!
Sirisian
Sirisian
Assuming a 2D GUI you just need a simple point in rectangle test. If you're designing a real GUI and not an ad-hoc menu system you'll want a hierarchy of controls. Normally one level of inheritance is all that is required. (Don't be afraid to put a lot of functionality into the base class). Break UI components into as many pieces as possible. A ListBox isn't just built from a ListBox. It's simply a panel with a vertical scrollbar with a number of ListBoxItems inside the panel.

Now this example isn't full-proof, but it should give you an idea:
public class Component
{
public List<Component> Components { get; set; }
public Component Parent { get; set; }
public Texture RenderBuffer { get; set; }
public bool IsRenderInvalidated { get; set; }
// Event system variables
// ...

public Component(Component parent)
{
Parent = parent;
Components = new List<Component>();
// Allocate RenderBuffer or grab it from a pool! You can deallocate it if it isn't used for a while
}

public void Render()
{
if (IsRenderInvalidated)
{
PreRender();
// Render components to RenderBuffer
foreach (var component in Components)
{
Render();
}
PostRender();
IsRenderInvalidated = false;
}
// Render the RenderBuffer to the parent RenderBuffer
// ...
}

public virtual void PreRender()
{
// Component specific rendering that renders before the Components are rendered
}

public virtual void PostRender()
{
// Component specific rendering that renders after the Components are rendered
}
}


Now for events you have to understand things like catching and bubbling. You'll end up creating a root component which is literally just:
Component root;
This will be inside of a GUIManager class. This is important since a GUI needs to hold onto "global UI information" such as keyboard focus.

Now when the user clicks you'll pass mouse input recursively. If you click on a button for instance in a window the message will propagate from the root to the window to the button. It's possible for any control to catch the mouse input also. This happens if the window is unenabled (meaning it doesn't pass user input through). The window could drop the input in that case when it sees it. Either way at each level the component will iterate its components and run a boundary test like:
// Generic behind the scenes mouse down event. This is virtual and overridden by certain components like a TextBox
public MouseDownEvent(MouseEvent event)
{
if (!Enabled)
{
event.Captured = true;
return;
}
MouseDown(event); // virtual method that is usually overridden by each derived components
for each component in components
{
if (component.MouseTest(event.Position))
{
component.MouseDown(event);
if (event.Captured) return;
}
}
}


Also in case you don't know the MouseTest for a point vs AABB is just:
return point.X > aabb.MinX && point.X < aabb.MaxX && point.Y > aabb.MinY && point.Y < aabb.MaxY;

Okay now for keyboard focus. If you click on a TextBox component and it captures the input then the MouseDownEvent would probably call upon the GUIManager. (Don't make it a singleton. Just pass down a reference so all the controls know about it). You would call gui.SetKeyboardFocus(this); which would set a variable like Component KeyboardFocus; which represents the component with KeyboardFocus. Now in you GUIManager when you get keyboard input you'd pass it off to that control. If another control takes keyboard focus inside of the SetKeyboardFocus method you need to call keyboardFocus.LostKeyboardFocus();. So:

public class GUIManager
{
public Component Root { get; set; }

private Component keyboardFocus;
public Component KeyboardFocus
{
get { return keyboardFocus; }
set
{
if (KeyboardFocus == null)
{
KeyboardFocus = value;
}
else if (!KeyboardFocus.LockedKeyboardFocus)
{
KeyboardFocus.LostKeyboardFocus();
KeyboardFocus = value;
}
if (KeyboardFocus != null)
{
KeyboardFocus.GotKeyboardFocus();
}
}
}

// MouseDown, MouseUp, KeyDown, KeyUp events simply work like:
public void MouseDown(MouseEvent event)
{
root.MouseDown(event);
}
}


Simple.
Ng
Ng
Thanks for the thorough reply, Sirisian.

My focus for this thread is actually along these lines:

Assuming a 2D GUI you just need a simple point in rectangle test.[/quote]

If it's 2D and all your "pickable" controls are represented by boxes, sure, just go ahead and do a point in rectangle test, simple enough. But you still need to specify those "pickable" areas, rectangles or not. Do you do that in the code? Can we use a bitmap representing the control layout and select the "pickable" areas from that? My questioning is actually about where and how do we specify the areas where GUI controls are "clickable" or not, and which shapes are allowed.

For example, take a scrollbar. I'll go ahead and do the ASCII-art thing:

|=======[]====|

The "[]" is the scroll button, the "======" is the scroll bar "body".

The user clicks somewhere around that scrollbar. To determine if I hit the scrollbar as a whole, I can test against it's bounding box. If there's a hit, we might go a bit further and test for collision againt the actual scroll button in the scroll bar, so if it's a drag operation, the user can select the scroll button and drag it around.

Any "classical" ways of writing the specs for this? Maybe it's parametric and something like: "scroll bar extents: X units wide, (X / 10) units tall / scroll button extents: (X / 10) units wide, (X / 10) units tall / scroll button starting position: 0 units (left to right).
Or maybe an artist can draw that control in a bitmap and the programmer have a utility that extracts those specs from that bitmap.

To sum it up: what's a decent way of defining all the "collision primitives" for GUI elements to present for picking?
Sirisian
Sirisian

If it's 2D and all your "pickable" controls are represented by boxes, sure, just go ahead and do a point in rectangle test, simple enough. But you still need to specify those "pickable" areas, rectangles or not. Do you do that in the code? Can we use a bitmap representing the control layout and select the "pickable" areas from that? My questioning is actually about where and how do we specify the areas where GUI controls are "clickable" or not, and which shapes are allowed.

Well I'd just use rectangles. You could use a bitmap or a set of polygons to define the clickable region. In my example MouseTest is a virtual method in the Component class that can be anything. It gets a position relative to itself (where the top left of the component is (0, 0)). and just returns true or false.


For example, take a scrollbar. I'll go ahead and do the ASCII-art thing:

|=======[]====|
^ ^
| scroll button
|
bar "body"

The user clicks somewhere around that scrollbar. To determine if I hit the scrollbar as a whole, I can test against it's bounding box. If there's a hit, we might go a bit further and test for collision againt the actual scroll button in the scroll bar, so if it's a drag operation, the user can select the scroll button and drag it around.

That's why I said to break a control apart into its components. A scrollbar is made up of 3 buttons essentially and an image in the background. (A different setup can be made). All of which are rectangle tests.

public class VerticalScrollbar : Component
{
private Image background;
private Button scrollUpButton;
private Button scrollDownButton;
private Button dragBar;

public Button Background { get { return background; } }
public Button ScrollUpButton { get { return scrollUpButton; } }
public Button ScrollDownButton { get { return scrollDownButton; } }
public Button DragBar { get { return dragBar; } }

public VerticalScrollbar(Component parent) : base(parent)
{
// Order matters
ScrollUpButton = new Button(this);
ScrollDownButton = new Button(this);
DragBar = new Button(this);
Background = new Image(this);


Background.OnClick += ...
// other events like the OnBeginDrag on the DragBar
}

//public override the ResizeEvent to handle resizing the control when width or height is set.
//...
}


Then to use it:
var scrollbar = VerticalScrollbar(root);
scrollbar.Width = 10;
scrollbar.Height = 100;
root.MouseDown(new MouseEvent(100, 100, MouseButtons.Left));

What you'd see as the event propogates is exactly what you described. It would go to root which would iterate over it's only component (the scrollbar) then the scrollbar would iterate over its children calling MouseDownEvent and seeing if any of them capture the event.

Does that make sense? I'm not a fan of the bitmap idea you have. I'd just define shapes probably over a bitmap. point vs polygon is only a few lines of code if you need odd shapes. What did you have in mind? I can't think of a control that requires odd testing rules.

Designing a GUI has a lot of edge cases. I usually tell people to reverse engineer WPF which handles things in a clean way. Make a ton of use cases that the GUI must fulfill. (Aiming to make the next uber flexible UI will lead you into years of development).
Ng
Ng
Ok,

Well I'd just use rectangles. You could use a bitmap or a set of polygons to define the clickable region. In my example MouseTest is a virtual method in the Component class that can be anything. It gets a position relative to itself (where the top left of the component is (0, 0)). and just returns true or false.[/quote]

and

That's why I said to break a control apart into its components. A scrollbar is made up of 3 buttons essentially and an image in the background. (A different setup can be made). All of which are rectangle tests.[/quote]

actually clarified things a lot, especially the second part about really making a scrollbar based on buttons. So it's probably enough to start off using only rectangles, and 2D.

What you'd see as the event propogates is exactly what you described. It would go to root which would iterate over it's only component (the scrollbar) then the scrollbar would iterate over its children calling MouseDownEvent and seeing if any of them capture the event.

Does that make sense? I'm not a fan of the bitmap idea you have. I'd just define shapes probably over a bitmap. point vs polygon is only a few lines of code if you need odd shapes. What did you have in mind? I can't think of a control that requires odd testing rules.[/quote]

Yes, it does make sense! Again, thanks a lot for clarifying these points for me. The bitmap idea revolved around odd shapes for controls, yes, but now I'm convinced it's not really necessary, especially as the main focus for this work will not be the GUI system! You said it all, about the uber-flexible approach: it's one I usually aspire to, but it's also the best way to drift away from your real objectives!

It's probably more than enough to just go ahead with the most basic controls, bounding rectangles and point-in-rectangle tests - maybe reflect window hierarchies as bounding rectangle hierarchies too, for faster picking, but that's it as far as uber-system-design goes!

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.