- Home /
Impossible: 2 toolbars show being clicked at the same time!
I noticed whenever I click a toolbar button on one toolbar, two buttons (in different windows and in different toolbars) show as being clicked at the same time!
I can't figure out how or why they are somehow connected! I have made sure that each of the windows has a unique window ID.
After further testing, I have found that it is the button that is horizontally closest to the button I am actually clicking that seems to also get the mouse down event. Here are some images to demonstrate the problem behavior:
These are the windows.
I click "Model" and "Serial" also shows that it is being clicked.
I click "Model" again, this time underneath "Job" and now "Job" shows as being clicked.
This time I click "Type". But again, "Job" shows as being clicked.
I click "Type" again, this time underneath "Part" and now "Part" shows as being clicked.
One last test to really drive home the problem: I relocate the window and click "Model" again. Instead of "Serial" showing as being clicked, "Part" shows as being clicked.
Even more strange is the fact that the button I didn't click doesn't actually select despite the appearance of being clicked! You can see this in the pictures ("Serial" stays selected despite "Job" and "Part" showing as depressed while I click other buttons below them).
I'm using Unity version 3.5.7f6, and here is the code:
MethodExtensions.cs
his is just for reference since it is actually called in the GUI classes. It simply returns the size of the widest GUIContent given a particular style.
using UnityEngine;
using System.Collections;
public static class MethodExtensions {
/**
* Return the size of the widest content given a style.
*/
public static Vector2 SizeOfWidest(this GUIStyle style, params string[] items) {
if (items.Length > 0) {
Vector2 widest = style.CalcSize(new GUIContent(items[0]));
foreach (string e in items) {
Vector2 size = style.CalcSize(new GUIContent(e));
widest.x = System.Math.Max(widest.x, size.x);
}
return widest;
}
return Vector2.zero;
}
}
GUIWindow.cs
his is the abstract base class for all of my GUI windows.
using UnityEngine;
using System.Collections;
public abstract class GUIWindow : MonoBehaviour {
public GUISkin skin;
public Rect rect;
public int padding = 10;
// True if this window should be drawn.
private bool draw;
// Unique among all registered windows.
private int id;
/**
* Obtain a unique window ID and register this window with the GUIWindowManager.
*/
public virtual void Start() {
this.id = GUIWindowManager.NewId;
this.draw = true;
GUIWindowManager.Register(this);
}
/**
* Draw this window if drawing is enabled.
*/
public void Draw() {
GUI.skin = this.skin;
if (this.draw) {
this.rect = GUI.Window(this.id, this.rect, WindowFunction, "");
}
}
/**
* Unique window ID.
*/
public int Id {
get { return this.id; }
}
/**
* Sets the position of this window.
*/
public void Position(Rect rect) {
this.rect.Set(rect.xMin, rect.yMin, rect.width, rect.height);
}
/**
* Turn off the drawing of this window.
*/
public void Hide() {
this.draw = false;
}
/**
* Turn on the drawing of this window.
*/
public void Show() {
this.draw = true;
}
/**
* Draws the content in the style such that it is centered at the top of the window.
*/
protected Vector2 DrawTitle(GUIContent content, GUIStyle style) {
Vector2 size = style.CalcSize(content);
GUI.Label(new Rect(0, 0, this.rect.width, size.y), content, style);
return size;
}
/**
* Window drawing function.
*/
protected abstract void WindowFunction(int id);
}
GUIWindowManager.cs
his class serves the purpose of drawing the windows in the correct order. Note that I have yet actually use that functionality. Currently it simply draws all of the windows in the order they were registered (since I don't ever change the order).
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class GUIWindowManager : MonoBehaviour {
private static int nextId = 0;
private static Dictionary<int, GUIWindow> windows = new Dictionary<int, GUIWindow>();
private static List<int> drawOrder = new List<int>();
public static int NewId {
get { return nextId++; }
}
public static void Register(GUIWindow window) {
if (windows.ContainsKey(window.Id)) {
Debug.Log("Window attempted duplicate registration with the window manager!");
} else {
windows.Add(window.Id, window);
drawOrder.Add(window.Id);
}
}
public static void DrawFirst(int windowId) {
if (windows.ContainsKey(windowId)) {
drawOrder.Remove(windowId);
drawOrder.Insert(0, windowId);
}
}
void OnGUI() {
foreach (int id in drawOrder) {
windows[id].Draw();
}
}
public GUIWindow Get(int id) {
if (windows.ContainsKey(id)) {
return windows[id];
}
return null;
}
}
SystemSearchGUI.cs
his class is responsible for drawing the System Search GUI.
using UnityEngine;
using System.Collections;
public class SystemSearchGUI : GUIWindow {
private int toolbarSelection = 0;
private string[] toolbarContents = new string[] {"Serial", "Job", "Part"};
private string input = "";
protected override void WindowFunction(int id) {
// Draw the title and get its size.
Vector2 titleSize = this.DrawTitle(new GUIContent("System Search"), GUI.skin.GetStyle("title"));
// Calculate the amount of padding for 2 borders, then use it to find the available width (inner width after
// padding is accounted for).
float doublePadding = 2 * this.padding;
float innerWidth = this.rect.width - doublePadding;
// Draw the toolbar below the title with padding space in between.
toolbarSelection = GUI.Toolbar(
new Rect(padding, titleSize.y + this.padding, innerWidth, titleSize.y),
toolbarSelection,
toolbarContents);
// Calculate the size of the search button. Add padding to it's width.
Vector2 buttonSize = GUI.skin.button.CalcSize(new GUIContent("Search"));
buttonSize.x += doublePadding;
// Calculate the position of the next row.
float row = 2 * (titleSize.y + this.padding);
// Calculate the size of the text field, but replace the calculated width with whatever space is left in the
// inner width after the button and padding have been subtracted.
Vector2 textfieldSize = GUI.skin.textField.CalcSize(new GUIContent(this.input));
textfieldSize.x = innerWidth - buttonSize.x - this.padding;
// Draw the text field.
this.input = GUI.TextField(
new Rect(this.padding, row, textfieldSize.x, textfieldSize.y),
this.input);
// Draw the button.
if (GUI.Button(new Rect(doublePadding + textfieldSize.x, row, buttonSize.x, buttonSize.y), "Search")) {
Debug.Log(
string.Format("Searching for systems with {0} # {1}",
this.toolbarContents[this.toolbarSelection], this.input));
}
// Update the size of this window to fit the contents vertically, then make it draggable.
this.rect.height = row + buttonSize.y + this.padding;
GUI.DragWindow();
}
}
PartSearchGUI.cs
nd lastly, this is the class for the Part Search GUI.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class PartSearchGUI : GUIWindow {
private string[] tabs = new string[] {"Single", "Multiple"};
private string[] subTabs = new string[] {"Model", "Type"};
private int tabId = 0;
private int subTabId = 0;
private string serialNumber = "";
protected override void WindowFunction(int id) {
// Draw the title.
Vector2 titleSize = this.DrawTitle(new GUIContent("Part Search"), GUI.skin.GetStyle("title"));
// Draw the top tabs.
tabId = GUI.Toolbar(
new Rect(this.padding, titleSize.y + this.padding, this.rect.width - 2 * this.padding, titleSize.y),
tabId,
tabs);
float y = 2 * (titleSize.y + this.padding);
// Draw different content depending on which tab is selected.
switch (tabId) {
case 0:
y = DrawSinglePartSearch(y);
break;
case 1:
y = DrawMultiplePartSearch(y);
break;
default:
Debug.LogError("Invalid tabId value of " + tabId);
break;
}
y += this.padding;
// Find the size of the widest button.
Vector2 buttonSize = GUI.skin.button.SizeOfWidest("Cancel", "Search");
// Forget that for now - make them as wide as possible.
buttonSize.x = (this.rect.width - 3 * this.padding) / 2.0f;
// Draw the Cancel and Search buttons.
if (GUI.Button(new Rect(this.padding, y, buttonSize.x, buttonSize.y), new GUIContent("Cancel"))) {
this.Hide();
Invoke("Show", 3.0f);
}
if (GUI.Button(new Rect(this.rect.width - this.padding - buttonSize.x, y, buttonSize.x, buttonSize.y), new GUIContent("Search"))) {
Debug.Log("Searching for one or more parts.");
}
// Update the height of this window so that it's contents fit just right.
this.rect.height = y + buttonSize.y + this.padding;
GUI.DragWindow();
}
/**
* Draw the form controls for searching for a single part.
*/
private float DrawSinglePartSearch(float y) {
// Create the content.
GUIContent labelContent = new GUIContent("Part Serial #:");
GUIContent textContent = new GUIContent(this.serialNumber);
// Determine the size of things.
Vector2 labelSize = GUI.skin.label.CalcSize(labelContent);
Vector2 textSize = GUI.skin.textField.CalcSize(textContent);
// Use the tallest one as the height.
labelSize.y = Mathf.Max(labelSize.y, textSize.y);
// Make the textfield extend to the edge of the window.
textSize.x = this.rect.width - (3 * this.padding) - labelSize.x;
// Draw the label and textfield.
GUI.Label(new Rect(this.padding, y, labelSize.x, labelSize.y), labelContent);
serialNumber = GUI.TextField(
new Rect(2 * this.padding + labelSize.x, y, textSize.x, labelSize.y),
serialNumber);
// Let the calling code know where to place the next control.
return y + labelSize.y + this.padding;
}
/**
* Draw the form controls for searching for multiple parts.
*/
private float DrawMultiplePartSearch(float y) {
// Doesn't matter about what the content is... just need height.
Vector2 subTabSize = GUI.skin.button.CalcSize(new GUIContent(subTabs[0]));
// Draw the second row of tabs.
subTabId = GUI.Toolbar(
new Rect(this.padding, y, this.rect.width - 2 * this.padding, subTabSize.y),
subTabId,
subTabs);
y += subTabSize.y + this.padding;
// TODO: Finish drawing the form controls for multiple part search.
GUIContent modelNumberLabel = new GUIContent("Part Model #");
// Let the calling code know where to place the next control.
return y;
}
}
I'm now pouring over the GUI.Toolbar code in the MonoDevelop's Assembly Browser... we'll see what fruit this yields, if any.
line 7 of the system search GUI has a empty statment " ".disclaimer this could mean nothing i am just pointing out something i saw.
On my screen line 7 reads: private string input = "";
Answer by Bunny83 · Dec 13, 2013 at 11:40 PM
Well, i just took a look at the Toolbar function (i never actually used it). I have to say "OMG" it would take 1-2 days to figure out how it actually works. The Toolbar just uses the internal function DoButtonGrid. They call several helper functions for calculating the rects of each button and go crazy with adding margin here substracting height there. Since you can't actively debug the code in there it's hard to tell if something is wrong in the code or if there's something wrong on your side.
I have a few counter questions:
Do you use a custom skin? If so did you set any strange margin / padding / offset / border values?
Which Toolbar function do you use and what parameters do you pass.
Do you change GUI.matrix in any way?
What windows are you using? GUI or GUILayout?
Do you use GUI.Toolbar or GUILayout.Toolbar?
Thanks for your reply.
I do use a custom skin, but the only change from the default I can think of is the font.
I use GUI.Toolbar(Rect, int, string[]).
I do not change or reference GUI.matrix anywhere in my code.
I use GUI, not GUILayout.
When I get back to work on $$anonymous$$onday I'll go ahead and post the code. The project is just one that I created specifically for designing these form windows, so there really isn't anything besides the two window functions that could be the source of the problem.