2016-07-15 01:48:18 +08:00
/// Credit drobina, w34edrtfg, playemgames
/// Sourced from - http://forum.unity3d.com/threads/sprite-icons-with-text-e-g-emoticons.265927/
using System ;
using System.Collections.Generic ;
using System.Text ;
using System.Text.RegularExpressions ;
using UnityEngine.Events ;
using UnityEngine.EventSystems ;
2017-11-07 04:44:20 +08:00
namespace UnityEngine.UI.Extensions {
2016-09-28 13:29:33 +08:00
// Image according to the label inside the name attribute to load, read from the Resources directory. The size of the image is controlled by the size property.
2017-11-07 04:44:20 +08:00
// Use: Add Icon name and sprite to the icons list
2016-09-28 13:29:33 +08:00
[AddComponentMenu("UI/Extensions/TextPic")]
2016-11-25 01:48:51 +08:00
2016-09-28 13:29:33 +08:00
[ExecuteInEditMode] // Needed for culling images that are not used //
2017-11-07 04:44:20 +08:00
public class TextPic : Text , IPointerClickHandler , IPointerExitHandler , IPointerEnterHandler , ISelectHandler {
2016-09-28 13:29:33 +08:00
/// <summary>
/// Image Pool
/// </summary>
private readonly List < Image > m_ImagesPool = new List < Image > ( ) ;
private readonly List < GameObject > culled_ImagesPool = new List < GameObject > ( ) ;
private bool clearImages = false ;
2016-11-25 01:48:51 +08:00
private Object thisLock = new Object ( ) ;
2016-09-28 13:29:33 +08:00
/// <summary>
/// Vertex Index
/// </summary>
private readonly List < int > m_ImagesVertexIndex = new List < int > ( ) ;
/// <summary>
/// Regular expression to replace
/// </summary>
private static readonly Regex s_Regex =
new Regex ( @"<quad name=(.+?) size=(\d*\.?\d+%?) width=(\d*\.?\d+%?) />" , RegexOptions . Singleline ) ;
2017-11-07 04:44:20 +08:00
private string fixedString ; [ SerializeField ]
2016-09-28 13:29:33 +08:00
2017-07-08 17:33:08 +08:00
[Tooltip("Allow click events to be received by parents, (default) blocks")]
2017-11-07 04:44:20 +08:00
2017-07-08 17:33:08 +08:00
private bool m_ClickParents ;
2017-11-07 04:44:20 +08:00
// Update the quad images when true
private bool updateQuad = false ;
public bool AllowClickParents {
2017-07-08 17:33:08 +08:00
get { return m_ClickParents ; }
set { m_ClickParents = value ; }
}
2017-11-07 04:44:20 +08:00
public override void SetVerticesDirty ( ) {
2016-09-28 13:29:33 +08:00
base . SetVerticesDirty ( ) ;
2017-11-07 04:44:20 +08:00
// Update the quad images
updateQuad = true ;
2016-09-28 13:29:33 +08:00
}
#if UNITY_EDITOR
2017-11-07 04:44:20 +08:00
protected override void OnValidate ( ) {
2016-09-28 13:29:33 +08:00
base . OnValidate ( ) ;
2017-11-07 04:44:20 +08:00
// Update the quad images
updateQuad = true ;
for ( int i = 0 ; i < inspectorIconList . Length ; i + + ) {
if ( inspectorIconList [ i ] . scale = = Vector2 . zero ) {
2017-07-10 04:10:42 +08:00
inspectorIconList [ i ] . scale = Vector2 . one ;
}
}
2016-09-28 13:29:33 +08:00
}
# endif
/// <summary>
/// After parsing the final text
/// </summary>
private string m_OutputText ;
[System.Serializable]
2017-11-07 04:44:20 +08:00
public struct IconName {
2016-09-28 13:29:33 +08:00
public string name ;
public Sprite sprite ;
2017-11-07 04:44:20 +08:00
public Vector2 offset ;
public Vector2 scale ;
2016-09-28 13:29:33 +08:00
}
2017-11-07 04:44:20 +08:00
2016-09-28 13:29:33 +08:00
public IconName [ ] inspectorIconList ;
2017-11-07 04:44:20 +08:00
[Tooltip("Global scaling factor for all images")]
2016-09-28 13:29:33 +08:00
public float ImageScalingFactor = 1 ;
// Write the name or hex value of the hyperlink color
public string hyperlinkColor = "blue" ;
// Offset image by x, y
[SerializeField]
public Vector2 imageOffset = Vector2 . zero ;
private Button button ;
private List < Vector2 > positions = new List < Vector2 > ( ) ;
2016-11-25 01:48:51 +08:00
2016-09-28 14:56:06 +08:00
/ * *
2017-11-07 04:44:20 +08:00
* Little hack to support multiple hrefs with same name
2016-09-28 14:56:06 +08:00
* /
private string previousText = "" ;
public bool isCreating_m_HrefInfos = true ;
2016-09-28 13:29:33 +08:00
2017-11-07 04:44:20 +08:00
new void Start ( ) {
button = GetComponent < Button > ( ) ;
ResetIconList ( ) ;
}
2017-07-10 04:10:42 +08:00
2017-11-07 04:44:20 +08:00
public void ResetIconList ( ) {
2016-11-25 01:48:51 +08:00
Reset_m_HrefInfos ( ) ;
2017-11-07 04:44:20 +08:00
base . Start ( ) ;
2016-09-28 13:29:33 +08:00
}
2017-11-07 04:44:20 +08:00
protected void UpdateQuadImage ( ) {
2018-12-31 23:54:49 +08:00
#if UNITY_EDITOR && !UNITY_2018_3_OR_NEWER
2017-11-07 04:44:20 +08:00
if ( UnityEditor . PrefabUtility . GetPrefabType ( this ) = = UnityEditor . PrefabType . Prefab ) {
2016-09-28 13:29:33 +08:00
return ;
}
# endif
m_OutputText = GetOutputText ( ) ;
2017-11-07 04:44:20 +08:00
MatchCollection matches = s_Regex . Matches ( m_OutputText ) ;
2016-09-28 13:29:33 +08:00
2017-11-07 04:44:20 +08:00
if ( matches ! = null & & matches . Count > 0 ) {
2017-11-08 05:13:24 +08:00
for ( int i = 0 ; i < matches . Count ; i + + ) {
2017-11-07 04:44:20 +08:00
m_ImagesPool . RemoveAll ( image = > image = = null ) ;
if ( m_ImagesPool . Count = = 0 ) {
GetComponentsInChildren < Image > ( true , m_ImagesPool ) ;
}
2017-11-08 05:13:24 +08:00
if ( matches . Count > m_ImagesPool . Count ) {
2017-11-07 04:44:20 +08:00
var resources = new DefaultControls . Resources ( ) ;
var go = DefaultControls . CreateImage ( resources ) ;
go . layer = gameObject . layer ;
var rt = go . transform as RectTransform ;
if ( rt ) {
rt . SetParent ( rectTransform ) ;
rt . anchoredPosition3D = Vector3 . zero ;
rt . localRotation = Quaternion . identity ;
rt . localScale = Vector3 . one ;
}
m_ImagesPool . Add ( go . GetComponent < Image > ( ) ) ;
}
2017-11-08 05:13:24 +08:00
var spriteName = matches [ i ] . Groups [ 1 ] . Value ;
2017-11-07 04:44:20 +08:00
2017-11-08 05:13:24 +08:00
var img = m_ImagesPool [ i ] ;
2017-11-07 04:44:20 +08:00
Vector2 imgoffset = Vector2 . zero ;
if ( img . sprite = = null | | img . sprite . name ! = spriteName ) {
if ( inspectorIconList ! = null & & inspectorIconList . Length > 0 ) {
foreach ( IconName icon in inspectorIconList ) {
if ( icon . name = = spriteName ) {
img . sprite = icon . sprite ;
img . preserveAspect = true ;
img . rectTransform . sizeDelta = new Vector2 ( fontSize * ImageScalingFactor * icon . scale . x , fontSize * ImageScalingFactor * icon . scale . y ) ;
imgoffset = icon . offset ;
break ;
}
}
}
}
img . enabled = true ;
2017-11-08 05:13:24 +08:00
if ( positions . Count > 0 & & i < positions . Count ) {
img . rectTransform . anchoredPosition = positions [ i ] + = imgoffset ;
2017-11-07 04:44:20 +08:00
}
}
}
else {
// If there are no matches, remove the images from the pool
2019-03-02 08:23:06 +08:00
for ( int i = m_ImagesPool . Count - 1 ; i > 0 ; i - - ) {
2017-11-07 04:44:20 +08:00
if ( m_ImagesPool [ i ] ) {
if ( ! culled_ImagesPool . Contains ( m_ImagesPool [ i ] . gameObject ) ) {
culled_ImagesPool . Add ( m_ImagesPool [ i ] . gameObject ) ;
m_ImagesPool . Remove ( m_ImagesPool [ i ] ) ;
}
}
}
}
// Remove any images that are not being used
2019-03-02 08:23:06 +08:00
for ( int i = m_ImagesPool . Count - 1 ; i > = matches . Count ; i - - ) {
if ( i > = 0 & & m_ImagesPool . Count > 0 ) {
if ( m_ImagesPool [ i ] ) {
if ( ! culled_ImagesPool . Contains ( m_ImagesPool [ i ] . gameObject ) ) {
culled_ImagesPool . Add ( m_ImagesPool [ i ] . gameObject ) ;
m_ImagesPool . Remove ( m_ImagesPool [ i ] ) ;
}
2017-11-07 04:44:20 +08:00
}
}
}
// Clear the images when it is safe to do so
if ( culled_ImagesPool . Count > 0 ) {
2016-09-28 13:29:33 +08:00
clearImages = true ;
}
}
2017-11-07 04:44:20 +08:00
protected override void OnPopulateMesh ( VertexHelper toFill ) {
2016-09-28 13:29:33 +08:00
var orignText = m_Text ;
2016-12-05 20:29:57 +08:00
m_Text = GetOutputText ( ) ;
2016-09-28 13:29:33 +08:00
base . OnPopulateMesh ( toFill ) ;
m_Text = orignText ;
positions . Clear ( ) ;
UIVertex vert = new UIVertex ( ) ;
2017-11-07 04:44:20 +08:00
for ( var i = 0 ; i < m_ImagesVertexIndex . Count ; i + + ) {
2016-09-28 13:29:33 +08:00
var endIndex = m_ImagesVertexIndex [ i ] ;
2017-11-07 04:44:20 +08:00
2017-11-08 05:13:24 +08:00
if ( endIndex < toFill . currentVertCount ) {
toFill . PopulateUIVertex ( ref vert , endIndex ) ;
positions . Add ( new Vector2 ( ( vert . position . x + fontSize / 2 ) , ( vert . position . y + fontSize / 2 ) ) + imageOffset ) ;
2017-11-07 04:44:20 +08:00
2017-11-08 05:13:24 +08:00
// Erase the lower left corner of the black specks
toFill . PopulateUIVertex ( ref vert , endIndex - 3 ) ;
var pos = vert . position ;
2017-11-07 04:44:20 +08:00
2017-11-08 05:13:24 +08:00
for ( int j = endIndex , m = endIndex - 3 ; j > m ; j - - ) {
2017-11-07 04:44:20 +08:00
toFill . PopulateUIVertex ( ref vert , endIndex ) ;
2017-11-08 05:13:24 +08:00
vert . position = pos ;
toFill . SetUIVertex ( vert , j ) ;
2017-11-07 04:44:20 +08:00
}
}
2016-09-28 13:29:33 +08:00
}
// Hyperlinks surround processing box
2017-11-07 04:44:20 +08:00
foreach ( var hrefInfo in m_HrefInfos ) {
2016-09-28 13:29:33 +08:00
hrefInfo . boxes . Clear ( ) ;
2017-11-07 04:44:20 +08:00
if ( hrefInfo . startIndex > = toFill . currentVertCount ) {
2016-09-28 13:29:33 +08:00
continue ;
}
// Hyperlink inside the text is added to surround the vertex index coordinate frame
toFill . PopulateUIVertex ( ref vert , hrefInfo . startIndex ) ;
var pos = vert . position ;
var bounds = new Bounds ( pos , Vector3 . zero ) ;
2017-11-07 04:44:20 +08:00
for ( int i = hrefInfo . startIndex , m = hrefInfo . endIndex ; i < m ; i + + ) {
if ( i > = toFill . currentVertCount ) {
2016-09-28 13:29:33 +08:00
break ;
}
toFill . PopulateUIVertex ( ref vert , i ) ;
pos = vert . position ;
2017-11-07 04:44:20 +08:00
// Wrap re-add surround frame
if ( pos . x < bounds . min . x ) {
2016-09-28 13:29:33 +08:00
hrefInfo . boxes . Add ( new Rect ( bounds . min , bounds . size ) ) ;
bounds = new Bounds ( pos , Vector3 . zero ) ;
}
2017-11-07 04:44:20 +08:00
else {
2016-09-28 13:29:33 +08:00
bounds . Encapsulate ( pos ) ; // Extended enclosed box
}
}
2017-11-07 04:44:20 +08:00
2016-09-28 13:29:33 +08:00
hrefInfo . boxes . Add ( new Rect ( bounds . min , bounds . size ) ) ;
}
2017-11-07 04:44:20 +08:00
// Update the quad images
updateQuad = true ;
2016-09-28 13:29:33 +08:00
}
/// <summary>
/// Hyperlink List
/// </summary>
private readonly List < HrefInfo > m_HrefInfos = new List < HrefInfo > ( ) ;
/// <summary>
/// Text Builder
/// </summary>
private static readonly StringBuilder s_TextBuilder = new StringBuilder ( ) ;
/// <summary>
/// Hyperlink Regular Expression
/// </summary>
private static readonly Regex s_HrefRegex =
new Regex ( @"<a href=([^>\n\s]+)>(.*?)(</a>)" , RegexOptions . Singleline ) ;
[Serializable]
public class HrefClickEvent : UnityEvent < string > { }
[SerializeField]
private HrefClickEvent m_OnHrefClick = new HrefClickEvent ( ) ;
/// <summary>
/// Hyperlink Click Event
/// </summary>
2017-11-07 04:44:20 +08:00
public HrefClickEvent onHrefClick {
2016-09-28 13:29:33 +08:00
get { return m_OnHrefClick ; }
set { m_OnHrefClick = value ; }
}
/// <summary>
/// Finally, the output text hyperlinks get parsed
/// </summary>
/// <returns></returns>
2017-11-07 04:44:20 +08:00
protected string GetOutputText ( ) {
2016-09-28 13:29:33 +08:00
s_TextBuilder . Length = 0 ;
2016-11-25 01:48:51 +08:00
2016-09-28 13:29:33 +08:00
var indexText = 0 ;
fixedString = this . text ;
2017-11-07 04:44:20 +08:00
if ( inspectorIconList ! = null & & inspectorIconList . Length > 0 ) {
foreach ( IconName icon in inspectorIconList ) {
if ( ! string . IsNullOrEmpty ( icon . name ) ) {
2016-09-28 13:29:33 +08:00
fixedString = fixedString . Replace ( icon . name , "<quad name=" + icon . name + " size=" + fontSize + " width=1 />" ) ;
}
}
}
2017-11-07 04:44:20 +08:00
2016-09-28 14:56:06 +08:00
int count = 0 ;
2017-11-07 04:44:20 +08:00
foreach ( Match match in s_HrefRegex . Matches ( fixedString ) ) {
2016-09-28 13:29:33 +08:00
s_TextBuilder . Append ( fixedString . Substring ( indexText , match . Index - indexText ) ) ;
s_TextBuilder . Append ( "<color=" + hyperlinkColor + ">" ) ; // Hyperlink color
var group = match . Groups [ 1 ] ;
2017-11-07 04:44:20 +08:00
if ( isCreating_m_HrefInfos ) {
2016-09-28 14:56:06 +08:00
var hrefInfo = new HrefInfo
2017-11-07 04:44:20 +08:00
2016-09-28 14:56:06 +08:00
{
startIndex = s_TextBuilder . Length * 4 , // Hyperlinks in text starting vertex indices
endIndex = ( s_TextBuilder . Length + match . Groups [ 2 ] . Length - 1 ) * 4 + 3 ,
name = group . Value
} ;
2017-11-07 04:44:20 +08:00
2016-09-28 14:56:06 +08:00
m_HrefInfos . Add ( hrefInfo ) ;
2017-11-07 04:44:20 +08:00
}
else {
2016-11-25 01:48:51 +08:00
if ( m_HrefInfos . Count > 0 ) {
2016-09-28 14:56:06 +08:00
m_HrefInfos [ count ] . startIndex = s_TextBuilder . Length * 4 ; // Hyperlinks in text starting vertex indices;
m_HrefInfos [ count ] . endIndex = ( s_TextBuilder . Length + match . Groups [ 2 ] . Length - 1 ) * 4 + 3 ;
count + + ;
}
}
2016-09-27 20:38:44 +08:00
2016-09-28 13:29:33 +08:00
s_TextBuilder . Append ( match . Groups [ 2 ] . Value ) ;
s_TextBuilder . Append ( "</color>" ) ;
indexText = match . Index + match . Length ;
}
2017-11-07 04:44:20 +08:00
2016-09-28 14:56:06 +08:00
// we should create array only once or if there is any change in the text
2017-11-07 04:44:20 +08:00
if ( isCreating_m_HrefInfos )
2016-09-28 14:56:06 +08:00
isCreating_m_HrefInfos = false ;
2016-11-25 01:48:51 +08:00
2016-09-28 13:29:33 +08:00
s_TextBuilder . Append ( fixedString . Substring ( indexText , fixedString . Length - indexText ) ) ;
2016-09-27 20:38:44 +08:00
2017-11-08 05:13:24 +08:00
m_OutputText = s_TextBuilder . ToString ( ) ;
m_ImagesVertexIndex . Clear ( ) ;
MatchCollection matches = s_Regex . Matches ( m_OutputText ) ;
if ( matches ! = null & & matches . Count > 0 ) {
foreach ( Match match in matches ) {
var picIndex = match . Index ;
var endIndex = picIndex * 4 + 3 ;
m_ImagesVertexIndex . Add ( endIndex ) ;
}
}
return m_OutputText ;
2016-09-28 13:29:33 +08:00
}
2016-09-27 20:38:44 +08:00
2016-09-28 13:29:33 +08:00
/// <summary>
/// Click event is detected whether to click a hyperlink text
/// </summary>
/// <param name="eventData"></param>
2017-11-07 04:44:20 +08:00
public void OnPointerClick ( PointerEventData eventData ) {
2016-09-28 13:29:33 +08:00
Vector2 lp ;
RectTransformUtility . ScreenPointToLocalPointInRectangle (
rectTransform , eventData . position , eventData . pressEventCamera , out lp ) ;
2017-11-07 04:44:20 +08:00
foreach ( var hrefInfo in m_HrefInfos ) {
2016-09-28 13:29:33 +08:00
var boxes = hrefInfo . boxes ;
2017-11-07 04:44:20 +08:00
for ( var i = 0 ; i < boxes . Count ; + + i ) {
if ( boxes [ i ] . Contains ( lp ) ) {
2016-09-28 13:29:33 +08:00
m_OnHrefClick . Invoke ( hrefInfo . name ) ;
return ;
}
}
}
}
2017-11-07 04:44:20 +08:00
public void OnPointerEnter ( PointerEventData eventData ) {
if ( m_ImagesPool . Count > = 1 ) {
foreach ( Image img in m_ImagesPool ) {
if ( button ! = null & & button . isActiveAndEnabled ) {
img . color = button . colors . highlightedColor ;
2016-09-28 13:29:33 +08:00
}
}
}
}
2017-11-07 04:44:20 +08:00
public void OnPointerExit ( PointerEventData eventData ) {
if ( m_ImagesPool . Count > = 1 ) {
foreach ( Image img in m_ImagesPool ) {
if ( button ! = null & & button . isActiveAndEnabled ) {
img . color = button . colors . normalColor ;
2016-09-28 13:29:33 +08:00
}
2017-11-07 04:44:20 +08:00
else {
2016-09-28 13:29:33 +08:00
img . color = color ;
}
}
}
}
2017-11-07 04:44:20 +08:00
public void OnSelect ( BaseEventData eventData ) {
if ( m_ImagesPool . Count > = 1 ) {
foreach ( Image img in m_ImagesPool ) {
if ( button ! = null & & button . isActiveAndEnabled ) {
img . color = button . colors . highlightedColor ;
2016-09-28 13:29:33 +08:00
}
}
}
}
2017-11-07 04:44:20 +08:00
public void OnDeselect ( BaseEventData eventData ) {
2017-07-10 04:10:42 +08:00
2017-11-07 04:44:20 +08:00
if ( m_ImagesPool . Count > = 1 ) {
foreach ( Image img in m_ImagesPool ) {
if ( button ! = null & & button . isActiveAndEnabled ) {
img . color = button . colors . normalColor ;
}
}
}
}
2017-07-10 04:10:42 +08:00
2016-09-28 13:29:33 +08:00
/// <summary>
/// Hyperlinks Info
/// </summary>
2017-11-07 04:44:20 +08:00
private class HrefInfo {
2016-09-28 13:29:33 +08:00
public int startIndex ;
public int endIndex ;
public string name ;
public readonly List < Rect > boxes = new List < Rect > ( ) ;
}
2016-11-25 01:48:51 +08:00
2017-11-07 04:44:20 +08:00
void LateUpdate ( ) {
// Reset the hrefs if text is changed
if ( previousText ! = text ) {
Reset_m_HrefInfos ( ) ;
// Update the quad on text change
updateQuad = true ;
}
// Need to lock to remove images properly
2016-11-25 01:48:51 +08:00
lock ( thisLock ) {
2017-11-07 04:44:20 +08:00
// Can only update the images when it is not in a rebuild, this prevents the error
if ( updateQuad ) {
UpdateQuadImage ( ) ;
updateQuad = false ;
}
// Destroy any images that are not in use
2016-11-25 01:48:51 +08:00
if ( clearImages ) {
2017-11-07 04:44:20 +08:00
for ( int i = 0 ; i < culled_ImagesPool . Count ; i + + ) {
2016-11-25 01:48:51 +08:00
DestroyImmediate ( culled_ImagesPool [ i ] ) ;
}
2017-11-07 04:44:20 +08:00
2016-11-25 01:48:51 +08:00
culled_ImagesPool . Clear ( ) ;
2017-11-07 04:44:20 +08:00
2016-11-25 01:48:51 +08:00
clearImages = false ;
}
}
2016-09-28 14:56:06 +08:00
}
2016-11-25 01:48:51 +08:00
2016-09-28 14:56:06 +08:00
// Reseting m_HrefInfos array if there is any change in text
2016-11-25 01:48:51 +08:00
void Reset_m_HrefInfos ( ) {
2016-09-28 14:56:06 +08:00
previousText = text ;
m_HrefInfos . Clear ( ) ;
isCreating_m_HrefInfos = true ;
2016-09-28 13:29:33 +08:00
}
2017-11-07 04:44:20 +08:00
protected override void OnEnable ( ) {
base . OnEnable ( ) ;
// Enable images on TextPic disable
if ( m_ImagesPool . Count > = 1 ) {
for ( int i = 0 ; i < m_ImagesPool . Count ; i + + ) {
if ( m_ImagesPool [ i ] ! = null ) {
m_ImagesPool [ i ] . enabled = true ;
}
}
}
// Update the quads on re-enable
updateQuad = true ;
}
protected override void OnDisable ( ) {
base . OnDisable ( ) ;
// Disable images on TextPic disable
if ( m_ImagesPool . Count > = 1 ) {
for ( int i = 0 ; i < m_ImagesPool . Count ; i + + ) {
2019-03-02 08:23:06 +08:00
if ( m_ImagesPool [ i ] ! = null ) {
2017-11-07 04:44:20 +08:00
m_ImagesPool [ i ] . enabled = false ;
}
}
}
}
2016-09-28 13:29:33 +08:00
}
2016-07-15 01:48:18 +08:00
}