I want to change GUI button text when I click a UI button. onGUI() method which I have written, is below.
This method is used to create new buttons when the button is clicked and to show the Sub-parts of a tree structure.
If I show you the change I want to make in the code, for example, when I click UI Latin Button, currentPart.EnglishTitle changes like currentPart.LatinTitle. I also mentioned this part as a comment in the code
currentPart is a HumanBodyPart object. HumanBodyPart is a class that stores my nodes of my tree structure.
I give you the necessary parts of my code. If there is a missing part in the code that I have given, I can edit the desired parts.
onGUI() method is here...
private void OnGUI()
{
Vector3 scale = new Vector3(Screen.width / nativeSize.x, Screen.height / nativeSize.y, 1.0f);
GUI.matrix = Matrix4x4.TRS(new Vector3(0, 0, 0), Quaternion.identity, scale);
float spacing = 25;
float x = 7 + spacing;
float y = 63;
HumanBodyPart mainBodyPart = bodyVisualizer.BodyData.Body.SubParts[0];
List<HumanBodyPart> nextPartsToRender = new List<HumanBodyPart>(new HumanBodyPart[] { mainBodyPart });
List<HumanBodyPart> allPartsToRender = new List<HumanBodyPart>(new HumanBodyPart[] { mainBodyPart });
scrollPosition = GUI.BeginScrollView(new Rect(7, y, 264, 485), scrollPosition, new Rect(7, y, 528, scrollPosition_y));
while (nextPartsToRender.Count > 0)
{
HumanBodyPart currentPart = nextPartsToRender[0];
nextPartsToRender.RemoveAt(0);
//The place I want to change above is the place "currentPart.English" in a bottom line
if (GUI.Button(new Rect(currentPart.DrawDepth * spacing + x, y, 200, 20), currentPart.EnglishTitle))
{
if (!currentPart.IsClicked)
{
currentPart.IsClicked = true;
HumanBodyVisualizer.ShowMode showModeFullBody = HumanBodyVisualizer.ShowMode.Invisible;
bodyVisualizer.ShowBody(showModeFullBody);
AllSubPartsAndRoot.Insert(AllSubPartsAndRoot.Count, currentPart);
addAllSubPartsOfClickButton(currentPart, AllSubPartsAndRoot, AllSubPartsAndRoot.Count - 1);
HumanBodyVisualizer.ShowMode showModeCurrentPart = HumanBodyVisualizer.ShowMode.LowTransparent;
for (int i = 0; i < AllSubPartsAndRoot.Count; i++)
{
bodyVisualizer.ShowBodyPart(showModeCurrentPart, AllSubPartsAndRoot[i]);
}
}
else
{
currentPart.IsClicked = false;
List<HumanBodyPart> RemoveBodyParts = new List<HumanBodyPart>();
RemoveBodyParts.Insert(0, currentPart);
addAllSubPartsOfClickButton(currentPart, RemoveBodyParts, 1);
for (int i = 0; i < RemoveBodyParts.Count; i++)
{
if (AllSubPartsAndRoot.Contains(RemoveBodyParts[i]))
{
bodyVisualizer.ShowBodyPart(HumanBodyVisualizer.ShowMode.Invisible, RemoveBodyParts[i]);
AllSubPartsAndRoot.Remove(RemoveBodyParts[i]);
}
}
if (AllSubPartsAndRoot.Count == 0)
{
bodyVisualizer.ShowBody(HumanBodyVisualizer.ShowMode.LowTransparent);
}
else
{
for (int ii = 0; ii < AllSubPartsAndRoot.Count; ii++)
{
bodyVisualizer.ShowBodyPart(HumanBodyVisualizer.ShowMode.LowTransparent, AllSubPartsAndRoot[ii]);
}
}
}
}
if (currentPart.SubParts.Count != 0)
{
if (GUI.Button(new Rect(x - spacing + currentPart.DrawDepth * spacing, y, 20, 20), ">"))
{
if (!currentPart.IsExpanded)
{
currentPart.IsExpanded = true;
}
else
currentPart.IsExpanded = false;
}
if (currentPart.IsExpanded)
{
nextPartsToRender.InsertRange(0, currentPart.SubParts);
allPartsToRender.InsertRange(allPartsToRender.Count - 1, currentPart.SubParts);
scrollPosition_y = allPartsToRender.Count * spacing;
}
}
y += spacing;
index++;
}
// End the scroll view that we began above.
GUI.EndScrollView();
}
public Button turkishButton;
public Button englishButton;
public Button latinButton;
The above is UI buttons in the script.
In general you shouldn't use OnGUI but rather the Unity UI system.
You could just have a string with the text to display and do e.g.
private string onGuiButtonLabel;
public Button turkishButton;
public Button englishButton;
public Button latinButton;
private void Awake()
{
turkishButton.onClick.AddListener(()=>{onGuiButtonLabel = "Turkish";})
englishButton.onClick.AddListener(()=>{onGuiButtonLabel = "English";})
latinButton.onClick.AddListener(()=>{onGuiButtonLabel = "Latin";})
}
and then in OnGUI you use
if (GUI.Button(new Rect(currentPart.DrawDepth * spacing + x, y, 200, 20), onGuiButtonLabel))
or maybe use a kind of Dictionary with an enum like
public Language
{
English,
Turkish,
Latin
}
private Dictionary<Language, string>() titles;
private Language curentLanguage;
// or wherever you want to initialize it
private void Awake()
{
titles = new Dictionary<Language, string>()
{
{Language.English, currentPart.EnglishTitle}
{Language.Turkish, currentPart.TurkishTitle}
{Language.Latin, currentPart.LatinTitle}
}
turkishButton.onClick.AddListener(()=>{curentLanguage = Language.Turkish;})
englishButton.onClick.AddListener(()=>{curentLanguage = Language.English;})
latinButton.onClick.AddListener(()=>{curentLanguage = Language.Latin;})
}
and then in OnGUI use
if (GUI.Button(new Rect(currentPart.DrawDepth * spacing + x, y, 200, 20), titles[currentLanguage]))
Related
Codes below:
using System;
using UnityEngine;
using UnityEngine.Serialization;
public class Chessboard : MonoBehaviour
{
[Header("Art")]
[SerializeField] private Material tileMaterial;
//Logic
private const int TILE_COUNT_X = 8;
private const int TILE_COUNT_Y = 8;
private GameObject[,] tiles;
private Camera currentCamera;
private Vector2Int currentHover = -Vector2Int.one;
private void Awake()
{
GenerateAllTiles(1, TILE_COUNT_X, TILE_COUNT_Y);
}
private void Update()
{
if (!currentCamera)
{
currentCamera = Camera.current;
return;
}
RaycastHit info;
Ray ray = currentCamera.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out info, 100, LayerMask.GetMask("Tile")))
{
// get the indexes of Tile i've hit
Vector2Int hitPosition = LookupTileIndex(info.transform.gameObject);
//If we are hovering a tile after not hovering any
if (currentHover == -Vector2Int.one)
{
tiles[hitPosition.x, hitPosition.y].layer = LayerMask.NameToLayer("Hover");
currentHover = hitPosition;
}
//If we were already hovering a tile, change the previous one
else if (currentHover != hitPosition)
{
tiles[currentHover.x, currentHover.y].layer = LayerMask.NameToLayer("Tile");
currentHover = hitPosition;
tiles[hitPosition.x, hitPosition.y].layer = LayerMask.NameToLayer("Hover");
}
}
else
{
if (currentHover != -Vector2Int.one)
{
tiles[currentHover.x, currentHover.y].layer = LayerMask.NameToLayer("Tile");
currentHover = -Vector2Int.one;
}
}
}
//Generate the board (useful)
private void GenerateAllTiles(float tileSize, int tileCountX, int tileCountY)
{
tiles = new GameObject[tileCountX, tileCountY];
for (int x = 0; x < tileCountX; x++)
for (int y = 0; y < tileCountY; y++)
tiles[x, y] = generate1tile(tileSize, x, y);
}
private GameObject generate1tile(float tileSize, int x, int y)
{
GameObject tileObject = new GameObject(string.Format("Tile X:{0}, Y:{1}", x, y));
tileObject.transform.parent = transform;
Mesh mesh = new Mesh();
tileObject.AddComponent<MeshFilter>().mesh = mesh;
tileObject.AddComponent<MeshRenderer>().material = tileMaterial;
Vector3[] vertices = new Vector3[4];
vertices[0] = new Vector3(x * tileSize, 0, y * tileSize);
vertices[1] = new Vector3(x * tileSize, 0, (y + 1) * tileSize);
vertices[2] = new Vector3((x + 1) * tileSize, 0, y * tileSize);
vertices[3] = new Vector3((x + 1) * tileSize, 0, (y + 1) * tileSize);
int[] tris = new int[]{0, 2, 1, 1, 2, 3};
mesh.vertices = vertices;
mesh.triangles = tris;
mesh.RecalculateNormals();
tileObject.layer = LayerMask.NameToLayer("Tile");
tileObject.AddComponent<BoxCollider>();
return tileObject;
}
private Vector2Int LookupTileIndex(GameObject hitInfo)
{
for(int x = 0; x < TILE_COUNT_X; x++)
for(int y = 0; y < TILE_COUNT_Y; y++)
if(tiles[x, y] == hitInfo)
return new Vector2Int(x, y);
return new Vector2Int(-1, -1); //Invalid
}
}
Error Message:
A game object can only be in one layer. The layer needs to be in the range [0...31]
UnityEngine.StackTraceUtility:ExtractStackTrace ()
Chessboard:generate1tile (single,int,int) (at Assets/scripts/Chessboard.cs:93)
Chessboard:GenerateAllTiles (single,int,int) (at Assets/scripts/Chessboard.cs:68)
Chessboard:Awake () (at Assets/scripts/Chessboard.cs:20)
I just get into Unity, so I might be missed some parts of it.
I tired using AI to config my problem, but it doesn't work. I am expecting i put my mouse on a tile, the tile change its color.
Here is the link to the tutorial I watched: https://www.youtube.com/watch?v=FtGy7J8XD90&list=PLmcbjnHce7SeAUFouc3X9zqXxiPbCz8Zp&index=2&ab_channel=Epitome
I am working on custom enum popup with search and facing next problem. When clicking button to show popup list of enums I can hover over that list and select items inside, but also I can as always move mouse outside popup rect and move around. And when I hover over other GUI element it reacts to that movement (hover highlight or so happens). I remembered that default Unity's enum don't have this feature and it blocks all events outside popup's rect exclude outside mouse click to close this popup.
So... My question is: How can I implement this feature in my popup window? I look into Unity's source code and don't find anything related to this. Except one moment when they are using internal function "GUI.GrabMouseControl(id);"
There are this internal function for enum button where they block mouse control somehow:
// A button that returns true on mouse down - like a popup button
internal static bool DropdownButton(int id, Rect position, GUIContent content, GUIStyle style)
{
Event evt = Event.current;
switch (evt.type)
{
case EventType.Repaint:
var hovered = position.Contains(Event.current.mousePosition);
if (showMixedValue)
{
BeginHandleMixedValueContentColor();
style.Draw(position, s_MixedValueContent, id, false, hovered);
EndHandleMixedValueContentColor();
}
else
style.Draw(position, content, id, false, hovered);
break;
case EventType.MouseDown:
if (GUIUtility.HitTest(position, evt) && evt.button == 0)
{
GUI.GrabMouseControl(id);
Event.current.Use();
return true;
}
break;
case EventType.MouseUp:
if (GUI.HasMouseControl(id))
{
GUI.ReleaseMouseControl();
Event.current.Use();
}
break;
case EventType.KeyDown:
if (GUIUtility.keyboardControl == id && evt.character == ' ')
{
Event.current.Use();
return true;
}
break;
}
return false;
}
I think that may be that magic thing to block mouse hovering. Also I think that GUIUtility.hotControll is second canditate to solve my issue. But I dont know how properly use it in my OnGUI() method. I am trying to it and get not correct result,
but I blocked hovering outside popup's rect. But it always spam console with these logs:
Should not grab hot control with an active capture. So I dropped this idea with my implementation, cause of these logs...
There are my code of popup window content:
using System;
using UnityEditor;
using UnityEngine;
public class SearchablePopup : PopupWindowContent
{
#region Popup Properties
private const float SymbolVerticalOffset = 0.75f;
private const int ClearButtonSize = 12;
private const string SearchControl = "searchablePopup_ctrl";
#endregion
public static void Show(Rect from, Type type, int current,
Action<int> onSelectionMade, int elementsToShow, int elementHeight,
int searchHeight, Texture backgroundTexture,
GUIStyle searchStyle, GUIStyle clearButtonStyle, GUIStyle elementStyle,
Color hover, Color selected, Color marker)
{
if (type.IsEnum is false) return;
var window = new SearchablePopup(type, current, onSelectionMade,
(int)from.width, elementsToShow, elementHeight, searchHeight,
backgroundTexture, searchStyle, clearButtonStyle, elementStyle,
hover, selected, marker);
PopupWindow.Show(from, window);
}
private readonly FilteredList _filteredList;
private readonly Action<int> _onSelectionMade;
private readonly int _width;
private readonly int _elementsToShow;
private readonly int _rowHeight;
private readonly int _searchHeight;
private readonly float _clearButtonPadding;
private readonly bool _clearButtonValid;
private readonly GUIStyle _searchStyle;
private readonly GUIStyle _searchTooltipStyle;
private readonly GUIStyle _clearButtonStyle;
private readonly GUIStyle _elementStyle;
private readonly Texture _backgroundTexture;
private readonly Texture _hoverTexture;
private readonly Texture _selectedTexture;
private readonly Texture _selectedCircleTexture;
private int _current;
private int _hover;
private int _keyboardIndex;
private int _scroll;
private int _controlHash = "SearchablePopup".GetHashCode();
private Vector2 _scrollPosition;
private Vector2 _clickPosition;
private bool _clickedThisFrame;
public SearchablePopup(Type enumType, int current, Action<int> onSelectionMade,
int width, int elementsToShow, int rowHeight, int searchHeight, Texture backgroundTexture,
GUIStyle searchStyle, GUIStyle clearButtonStyle, GUIStyle elementStyle,
Color hover, Color selected, Color marker)
{
_filteredList = new FilteredList(enumType, current);
_hoverTexture = SmallTexture(hover);
_selectedTexture = SmallTexture(selected);
_selectedCircleTexture = DrawCircle(4, marker);
_current = current;
_hover = _current;
_scroll = _current;
_keyboardIndex = _filteredList.CurrentIndex;
_onSelectionMade = onSelectionMade;
_elementsToShow = elementsToShow;
_width = width;
_rowHeight = rowHeight;
_searchHeight = searchHeight;
_backgroundTexture = backgroundTexture;
_searchStyle = searchStyle;
_clearButtonValid = clearButtonStyle != null;
_clearButtonStyle = clearButtonStyle;
_clearButtonPadding = _clearButtonValid ? clearButtonStyle.padding.left : 4;
_elementStyle = elementStyle;
if (_clearButtonValid is false)
{
_elementStyle = new GUIStyle(_elementStyle);
_elementStyle.alignment = TextAnchor.MiddleLeft;
}
_searchTooltipStyle = new GUIStyle(_searchStyle);
_searchTooltipStyle.normal.textColor *= 0.75f;
_searchTooltipStyle.hover.textColor = _searchTooltipStyle.normal.textColor;
_searchTooltipStyle.fontSize = (int)(_searchTooltipStyle.fontSize * 0.75f);
}
public override Vector2 GetWindowSize()
{
float height = Mathf.Min(
_filteredList.Entries.Count, _elementsToShow) * _rowHeight
+ _searchHeight;
//Strange hot-fix for Unity's Scrollview:
//We are increasing total height of our window's rect
//to be a litte bigger than actual content because
//if Scrollview's height >= Window's height than scrollbar is displayed
//what we don't want to see if our current count of entires is less than show amount
if (_filteredList.Entries.Count < _elementsToShow)
height += 0.25f;
return new Vector2(_width, height);
}
public override void OnOpen()
{
base.OnOpen();
EditorApplication.update += Repaint;
}
public override void OnClose()
{
base.OnClose();
EditorApplication.update -= Repaint;
}
private void Repaint()
{
EditorWindow.focusedWindow.Repaint();
}
public override void OnGUI(Rect rect)
{
GUI.DrawTexture(rect, _backgroundTexture);
if (Event.current.type == EventType.MouseDown)
{
_clickedThisFrame = true;
_clickPosition = Event.current.mousePosition;
}
Rect search = new Rect(0, 0, rect.width, _searchHeight);
Rect clear = Rect.MinMaxRect(search.xMax - ClearButtonSize - _clearButtonPadding,
search.center.y - ClearButtonSize * 0.5f, rect.xMax - _clearButtonPadding,
search.center.y + ClearButtonSize * 0.5f);
Rect scroll = Rect.MinMaxRect(0, search.yMax, rect.xMax, rect.yMax);
HandleKeyboardInput();
DrawSearch(search, clear);
DrawScroll(scroll);
_clickedThisFrame = false;
}
private void DrawSearch(Rect search, Rect clear)
{
GUI.FocusControl(SearchControl);
GUI.SetNextControlName(SearchControl);
var filter = TextfieldWithTooltip(search, _filteredList.Filter,
"Type to search...", _searchStyle, _searchTooltipStyle);
if (_filteredList.Refresh(filter))
{
_scrollPosition = Vector2.zero;
}
if (string.IsNullOrEmpty(_filteredList.Filter) is false)
{
if (_clearButtonValid)
{
GUI.Box(clear, GUIContent.none, _clearButtonStyle);
}
else
{
GUI.Box(clear, "x", GUI.skin.label);
}
if (_clickedThisFrame && clear.Contains(_clickPosition))
{
_filteredList.Refresh("");
_scrollPosition = Vector2.zero;
}
}
}
private void DrawScroll(Rect scroll)
{
Rect contentRect = new Rect(0, 0,
scroll.width - GUI.skin.verticalScrollbar.fixedWidth,
_filteredList.Entries.Count * _rowHeight);
_scrollPosition = GUI.BeginScrollView(scroll, _scrollPosition, contentRect);
Rect element = new Rect(0, 0, scroll.width, _rowHeight);
var eventType = Event.current.type;
for (int i = 0; i < _filteredList.Entries.Count; i++)
{
if (_scroll == _filteredList.Entries[i].Value && (eventType == EventType.Repaint || eventType == EventType.Layout))
{
GUI.ScrollTo(element);
_scrollPosition.x = 0;
_scroll = -1;
}
if (element.Contains(Event.current.mousePosition))
{
if (Event.current.type == EventType.MouseMove ||
Event.current.type == EventType.ScrollWheel)
{
_hover = _filteredList.Entries[i].Value;
_keyboardIndex = i;
}
if (Event.current.type == EventType.MouseDown)
{
_onSelectionMade(_filteredList.Entries[i].Value);
EditorWindow.focusedWindow.Close();
}
}
DrawElement(element, _filteredList.Entries[i].Value, i);
element.y = element.yMax;
}
GUI.EndScrollView();
}
private void DrawElement(Rect rect, int value, int index)
{
if (value == _current)
{
DrawBox(rect, _selectedTexture, _selectedCircleTexture);
rect.xMin += _selectedCircleTexture.width * 2f;
}
else if (value == _hover)
{
DrawBox(rect, _hoverTexture);
}
GUI.Label(rect, _filteredList.Entries[index].Content, _elementStyle);
}
private void DrawBox(Rect rect, Texture texture, Texture symbol = null)
{
GUI.DrawTexture(rect, texture);
if (symbol == null) return;
rect.xMin += symbol.width * 0.5f;
rect.y = rect.center.y - symbol.height * 0.5f + SymbolVerticalOffset;
rect.width = symbol.width;
rect.height = symbol.height;
GUI.DrawTexture(rect, symbol);
}
private void HandleKeyboardInput()
{
if (Event.current.isKey is false || Event.current.type != EventType.KeyDown) return;
var keyCode = Event.current.keyCode;
if (keyCode == KeyCode.Escape)
{
EditorWindow.focusedWindow.Close();
return;
}
if (keyCode == KeyCode.Return)
{
_onSelectionMade(_filteredList.Entries[_keyboardIndex].Value);
EditorWindow.focusedWindow.Close();
return;
}
if (keyCode == KeyCode.DownArrow)
{
_keyboardIndex = Mathf.Min(_filteredList.Entries.Count - 1, _keyboardIndex + 1);
_hover = _filteredList.Entries[_keyboardIndex].Value;
Event.current.Use();
_scroll = _hover;
}
else if (keyCode == KeyCode.UpArrow)
{
_keyboardIndex = Mathf.Max(0, _keyboardIndex - 1);
_hover = _filteredList.Entries[_keyboardIndex].Value;
GUIUtility.hotControl = 0;
Event.current.Use();
_scroll = _hover;
}
}
#region Helpers
public static Rect Shrink(Rect rect, float amount)
{
return new Rect(rect.x + amount, rect.y + amount, rect.width - amount * 2, rect.height - amount * 2);
}
public static Texture SmallTexture(string hex)
{
Texture2D texture = new Texture2D(4, 4);
var color = HexToRGB(hex);
for (int i = 0; i < texture.width; i++)
{
for (int j = 0; j < texture.height; j++)
{
texture.SetPixel(i, j, color);
}
}
texture.Apply();
return texture;
}
public static Texture SmallTexture(Color color)
{
Texture2D texture = new Texture2D(4, 4);
for (int i = 0; i < texture.width; i++)
{
for (int j = 0; j < texture.height; j++)
{
texture.SetPixel(i, j, color);
}
}
texture.Apply();
return texture;
}
public static Texture DrawCircle(int radius, string hex)
{
Texture2D texture = new Texture2D(radius * 2, radius * 2);
var color = HexToRGB(hex);
float cX = radius;
float cY = radius;
float sizeSquared = radius * radius;
for (int i = 0; i < texture.width; i++)
{
for (int j = 0; j < texture.height; j++)
{
if ((cX - i) * (cX - i) + (cY - j) * (cY - j) < sizeSquared)
{
texture.SetPixel(i, j, color);
}
else texture.SetPixel(i, j, Color.clear);
}
}
texture.Apply();
return texture;
}
public static Texture DrawCircle(int radius, Color color)
{
Texture2D texture = new Texture2D(radius * 2, radius * 2);
float cX = radius;
float cY = radius;
float sizeSquared = radius * radius;
for (int i = 0; i < texture.width; i++)
{
for (int j = 0; j < texture.height; j++)
{
if ((cX - i) * (cX - i) + (cY - j) * (cY - j) < sizeSquared)
{
texture.SetPixel(i, j, color);
}
else texture.SetPixel(i, j, Color.clear);
}
}
texture.Apply();
return texture;
}
public static Color HexToRGB(string hex)
{
hex = hex.TrimStart('#');
if (hex.Length != 6) return Color.black;
int r = Int16.Parse(hex.Substring(0, 2),
System.Globalization.NumberStyles.AllowHexSpecifier);
int g = Int16.Parse(hex.Substring(2, 2),
System.Globalization.NumberStyles.AllowHexSpecifier);
int b = Int16.Parse(hex.Substring(4, 2),
System.Globalization.NumberStyles.AllowHexSpecifier);
return new Color(r / 255f, g / 255f, b / 255f);
}
public static string TextfieldWithTooltip(Rect rect, string text, string tooltip,
GUIStyle textfieldStyle, GUIStyle tooltipStyle)
{
text = GUI.TextField(rect, text, textfieldStyle);
if (text.Length == 0)
GUI.Label(rect, tooltip, tooltipStyle);
return text;
}
#endregion
}
I will be realy glad to see some advices or straight anwsers on this problem is someone can solve it :)
Thank you!
The thing that I want to do is writing a method which can call in onGUI() method.
I wrote this method. However when I run the program , method did not show the effect.
private void ShowSubPartsOnClick(float x, float y, float widthLABEL, float heigth, HumanBodyPart bodyPart)
{
x = x + 14;
for(int i = 0; i < bodyPart.SubParts.Count; i++)
{
y = y + 14;
GUI.Label(new Rect(x+14,y,widthLABEL,heigth), bodyPart.SubParts[i].EnglishTitle);
if(GUI.Button(new Rect(x, y, 14, heigth),"+"))
{
ShowSubPartsOnClick(x, y, widthLABEL, heigth, bodyPart.SubParts[i]);
}
}
}
}
private void OnGUI()
{
GUI.Label(new Rect(text.transform.position.x+14, text.transform.position.y, text.rectTransform.sizeDelta.x, 14),bodyVisualizer.BodyData.Body.SubParts[0].EnglishTitle);
if(GUI.Button(new Rect(text.transform.position.x, text.transform.position.y, 14, 14), "+"))
{
ShowSubPartsOnClick(text.transform.position.x, text.transform.position.y, text.rectTransform.sizeDelta.x, 14, bodyVisualizer.BodyData.Body.SubParts[0]);
}
}
How can I fix this or what is the problem?
The challenge here is functions like GUI.Label and GUI.Button must be invoked directly from OnGUI to work:
From Unity Forums: "The only place you can draw/create GUI elements is by triggering them from inside an OnGUI function."
Given the recommendation there, one solution is to run an iterative depth first search via a while loop. See attached example.
That being said, I'd highly recommend using Unity Canvas instead of OnGUI. It's much more powerful and its programmatic logic is not constrained to a single function.
OnGUI Snippet:
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class HumanBodyPart
{
public string EnglishTitle;
public List<HumanBodyPart> SubParts;
public bool IsExpanded;
public int DrawDepth;
public HumanBodyPart(string title, HumanBodyPart[] subParts)
{
this.EnglishTitle = title;
this.SubParts = new List<HumanBodyPart>();
this.SubParts.AddRange(subParts);
this.IsExpanded = false;
this.DrawDepth = 0;
}
}
public class Script : MonoBehaviour
{
[SerializeField]
Text text;
HumanBodyPart mainBodyPart;
private void Start()
{
HumanBodyPart subSubSubBodyPart = new HumanBodyPart("SubSubSubBodyPart", new HumanBodyPart[] { });
HumanBodyPart subSubBodyPart1 = new HumanBodyPart("SubSubBodyPart1", new HumanBodyPart[] { subSubSubBodyPart });
HumanBodyPart subSubBodyPart2 = new HumanBodyPart("SubSubBodyPart2", new HumanBodyPart[] { });
HumanBodyPart subBodyPart = new HumanBodyPart("SubBodyPart", new HumanBodyPart[] { subSubBodyPart1, subSubBodyPart2});
mainBodyPart = new HumanBodyPart("BodyPart", new HumanBodyPart[] { subBodyPart });
UpdateDrawDepths(mainBodyPart);
}
private void UpdateDrawDepths(HumanBodyPart currentBodyPart, int currentDrawDepth=0)
{
currentBodyPart.DrawDepth = currentDrawDepth;
foreach (HumanBodyPart bodyPart in currentBodyPart.SubParts)
{
UpdateDrawDepths(bodyPart, currentDrawDepth + 1);
}
}
private void OnGUI()
{
float spacing = 30;
float x = text.transform.position.x + spacing;
float y = text.transform.position.y;
int drawDepth = 0;
List<HumanBodyPart> nextPartsToRender = new List<HumanBodyPart>(new HumanBodyPart[] { mainBodyPart });
while (nextPartsToRender.Count > 0)
{
HumanBodyPart currentPart = nextPartsToRender[0];
GUI.Label(new Rect(currentPart.DrawDepth * spacing + x, y, 200, 20), currentPart.EnglishTitle);
nextPartsToRender.RemoveAt(0);
if (GUI.Button(new Rect(x - spacing + currentPart.DrawDepth * spacing, y, 20, 20), "+"))
{
currentPart.IsExpanded = true;
}
if (currentPart.IsExpanded)
{
nextPartsToRender.InsertRange(0, currentPart.SubParts);
}
y += spacing;
}
}
}
Trying to display a graph on a Form application.
Created a PictureBox control and initialized this class with its value.
On resize, graph always updates; on mouse scroll, it hardly does.
It's GraphBox , PictureBox control, inside a GraphBoxPanel, Panel control.
This the class:
public struct DLT_measure_item
{
public DateTime ts;
public float value;
public int id;
public int X;
public int Y;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct dlt_ser_meas
{
public byte msg_id; // 'D'
public byte meas_count; // Number of measures
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public byte[] port; // Module ID (4b) + Port ID (4b)
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public float[] meas; // measure
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public byte[] msg_end;
}
public class manageGraph
{
private PictureBox box;
public bool displayGrid = true;
private int horAxisMin_p = 0;
private int horAxisMax_p = 300;
private float verAxisMin_p = 0;
private float verAxisMax_p = 40;
public int horAxisMin
{
get { return this.horAxisMin_p; }
set
{
if (value < horAxisMax_p)
{
this.horAxisMin_p = value;
reDraw();
}
}
}
public int horAxisMax
{
get { return this.horAxisMax_p; }
set
{
if (value > horAxisMin_p)
{
this.horAxisMax_p = value;
reDraw();
}
}
}
public float verAxisMin
{
get { return this.verAxisMin_p; }
set
{
if (value < verAxisMax_p)
{
this.verAxisMin_p = value;
verPointPerUnit = graphArea.Height / (verAxisMax_p - this.verAxisMin_p);
}
}
}
public float verAxisMax
{
get { return this.verAxisMax_p; }
set
{
if (value > verAxisMin_p)
{
this.verAxisMax_p = value;
verPointPerUnit = graphArea.Height / (this.verAxisMax_p - verAxisMin_p);
}
}
}
Pen axes = new Pen(Color.Black, (float)1.5);
public int horAxisSpacing = 30;
public int verAxisSpacing = 20;
public int horAxis = 20;
public int verAxis = 20;
private float horPointPerUnit = 1;
private float verPointPerUnit = 1;
public int horAxisTickLen = 5;
public int verAxisTickLen = 5;
public bool horAxisShowTime = false;
private Rectangle graphArea = new Rectangle();
public void reDraw()
{
box.Image.Dispose();
Bitmap GraphBlankImage = new Bitmap(box.Width, box.Height);
box.Image = GraphBlankImage;
updatePointPerUnit();
drawGrid();
box.Refresh();
}
public manageGraph(PictureBox targetImageBoxbox)
{
box = targetImageBoxbox;
horAxisMin_p = 0;
horAxisMax_p = 300;
verAxisMin_p = 0F;
verAxisMax_p = 50F;
updatePointPerUnit();
}
private Point measToPoint(DLT_measure_item measure) {
Point coords = new Point();
coords.X = graphArea.Width - (int)(
((DateTime.Now - measure.ts).TotalSeconds + horAxisMin_p) * horPointPerUnit ) ;
coords.Y = graphArea.Height - (int)(
((measure.value - verAxisMin_p) * verPointPerUnit));
return coords;
}
public manageGraph(PictureBox targetImageBoxbox,
int xmin, int xmax, float ymin, float ymax)
{
box = targetImageBoxbox;
horAxisMin_p = xmin;
horAxisMax_p = xmax;
verAxisMin_p = ymin;
verAxisMax_p = ymax;
updatePointPerUnit();
}
private void updateGraphArea()
{
graphArea = new Rectangle(0, 0, box.Width - horAxis, box.Height - verAxis);
}
private void updatePointPerUnit()
{
updateGraphArea();
horPointPerUnit = graphArea.Width / (horAxisMax_p - horAxisMin_p);
verPointPerUnit = graphArea.Height / (verAxisMax_p - verAxisMin_p);
}
public void drawGrid()
{
//updatePointPerUnit();
using (Graphics g = Graphics.FromImage(box.Image))
{
// X axis
g.DrawLine(axes, graphArea.Left, graphArea.Bottom, box.Width, graphArea.Bottom);
// Y axis
g.DrawLine(axes, graphArea.Right + 1, graphArea.Top, graphArea.Right +1, graphArea.Bottom);
using (Font ArialFont = new Font("Arial", 10))
{
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
// Put x labels
for (int i = 1; i <= (graphArea.Width / horPointPerUnit); i = i + (int)(horAxisSpacing / horPointPerUnit ) + 1)
{
g.DrawString((i).ToString(), ArialFont, Brushes.Black, graphArea.Width - ( i * horPointPerUnit) - 5, graphArea.Bottom +5);
g.DrawLine(axes, graphArea.Width - (i * horPointPerUnit), graphArea.Bottom + (horAxisTickLen / 2), graphArea.Width - (i * horPointPerUnit), graphArea.Bottom - (horAxisTickLen / 2));
}
// Put y labels
for (int i = 1; i <= (graphArea.Height / verPointPerUnit); i = i + (int)(verAxisSpacing / verPointPerUnit) +1)
{
g.DrawString((i).ToString(), ArialFont, Brushes.Black, graphArea.Right + 1 , graphArea.Height - (i * verPointPerUnit) - 8);
g.DrawLine(axes, graphArea.Width - (verAxisTickLen / 2), (i * verPointPerUnit), graphArea.Width + (verAxisTickLen / 2), (i * verPointPerUnit));
}
}
/*Put some random data*/
DLT_measure_item testmeas = new DLT_measure_item();
Point testGraphPoint = new Point();
testmeas.ts = DateTime.Now;
testmeas.value = 0;
testGraphPoint = measToPoint(testmeas);
g.FillEllipse(Brushes.Blue, testGraphPoint.X, testGraphPoint.Y, 4, 4);
for (double i = 0; i < 300; i++)
{
double x = i;
double freq = 10;
double y = 30 - (i/10);
testmeas.value = (float)y;
testmeas.ts = DateTime.Now.AddSeconds(-1 * i);
testGraphPoint = measToPoint(testmeas);
g.FillEllipse(Brushes.Red, testGraphPoint.X, testGraphPoint.Y, 2,2);
}
}
}
}
The initialization:
public DLThermalogMainForm()
{
InitializeComponent();
Bitmap GraphBlankImage = new Bitmap(GraphBox.Width, GraphBox.Height);
GraphBox.Image = GraphBlankImage;
myGraph = new manageGraph(GraphBox);
myGraph.drawGrid();
}
Those the handlers:
private void Form1_ResizeEnd(object sender, EventArgs e)
{
myGraph.reDraw();
OutputTextBox.AppendText("Resize." + Environment.NewLine);
}
private void GraphBox_MouseMove(object sender, MouseEventArgs e)
{
//Get the focus to have the wheel working
GraphBoxPanel.Focus();
}
private void GraphBoxPanel_MouseWheel(object sender, MouseEventArgs e)
{
// Change x axis max value to zoom in/out the graph
myGraph.horAxisMax += e.Delta/ 120;
myGraph.reDraw();
}
On resize event, it always redraws; on mouse wheel, it does quickly only with small horAxisMax values (does it makes sense???), but for larger, it takes many seconds to update, or doesn't at all.
Thank you very much
Change reDraw like this:
public void reDraw()
{
box.Image.Dispose();
Bitmap GraphBlankImage = new Bitmap(box.ClientSize.Width, box.ClientSize.Height);
updatePointPerUnit();
drawGrid(GraphBlankImage);
box.Image = GraphBlankImage;
}
and drawGrid like this:
public void drawGrid(Bitmap bmp)
{
//updatePointPerUnit(); //??
using (Graphics g = Graphics.FromImage(bmp))
{
...
...
...
}
}
Now the Bitmap with the grid should immediately show up in the PictureBox.
As mentioned a Graphics object is a tool to change an associated Bitmap. To pick it up the Bitmap should be assigned to the PictureBoxe's Image.
Also note, that unless your PictureBox has no Border, there is a small difference between the outer size (aka Bounds) and the inner size, the ClientRectangle / ClientSize. The Image should have the ClientSize
You may wonder why your original code doesn't work? After all an Image is a reference type, so changing it, as you did should be enough..
But looking deeper into the source code we find the reason:
The PictureBox's Image is a property and in its setter there is a call to InstallNewImage:
public Image Image {
get {
return image;
}
set {
InstallNewImage(value, ImageInstallationType.DirectlySpecified);
}
}
The same call is also in a few other places like Load or in the setter of ImageLocation. But changing the Image behind the scene alone will not force the PictureBox to make that call. A Refresh() should also do it.. And, as you found out, resizing it will also cause the PictureBox to pick up the changed data in the Image Bitmap..
The easiest way to force updating is simply to invalidate the control.
timer = new Timer();
timer.Interval = 200; //refreshes every 200 ms
timer.Tick += (sender,e) => targetImageBoxbox.Invalidate();
timer.Start();
so i have drawn a few objects , circles squares or even lines. This is the code i use to draw the images:
Graphics surface = this.splitContainer1.Panel2.CreateGraphics();
Pen pen1 = new Pen(ColorR.BackColor, float.Parse(boxWidth.Text));
switch (currentObject)
{
case "line":
if (step == 1)
{
splitContainer1.Panel2.Focus();
one.X = e.X;
one.Y = e.Y;
boxX.Text = one.X.ToString();
boxY.Text = one.Y.ToString();
step = 2;
}
else
{
two.X = e.X;
two.Y = e.Y;
boxX2.Text = two.X.ToString();
boxY2.Text = two.Y.ToString();
surface.DrawLine(pen1, one, two);
step = 1;
}
break;
case "circle":
if (step == 1)
{
boxX.Text = e.X.ToString();
boxY.Text = e.Y.ToString();
step = 2;
}
else
{
int tempX = int.Parse(boxX.Text);
int tempY = int.Parse(boxY.Text);
int tempX2 = e.X;
int tempY2 = e.Y;
int sideX, sideY;
if (tempX > tempX2)
{
sideX = tempX - tempX2;
}
else
{
sideX = tempX2 - tempX;
}
if (tempY > tempY2)
{
sideY = tempY - tempY2;
}
else
{
sideY = tempY2 - tempY;
}
double tempRadius;
tempRadius = Math.Sqrt(sideX * sideX + sideY * sideY);
tempRadius *= 2;
bWidth.Text = bHeight.Text = Convert.ToInt32(tempRadius).ToString();
surface.DrawEllipse(
pen1,
int.Parse(boxX.Text) - int.Parse(bWidth.Text) / 2,
int.Parse(boxY.Text) - int.Parse(bHeight.Text) / 2,
float.Parse(bWidth.Text), float.Parse(bHeight.Text));
step = 1;
}
break;
case "square":
if (step == 1)
{
boxX.Text = e.X.ToString();
boxY.Text = e.Y.ToString();
step = 2;
}
else if (step == 2)
{
int tempX = e.X;
if (tempX > int.Parse(boxX.Text))
{
bWidth.Text = (tempX - int.Parse(boxX.Text)).ToString();
}
else
{
bWidth.Text = (int.Parse(boxX.Text) - tempX).ToString();
}
step = 3;
}
else
{
int tempY = e.Y;
if (tempY > int.Parse(boxY.Text))
{
bHeight.Text = (tempY - int.Parse(boxY.Text)).ToString();
}
else
{
bHeight.Text = (int.Parse(boxY.Text) - tempY).ToString();
}
surface.DrawRectangle(
pen1,
int.Parse(boxX.Text),
int.Parse(boxY.Text),
int.Parse(bWidth.Text),
int.Parse(bHeight.Text));
step = 1;
}
break;
}
So after I draw the images, I want to be able to select a figure and--for example--change the color or rotate it. But I cant seem to figure it out how to do it.
I suggest defining a base abstract shape class that has methods all shapes should provide, such as a method to draw itself on a graphics object, a method that says whether a point is within it / should could as selecting it, a method to rotate it by a given amount and a method to change the color.
Once you've got your shape class then you've got to work out how to fill in the methods for each derived shape. For drawing you've already got the code. For selecting it, that will be dependent on the shape. For something like a circle it's fairly easy, just calculate the distance between the center of the circle, and the point clicked, for something like a line it's harder as you don't want the user to have to click it exactly.
That leaves rotating and changing the colour. Changing the colour is easy, just have a Color property on the Shape class, then when you draw your shapes, use that colour to create a brush or pen.
As for rotation, take a look at Graphics.RotateTransform.
public abstract class Shape
{
public Color Color { get; set; }
public float Rotation { get; set; }
public Point Position { get; set; }
public Shape(Color color, float rotation, Point position)
{
Color = color;
Rotation = rotation;
Position = position;
}
public void ChangeRotation(float amount)
{
Rotation += amount;
}
public abstract void Draw(Graphics graphics);
public abstract bool WithinBounds(Point point);
}
public class Circle : Shape
{
public float Radius { get; set; }
public Circle(Color color, float rotation, Point position)
:base(color, rotation, position)
{
}
public override void Draw(Graphics graphics)
{
}
public override bool WithinBounds(Point point)
{
if (Math.Sqrt(Math.Pow(point.X - Position.X, 2) + Math.Pow(point.Y - Position.Y, 2)) <= Radius)
return true;
else
return false;
// Note, if statement could be removed to become the below:
//return Math.Sqrt(Math.Pow(point.X - Position.X, 2) + Math.Pow(point.Y - Position.Y, 2)) <= Radius;
}
}
Have a look at the RotateTransform method of the Graphics object. There is a TranslateTransform method too.