diff options
Diffstat (limited to 'Runtime/IMGUI')
32 files changed, 7077 insertions, 0 deletions
diff --git a/Runtime/IMGUI/GUIButton.cpp b/Runtime/IMGUI/GUIButton.cpp new file mode 100644 index 0000000..d83462b --- /dev/null +++ b/Runtime/IMGUI/GUIButton.cpp @@ -0,0 +1,77 @@ +#include "UnityPrefix.h" +#include "Runtime/IMGUI/GUIButton.h" +#include "Runtime/IMGUI/GUIStyle.h" +#include "Runtime/IMGUI/GUIState.h" +#include "Runtime/IMGUI/IMGUIUtils.h" + +static const int kGUIButtonHash = 2001146706; + +namespace IMGUI +{ + +bool GUIButton (GUIState &state, const Rectf &position, GUIContent &content, GUIStyle &style, int id) +{ + InputEvent &evt (*state.m_CurrentEvent); + switch (GetEventTypeForControl (state, evt, id)) + { + case InputEvent::kMouseDown: + // If the mouse is inside the button, we say that we're the hot control +#if ENABLE_NEW_EVENT_SYSTEM + if (position.Contains (evt.touch.pos)) +#else + if (position.Contains (evt.mousePosition)) +#endif + { + GrabMouseControl (state, id); + evt.Use (); + } + break; + case InputEvent::kMouseUp: + if (HasMouseControl (state, id)) + { + ReleaseMouseControl (state); + + // If we got the mousedown, the mouseup is ours as well + // (no matter if the click was in the button or not) + evt.Use (); + + // toggle the passed-in value if the mouse was over the button & return true +#if ENABLE_NEW_EVENT_SYSTEM + if (position.Contains (evt.touch.pos)) +#else + if (position.Contains (evt.mousePosition)) +#endif + { + state.m_OnGUIState.m_Changed = true; + return true; + } + } + break; + case InputEvent::kKeyDown: + if (evt.character == 32 && state.m_MultiFrameGUIState.m_KeyboardControl == id) + { + evt.Use (); + state.m_OnGUIState.m_Changed = true; + return true; + } + break; + case InputEvent::kMouseDrag: + if (HasMouseControl (state, id)) + evt.Use (); + break; + + case InputEvent::kRepaint: + style.Draw (state, position, content, id, false); + break; + } + return false; +} + +bool GUIButton (GUIState &state, const Rectf &position, GUIContent &content, GUIStyle &style) +{ + int id = GetControlID (state, kGUIButtonHash, kNative, position); + return GUIButton (state, position, content, style, id); +} + + +} diff --git a/Runtime/IMGUI/GUIButton.h b/Runtime/IMGUI/GUIButton.h new file mode 100644 index 0000000..29b2404 --- /dev/null +++ b/Runtime/IMGUI/GUIButton.h @@ -0,0 +1,16 @@ +#ifndef GUIBUTTON_H +#define GUIBUTTON_H + +#include "Runtime/Math/Rect.h" + +struct GUIState; +struct GUIContent; +class GUIStyle; + +namespace IMGUI +{ + bool GUIButton (GUIState &state, const Rectf &position, GUIContent &content, GUIStyle &style, int id); + bool GUIButton (GUIState &state, const Rectf &position, GUIContent &content, GUIStyle &style); +} + +#endif diff --git a/Runtime/IMGUI/GUIClip.cpp b/Runtime/IMGUI/GUIClip.cpp new file mode 100644 index 0000000..203c1e9 --- /dev/null +++ b/Runtime/IMGUI/GUIClip.cpp @@ -0,0 +1,419 @@ +#include "UnityPrefix.h" +#include "Runtime/IMGUI/GUIClip.h" +#include "Runtime/Graphics/ScreenManager.h" +#include "Runtime/Camera/CameraUtil.h" +#include "Runtime/IMGUI/GUIStyle.h" +#include "Runtime/Graphics/RenderTexture.h" +#include "Runtime/Camera/RenderManager.h" +#include "Runtime/Math/Quaternion.h" + +static const float ko1 = -10000, ko2 = 10000, ko3= 0, ko4 = 0; + +GUIClipState::GUIClipState() +{ + m_Enabled = 0; +} + +GUIClipState::~GUIClipState () +{ +} + + +/// Push a clip rect to the stack with pixel offsets. +/// This is the low-level function for doing clipping rectangles. Unless you're working with embedded +/// temporary render buffers, this is most likely not what you want. +/// /absoluteRect/ is the absolute device coordinates this element will be mapped to. +/// /scrollOffset/ is a scrolling offset to apply. +/// /renderOffset/ is the rendering offset of the absoluteRect from source to destination. Used to map from an on-screen rectangle to a +/// destination inside a render buffer. +void GUIClipState::Push (InputEvent& event, const Rectf& screenRect, Vector2f scrollOffset, const Vector2f& renderOffset, bool resetOffset) +{ + if (m_GUIClips.empty()) + { + ErrorString("GUIClip pushing empty stack not allowed."); + return; + } + + GUIClip& topmost = m_GUIClips.back (); + // build absolute offsets by adding parent's position & scroll to the screenRect's positions + float physicalxMin = screenRect.x + topmost.physicalRect.x + topmost.scrollOffset.x; + float physicalxMax = screenRect.GetXMax() + topmost.physicalRect.x + topmost.scrollOffset.x; + float physicalyMin = screenRect.y + topmost.physicalRect.y + topmost.scrollOffset.y; + float physicalyMax = screenRect.GetYMax() + topmost.physicalRect.y + topmost.scrollOffset.y; + + // If the user tries to push a GUIClip with an xMin that goes outside the parent's clipping, we cannot allow that + // so we move the xMin in and use scrollOffset to hide this. + if (physicalxMin < topmost.physicalRect.x) + { + scrollOffset.x += physicalxMin - topmost.physicalRect.x; + physicalxMin = topmost.physicalRect.x; + } + + // Clip top side (yMin) to the parent as well. + if (physicalyMin < topmost.physicalRect.y) + { + scrollOffset.y += physicalyMin - topmost.physicalRect.y; + physicalyMin = topmost.physicalRect.y; + } + + // Clip right side (xMax) as well. + if (physicalxMax > topmost.physicalRect.GetXMax()) + { + physicalxMax = topmost.physicalRect.GetXMax(); + } + + // Clip bottom side (yMax) as well. + if (physicalyMax > topmost.physicalRect.GetYMax()) + { + physicalyMax = topmost.physicalRect.GetYMax(); + } + + // if the new GUIClip is completely outside parent, sizes can get negative. + // We just make them be 0, so no rendering is performed. + if (physicalxMax <= physicalxMin) + physicalxMax = physicalxMin; + if (physicalyMax <= physicalyMin) + physicalyMax = physicalyMin; + + // Build the rect straight away + Rectf absoluteRect = MinMaxRect (physicalxMin, physicalyMin, physicalxMax, physicalyMax); + + if (!resetOffset) + { + // Maintian global render offset + m_GUIClips.push_back(GUIClip (screenRect, absoluteRect, scrollOffset, topmost.renderOffset + renderOffset, topmost.globalScrollOffset + scrollOffset)); + } + else + { + // Maintian global scroll offset + m_GUIClips.push_back (GUIClip (screenRect, absoluteRect, scrollOffset, + Vector2f(absoluteRect.x + scrollOffset.x + renderOffset.x, absoluteRect.y + scrollOffset.y + renderOffset.y), + topmost.globalScrollOffset + scrollOffset)); + } + + Apply(event, m_GUIClips.back()); +} + +/// Removes the topmost clipping rectangle, undoing the effect of the latest GUIClip.Push +void GUIClipState::Pop (InputEvent& event) +{ + if (m_GUIClips.size() < 2) + { + ErrorString("Invalid GUIClip stack popping"); + return; + } + + m_GUIClips.pop_back(); + Apply(event, m_GUIClips.back()); +} + +Vector2f GUIClipState::Unclip (const Vector2f& pos) +{ + if (!m_GUIClips.empty()) + { + GUIClip& topmost = m_GUIClips.back(); + Vector3f res; + m_Matrix.PerspectiveMultiplyPoint3 (Vector3f (pos.x, pos.y, 0.0F), res); + return Vector2f(res.x, res.y) + topmost.scrollOffset + Vector2f (topmost.physicalRect.x, topmost.physicalRect.y); + } + else + { + return Vector2f (0,0); + } +} + +Rectf GUIClipState::Unclip (const Rectf& rect) +{ + if (!m_GUIClips.empty()) + { + GUIClip& topmost = m_GUIClips.back(); + + return Rectf (rect.x + topmost.scrollOffset.x + topmost.physicalRect.x, + rect.y + topmost.scrollOffset.y + topmost.physicalRect.y, + rect.width, rect.height); + } + else + { + return Rectf (0,0,0,0); + } +} + +/// Clips /absolutePos/ to drawing coordinates +/// Used for reconverting values calculated from ::ref::Unclip +Vector2f GUIClipState::Clip (const Vector2f& absolutePos) +{ + if (!m_GUIClips.empty()) + { + GUIClip& topmost = m_GUIClips.back(); + + Vector3f transformedPoint; + m_InverseMatrix.PerspectiveMultiplyPoint3(Vector3f(absolutePos.x, absolutePos.y, 0.0F), transformedPoint); + + // return (Vector2)s_InverseMatrix.MultiplyPoint (absolutePos) - topmost.globalScrollOffset - new Vector2 (topmost.physicalRect.x, topmost.physicalRect.y); + Vector2f res = Vector2f(transformedPoint.x, transformedPoint.y) - topmost.scrollOffset - Vector2f (topmost.physicalRect.x, topmost.physicalRect.y); + return Vector2f(res.x, res.y); + } + else + { + return Vector2f (0,0); + } +} + +/// Convert /absoluteRect/ to drawing coordinates +/// Used for reconverting values calculated from ::ref::Unclip +Rectf GUIClipState::Clip (const Rectf& absoluteRect) +{ + if (!m_GUIClips.empty()) + { + GUIClip& topmost = m_GUIClips.back(); + + return Rectf (absoluteRect.x - topmost.globalScrollOffset.x - topmost.physicalRect.x, + absoluteRect.y - topmost.globalScrollOffset.y - topmost.physicalRect.y, + absoluteRect.width, absoluteRect.height); + } + else + { + return Rectf (0,0,0,0); + } +} + +// Return the rect for the topmost clip in screen space +Rectf GUIClipState::GetTopRect () +{ + if (!m_GUIClips.empty()) + { + GUIClip& topmost = m_GUIClips.back(); + return topmost.screenRect; + } + else + { + return Rectf (0,0,0,0); + } +} + + +/// Reapply the clipping info. +/// Call this after switching render buffers. +void GUIClipState::Reapply (InputEvent& event) +{ + if (!m_GUIClips.empty()) + Apply (event, m_GUIClips.back()); +} + + +void GUIClipState::SetMatrix (InputEvent& event, const Matrix4x4f& m) +{ + m_Matrix = m; + + Matrix4x4f inverse; + bool success = Matrix4x4f::Invert_Full(m, inverse); + if (!success) + { + ErrorString ("Ignoring invalid matrix assinged to GUI.matrix - the matrix needs to be invertible. Did you scale by 0 on Z-axis?"); + return; + } + + m_Matrix = m; // Store the value + m_InverseMatrix = inverse; + Reapply (event); // Reapply the toplevel cliprect. +} + +/// constructor +GUIClip::GUIClip (const Rectf& iscreenRect, const Rectf& iphysicalRect, const Vector2f& iscrollOffset, const Vector2f& irenderOffset, const Vector2f& iglobalScrollOffset) +{ + // Debug.Log ("GUIClipping: " + physicalRect + scrollOffset + renderOffset + globalScrollOffset); + + screenRect = iscreenRect; + physicalRect = iphysicalRect; + scrollOffset = iscrollOffset; + renderOffset = irenderOffset; + globalScrollOffset = iglobalScrollOffset; +} + +// Recalculate the mouse values from the absolute screen position into local GUI coordinates, taking cliprects & all into account. +void GUIClipState::CalculateMouseValues (InputEvent& event) +{ + if (!m_GUIClips.empty()) + { +#if ENABLE_NEW_EVENT_SYSTEM + event.touch.pos = Clip (m_AbsoluteMousePosition); +#else + event.mousePosition = Clip (m_AbsoluteMousePosition); +#endif + + // Check if we're outside the cliprect & set the event ignore flag + Vector3f res; + m_InverseMatrix.PerspectiveMultiplyPoint3 (Vector3f(m_AbsoluteMousePosition.x, m_AbsoluteMousePosition.y, 0.0F), res); + + GUIClip& topmost = m_GUIClips.back(); + m_Enabled = topmost.physicalRect.Contains (res.x, res.y) ? -1 : 0; + + // scrollwheel is a specialcase + if (event.type != InputEvent::kScrollWheel) + { +#if ENABLE_NEW_EVENT_SYSTEM + event.touch.deltaPos = event.touch.pos - Clip (m_AbsoluteLastMousePosition); +#else + event.delta = event.mousePosition - Clip (m_AbsoluteLastMousePosition); +#endif + } + } +} + +/// Apply the current clip rect to OpenGL's viewport & scissor rects. +void GUIClipState::Apply (InputEvent& event, GUIClip &topmost) +{ + // Warp the current event to the correct place in the new coordinate space + + CalculateMouseValues (event); + + m_VisibleRect = Rectf (-topmost.scrollOffset.x, -topmost.scrollOffset.y, topmost.physicalRect.width, topmost.physicalRect.height); + + // From here on out, we're only setting up OpenGL, so abort if we're not repainting + if (event.type != InputEvent::kRepaint) + return; + + // Calculate the viewport (where we end up on screen) + Rectf r = topmost.physicalRect; + + if (r.width < 0) r.width = 0; + if (r.height < 0) r.height = 0; + + r.x -= topmost.renderOffset.x; + r.y -= topmost.renderOffset.y; + + + r.x = RoundfToInt(r.x); + r.y = RoundfToInt (r.y); + r.width = RoundfToIntPos (r.width); + r.height = RoundfToIntPos (r.height); + + Matrix4x4f viewportMatrix; + viewportMatrix.SetIdentity(); + + float width, height; + RenderTexture *rTex = RenderTexture::GetActive (); + if (rTex) + { + width = rTex->GetWidth (); + height = rTex->GetHeight (); + } + else + { + width = GetScreenManager ().GetWidth (); + height = GetScreenManager ().GetHeight (); + } + + Vector3f scaleFac = Vector3f (r.width / width, r.height / height,1); + Vector3f move; + m_Matrix.PerspectiveMultiplyPoint3 (Vector3f (r.x, r.y,0), move); + + ///@TODO: OPTIMIZE non rotation + viewportMatrix.SetTRS(Vector3f(move.x * scaleFac.x, move.y * scaleFac.y, 0.0F), Quaternionf::identity(), scaleFac); + + SetGLViewport (Rectf (0,0, width, height)); + + // The ortho bounds passed to LoadPixelMatrix gets multiplied by the matrix as well + // We need to counter that for the scroll offsets - so stuff like scale doesn't apply to scrolling + Vector3f sOffset; + m_Matrix.PerspectiveMultiplyPoint3 (Vector3f(-topmost.scrollOffset.x, -topmost.scrollOffset.y, 0.0F), sOffset); + sOffset.x *= scaleFac.x; + sOffset.y *= scaleFac.y; + + // Scale X & Y + // Upload the client coordinate system. + // Here we multiply in the scaleFac on the scrollOffsets - its something with Matrix ordering - a case of "iterative debugging": I kept chanigng it (at random) until it worked... + Matrix4x4f pixelMatrix; + MultiplyMatrices4x4 (&viewportMatrix, &m_Matrix, &pixelMatrix); + LoadPixelMatrix( + sOffset.x, Roundf (topmost.physicalRect.width) + sOffset.x, + Roundf (topmost.physicalRect.height) + sOffset.y, sOffset.y, + pixelMatrix + ); + GUIStyle::SetGUIClipRect(m_VisibleRect); +} + +/// Load the pixel matrix and also multiply in a GUIMatrix +void GUIClipState::LoadPixelMatrix(float left, float right, float bottom, float top, const Matrix4x4f& mat) +{ + Rectf rect( left, bottom, right-left, top-bottom ); + Matrix4x4f matrix; + CalcPixelMatrix (rect, matrix); + + // Important: apply half-texel offsets after multiplying the matrix! + matrix *= mat; + ApplyTexelOffsetsToPixelMatrix( true, matrix ); + + GfxDevice& device = GetGfxDevice(); + device.SetProjectionMatrix(matrix); + device.SetViewMatrix (Matrix4x4f::identity.GetPtr()); // implicitly sets world to identity +} + +Rectf GUIClipState::GetTopMostPhysicalRect () +{ + return m_GUIClips.back().physicalRect; +} + +//////@TODO: +// CSRAW public override string ToString () { +// return System.String.Format ("GUIClip: physicalRect {0}, scrollOffset {1}, renderOffset {2}, globalScrollOffset{3}", physicalRect, scrollOffset, renderOffset, globalScrollOffset); +// } + +/// Set up the clip rect to contain the entire display. +/// called by GUI.Begin to initialize the view. +void GUIClipState::BeginOnGUI (InputEvent& event) +{ +#if ENABLE_NEW_EVENT_SYSTEM + m_AbsoluteMousePosition = event.touch.pos; + m_AbsoluteLastMousePosition = m_AbsoluteMousePosition - event.touch.deltaPos; +#else + m_AbsoluteMousePosition = event.mousePosition; + m_AbsoluteLastMousePosition = m_AbsoluteMousePosition - event.delta; +#endif + m_Matrix.SetIdentity(); + m_InverseMatrix.SetIdentity(); + + m_GUIClips.resize (0); + // Push in a really large screen, so GUI.matrix scales doesn't crop the root level. + m_GUIClips.push_back (GUIClip (Rectf (ko1, ko1, 40000,40000), Rectf (ko1, ko1, 40000,40000), Vector2f (ko2, ko2), Vector2f (ko3,ko3), Vector2f (ko4,ko4))); + + Apply (event, m_GUIClips.back()); +} + +void GUIClipState::SetAbsoluteMousePosition (const Vector2f& val) +{ + m_AbsoluteMousePosition = val; +} + +/// *End the current GUI stuff. +void GUIClipState::EndOnGUI (InputEvent &event) +{ + InputEvent::Type eventType = event.type; + if (m_GUIClips.size() != 1 && eventType != InputEvent::kIgnore && eventType != InputEvent::kUsed) + { + if (m_GUIClips.size() > 1) + { + ErrorString ("GUI Error: You are pushing more GUIClips than you are popping. Make sure they are balanced)"); + } + else + { + ErrorString ("GUI Error: You are popping more GUIClips than you are pushing. Make sure they are balanced)"); + return; + } + } + m_GUIClips.pop_back(); + // Make sure we restore the mouse values. Otherwise things like GUi.Matrix's modifications to + // MouseEvents will seep into the next OnGUI +#if ENABLE_NEW_EVENT_SYSTEM + event.touch.deltaPos = m_AbsoluteMousePosition - m_AbsoluteLastMousePosition; + event.touch.pos = m_AbsoluteMousePosition; +#else + event.delta = m_AbsoluteMousePosition - m_AbsoluteLastMousePosition; + event.mousePosition = m_AbsoluteMousePosition; +#endif +} + +void GUIClipState::EndThroughException () +{ + m_GUIClips.resize(0); +} diff --git a/Runtime/IMGUI/GUIClip.h b/Runtime/IMGUI/GUIClip.h new file mode 100644 index 0000000..3ef5e05 --- /dev/null +++ b/Runtime/IMGUI/GUIClip.h @@ -0,0 +1,107 @@ +#pragma once + +#include "Runtime/Math/Matrix4x4.h" +#include "Runtime/Math/Vector2.h" +#include "Runtime/Math/Vector3.h" +#include "Runtime/Math/Rect.h" +#include "Runtime/Misc/InputEvent.h" + +#include "Runtime/GfxDevice/GfxDevice.h" + +class GUIClip +{ + public: + + GUIClip () {} + + + /// The rectangle of this clipping rect. + /// This is stored absolute coordinates + Rectf physicalRect; + + // The rectangle of the clip in screen space + Rectf screenRect; + + /// physical scrolling offset for coordinates - this is relative to parent. + Vector2f scrollOffset; + /// This is absolute + Vector2f globalScrollOffset; + + /// rendering offset. This is the global GUIClip->buffer coordinates + Vector2f renderOffset; + + /// constructor + GUIClip (const Rectf& iscreenRect, const Rectf& iphysicalRect, const Vector2f& iscrollOffset, const Vector2f& irenderOffset, const Vector2f& iglobalScrollOffset); +}; + +// MUST BYTE-MATCH CORRESPONDING STRUCT IN GUISTATEMONO +struct GUIClipState +{ + typedef std::vector<GUIClip> ClipStack; + ClipStack m_GUIClips; + + private: + Matrix4x4f m_Matrix; + Matrix4x4f m_InverseMatrix; + + // Where is the mouse onscreen, and where was it last frame (so we can calculate deltas correctly) + Vector2f m_AbsoluteMousePosition; + Vector2f m_AbsoluteLastMousePosition; + + Rectf m_VisibleRect; + + // Should we disable events? + int m_Enabled; + + public: + GUIClipState (); + ~GUIClipState (); + + Rectf GetTopMostPhysicalRect (); + + /// Set up the clip rect to contain the entire display. + /// called by GUI.Begin to initialize the view. + void BeginOnGUI (InputEvent& ievent); + void EndOnGUI (InputEvent& ievent); + void EndThroughException (); + + /// This is the low-level function for doing clipping rectangles. Unless you're working with embedded temporary render buffers, this is most likely not what you want. + /// /absoluteRect/ is the absolute device coordinates this element will be mapped to. + /// /scrollOffset/ is a scrolling offset to apply. + /// /renderOffset/ is the rendering offset of the absoluteRect from source to destination. Used to map from an on-screen rectangle to a destination inside a render buffer. + void Push (InputEvent& ievent, const Rectf& screenRect, Vector2f scrollOffset, const Vector2f& renderOffset, bool resetOffset); + /// Removes the topmost clipping rectangle, undoing the effect of the latest GUIClip.Push + void Pop (InputEvent& ievent); + + /// Unclips /pos/ to physical device coordinates. + Vector2f Unclip (const Vector2f& pos); + Rectf Unclip (const Rectf& rect); + + /// Clips /absolutePos/ to drawing coordinates + Vector2f Clip (const Vector2f& absolutePos); + Rectf Clip (const Rectf& absoluteRect); + + // Return the rect for the topmost clip in screen space + Rectf GetTopRect(); + + + /// Reapply the clipping info. Call this after switching render buffers. + void Reapply (InputEvent& ievent); + // Set the GUIMatrix. This is here as this class handles all coordinate transforms anyways. + const Matrix4x4f& GetMatrix () { return m_Matrix; } + void SetMatrix (InputEvent& ievent, const Matrix4x4f& m); + + Vector2f GetAbsoluteMousePosition () { return m_AbsoluteMousePosition; } + Rectf GetVisibleRect () { return m_VisibleRect; } + + bool GetEnabled () const { return m_Enabled; } + private: + // Recalculate the mouse values from the absolute screen position into local GUI coordinates, taking cliprects & all into account. + void CalculateMouseValues (InputEvent& ievent); + static void LoadPixelMatrix(float left, float right, float bottom, float top, const Matrix4x4f& mat); + void SetAbsoluteMousePosition (const Vector2f& absoluteMousePosition); + + + // Apply the current clip rect to event pointer positions & render settings for culling, etc. + void Apply (InputEvent& ievent, GUIClip &topmost); +}; diff --git a/Runtime/IMGUI/GUIContent.cpp b/Runtime/IMGUI/GUIContent.cpp new file mode 100644 index 0000000..6b811c7 --- /dev/null +++ b/Runtime/IMGUI/GUIContent.cpp @@ -0,0 +1,58 @@ +#include "UnityPrefix.h" +#include "Runtime/IMGUI/GUIContent.h" +#include "Runtime/Scripting/ScriptingUtility.h" + +#if ENABLE_SCRIPTING +static GUIContent s_TempGUIContent; +#if UNITY_WINRT +void MonoGUIContentToTempNativeCallback(Platform::String^ text, Platform::String^ tooltip, long long image) +{ + s_TempGUIContent.m_Text.BorrowString (text); + s_TempGUIContent.m_Tooltip.BorrowString (tooltip); + s_TempGUIContent.m_Image = ScriptingObjectToObject<Texture> (ScriptingObjectPtr(image)); +} +BridgeInterface::ScriptingGUIContentToTempNativeDelegateGC^ GetMonoGUIContentToTempNativeCallback() +{ + static BridgeInterface::ScriptingGUIContentToTempNativeDelegateGC^ s_Callback = ref new BridgeInterface::ScriptingGUIContentToTempNativeDelegateGC(MonoGUIContentToTempNativeCallback); + return s_Callback; +} + +#endif + +GUIContent &MonoGUIContentToTempNative (ScriptingObjectPtr scriptingContent) +{ + MonoGUIContentToNative (scriptingContent, s_TempGUIContent); + return s_TempGUIContent; +} + +void MonoGUIContentToNative (ScriptingObjectPtr scriptingContent, GUIContent& cppContent) +{ + if (scriptingContent == SCRIPTING_NULL) + { + WarningString("GUIContent is null. Use GUIContent.none."); + cppContent.m_Text = (UTF16String) ""; + cppContent.m_Tooltip = (UTF16String) ""; + cppContent.m_Image = NULL; + + return; + } + +#if UNITY_WINRT + GetWinRTUtils()->ScriptingGUIContentToTempNativeGC(scriptingContent.GetHandle(), GetMonoGUIContentToTempNativeCallback()); +#else + MonoGUIContent nativeContent; + MarshallManagedStructIntoNative(scriptingContent, &nativeContent); + +# if ENABLE_MONO + cppContent.m_Text.BorrowString (nativeContent.m_Text); + cppContent.m_Tooltip.BorrowString (nativeContent.m_Tooltip); +# elif UNITY_FLASH + Ext_WriteTextAndToolTipIntoUTF16Strings(scriptingContent,&cppContent.m_Text,&cppContent.m_Tooltip); +# endif + + cppContent.m_Image = ScriptingObjectToObject<Texture> (nativeContent.m_Image); +#endif + return; +} + +#endif diff --git a/Runtime/IMGUI/GUIContent.h b/Runtime/IMGUI/GUIContent.h new file mode 100644 index 0000000..5570bf8 --- /dev/null +++ b/Runtime/IMGUI/GUIContent.h @@ -0,0 +1,36 @@ +#ifndef GUIContent_H +#define GUIContent_H + +#include "Runtime/IMGUI/TextUtil.h" +#include "Runtime/Graphics/Texture.h" +#include "Runtime/Utilities/dynamic_array.h" +#include "Runtime/Misc/UTF8.h" + +struct GUIContent +{ + UTF16String m_Text; + UTF16String m_Tooltip; + PPtr<Texture> m_Image; + + void operator = (const GUIContent& other) + { + m_Text.CopyString(other.m_Text); + m_Tooltip.CopyString(other.m_Tooltip); + m_Image = other.m_Image; + } +}; + +#if ENABLE_SCRIPTING + +struct MonoGUIContent +{ + ScriptingStringPtr m_Text; + ScriptingObjectPtr m_Image; + ScriptingStringPtr m_Tooltip; +}; + +GUIContent &MonoGUIContentToTempNative (ScriptingObjectPtr monoContent); +void MonoGUIContentToNative (ScriptingObjectPtr monoContent, GUIContent& cppContent); +#endif + +#endif diff --git a/Runtime/IMGUI/GUIContentTests.cpp b/Runtime/IMGUI/GUIContentTests.cpp new file mode 100644 index 0000000..4f45a64 --- /dev/null +++ b/Runtime/IMGUI/GUIContentTests.cpp @@ -0,0 +1,28 @@ +#include "UnityPrefix.h" + +#if ENABLE_UNIT_TESTS + +#include "External/UnitTest++/src/UnitTest++.h" +#include "Runtime/IMGUI/GUIContent.h" +#include "Runtime/Testing/TestFixtures.h" +#include "Runtime/Testing/Testing.h" + +SUITE (GUIContentTests) +{ + typedef TestFixtureBase Fixture; + + TEST_FIXTURE (Fixture, NullContentDoesNotCrash) + { + //Set expectations. + EXPECT (Warning, "GUIContent is null. Use GUIContent.none."); + + // Do. + GUIContent content = MonoGUIContentToTempNative (SCRIPTING_NULL); + + // Assert. + CHECK ((UTF16String)"" == content.m_Text); + CHECK ((UTF16String)"" == content.m_Tooltip); + CHECK (content.m_Image.IsNull()); + } +} +#endif diff --git a/Runtime/IMGUI/GUILabel.cpp b/Runtime/IMGUI/GUILabel.cpp new file mode 100644 index 0000000..a483a27 --- /dev/null +++ b/Runtime/IMGUI/GUILabel.cpp @@ -0,0 +1,29 @@ +#include "UnityPrefix.h" +#include "Runtime/IMGUI/GUIButton.h" +#include "Runtime/IMGUI/GUIStyle.h" +#include "Runtime/IMGUI/GUIState.h" +#include "Runtime/IMGUI/IMGUIUtils.h" + +namespace IMGUI +{ +void GUILabel (GUIState &state, const Rectf &position, GUIContent &content, GUIStyle &style) +{ + InputEvent &evt (*state.m_CurrentEvent); + + if (evt.type == InputEvent::kRepaint) + { + style.Draw (state, position, content, false, false, false, false); + + // Is inside label AND inside guiclip visible rect (prevents tooltips on labels that are clipped) +#if ENABLE_NEW_EVENT_SYSTEM + if (content.m_Tooltip.length != 0 && position.Contains (evt.touch.pos) && + state.m_CanvasGUIState.m_GUIClipState.GetVisibleRect().Contains(evt.touch.pos)) +#else + if (content.m_Tooltip.length != 0 && position.Contains (evt.mousePosition) && + state.m_CanvasGUIState.m_GUIClipState.GetVisibleRect().Contains(evt.mousePosition)) +#endif + GUIStyle::SetMouseTooltip (state, content.m_Tooltip, position); + } +} + +} // namespace diff --git a/Runtime/IMGUI/GUILabel.h b/Runtime/IMGUI/GUILabel.h new file mode 100644 index 0000000..61b8d56 --- /dev/null +++ b/Runtime/IMGUI/GUILabel.h @@ -0,0 +1,15 @@ +#ifndef GUILABEL_H +#define GUILABEL_H + +#include "Runtime/Math/Rect.h" + +struct GUIState; +struct GUIContent; +class GUIStyle; + +namespace IMGUI +{ + void GUILabel (GUIState &state, const Rectf &position, GUIContent &content, GUIStyle &style); +} + +#endif diff --git a/Runtime/IMGUI/GUIManager.cpp b/Runtime/IMGUI/GUIManager.cpp new file mode 100644 index 0000000..6270421 --- /dev/null +++ b/Runtime/IMGUI/GUIManager.cpp @@ -0,0 +1,571 @@ +#include "UnityPrefix.h" +#include "Configuration/UnityConfigure.h" +#include "GUIManager.h" +#include "Runtime/Graphics/ScreenManager.h" +#include "Runtime/Input/InputManager.h" +#include "Runtime/Mono/MonoManager.h" +#include "Runtime/Math/Rect.h" +#include "Runtime/Camera/RenderManager.h" +#include "Runtime/Input/TimeManager.h" +#include "Runtime/IMGUI/GUIState.h" +#include "Runtime/IMGUI/GUIWindows.h" +#include "Runtime/Profiler/Profiler.h" +#include "Runtime/GfxDevice/GfxDevice.h" +#include "Runtime/Utilities/UserAuthorizationManager.h" +#include "Runtime/Scripting/ScriptingUtility.h" +#include "Runtime/Scripting/Backend/ScriptingInvocation.h" + +#if SUPPORT_REPRODUCE_LOG +#include <fstream> +#include "Runtime/Misc/ReproductionLog.h" +#endif + +#if ENABLE_UNITYGUI +static GUIManager* s_GUIManager = NULL; + +void InitGUIManager () +{ + AssertIf(s_GUIManager != NULL); + s_GUIManager = new GUIManager(); + + InitGUIState (); + +#if UNITY_HAS_DEVELOPER_CONSOLE + InitializeDeveloperConsole (); +#endif // UNITY_HAS_DEVELOPER_CONSOLE +} + +void CleanupGUIManager () +{ +#if UNITY_HAS_DEVELOPER_CONSOLE + CleanupDeveloperConsole(); +#endif // UNITY_HAS_DEVELOPER_CONSOLE + CleanupGUIState (); + + AssertIf(s_GUIManager == NULL); + delete s_GUIManager; + s_GUIManager = NULL; +} + +GUIManager &GetGUIManager () { + AssertIf(s_GUIManager == NULL); + return *s_GUIManager; +} + +#if UNITY_EDITOR +void GUIManager::Reset () +{ + m_MasterState.Reset (); +} +#endif + +GUIManager::GUIManager () : + m_GUIPixelOffset (0.0f,0.0f) +{ + m_LastInputEventTime = 0.0f; + m_DidGUIWindowsEatLastEvent = false; + m_MouseUsed = false; + m_mouseButtonsDown = 0; +} + +void GUIManager::AddGUIScript (MonoBehaviourListNode& beh) +{ + m_GUIScripts.push_back(beh); +} + +PROFILER_INFORMATION(gGUIRepaintProfile, "GUI.Repaint", kProfilerGUI) +PROFILER_INFORMATION(gGUIEventProfile, "GUI.ProcessEvents", kProfilerGUI) + +void GUIManager::Repaint () { + GetInputManager().SetTextFieldInput(false); + + InputEvent ie; + ie = m_LastEvent; + ie.type = InputEvent::kRepaint; + + DoGUIEvent(ie, false); +} + +bool GUIManager::AnyMouseButtonsDown() +{ + return GetGUIManager().m_mouseButtonsDown != 0; +} + +#if UNITY_EDITOR +void GUIManager::SetEditorGUIInfo (Vector2f guiPixelOffset) { + m_GUIPixelOffset = guiPixelOffset; + if (MONO_COMMON.setViewInfo) { + ScriptingInvocation invocation(MONO_COMMON.setViewInfo); + invocation.AddStruct(&m_GUIPixelOffset); + invocation.Invoke(); + } +} + +Vector2f GUIManager::GetEditorGUIInfo() const +{ + return m_GUIPixelOffset; +} +#endif + +void GUIManager::SendQueuedEvents () +{ + #if UNITY_EDITOR + // Inside editor: make the Game GUI behave like it does at runtime. + if (MONO_COMMON.setViewInfo) { + Vector2f screenPosition (0,0); + + ScriptingInvocation invocation(MONO_COMMON.setViewInfo); + invocation.AddStruct(&screenPosition); + invocation.Invoke(); + } + #endif + + while (!m_Events.empty()) + { + DoGUIEvent(m_Events.front(), true); + m_Events.pop_front(); + } +} + +bool GUIManager::GetDidGUIWindowsEatLastEvent () { + return GetGUIManager().m_DidGUIWindowsEatLastEvent; +} + +void GUIManager::SetDidGUIWindowsEatLastEvent (bool value) { + GetGUIManager().m_DidGUIWindowsEatLastEvent = value; +} + +struct OldSortScript : std::binary_function<GUIManager::SortedScript&, GUIManager::SortedScript&, std::size_t> +{ + bool operator () (GUIManager::SortedScript& lhs, GUIManager::SortedScript& rhs) const { return lhs.depth < rhs.depth; } +}; + +struct NewSortScript : std::binary_function<GUIManager::SortedScript&, GUIManager::SortedScript&, std::size_t> +{ + bool operator () (GUIManager::SortedScript& lhs, GUIManager::SortedScript& rhs) const { return lhs.depth > rhs.depth; } +}; + + + +static bool MonoBehaviourDoGUI(void* beh, MonoBehaviour::GUILayoutType layoutType, int skin) +{ + Assert( NULL != beh ); + return reinterpret_cast<MonoBehaviour *>(beh)->DoGUI(layoutType, skin); +} + +static ObjectGUIState& MonoBehaviourGetObjectGUIState(void* beh) +{ + Assert( NULL != beh ); + return reinterpret_cast<MonoBehaviour *>(beh)->GetObjectGUIState(); +} + +GUIManager::GUIObjectWrapper::GUIObjectWrapper(MonoBehaviour* beh) + : wrapped_ptr(beh) + , do_gui_func(&MonoBehaviourDoGUI) + , get_gui_state_func(&MonoBehaviourGetObjectGUIState) +{} + +#if UNITY_HAS_DEVELOPER_CONSOLE +static bool DeveloperConsoleDoGUI(void* dev, MonoBehaviour::GUILayoutType layoutType, int skin) +{ + Assert( NULL != dev ); + return reinterpret_cast<DeveloperConsole *>(dev)->DoGUI(); +} + +static ObjectGUIState& DeveloperConsoleGetObjectGUIState(void* dev) +{ + Assert( NULL != dev ); + return reinterpret_cast<DeveloperConsole *>(dev)->GetObjectGUIState(); +} + +GUIManager::GUIObjectWrapper::GUIObjectWrapper(DeveloperConsole* dev) + : wrapped_ptr(dev) + , do_gui_func(&DeveloperConsoleDoGUI) + , get_gui_state_func(&DeveloperConsoleGetObjectGUIState) +{} +#endif // UNITY_HAS_DEVELOPER_CONSOLE + + +void GUIManager::DoGUIEvent (InputEvent &eventToSend, bool frontToBack) +{ + #if ENABLE_PROFILER + ProfilerInformation* information = &gGUIEventProfile; + if (eventToSend.type == InputEvent::kRepaint) + information = &gGUIRepaintProfile; + + PROFILER_AUTO(*information, NULL) + #endif + + GUIState &state = GetGUIState(); + state.SetEvent (eventToSend); + InputEvent &e = *state.m_CurrentEvent; + + m_MasterState.LoadIntoGUIState (state); + state.BeginFrame (); + MonoBehaviour* authorizationDialog = GetUserAuthorizationManager().GetAuthorizationDialog(); + + // Update the lists of which scripts we _actually_ want to execute. +#if UNITY_HAS_DEVELOPER_CONSOLE + if (m_GUIScripts.empty() && authorizationDialog == NULL && !GetDeveloperConsole().IsVisible()) +#else + if (m_GUIScripts.empty() && authorizationDialog == NULL) +#endif // UNITY_HAS_DEVELOPER_CONSOLE + { + m_MouseUsed = false; + return; + } + + // Move the event mouse position away if the screen is locked. We don't want the cursor to interact + // with the GUI when it is in an arbitrary, fixed position. + if (GetScreenManager().GetLockCursor() +#if UNITY_METRO + && e.touchType == InputEvent::kMouseTouch +#endif + ) + { +#if ENABLE_NEW_EVENT_SYSTEM + e.touch.pos = Vector2f (-10000, -10000); +#else + e.mousePosition = Vector2f (-10000, -10000); +#endif + } + + // ok - first we send them the layout event and find out the layering + InputEvent::Type originalType = e.type; + + int handleTab = 0; + if (e.type == InputEvent::kKeyDown && (e.character == '\t' || e.character == 25)) + handleTab = ((e.modifiers & InputEvent::kShift) == 0) ? 1 : -1; + + // Execute all the no-layout scripts + bool eventUsed = false; + std::vector<GUIObjectWrapper> layoutedScripts; + if (authorizationDialog) + { + layoutedScripts.push_back (GUIObjectWrapper(authorizationDialog)); + } + else + { + layoutedScripts.reserve(m_GUIScripts.size_slow()); + SafeIterator<MonoBehaviourList> guiScriptIterator (m_GUIScripts); + while (guiScriptIterator.Next()) + { + MonoBehaviour& beh = **guiScriptIterator; + + if (beh.GetUseGUILayout()) + layoutedScripts.push_back(GUIObjectWrapper(&beh)); + else + { + if (!eventUsed) + eventUsed |= beh.DoGUI (MonoBehaviour::kNoLayout, 0); + } + } + } + // TODO post 3.5: We can't bail here - nolayout scripts need tab handling as well +#if UNITY_HAS_DEVELOPER_CONSOLE + layoutedScripts.push_back(GUIObjectWrapper(&GetDeveloperConsole())); +#endif // UNITY_HAS_DEVELOPER_CONSOLE + + int current = 1; + bool hasModalWindow = state.m_MultiFrameGUIState.m_Windows != NULL && state.m_MultiFrameGUIState.m_Windows->m_ModalWindow != NULL; + m_SortedScripts.clear (); + if (!layoutedScripts.empty ()) + { + // Send the layout event to all scripts that have that. + e.type = InputEvent::kLayout; + + IMGUI::BeginWindows (state, true, !hasModalWindow); // We need to enable clipping as otherwise it hasn't been set up. + + for (std::vector<GUIObjectWrapper>::iterator i = layoutedScripts.begin (); i != layoutedScripts.end (); ++i) + { + GUIObjectWrapper beh = *i; + if (beh) + { + beh.DoGUI (MonoBehaviour::kGameLayout, 0); + m_SortedScripts.push_back (SortedScript (GetGUIState().m_OnGUIState.m_Depth, beh)); + current++; + } + } + // Remove any unused windows + state.m_CanvasGUIState.m_GUIClipState.BeginOnGUI (*state.m_CurrentEvent); + IMGUI::EndWindows (state, !hasModalWindow); + state.m_CanvasGUIState.m_GUIClipState.EndOnGUI (*state.m_CurrentEvent); + + OldSortScript sort; + // Next, we sort by depth + m_SortedScripts.sort (sort); + e.type = originalType; + } + + bool hasSentEndWindows = false; + + current = 1; // reset the count so we can send the DoWindows. + if (frontToBack) + { + // This gets cleared during REPAINT event. For normal event processing, we'll just re-read that. + state.m_CanvasGUIState.m_IsMouseUsed = m_MouseUsed; + + for (SortedScripts::iterator i = m_SortedScripts.begin(); i != m_SortedScripts.end(); i++) + { + // If this script used the event, we terminate the loop + if (eventUsed) + break; + + // If this is the first script in layer 0, Put BeginWindows / EndWindows around the OnGUI call + bool endWindows = false; + if ((hasModalWindow || (current == m_SortedScripts.size() || i->depth > 0)) && !hasSentEndWindows) + { + IMGUI::BeginWindows (state, true, !hasModalWindow); + if (eventUsed) + break; + hasSentEndWindows = true; + endWindows = true; + } + current++; + eventUsed = i->beh.DoGUI (MonoBehaviour::kGameLayout, 0); + if (endWindows) + IMGUI::EndWindows (state, !hasModalWindow); + } + + // Remove keyboard focus when clicking on empty GUI area + // (so text fields don't take away game view input). + if (originalType == InputEvent::kMouseDown && !eventUsed) + { + GUIState &state = GetGUIState(); + state.m_MultiFrameGUIState.m_KeyboardControl = 0; + } + + // Handle mouse focus: We want the new GUI system to eat any mouse events. This means that we need to check this during repaint or mouseDown/Up + // and set a global variable accordingly. + if (originalType == InputEvent::kMouseDown || originalType == InputEvent::kMouseUp) + state.m_CanvasGUIState.m_IsMouseUsed = (bool)state.m_CanvasGUIState.m_IsMouseUsed | eventUsed; + } + else + { + // We clear mouse used at repaint time, then let it run through any event processing for next frame, before reading it back + m_MouseUsed = state.m_CanvasGUIState.m_IsMouseUsed = false; + + // I'm iterating backwards here - used by repainting + // this time, users can't bail out of the event processing + IMGUI::BeginWindows (state, true, !hasModalWindow); + for (SortedScripts::iterator i = m_SortedScripts.end(); i != m_SortedScripts.begin();) + { + i--; + bool endWindows = false; + + + // If this window is the last, OR the next one is above 0 depth, we should do repaint all popup windows NOW + if (!hasSentEndWindows) // If we haven't already sent it + { + if (current == m_SortedScripts.size()) // If this is the last script, we must call it now + { + hasSentEndWindows = true; + endWindows = true; + } + else + { + // If this is the last script with a depth > 0, we must call it now. + SortedScripts::iterator j = i; + j--; + if (j->depth <= 0) + { + hasSentEndWindows = true; + endWindows = true; + } + } + } + i->beh.DoGUI (MonoBehaviour::kGameLayout, 0); + if (endWindows) + { + state.m_CanvasGUIState.m_GUIClipState.BeginOnGUI (*state.m_CurrentEvent); + IMGUI::EndWindows (state, !hasModalWindow); // don't ignore modal windows if we have them + state.m_CanvasGUIState.m_GUIClipState.EndOnGUI (*state.m_CurrentEvent); + } + + current++; + } + + if(hasModalWindow) + { + // Ensure modal windows are always on top by painting them last. + state.m_CanvasGUIState.m_GUIClipState.BeginOnGUI (*state.m_CurrentEvent); + IMGUI::RepaintModalWindow (state); + state.m_CanvasGUIState.m_GUIClipState.EndOnGUI (*state.m_CurrentEvent); + } + } + + if (handleTab != 0 && !eventUsed && m_SortedScripts.size() != 0) + { + // Build the list of IDLists to cycle through + std::vector<IDList*> keyIDLists; + + IMGUI::GUIWindow* focusedWindow = IMGUI::GetFocusedWindow (state); + if (focusedWindow) + keyIDLists.push_back (&focusedWindow->m_ObjectGUIState.m_IDList); + else + { + keyIDLists.reserve (m_SortedScripts.size()); + for (SortedScripts::iterator i = m_SortedScripts.begin(); i != m_SortedScripts.end(); i++) + { + keyIDLists.push_back (&i->beh.GetObjectGUIState().m_IDList); + } + } + + state.CycleKeyboardFocus(keyIDLists, handleTab == 1); + } + + m_MasterState.SaveFromGUIState (state); + m_MasterState.EndFrame (); + m_MouseUsed = state.m_CanvasGUIState.m_IsMouseUsed; +} + +void GUIManager::QueueEvent (InputEvent &ie) +{ + QueueEventImmediate(ie); +} + + +void GUIManager::QueueEventImmediate (InputEvent &ie) +{ + // MouseMove events are not sent. + // The same info can be obtained from repaint events. + if (ie.type == InputEvent::kMouseMove) + { + // We still use them as last event to update the cursor position. + m_LastEvent = ie; + return; + } + if (ie.type == InputEvent::kIgnore) + return; + + if ( ie.type == InputEvent::kMouseDown ) + m_mouseButtonsDown |= (1<<ie.button); + else if ( ie.type == InputEvent::kMouseUp ) + m_mouseButtonsDown &= ~(1<<ie.button); + + switch (ie.type) { + case InputEvent::kMouseDown: + case InputEvent::kMouseUp: + case InputEvent::kKeyDown: + ResetCursorFlash (); + break; + } + + m_LastEvent = ie; + m_Events.push_back(ie); +} + +void GUIManager::ResetCursorFlash () +{ + #if SUPPORT_REPRODUCE_LOG + if (RunningReproduction()) + return; + #endif + + GetGUIManager().m_LastInputEventTime = GetTimeManager().GetRealtime (); +} + +float GUIManager::GetCursorFlashTime () +{ + return GetGUIManager().m_LastInputEventTime; +} + + +GUIKeyboardState &GUIManager::GetMasterGUIState () +{ + return GetGUIManager().m_MasterState; +} + +#if SUPPORT_REPRODUCE_LOG +void WriteInputEvent (InputEvent& event, std::ostream& out) +{ + out << (int&)event.type << ' '; + WriteFloat(out, event.mousePosition.x); out << ' '; + WriteFloat(out, event.mousePosition.y); out << ' '; + WriteFloat(out, event.delta.x); out << ' '; + WriteFloat(out, event.delta.y); out << ' '; + + out << event.button << ' ' << event.modifiers << ' '; + WriteFloat(out, event.pressure); out << ' '; + out << event.clickCount << ' ' << event.character << ' ' << event.keycode << ' '; + + // if (event.commandString) + // WriteReproductionString(out, event.commandString); + // else + // WriteReproductionString(out, ""); +} + +void ReadInputEvent (InputEvent& event, std::istream& in, int version) +{ + event.Init(); + + in >> (int&)event.type; + ReadFloat(in, event.mousePosition.x); + ReadFloat(in, event.mousePosition.y); + ReadFloat(in, event.delta.x); + ReadFloat(in, event.delta.y); + in >> event.button >> event.modifiers; + ReadFloat(in, event.pressure); + in >> event.clickCount >> event.character >> event.keycode; + + //if (version >= 6) + //{ + // std::string commandString; + // ReadReproductionString(in, commandString); + // event.commandString = new char[commandString.size() + 1]; + // memcpy(event.commandString, commandString.c_str(), commandString.size() + 1); + //} +} + +void GUIManager::WriteLog (std::ofstream& out) +{ + out << "Events" << std::endl; + + out << m_Events.size() << std::endl; + + for (int i=0;i<m_Events.size();i++) + { + InputEvent& event = m_Events[i]; + WriteInputEvent(event, out); + } + + // Hover events seem to require the last event as well! + InputEvent& event = m_LastEvent; + WriteInputEvent (event, out); + + if (GetReproduceVersion () >= 6) + { + WriteReproductionString(out, GetInputManager().GetCompositionString()); + out << (int)(GetInputManager().GetTextFieldInput()) << ' '; + } + + out << std::endl; +} + +void GUIManager::ReadLog (std::ifstream& in) +{ + CheckReproduceTagAndExit("Events", in); + + int size; + in >> size; + m_Events.clear(); + for (int i=0;i<size;i++) + { + InputEvent event; + ReadInputEvent (event, in, GetReproduceVersion()); + m_Events.push_back(event); + } + + // Hover events seem to require the last event as well! + ReadInputEvent (m_LastEvent, in, GetReproduceVersion()); + + if (GetReproduceVersion () >= 6) + { + ReadReproductionString(in, GetInputManager().GetCompositionString()); + int textFieldInput; + in >> textFieldInput; + GetInputManager().SetTextFieldInput(textFieldInput); + } +} +#endif +#endif diff --git a/Runtime/IMGUI/GUIManager.h b/Runtime/IMGUI/GUIManager.h new file mode 100644 index 0000000..eb35c45 --- /dev/null +++ b/Runtime/IMGUI/GUIManager.h @@ -0,0 +1,135 @@ +#ifndef GUIMANAGER_H +#define GUIMANAGER_H + +#include "Configuration/UnityConfigure.h" +#include "Runtime/Mono/MonoBehaviour.h" +#include "Runtime/Misc/InputEvent.h" +#include "Runtime/Utilities/MemoryPool.h" +#include "Runtime/IMGUI/GUIState.h" +#include "Runtime/Misc/DeveloperConsole.h" + +#include <deque> + +struct InputEvent; +class GUIManager { + public: + GUIManager (); + + // Issue a repaint event - used by players... + // Mouse position & modifiers are read from the input manager. + // TODO: implement Modifier keys + void Repaint (); + + // Add/remove - called from MonoBehaviour + void AddGUIScript (MonoBehaviourListNode& beh); + + // Send an event to all in-game GUI scripts + // e - the event to send. + // mask - GOLayer mask to match. + // frontToBack Send event to the frontmost GUI script first (true for normal event processing, false for repaint) + void DoGUIEvent (InputEvent &e, bool frontToBack); + + // Send an input event (keydown, mousemove, mouseup, etc) to the GUI scripts. + void QueueEvent (InputEvent &ie); + void QueueEventImmediate (InputEvent &ie); + + void SendQueuedEvents (); + + InputEvent GetLastInputEvent() { return m_LastEvent;} + + static bool AnyMouseButtonsDown(); + + // Did GUI code inside BeginWindows make it irrelevant for OnGUI code to be called at all? (e.g. mouseClick inside a GUI.window) + // We used to do this by calling event.Use (), but that causes repaints, so instead this function is called that sets a var to track it + static void SetDidGUIWindowsEatLastEvent (bool value); + static bool GetDidGUIWindowsEatLastEvent (); + + #if UNITY_EDITOR + // Clear all setting for entering / exiting playmode + void Reset (); + #endif + + static void ResetCursorFlash (); + static float GetCursorFlashTime (); + + #if UNITY_EDITOR + void SetEditorGUIInfo (Vector2f guiPixelOffset); + Vector2f GetGUIPixelOffset () { return m_GUIPixelOffset; } + Vector2f GetEditorGUIInfo() const; + #endif + + #if SUPPORT_REPRODUCE_LOG + void WriteLog (std::ofstream& out); + void ReadLog (std::ifstream& in); + #endif + + struct GUIObjectWrapper + { + public: + explicit GUIObjectWrapper(MonoBehaviour* beh); + #if UNITY_HAS_DEVELOPER_CONSOLE + + explicit GUIObjectWrapper(DeveloperConsole* dev); + + #endif // UNITY_HAS_DEVELOPER_CONSOLE + + // Wrapped functions for GUI objects + bool DoGUI(MonoBehaviour::GUILayoutType layoutType, int skin) const { + return do_gui_func(wrapped_ptr, layoutType, skin); + } + + ObjectGUIState& GetObjectGUIState() const { + return get_gui_state_func(wrapped_ptr); + } + + // This avoids a plethora of pitfalls in situations, + // where implicit conversions may happen + typedef void * GUIObjectWrapper::*unspecified_bool_type; + operator unspecified_bool_type() const { // never throws + return wrapped_ptr == 0? 0: &GUIObjectWrapper::wrapped_ptr; + } + + private: + typedef bool (*dogui_function_type)(void*, MonoBehaviour::GUILayoutType, int); + typedef ObjectGUIState& (*get_gui_state_function_type)(void*); + + void* wrapped_ptr; + + dogui_function_type do_gui_func; + get_gui_state_function_type get_gui_state_func; + }; + + struct SortedScript + { + int depth; + GUIObjectWrapper beh; + SortedScript (int dep, GUIObjectWrapper b) : depth(dep), beh(b) {} + }; + + inline std::deque<InputEvent>& GetQueuedEvents() {return m_Events;} + bool GetMouseUsed () const { return m_MouseUsed; } + + static GUIKeyboardState &GetMasterGUIState (); + +private: + typedef List<MonoBehaviourListNode> MonoBehaviourList; + MonoBehaviourList m_GUIScripts; + std::deque<InputEvent> m_Events; + + bool m_MouseUsed, m_DidGUIWindowsEatLastEvent; + int m_mouseButtonsDown; + Vector2f m_GUIPixelOffset; + typedef std::list<SortedScript, memory_pool<SortedScript> > SortedScripts; + SortedScripts m_SortedScripts; + + float m_LastInputEventTime; + InputEvent m_LastEvent; + GUIKeyboardState m_MasterState; +}; + +GUIManager &GetGUIManager (); + +void InitGUIManager (); +void CleanupGUIManager (); + +#endif diff --git a/Runtime/IMGUI/GUIState.cpp b/Runtime/IMGUI/GUIState.cpp new file mode 100644 index 0000000..90db27a --- /dev/null +++ b/Runtime/IMGUI/GUIState.cpp @@ -0,0 +1,519 @@ +#include "UnityPrefix.h" + +#if ENABLE_UNITYGUI + +#include "Runtime/IMGUI/GUIState.h" +#include "Runtime/Math/Color.h" +#include "Runtime/Misc/InputEvent.h" +#include "Runtime/IMGUI/IDList.h" +#include "Runtime/IMGUI/NamedKeyControlList.h" +#include "Runtime/IMGUI/GUIWindows.h" +#include "Runtime/IMGUI/TextUtil.h" +#include "Runtime/Scripting/ScriptingUtility.h" +#include "Runtime/Scripting/CommonScriptingClasses.h" +#include "Runtime/Scripting/ScriptingManager.h" +#include "Runtime/Scripting/Backend/ScriptingArguments.h" + +static EternalGUIState* gEternalGUIState = NULL; +static IDList* gEternalIDList = NULL; +static GUIState *gGUIState = NULL; + +GUIState &GetGUIState () +{ + AssertIf (!gGUIState); + return *gGUIState; +} + + + +EternalGUIState *GetEternalGUIState () +{ + if (gEternalGUIState == NULL) + gEternalGUIState = new EternalGUIState (); + return gEternalGUIState; +} + +static IDList* GetEternalIDList () +{ + if (gEternalIDList == NULL) + gEternalIDList = new IDList(); + return gEternalIDList; +} + + +MultiFrameGUIState::MultiFrameGUIState () +{ + m_KeyboardControl = 0; + m_NamedKeyControlList = NULL; + m_Windows = NULL; +} + +MultiFrameGUIState::~MultiFrameGUIState () +{ + delete m_NamedKeyControlList; + delete m_Windows; +} + +void MultiFrameGUIState::Reset () +{ + delete m_Windows; + m_Windows = 0; + + delete m_NamedKeyControlList; + m_NamedKeyControlList = 0; +} + +IMGUI::NamedControl *MultiFrameGUIState::GetControlNamed (const std::string &name) +{ + if (m_NamedKeyControlList == NULL) + return NULL; + return m_NamedKeyControlList->GetControlNamed (name); +} + +void MultiFrameGUIState::AddNamedControl (std::string &name, int id, IMGUI::GUIWindow* window) +{ + if (m_NamedKeyControlList == NULL) + m_NamedKeyControlList = new IMGUI::NamedKeyControlList (); + m_NamedKeyControlList->AddNamedControl(name, id, window ? window->m_ID : -1); +} + +void MultiFrameGUIState::ClearNamedControls () +{ + if (m_NamedKeyControlList) + m_NamedKeyControlList->Clear (); +} + + +ObjectGUIState::ObjectGUIState () +{ +} + +ObjectGUIState::~ObjectGUIState () +{ +} + +void ObjectGUIState::BeginOnGUI () +{ + m_IDList.BeginOnGUI (); +} + +void OnGUIState::SetNameOfNextKeyboardControl (const std::string &nextName) +{ + delete m_NameOfNextKeyboardControl; + m_NameOfNextKeyboardControl = new string (nextName); +} + +void OnGUIState::ClearNameOfNextKeyboardControl () +{ + delete m_NameOfNextKeyboardControl; + m_NameOfNextKeyboardControl = NULL; +} + +OnGUIState::OnGUIState () +{ + m_NameOfNextKeyboardControl = NULL; + m_MouseTooltip = m_KeyTooltip = NULL; + m_CaptureBlock = NULL; + m_Color = m_BackgroundColor = m_ContentColor = ColorRGBAf (1.0f,1.0f,1.0f,1.0f); + m_Enabled = 1; + m_Changed = 0; + m_Depth = 0; + m_ShowKeyboardControl = 1; + +} + +OnGUIState::~OnGUIState () +{ + delete m_NameOfNextKeyboardControl; + delete m_MouseTooltip; + delete m_KeyTooltip; +} + +void OnGUIState::BeginOnGUI () +{ + m_Color = m_BackgroundColor = m_ContentColor = ColorRGBAf(1.0f,1.0f,1.0f,1.0f); + m_Enabled = true; + m_Changed = false; + m_Depth = 1; +} + +void OnGUIState::EndOnGUI () +{ + delete m_NameOfNextKeyboardControl; + m_NameOfNextKeyboardControl = NULL; + delete m_MouseTooltip; + m_MouseTooltip = NULL; + delete m_KeyTooltip; + m_KeyTooltip = NULL; +} + + +void OnGUIState::SetMouseTooltip (const UTF16String &tooltip) +{ + delete m_MouseTooltip; + m_MouseTooltip = new UTF16String (tooltip); +} + +void OnGUIState::SetKeyTooltip (const UTF16String &tooltip) +{ + delete m_KeyTooltip; + m_KeyTooltip = new UTF16String (tooltip); +} + +GUIState::GUIState () +{ + m_CurrentEvent = NULL; + m_ObjectGUIState = NULL; + m_OnGUIDepth = 0; +} + +GUIState::~GUIState() +{ +} + + +void GUIState::BeginFrame () +{ + +} + +void GUIState::EndFrame () +{ + +} + +void GUIState::BeginOnGUI (ObjectGUIState &objectGUIState) +{ + m_ObjectGUIState = &objectGUIState; + + m_OnGUIState.BeginOnGUI (); + m_ObjectGUIState->BeginOnGUI (); + m_OnGUIDepth++; +} + +void GUIState::EndOnGUI () +{ + m_OnGUIState.EndOnGUI(); + m_ObjectGUIState = NULL; + + AssertIf (m_OnGUIDepth < 1); + m_OnGUIDepth--; +} + +static int GetControlID (GUIState& state, int hint, FocusType focusType, const Rectf& rect, bool useRect) +{ + int id; + + if (state.m_ObjectGUIState == NULL) + { + id = state.m_EternalGUIState->GetNextUniqueID (); + +#if (UNITY_EDITOR) + // Editor GUI needs some additional things to happen when calling GetControlID. + // While this will also be called for runtime code running in Play mode in the Editor, + // it won't have any effect. EditorGUIUtility.s_LastControlID will be set to the id, + // but this is only used inside the handling of a single control. + ScriptingInvocation invocation(MONO_COMMON.handleControlID); + invocation.AddInt(id); + invocation.Invoke(); +#endif + + return id; + } + + if (useRect) + id = state.m_ObjectGUIState->m_IDList.GetNext (state, hint, focusType, rect); + else + id = state.m_ObjectGUIState->m_IDList.GetNext (state, hint, focusType); + + // maybe in future this should check for focusType == keyboard + // the issue currently is that there is no way to focus buttons via the keyboard + // this means that if you have a game with no mouse input you can not 'tab' to the + // button using a joystic or keys. + // + // because of this we need to allow custom named controls of all types (apart from passive) + // to be selectable. + if (focusType != kPassive && state.m_OnGUIState.GetNameOfNextKeyboardControl () != NULL) + { + IMGUI::GUIWindow* currentWindow = NULL; + if (state.m_MultiFrameGUIState.m_Windows) + currentWindow = state.m_MultiFrameGUIState.m_Windows->m_CurrentWindow; + state.m_MultiFrameGUIState.AddNamedControl (*state.m_OnGUIState.GetNameOfNextKeyboardControl (), id, currentWindow); + state.m_OnGUIState.ClearNameOfNextKeyboardControl (); + } + +#if (UNITY_EDITOR) + // Same as above; see comment there. + ScriptingInvocation invocation(MONO_COMMON.handleControlID); + invocation.AddInt(id); + invocation.Invoke(); +#endif + + return id; +} + +int GUIState::GetControlID (int hint, FocusType focusType) +{ + return ::GetControlID (*this, hint, focusType, Rectf(), false); +} + +int GUIState::GetControlID (int hint, FocusType focusType, const Rectf& rect) +{ + return ::GetControlID (*this, hint, focusType, rect, true); +} + +int GUIState::GetIDOfNamedControl (const std::string &name) +{ + IMGUI::NamedControl *ctrl = m_MultiFrameGUIState.GetControlNamed (name); + if (ctrl) + return ctrl->ID; + return 0; +} + +std::string GUIState::GetNameOfFocusedControl () +{ + if (m_MultiFrameGUIState.m_NamedKeyControlList) + return m_MultiFrameGUIState.m_NamedKeyControlList->GetNameOfControl (m_MultiFrameGUIState.m_KeyboardControl); + return ""; +} + +void GUIState::FocusKeyboardControl (const std::string &name) +{ + IMGUI::NamedControl *ctrl = m_MultiFrameGUIState.GetControlNamed (name); + if (ctrl) + { + m_MultiFrameGUIState.m_KeyboardControl = ctrl->ID; + IMGUI::FocusWindow (*this, ctrl->windowID); + } + else + { + m_MultiFrameGUIState.m_KeyboardControl = 0; + IMGUI::FocusWindow (*this, -1); + } +} + +void GUIState::SetEvent( const InputEvent& event ) +{ + ScriptingInvocationNoArgs invocation; + invocation.method = MONO_COMMON.makeMasterEventCurrent; + invocation.Invoke(); + *m_CurrentEvent = event; +} + +void GUIState::Internal_SetManagedEvent (void *event) +{ + m_CurrentEvent = (InputEvent*)event; +} + +void GUIState::MoveAllDataTo (GUIState &dest, bool saving) +{ + dest.m_MultiFrameGUIState = m_MultiFrameGUIState; + m_MultiFrameGUIState.m_NamedKeyControlList = NULL; + m_MultiFrameGUIState.m_Windows = NULL; + + dest.m_OnGUIState = m_OnGUIState; + m_OnGUIState.m_CaptureBlock = NULL; + m_OnGUIState.m_NameOfNextKeyboardControl = NULL; + m_OnGUIState.m_MouseTooltip = NULL; + m_OnGUIState.m_KeyTooltip = NULL; + + dest.m_ObjectGUIState = m_ObjectGUIState; + + // Move over the clipping info. + dest.m_CanvasGUIState = m_CanvasGUIState; + + // This one is truly eternal, so we don't want to clear it from the current. + dest.m_EternalGUIState = m_EternalGUIState; + + // Move over the event. Since the event is shared in weird ways between Mono and C++, we'll copy it. + if (saving) + { + dest.m_BackupEvent = *m_CurrentEvent; + dest.m_CurrentEvent = m_CurrentEvent; + } + else + { + + *(dest.m_CurrentEvent) = m_BackupEvent; + } +} + +GUIState* GUIState::GetPushState () +{ + // Set up state + GUIState *pushState = new GUIState (); + GetGUIState().MoveAllDataTo (*pushState, true); + return pushState; +} + +void GUIState::PopAndDelete (GUIState *pushState) +{ + pushState->MoveAllDataTo (GetGUIState(), false); + delete pushState; +} + +static IDList* FindWhichIDListHasKeyboardControl (std::vector<IDList *> &idListsToSearch) +{ + // Find which IDList has the keyboard Control. + for (std::vector<IDList *>::iterator i = idListsToSearch.begin(); i != idListsToSearch.end(); i++) + { + if ((*i)->HasKeyboardControl ()) + { + return *i; + } + } + return NULL; +} + +void GUIState::CycleKeyboardFocus (std::vector<IDList *> &idListsToSearch, bool searchForward) +{ + m_MultiFrameGUIState.m_KeyboardControl = GetNextKeyboardControlID(idListsToSearch, searchForward); +} + +int GUIState::GetNextKeyboardControlID (std::vector<IDList *> &idListsToSearch, bool searchForward) +{ + IDList *start = FindWhichIDListHasKeyboardControl (idListsToSearch); + + if (searchForward) + { + if (start && start->GetNextKeyboardControlID() != -1) + { + return start->GetNextKeyboardControlID(); + } + + // Which script did we start the keyboard search FROM. + int startIdx = -1; + // Which script are we scanning for keyboard controls. + int listIdx = -1; + if (start != NULL) + { + for (int i = 0; i < idListsToSearch.size(); i++) + { + if (idListsToSearch[i] == start) + { + startIdx = listIdx = (i + 1) % idListsToSearch.size(); + break; + } + } + } else { + startIdx = 0;//idListsToSearch.size(); + listIdx = 0; + } + + do { + int firstInList = idListsToSearch[listIdx]->GetFirstKeyboardControlID (); + if (firstInList != -1) + { + return firstInList; + } + listIdx++; + listIdx = listIdx % idListsToSearch.size(); + } while (listIdx != startIdx); + return 0; + } + else + { + if (start && start->GetPreviousKeyboardControlID() != -1) + { + return start->GetPreviousKeyboardControlID(); + } + + int startIdx = 0, listIdx = idListsToSearch.size(); + if (start != NULL) + { + for (int i = 0; i < idListsToSearch.size(); i++) + { + if (idListsToSearch[i] == start) + { + startIdx = listIdx = i; + break; + } + } + } + + do { + listIdx = listIdx - 1; + if (listIdx == -1) + listIdx = idListsToSearch.size() - 1; + + int firstInList = idListsToSearch[listIdx]->GetLastKeyboardControlID (); + if (firstInList != -1) + { + return firstInList; + } + } while (listIdx != startIdx); + return 0; + } +} + +GUIKeyboardState::GUIKeyboardState () +{ + m_KeyboardControl = 0; + m_FocusedGUIWindow = -1; + m_ShowKeyboardControl = true; + m_Windows = NULL; + m_NamedKeyControlList = NULL; +} + +GUIKeyboardState::~GUIKeyboardState () +{ + delete m_Windows; + delete m_NamedKeyControlList; +} + +void GUIKeyboardState::LoadIntoGUIState (GUIState &dest) +{ + dest.m_MultiFrameGUIState.m_KeyboardControl = m_KeyboardControl; + + Assert (!dest.m_MultiFrameGUIState.m_NamedKeyControlList); + dest.m_MultiFrameGUIState.m_NamedKeyControlList = m_NamedKeyControlList; + + Assert (!dest.m_MultiFrameGUIState.m_Windows); + dest.m_MultiFrameGUIState.m_Windows = m_Windows; + + dest.m_OnGUIState.m_ShowKeyboardControl = m_ShowKeyboardControl; + m_Windows = NULL; +} +void GUIKeyboardState::SaveFromGUIState (GUIState &src) +{ + m_KeyboardControl = src.m_MultiFrameGUIState.m_KeyboardControl; + m_NamedKeyControlList = src.m_MultiFrameGUIState.m_NamedKeyControlList; + src.m_MultiFrameGUIState.m_NamedKeyControlList = NULL; + m_Windows = src.m_MultiFrameGUIState.m_Windows; + m_ShowKeyboardControl = src.m_OnGUIState.m_ShowKeyboardControl; + src.m_MultiFrameGUIState.m_Windows = NULL; +} + +void GUIKeyboardState::Reset () +{ + m_KeyboardControl = m_FocusedGUIWindow = 0; + delete m_Windows; + m_Windows = NULL; + delete m_NamedKeyControlList; + m_NamedKeyControlList = NULL; +} + +void GUIKeyboardState::EndFrame () +{ + if (m_Windows) + m_Windows->ReleaseScriptingObjects (); +} + +void InitGUIState () +{ + Assert(gGUIState == NULL); + gGUIState = new GUIState (); + gGUIState->m_EternalGUIState = GetEternalGUIState(); + gGUIState->m_CurrentEvent = new InputEvent (); + gGUIState->m_CurrentEvent->Init(); +} + + +void CleanupGUIState () +{ + Assert(gGUIState != NULL); + delete gGUIState; + gGUIState = NULL; +} + +#endif diff --git a/Runtime/IMGUI/GUIState.h b/Runtime/IMGUI/GUIState.h new file mode 100644 index 0000000..9ab0ad6 --- /dev/null +++ b/Runtime/IMGUI/GUIState.h @@ -0,0 +1,231 @@ +#ifndef GUISTATE_H +#define GUISTATE_H + +#include "Runtime/Math/Color.h" +#include "Runtime/Math/Rect.h" +#include "Runtime/IMGUI/IDList.h" +#include "Runtime/IMGUI/NamedKeyControlList.h" +#include "Runtime/IMGUI/GUIClip.h" + +namespace IMGUI +{ + struct GUIWindowState; + struct GUIWindow; +} + + +struct InputEvent; +struct MonoObject; +struct GUIGraphicsCacheBlock; +struct UTF16String; + +// Lasts multiple frames. +// There is ONE for user's games and one for each Editor Window. +struct MultiFrameGUIState +{ + // Which control has keyboard focus + int m_KeyboardControl; + + // List of named keyboard controls. NULL if none + IMGUI::NamedKeyControlList *m_NamedKeyControlList; + + // List of GUI.Window IDLists + IMGUI::GUIWindowState *m_Windows; + + + IMGUI::NamedControl *GetControlNamed (const std::string &name); + void AddNamedControl (std::string &name, int id, IMGUI::GUIWindow* window); + void ClearNamedControls (); + void Reset (); + + MultiFrameGUIState (); + ~MultiFrameGUIState (); +}; + +// State that is reset each OnGUI. +struct OnGUIState +{ + OnGUIState (); + ~OnGUIState (); + + // Colors for rendering + ColorRGBAf m_Color, m_BackgroundColor, m_ContentColor; + + // Is the GUI enabled + int m_Enabled; + + // Has the GUI changed + int m_Changed; + + // Depth of the current GUIBehaviour's OnGUI - not used by GUI.Window or EditorWindow + int m_Depth; + + int m_ShowKeyboardControl; + + // If IMGUI is rendered inside NewGUI, this is a pointer to the batching object. All rendering commands go into this. + std::vector<GUIGraphicsCacheBlock>* m_CaptureBlock; + + // Name of the next keyboard control. It's a pointer to maintain size with Mono, but owned by OnGUIState + // Don't set this pointer (unless you're implementing nested OnGUI calls) - use the getters and setters + std::string *m_NameOfNextKeyboardControl; + + UTF16String *m_MouseTooltip, *m_KeyTooltip; + + void SetNameOfNextKeyboardControl (const std::string &name); + std::string *GetNameOfNextKeyboardControl () { return m_NameOfNextKeyboardControl; } + + void SetMouseTooltip (const UTF16String &tooltip); + UTF16String *GetMouseTooltip () { return m_MouseTooltip; } + + void SetKeyTooltip (const UTF16String &tooltip); + UTF16String *GetKeyTooltip () { return m_KeyTooltip; } + + void ClearNameOfNextKeyboardControl (); + // Reset the values to sane defaults for an OnGUI call + void BeginOnGUI (); + void EndOnGUI (); +}; + +// State that is stored in the MonoBehaviour and pulled from it every OnGUI. +struct ObjectGUIState +{ + IDList m_IDList; // per monobehaviour + 1 per GUI.Window + + void BeginOnGUI (); + + ObjectGUIState (); + ~ObjectGUIState (); +}; + +// State that exists per new-style canvas, and per old-style MonoBehaviour with an OnGUI. +struct CanvasGUIState +{ + GUIClipState m_GUIClipState; + int m_IsMouseUsed; +}; + +/// This one is always available. +struct EternalGUIState +{ +private: + int m_UniqueID; + // Which control has touch / mouse focus +public: + int m_HotControl; // TODO:should be int[kMaxSupportedPointers] + bool m_AllowHover; + + int GetNextUniqueID () + { + return m_UniqueID++; + } + + EternalGUIState () + { + m_UniqueID = 1; + m_HotControl = 0; + m_AllowHover = true; + } +}; +EternalGUIState *GetEternalGUIState (); + +// All state used by the GUI (so be it!) +struct GUIState +{ + GUIState (); + ~GUIState(); + + MultiFrameGUIState m_MultiFrameGUIState; + OnGUIState m_OnGUIState; + ObjectGUIState* m_ObjectGUIState; // The state owned by the monobehaviour we're calling on + CanvasGUIState m_CanvasGUIState; // The state for the surface we're rendering into + EternalGUIState* m_EternalGUIState; + InputEvent* m_CurrentEvent; // C++ side. Maps to the memory INSIDE the ManagedCurrentEvent. + + InputEvent m_BackupEvent; + + int m_OnGUIDepth; // How deep we are inside OnGUI calls. 0 = we haven't called at all + + // Whether the current event has been marked as used +#if ENABLE_NEW_EVENT_SYSTEM + bool GetIsEventUsed() const { return (m_CurrentEvent != NULL) && m_CurrentEvent->type == InputEvent::kUsed; } + + // Begin and End simply make it possible to use Event.current from C# outside of OnGUI code + void BeginUsingEvents () { ++m_OnGUIDepth; } + void EndUsingEvents() { --m_OnGUIDepth; } +#endif + + // Call this to intialize a game frame or window frame - not for each event we send into the system. + void BeginFrame (); + + // Call this when we're done with one OS-level frame. + void EndFrame (); + + // Call this to intialize ONE onGUI call. Must be called between e.g. layout and repaint events + // Assumes all states have been assigned correctly. + void BeginOnGUI (ObjectGUIState &objectGUIState); + + /// Call this to end an OnGUI call. It will call all substates with EndOnGUI, so they can clean up. + void EndOnGUI (); + + + int GetControlID (int hint, FocusType focusType, const Rectf &rect); + int GetControlID (int hint, FocusType focusType); + + // Handle Named Controls + void SetNameOfNextKeyboardControl (const std::string &name) { m_OnGUIState.SetNameOfNextKeyboardControl (name); } + std::string GetNameOfFocusedControl (); + int GetIDOfNamedControl (const std::string &name); + void FocusKeyboardControl (const std::string &name); + + //Not sure where this should go. It's called by GUIManager & EditorWindows. For now, I'll put it here... + void CycleKeyboardFocus (std::vector<IDList *> &IDListsToSearch, bool searchForward); + int GetNextKeyboardControlID (std::vector<IDList *> &IDListsToSearch, bool searchForward); + + void SetEvent (const InputEvent& event); + void SetObjectGUIState (ObjectGUIState &objectGUIState); + + // Make a copy of the current managed gui state (to implement nesting) + // When done with an OnGUI call, call PopAndDelete to restore. + static GUIState* GetPushState (); + static void PopAndDelete (GUIState *pushState); + + // Move all GUI state into dest, NULLing all pointer fields in this + // Used by GetPushState & PopAndDelete + void MoveAllDataTo (GUIState &dest, bool saving); + + // Called from C# whenever the user assigns into Event.current. + // Don't call this from C++; + void Internal_SetManagedEvent (void *event); +}; + +GUIState &GetGUIState (); + +// Struct for saving GUI State between in-game frames. Each editor window is also a separate "world" in this regard. +// So E.g. Keyboard control names are shared between all scripts in a game, but each editor window has it's own separate world. +struct GUIKeyboardState +{ + // Which controlID has focus last frame + int m_KeyboardControl; + // Should we show the keyboard control? false if editor window (or webplayer I guess) doesn't have focus. + int m_ShowKeyboardControl; + // IDLists for each window. Null if none + IMGUI::GUIWindowState* m_Windows; + + IMGUI::NamedKeyControlList *m_NamedKeyControlList; + + int m_FocusedGUIWindow; + + void LoadIntoGUIState (GUIState &dest); + void SaveFromGUIState (GUIState &src); + + void EndFrame (); + + void Reset (); + + GUIKeyboardState (); + ~GUIKeyboardState (); +}; + +void InitGUIState (); +void CleanupGUIState (); +#endif diff --git a/Runtime/IMGUI/GUIStyle.cpp b/Runtime/IMGUI/GUIStyle.cpp new file mode 100644 index 0000000..ac4e632 --- /dev/null +++ b/Runtime/IMGUI/GUIStyle.cpp @@ -0,0 +1,1154 @@ +#include "UnityPrefix.h" +#include "Runtime/IMGUI/GUIStyle.h" +#include "Runtime/Camera/RenderLayers/GUITexture.h" +#include "Runtime/Filters/Misc/Font.h" +#include "TextMeshGenerator2.h" +#include "Runtime/GfxDevice/GfxDevice.h" +#include "External/shaderlab/Library/FastPropertyName.h" +#include "Runtime/Shaders/Material.h" +#include "Runtime/Misc/ResourceManager.h" +#include "External/shaderlab/Library/properties.h" +#include "Runtime/Graphics/GeneratedTextures.h" +#include "External/shaderlab/Library/FastPropertyName.h" +#include "Runtime/Graphics/TextureGenerator.h" +#include "External/shaderlab/Library/texenv.h" +#include "Runtime/Shaders/Shader.h" +#include "External/shaderlab/Library/intshader.h" +#include "Runtime/IMGUI/GUIState.h" +#include "Runtime/IMGUI/GUIContent.h" +#include "Runtime/IMGUI/IMGUIUtils.h" +#include "Runtime/IMGUI/GUIManager.h" +#include "Runtime/Misc/BuildSettings.h" +#include "Runtime/Misc/AssetBundle.h" + +#if UNITY_EDITOR +#include "Editor/Src/AssetPipeline/AssetDatabase.h" +#include "Editor/Src/OptimizedGUIBlock.h" +#include "Editor/Src/TooltipManager.h" +#include "Editor/Src/Highlighter/HighlighterCore.h" +#include "Editor/Src/EditorResources.h" +#include "Editor/Src/EditorHelper.h" +#endif + +const float s_TabSize = 16; +using namespace std; +static Rectf s_GUIClipRect (0,0,1,1); + +namespace GUIStyle_Static +{ + PPtr<Font> s_DefaultFont = NULL, s_BuiltinFont = NULL; +} // namespace GUIStyle_Static + +// if these are set to 0 icons scale to fit the text +float s_GUIStyleIconSizeX = 0; +float s_GUIStyleIconSizeY = 0; + + +// A minimal (4x4) texture is not enough because of limited subtexel precision on some cards. +// With large clipping rectangles, one texel in clipping texture can span lots of pixels on screen, +// and because of limited precision there can be one pixel errors. +// E.g. Radeon HD 3850 seems to need at least 16x16 texture. +const float kGUIClipTextureSize = 16.0f; + +static Material *kGUITextMaterial = NULL; +static Material *kBlendMaterial = NULL; +static Material *kBlitMaterial = NULL; + +inline void GUIClipTexture (Texture2D *tex, unsigned char *data, int x, int y, int maxX, int maxY) +{ + if (x == 0 || y == 0 || x == maxX - 1 || y == maxY - 1) + *data = 0; + else + *data = 255; +} + +static Texture2D *gGUIClipTexture = NULL; + +Texture2D *GUIStyle::GetClipTexture () +{ + return gGUIClipTexture; +} + +void InitializeGUIClipTexture () +{ + if (gGUIClipTexture) + return; + + gGUIClipTexture = BuildTexture <unsigned char> (int(kGUIClipTextureSize), int(kGUIClipTextureSize), kTexFormatAlpha8, &GUIClipTexture); + gGUIClipTexture->SetFilterMode (kTexFilterNearest); + gGUIClipTexture->SetWrapMode(kTexWrapClamp); + ShaderLab::PropertySheet *props = ShaderLab::g_GlobalProperties; + ShaderLab::TexEnv *te = props->SetTexture (ShaderLab::Property ("_GUIClipTexture"),gGUIClipTexture); + te->SetMatrixName (ShaderLab::Property("_GUIClipTextureMatrix")); + te->SetTexGen (kTexGenEyeLinear); +} + +Material* GetGUIBlitMaterial () +{ + if (!kBlitMaterial) + { + Shader* shader = GetBuiltinResource<Shader> ("Internal-GUITextureBlit.shader"); + kBlitMaterial = Material::CreateMaterial (*shader, Object::kHideAndDontSave); + InitializeGUIClipTexture(); + } + + return kBlitMaterial; +} + +Material* GetGUIBlendMaterial () +{ + if (!kBlendMaterial) + { + Shader* shader = GetBuiltinResource<Shader> ("Internal-GUITextureClip.shader"); + kBlendMaterial = Material::CreateMaterial (*shader, Object::kHideAndDontSave); + InitializeGUIClipTexture(); + } + + return kBlendMaterial; +} + +static Material* GetGUITextMaterial () +{ + if (!kGUITextMaterial) + { + Shader* shader = GetBuiltinResource<Shader> ("Internal-GUITextureClipText.shader"); + kGUITextMaterial = Material::CreateMaterial(*shader, Object::kHideAndDontSave); + InitializeGUIClipTexture(); + } + + return kGUITextMaterial; +} + +GUIStyle::GUIStyle() +{ + m_Alignment = 0; + m_RichText = true; + m_WordWrap = 0; + m_Clipping = 0; + m_ImagePosition = 0; + m_ContentOffset = Vector2f(0.0f, 0.0f); + m_ClipOffset = Vector2f(0.0f, 0.0f); + m_FixedWidth = 0; + m_FixedHeight = 0; + m_FontSize = 0; + m_FontStyle = 0; + m_StretchWidth = true; + m_StretchHeight = false; + +} + +GUIStyle::GUIStyle (const GUIStyle &other) +{ + m_Name = other.m_Name; + m_Normal = other.m_Normal; + m_Hover = other.m_Hover; + m_Active = other.m_Active; + m_Focused = other.m_Focused; + m_OnNormal = other.m_OnNormal; + m_OnHover = other.m_OnHover; + m_OnActive = other.m_OnActive; + m_OnFocused = other.m_OnFocused; + m_Border = other.m_Border; + m_Margin = other.m_Margin; + m_Padding = other.m_Padding; + m_Overflow = other.m_Overflow; + m_Font = other.m_Font; + m_Alignment = other.m_Alignment; + m_RichText = other.m_RichText; + m_WordWrap = other.m_WordWrap; + m_Clipping = other.m_Clipping; + m_ImagePosition = other.m_ImagePosition; + m_ContentOffset = other.m_ContentOffset; + m_ClipOffset = other.m_ClipOffset; + m_FixedWidth = other.m_FixedWidth; + m_FixedHeight = other.m_FixedHeight; + m_FontSize = other.m_FontSize; + m_FontStyle = other.m_FontStyle; + m_StretchWidth = other.m_StretchWidth; + m_StretchHeight = other.m_StretchHeight; +} + + +void GUIStyle::SetDefaultFont (Font *font) +{ + using namespace GUIStyle_Static; + if (font != NULL) + s_DefaultFont = font; + else + s_DefaultFont = GetBuiltinResource<Font> (kDefaultFontName); +} +Font*GUIStyle::GetDefaultFont () +{ + return GUIStyle_Static::s_DefaultFont; +} + +void GUIStyle::Draw (GUIState &state, const Rectf &screenRect, GUIContent &content, int controlID, bool on) const +{ + InputEvent &evt (*state.m_CurrentEvent); + int hot = IMGUI::GetHotControl (state); + bool enabled = state.m_OnGUIState.m_Enabled; +#if ENABLE_NEW_EVENT_SYSTEM + bool mouseOver = screenRect.Contains (evt.touch.pos); +#else + bool mouseOver = screenRect.Contains (evt.mousePosition); +#endif + bool isHover = mouseOver && state.m_CanvasGUIState.m_GUIClipState.GetEnabled(); + bool isHover2 = isHover && (hot == controlID || hot == 0); + + if (isHover) + state.m_CanvasGUIState.m_IsMouseUsed = true; + + bool isActive = (controlID == hot) && enabled && mouseOver; + bool hasKey = state.m_MultiFrameGUIState.m_KeyboardControl == controlID && enabled && state.m_OnGUIState.m_ShowKeyboardControl; + + Draw (state, screenRect, content, isHover2, isActive, on, hasKey); + + + // #if UNITY_GUI_SUPPORT_TOOLTIP + if (content.m_Tooltip.text != NULL && content.m_Tooltip.length != 0) + { + if (isHover || isActive || hot == controlID) + { + SetMouseTooltip (state, content.m_Tooltip, screenRect); + } + if (hasKey) + state.m_OnGUIState.SetKeyTooltip (content.m_Tooltip); + } + // #endif +} + +void GUIStyle::SetMouseTooltip (GUIState& state, const UTF16String& tooltip, const Rectf& screenRect) +{ + state.m_OnGUIState.SetMouseTooltip (tooltip); +#if UNITY_EDITOR + Vector2f v = GetGUIManager().GetGUIPixelOffset(); + v += state.m_CanvasGUIState.m_GUIClipState.Unclip (Vector2f (screenRect.x, screenRect.y)); + GetTooltipManager().SetTooltip (tooltip, Rectf (v.x, v.y, screenRect.width, screenRect.height)); +#endif + +} + + +void GUIStyle::Draw (GUIState &state, const Rectf &screenRect, GUIContent &content, bool isHover, bool isActive, bool on, bool hasKeyboardFocus) const +{ + #if UNITY_EDITOR + if (HighlighterCore::s_SearchMode == kHighlightByContent || HighlighterCore::s_SearchMode == kHighlightAuto) + HighlighterCore::Handle (state, screenRect, content.m_Text); + #endif + + // always draw of pixel coordinates + Rectf rounded = ClampRect (screenRect); + float right = Roundf (rounded.x + rounded.width); + float bottom = Roundf (rounded.y + rounded.height); + rounded.x = Roundf(rounded.x); + rounded.y = Roundf(rounded.y); + rounded.width = right - rounded.x; + rounded.height = bottom - rounded.y; + + Rectf visibleRect = state.m_CanvasGUIState.m_GUIClipState.GetVisibleRect(); + + if (bottom < visibleRect.y || rounded.y > visibleRect.GetBottom()) + return; + + isHover = state.m_EternalGUIState->m_AllowHover && isHover; + const GUIStyleState *gss = GetGUIStyleState (state, isHover, isActive, on, hasKeyboardFocus); + DrawBackground (state, rounded, gss); + DrawContent (state, rounded, content, gss); +} + +void GUIStyle::DrawWithTextSelection (GUIState &state, const Rectf &screenRect, GUIContent &content, bool isHover, bool isActive, bool on, bool hasKeyboardFocus, bool drawSelectionAsComposition, int cursorFirst, int cursorLast, const ColorRGBAf &cursorColor, const ColorRGBAf &selectionColor) const { + // always draw of pixel coordinates + Rectf rounded = ClampRect (screenRect); + rounded.x = Roundf(rounded.x); + rounded.y = Roundf(rounded.y); + rounded.width = Roundf(rounded.width); + rounded.height = Roundf(rounded.height); + + const GUIStyleState *gss = GetGUIStyleState (state, isHover, isActive, on, hasKeyboardFocus); + DrawBackground (state, rounded, gss); + if (hasKeyboardFocus) + { + if (drawSelectionAsComposition) + { + DrawTextUnderline (state, rounded, content, cursorFirst, cursorLast, gss); + // draw cursor. + DrawTextSelection (state, rounded, content, cursorLast, cursorLast, cursorColor, selectionColor); + } + else + DrawTextSelection (state, rounded, content, cursorFirst, cursorLast, cursorColor, selectionColor); + } + DrawContent (state, rounded, content, gss); +} + +void GUIStyle::CalcMinMaxWidth (GUIContent &content, float *minWidth, float *maxWidth) const { + // If we have a fixed width, that becoms both min and max + if (m_FixedWidth != 0) { + *minWidth = *maxWidth = m_FixedWidth; + return; + } + + TextMeshGenerator2 &tmgen = TextMeshGenerator2::Get (content.m_Text, &GetCurrentFont(), (TextAnchor)m_Alignment, kAuto, 0, s_TabSize, 1.0f, m_RichText, true, ColorRGBA32(0xffffffff), m_Font ? m_FontSize : 0, m_Font ? m_FontStyle : 0); + Vector2f size = tmgen.GetSize (); + // If we're word wrapping, we'll never go below 32 pixels. + // Ideally, we should get the size of the largest word, but that is just too painful. + *maxWidth = size.x; + if (m_WordWrap) + *minWidth = min (32.0f, size.x); + else + *minWidth = size.x; + + if (content.m_Image) { + float iWidth = content.m_Image->GetDataWidth(); + switch (m_ImagePosition) { + case kImageLeft: + *minWidth += iWidth; + *maxWidth += iWidth; + break; + case kImageOnly: + *minWidth = *maxWidth = iWidth; + break; + case kImageAbove: + *minWidth = max (iWidth, *minWidth); + *maxWidth = max (iWidth, *maxWidth); + break; + case kTextOnly: + break; + } + } + + *minWidth += m_Padding.left + m_Padding.right; + *maxWidth += m_Padding.left + m_Padding.right; +} + +Vector2f GUIStyle::GetCursorPixelPosition (const Rectf &screenRect, GUIContent &content, int cursorStringIndex) const { + TextMeshGenerator2 *tmgen = GetGenerator (screenRect, content); + if (tmgen) + return tmgen->GetCursorPosition(m_Padding.Remove (screenRect), cursorStringIndex) + (m_ContentOffset + m_ClipOffset); + else + return Vector2f (0,0); +} + +int GUIStyle::GetCursorStringIndex (const Rectf &screenRect, GUIContent &content, const Vector2f &cursorPixelPosition) const { + TextMeshGenerator2 *tmgen = GetGenerator (screenRect, content); + if (tmgen) + return tmgen->GetCursorIndexAtPosition(m_Padding.Remove (screenRect), cursorPixelPosition - (m_ContentOffset + m_ClipOffset)); + return 0; +} + +Font& GUIStyle::GetCurrentFont () const +{ + using namespace GUIStyle_Static; + Font* thisFont = m_Font; + if (thisFont != NULL) + return *thisFont; + + Font* defaultFont = s_DefaultFont; + if (defaultFont != NULL) + return *defaultFont; + + return GetBuiltinFont (); +} + +Font &GUIStyle::GetBuiltinFont () +{ + Font* builtinFont = GUIStyle_Static::s_BuiltinFont; + if (builtinFont != NULL) + return *builtinFont; + + GUIStyle_Static::s_BuiltinFont = builtinFont = GetBuiltinResource<Font> (kDefaultFontName); + if (builtinFont == NULL) + { + LogString ("Couldn't load default font or font material!"); + } + + return *builtinFont; +} + +float GUIStyle::GetLineHeight () const { + Font& f = GetCurrentFont(); + return f.GetLineSpacing (m_FontSize); +} + +int GUIStyle::GetNumCharactersThatFitWithinWidth (const UTF16String &text, float width) const +{ + Font &f = GetCurrentFont(); + + // Call before GetCharacterWidth to ensure character data has been setup + f.CacheFontForText (text.text, text.length); + + width -= m_Padding.left + m_Padding.right; + + float currentWidth = 0; + unsigned stringlen = text.length; + int numChars = stringlen; + for (int i=0; i<stringlen; i++) + { + int c = text[i]; + float characterWidth = f.GetCharacterWidth (c, m_FontSize, m_FontStyle); + if (characterWidth == 0.f) + { + return -1; // failed to get character width + } + + currentWidth += characterWidth; + if (currentWidth > width) + { + numChars = i; + break; + } + } + + return numChars; +} + +float GUIStyle::CalcHeight (GUIContent &content, float width) const { + if (m_FixedHeight != 0.0f) + return m_FixedHeight; + + Vector2f imageSize (0,0), textSize (0,0); + Texture *image = content.m_Image; + if (image != NULL) + imageSize = Vector2f (image->GetDataWidth(), image->GetDataHeight()); + + float contentHeight = 0; + + TextMeshGenerator2 *tmgen = GetGenerator (Rectf (0,0,width, 1000), content); + if (tmgen) + textSize = tmgen->GetSize (); + switch (m_ImagePosition) { + case kImageLeft: { + contentHeight = max (textSize.y, imageSize.y); + break; + } + case kImageAbove: { + contentHeight = textSize.y + imageSize.y; + break; + } + case kImageOnly: + contentHeight = imageSize.y; + break; + case kTextOnly: { + contentHeight = textSize.y; + break; + } + } + + return contentHeight + m_Padding.top + m_Padding.bottom; +} + +Vector2f GUIStyle::CalcSize (GUIContent &content) const { + Texture *image = content.m_Image; + + if (m_FixedHeight != 0.0f && m_FixedWidth != 0.0f) + return Vector2f (m_FixedWidth, m_FixedHeight); + + Vector2f textSize (0,0), imageSize (0,0); + if (content.m_Text.length != 0 && m_ImagePosition != kImageOnly) + textSize = GetGenerator(Rectf (0,0,0,0), content)->GetSize (); + if (image != NULL && m_ImagePosition != kTextOnly) + imageSize = Vector2f (image->GetDataWidth(), image->GetDataHeight()); + + Vector2f size (0,0); + switch (m_ImagePosition) { + case kImageLeft: + if (imageSize.x > 0) + { + if ((s_GUIStyleIconSizeX != 0) && (s_GUIStyleIconSizeY != 0)) + { + imageSize.x = s_GUIStyleIconSizeX; + imageSize.y = s_GUIStyleIconSizeY; + } + } + size = Vector2f (textSize.x + imageSize.x, max (textSize.y, imageSize.y)); + break; + case kImageAbove: + size = Vector2f (max (textSize.x, imageSize.x), textSize.y + imageSize.y); + break; + case kImageOnly: + size = imageSize; + break; + case kTextOnly: + size = textSize; + break; + } + + // If we + if (content.m_Text.length == 0 && image == NULL && m_ImagePosition != kImageOnly) + { + size.y = GetCurrentFont().GetLineSpacing(m_FontSize); + } + size+= m_Padding.Size (); + if (m_FixedWidth != 0.0f) + size.x = m_FixedWidth; + if (m_FixedHeight != 0.0f) + size.y = m_FixedHeight; + return size; +} + +const GUIStyleState *GUIStyle::GetGUIStyleState (GUIState &state, bool isHover, bool isActive, bool on, bool hasKeyboardFocus) const +{ + const GUIStyleState *t = NULL; + if (!on) { + // If the GUI is disabled, return the normal style + if (isHover) + t = m_Hover.background ? &m_Hover : t; + if (hasKeyboardFocus) + t = m_Focused.background ? &m_Focused : (m_Hover.background ? &m_Hover : t); + if (isActive && isHover) + t = m_Active.background ? &m_Active : t; + if (!state.m_OnGUIState.m_Enabled) + t = &m_Normal; + } else { + // If the GUI is disabled, return the normal style + if (isHover) + t = m_OnHover.background ? &m_OnHover : t; + if (hasKeyboardFocus) + t = m_OnFocused.background ? &m_OnFocused : (m_OnHover.background ? &m_OnHover : t); + if (isActive && isHover) + t = m_OnActive.background ? &m_OnActive : t; + if (!state.m_OnGUIState.m_Enabled) + t = &m_Normal; + + if (t == NULL || !t->background || !state.m_OnGUIState.m_Enabled) + t = &m_OnNormal; + } + + if (t == NULL || !t->background) + t = &m_Normal; + return t; +} + +static void DrawClippedTexture (const Rectf &screenRect, Texture *image, float leftBorder, float rightBorder, float topBorder, float bottomBorder, const ColorRGBAf &color) +{ + DrawGUITexture (screenRect, image, int(leftBorder), int(rightBorder), int(topBorder), int(bottomBorder), color, GetGUIBlendMaterial()); +} + + +void GUIStyle::DrawBackground (GUIState &state, const Rectf &screenRect, const GUIStyleState *gss) const { +#if ENABLE_RETAINEDGUI + if (state.m_OnGUIState.m_CaptureBlock) + { + if (gss->background) + { + GUIVertexData* data = new GUIVertexData (&GUIVertexDataFormat::vtxPosColorUV0UV1); + GUIUtils::BuildImage (m_Overflow.Add (screenRect), m_Border, gss->background, Vector4f(1.0f, 1.0f, 0.0f, 0.0f), *data); + GUIClipRegion temp (state.m_CanvasGUIState.m_GUIClipState.GetVisibleRect()); + GUIUtils::BuildClipCoords (&temp, *data); + + ColorRGBAf backgroundColor = state.m_OnGUIState.m_Color * state.m_OnGUIState.m_BackgroundColor; + if (!state.m_OnGUIState.m_Enabled) + backgroundColor.a *= .5f; + GUIUtils::BuildColor(backgroundColor, *data); + + state.m_OnGUIState.m_CaptureBlock->push_back (GUIGraphicsCacheBlock (data, GetGUIBlendMaterial())); + } + return; + } +#endif + + Rectf visibleRect = state.m_CanvasGUIState.m_GUIClipState.GetVisibleRect(); + SetGUIClipRect(visibleRect); + + if (gss->background) { + ColorRGBAf backgroundColor = state.m_OnGUIState.m_Color * state.m_OnGUIState.m_BackgroundColor; + if (!state.m_OnGUIState.m_Enabled) + backgroundColor *= ColorRGBAf (1,1,1,.5f); + +#if UNITY_EDITOR + OptimizedGUIBlock *block = GetCaptureGUIBlock (); + if (block) + { + block->QueueBackground (m_Overflow.Add (screenRect), gss->background, m_Border, backgroundColor, state.m_CanvasGUIState.m_GUIClipState.GetVisibleRect()); + return; + } +#endif + + DrawClippedTexture (m_Overflow.Add (screenRect), gss->background, m_Border.left, m_Border.right, m_Border.top, m_Border.bottom, backgroundColor); + } +} + +void GUIStyle::RenderText (const Rectf &screenRect, TextMeshGenerator2 &tmgen, ColorRGBAf color) const { + // Configure the shaders + Font& currentFont = GetCurrentFont(); + + Material *material = GetGUITextMaterial (); + + // now we set color using TextMeshGenerator vertices, rather then using the material, so set material color to white. + if (IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_2_a1)) + color = ColorRGBA32(0xffffffff); + + static SHADERPROP (Color); + static SHADERPROP (MainTex); + ShaderLab::PropertySheet& properties = material->GetWritableProperties(); + + properties.SetVector(kSLPropColor, color.GetPtr()); + + Texture* fontTexture = currentFont.GetTexture(); + + // In the case when font is a custum font, GetTexture() will return NULL, so in this case take the main texture from font's material + if (fontTexture == NULL && currentFont.GetMaterial()) + { + fontTexture = currentFont.GetMaterial()->GetTexture(kSLPropMainTex); + } + + properties.SetTexture(kSLPropMainTex, fontTexture); + + GfxDevice &device = GetGfxDevice (); + + float matWorld[16], matView[16]; + + CopyMatrix(device.GetViewMatrix(), matView); + CopyMatrix(device.GetWorldMatrix(), matWorld); + + Matrix4x4f textMatrix; + + Vector2f offset = tmgen.GetTextOffset (screenRect); + + textMatrix.SetTranslate (Vector3f (offset.x, offset.y, 0.0f)); + + device.SetViewMatrix (textMatrix.GetPtr()); + + int passCount = material->GetPassCount (); + for (int i=0;i < passCount ;i++) + { + const ChannelAssigns* channels = material->SetPass (i); + tmgen.RenderRaw (*channels); + } + device.SetViewMatrix(matView); + device.SetWorldMatrix(matWorld); +} + +TextMeshGenerator2* GUIStyle::GetGenerator (const Rectf &screenRect, GUIContent &content, ColorRGBA32 color) const +{ + if (!IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_2_a1)) + color = 0xffffffff; + return IMGUI::GetGenerator (m_Padding.Remove (screenRect), content, GetCurrentFont(), (TextAnchor)m_Alignment, m_WordWrap, m_RichText, color, m_FontSize, m_FontStyle, (ImagePosition)m_ImagePosition); +} + +TextMeshGenerator2* GUIStyle::GetGenerator (const Rectf &screenRect, GUIContent &content) const +{ + ColorRGBA32 color = 0xffffffff; + if (IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_2_a1)) + { + GUIState &state = GetGUIState(); + ColorRGBAf imageColor = state.m_OnGUIState.m_Color * state.m_OnGUIState.m_ContentColor; + ColorRGBAf textColor = m_Normal.textColor * imageColor; + if (!state.m_OnGUIState.m_Enabled) { + textColor.a *= .5f; + } + color = textColor; + } + return IMGUI::GetGenerator (m_Padding.Remove (screenRect), content, GetCurrentFont(), (TextAnchor)m_Alignment, m_WordWrap, m_RichText, color, m_FontSize, m_FontStyle, (ImagePosition)m_ImagePosition); +} + + +void GUIStyle::CalcContentRects (const Rectf &contentRect, Vector2f imageSize, Vector2f textSize, Rectf &imageRect, Rectf &textRect, float &width, float &height, int imagePosition, int alignment, Vector2f contentOffset) +{ + // Sum them up depending on contents layout. + width = 0; + height = 0; + switch (imagePosition) { + case kImageLeft: + // Scale the image down if we need the room + if (imageSize.x > 0) + { + if ((s_GUIStyleIconSizeX == 0) || (s_GUIStyleIconSizeY == 0)) + { + float imageScale = clamp (min ((contentRect.Width() - textSize.x) / imageSize.x, contentRect.Height() / imageSize.y), 0.0f, 1.0f); + + imageSize.x = Roundf (imageSize.x * imageScale); + imageSize.y = Roundf (imageSize.y * imageScale); + } + else + { + imageSize.x = s_GUIStyleIconSizeX; + imageSize.y = s_GUIStyleIconSizeY; + } + } + + width = imageSize.x + textSize.x; + height = max (imageSize.y, textSize.y); + break; + case kImageAbove: + // Scale the image down if we need the room + if (imageSize.x > 0) + { + if ((s_GUIStyleIconSizeX == 0) || (s_GUIStyleIconSizeY == 0)) + { + float imageScale = clamp (min ((contentRect.Height() - textSize.y) / imageSize.y, contentRect.Width() / imageSize.x), 0.0f, 1.0f); + imageSize.x = Roundf (imageSize.x * imageScale); + imageSize.y = Roundf (imageSize.y * imageScale); + } + else + { + imageSize.x = s_GUIStyleIconSizeX; + imageSize.y = s_GUIStyleIconSizeY; + } + } + + width = max (imageSize.x, textSize.x); + height = imageSize.y + textSize.y; + break; + case kImageOnly: + // Scale the image down if we need the room + if (imageSize.x > 0) + { + if ((s_GUIStyleIconSizeX == 0) || (s_GUIStyleIconSizeY == 0)) + { + float imageScale = min (min (contentRect.Width() / imageSize.x, contentRect.Height() / imageSize.y), 1.0f); + imageSize.x = Roundf (imageSize.x * imageScale); + imageSize.y = Roundf (imageSize.y * imageScale); + } + else + { + imageSize.x = s_GUIStyleIconSizeX; + imageSize.y = s_GUIStyleIconSizeY; + } + } + + width = imageSize.x; + height = imageSize.y; + break; + case kTextOnly: + width = textSize.x; + height = textSize.y; + break; + } + + // We now have the combined sizes - need to know where to put them. + float xAlign = 0, yAlign = 0; + switch (alignment) { + case kUpperLeft: + xAlign = 0; yAlign = 0; break; + case kUpperCenter: + xAlign = .5f; yAlign = 0; break; + case kUpperRight: + xAlign = 1; yAlign = 0; break; + case kMiddleLeft: + xAlign = 0; yAlign = .5f; break; + case kMiddleCenter: + xAlign = .5f; yAlign = .5f; break; + case kMiddleRight: + xAlign = 1; yAlign = .5f; break; + case kLowerLeft: + xAlign = 0; yAlign = 1; break; + case kLowerCenter: + xAlign = .5f; yAlign = 1; break; + case kLowerRight: + xAlign = 1; yAlign = 1; break; + } + // We need to round as centering the text can position it on half a pixel + float x = Roundf (contentRect.x + (contentRect.width - width) * xAlign + contentOffset.x); + float y = Roundf (contentRect.y + (contentRect.height - height) * yAlign + contentOffset.y); + + switch (imagePosition) { + case kImageLeft: + if (imageSize.x > 0.0f) + imageRect = Rectf (x, y + (height - imageSize.y) * .5f, imageSize.x, imageSize.y); + if (textSize.x > 0.0f && imageSize.x > 0.0f) + textRect = Rectf (x + imageSize.x + 1, y + (height - textSize.y) * .5f, textSize.x, textSize.y); + else if (textSize.x > 0.0f) + textRect = Rectf (x, y + (height - textSize.y) * .5f, textSize.x, textSize.y); + break; + case kImageAbove: + if (imageSize.x > 0.0f) + imageRect = Rectf (Roundf (x + (width - imageSize.x) * .5f), y, imageSize.x, imageSize.y); + if (textSize.x > 0.0f) + textRect = Rectf (Roundf (x + (width - textSize.x) * .5f), y + imageSize.y, textSize.x, textSize.y); + + break; + case kImageOnly: + if (imageSize.x > 0.0f) + imageRect = Rectf (Roundf (x + (width - imageSize.x) * .5f), y, imageSize.x, imageSize.y); + break; + case kTextOnly: + if (textSize.x > 0.0f) + textRect = Rectf (x, y, textSize.x, textSize.y); + break; + } +} + +void GUIStyle::DrawContent (GUIState &state, const Rectf &screenRect, GUIContent &content, const GUIStyleState *gss) const +{ + Vector2f textSize (0,0), imageSize (0,0); + TextMeshGenerator2 *tmgen = NULL; + + // we now have the location of the contents in the rects. Now to draw it. + // Setup the color + ColorRGBAf imageColor = state.m_OnGUIState.m_Color * state.m_OnGUIState.m_ContentColor; + ColorRGBAf textColor = gss->textColor * imageColor; + if (!state.m_OnGUIState.m_Enabled) { + textColor.a *= .5f; + imageColor.a *= .5f; + } + + if (m_ImagePosition != kImageOnly && content.m_Text.length != 0) + { + tmgen = GetGenerator (screenRect, content, textColor); + textSize = tmgen->GetSize(); + } + + // TODO: If we use word wrapping and imageLeft we should somehow subtract the width... + Texture *image = content.m_Image; + if (image != NULL && m_ImagePosition != kTextOnly) + imageSize = Vector2f (image->GetDataWidth(), image->GetDataHeight()); + + Rectf imageRect (0,0,0,0), textRect (0,0,0,0); + float width, height; + Rectf contentRect = m_Padding.Remove (screenRect); + CalcContentRects (contentRect, imageSize, textSize, imageRect, textRect, width, height, m_ImagePosition, m_Alignment, m_ContentOffset); + +#if ENABLE_RETAINEDGUI + if (state.m_OnGUIState.m_CaptureBlock) + { + GUIVertexData* data = new GUIVertexData (&GUIVertexDataFormat::vtxPosColorUV0UV1); + GUIUtils::BuildText(textRect, *tmgen, *data); + + GUIClipRegion temp (state.m_CanvasGUIState.m_GUIClipState.GetVisibleRect()); + GUIUtils::BuildClipCoords (&temp, *data); + + GUIUtils::BuildColor(textColor, *data); + + state.m_OnGUIState.m_CaptureBlock->push_back (GUIGraphicsCacheBlock (data, GetGUITextMaterial())); + return; + } +#endif + // We know where the various components go, now we draw them (with lots of clipping shit) + Rectf visibleRect = state.m_CanvasGUIState.m_GUIClipState.GetVisibleRect(); + +#if UNITY_EDITOR + OptimizedGUIBlock *block = GetCaptureGUIBlock (); + if (block) + { + Rectf newClipRect, clipRect = visibleRect; + if (m_Clipping != kOverflow && (width > contentRect.width || height > contentRect.height)) + { + newClipRect = contentRect; + newClipRect.x += (m_ContentOffset.x + m_ClipOffset.x); + newClipRect.y += (m_ContentOffset.y + m_ClipOffset.y); + newClipRect.Clamp (clipRect); + } else { + newClipRect = clipRect; + } + + // Queue the text for later processing + if (textRect.width != 0.0f) + { + block->QueueText (textRect, tmgen, newClipRect); + } + // Render image + if (imageRect.width != 0.0f) + block->QueueTexture (imageRect, image, imageColor, clipRect); + return; + } +#endif + + // Do clipping of content + bool needClipping = false; + Rectf newClipRect; + if (m_Clipping != kOverflow && (width > contentRect.width || height > contentRect.height)) { + needClipping = true; + newClipRect = contentRect; + newClipRect.x += (m_ContentOffset.x + m_ClipOffset.x); + newClipRect.y += (m_ContentOffset.y + m_ClipOffset.y); + newClipRect.Clamp (visibleRect); + if (newClipRect.width == 0.0f || newClipRect.height == 0.0f) + return; + SetGUIClipRect (newClipRect); + } else { + SetGUIClipRect(visibleRect); + } + + // Render text + if (textRect.width != 0.0f && tmgen != NULL) + { + RenderText (textRect, *tmgen, textColor); + } + // Render image + if (imageRect.width != 0.0f) + DrawClippedTexture (imageRect, image, 0,0,0,0,imageColor); + + // If we used custom clipping, restore it + if (needClipping) + SetGUIClipRect (visibleRect); + +} + +Rectf GUIStyle::ClampRect (const Rectf &screenRect) const +{ + return Rectf (screenRect.x, screenRect.y, m_FixedWidth ? m_FixedWidth : screenRect.Width(), m_FixedHeight ? m_FixedHeight : screenRect.Height()); +} + +void GUIStyle::DrawCursor(GUIState &state, const Rectf &screenRect, GUIContent &content, int position, const ColorRGBAf &cursorColor) const +{ + if (!state.m_OnGUIState.m_Enabled) + return; + + Texture *tex = builtintex::GetWhiteTexture(); + Font& currentFont = GetCurrentFont(); + + float lineHeight = currentFont.GetLineSpacing (m_FontSize); + Material *material = GetGUIBlendMaterial(); + + ColorRGBA32 textureColor = cursorColor * state.m_OnGUIState.m_Color; + Vector2f cursorPos = GetCursorPixelPosition (screenRect, content, position) - m_ClipOffset; + DrawGUITexture(Rectf (cursorPos.x, cursorPos.y, 1, lineHeight),tex,textureColor,material); +} + +void GUIStyle::DrawTextSelection (GUIState &state, const Rectf &screenRect, GUIContent &content, int first, int last, const ColorRGBAf &cursorColor, const ColorRGBAf &selectionColor) const { + if (!state.m_OnGUIState.m_Enabled) + return; + Texture *tex = builtintex::GetWhiteTexture(); + + Font& currentFont = GetCurrentFont(); + + float lineHeight = currentFont.GetLineSpacing (m_FontSize); + Material *material = GetGUIBlendMaterial(); + + Rectf visibleRect = state.m_CanvasGUIState.m_GUIClipState.GetVisibleRect(); + SetGUIClipRect(visibleRect); + + // If the style uses clipping, just apply it - there's only ever one of these anyways + Rectf innerRect = m_Padding.Remove (screenRect); + Rectf clipRect; + if (m_Clipping) { + clipRect = visibleRect; + innerRect.Clamp (visibleRect); + innerRect.x += (m_ContentOffset.x + m_ClipOffset.x); + innerRect.y += (m_ContentOffset.y + m_ClipOffset.y); + + SetGUIClipRect (innerRect); + } + + // We don't have a selection, only a cursor position + if (first == last) { + ColorRGBA32 textureColor = cursorColor * state.m_OnGUIState.m_Color; + Vector2f cursorPos = GetCursorPixelPosition (screenRect, content, first) - m_ClipOffset; + + // if we auto-size the GUIStyle to the text, and the cursor is after the last character, it always gets clipped. + // Move it one pixel to the left, so it does not clip + if (first == content.m_Text.length && cursorPos.x >= screenRect.x + screenRect.width) + cursorPos.x--; + +#if UNITY_EDITOR + OptimizedGUIBlock *block = GetCaptureGUIBlock (); + if (block) + { + block->QueueTexture (Rectf (cursorPos.x, cursorPos.y, 1, lineHeight),tex,textureColor,visibleRect); + } else { + DrawGUITexture(Rectf (cursorPos.x, cursorPos.y, 1, lineHeight),tex,textureColor,material); + } +#else + DrawGUITexture(Rectf (cursorPos.x, cursorPos.y, 1, lineHeight),tex,textureColor,material); +#endif + } else { + ColorRGBA32 textureColor = selectionColor * state.m_OnGUIState.m_Color; + + int min = first < last ? first : last; + int max = first > last ? first : last; + + Vector2f minPos = GetCursorPixelPosition (screenRect, content, min) - m_ClipOffset; + Vector2f maxPos = GetCursorPixelPosition (screenRect, content, max) - m_ClipOffset; + + if (minPos.y == maxPos.y) + { +#if UNITY_EDITOR + OptimizedGUIBlock *block = GetCaptureGUIBlock (); + if (block) + { + block->QueueTexture (Rectf (minPos.x, minPos.y, maxPos.x - minPos.x + 1, lineHeight), tex, textureColor, visibleRect); + } else { +#endif + DrawGUITexture (Rectf (minPos.x, minPos.y, maxPos.x - minPos.x + 1, lineHeight), tex, textureColor, material); +#if UNITY_EDITOR + } +#endif + } else { +#if UNITY_EDITOR + OptimizedGUIBlock *block = GetCaptureGUIBlock (); + if (block) + { + // draw the first line - including end part + block->QueueTexture (Rectf (minPos.x, minPos.y, innerRect.GetRight() - minPos.x, lineHeight), tex, textureColor, visibleRect); + // Draw the middle part + block->QueueTexture (Rectf (innerRect.x, minPos.y + lineHeight, innerRect.Width(), maxPos.y - minPos.y - lineHeight), tex, textureColor, visibleRect); + // Draw the bottom line - up to selection + if (maxPos.x != innerRect.x) // Don't draw silly 1px line at the left when a newline is selected + block->QueueTexture (Rectf (innerRect.x, maxPos.y, maxPos.x - innerRect.x + 1, lineHeight), tex, textureColor, visibleRect); + } else { +#endif + // draw the first line - including end part + DrawGUITexture (Rectf (minPos.x, minPos.y, innerRect.GetRight() - minPos.x, lineHeight), tex, textureColor, material); + // Draw the middle part + DrawGUITexture (Rectf (innerRect.x, minPos.y + lineHeight, innerRect.Width(), maxPos.y - minPos.y - lineHeight), tex, textureColor, material); + // Draw the bottom line - up to selection + if (maxPos.x != innerRect.x) // Don't draw silly 1px line at the left when a newline is selected + DrawGUITexture (Rectf (innerRect.x, maxPos.y, maxPos.x - innerRect.x + 1, lineHeight), tex, textureColor, material); +#if UNITY_EDITOR + } +#endif + } + } + if (m_Clipping) + SetGUIClipRect (clipRect); + +} + +void GUIStyle::DrawTextUnderline (GUIState &state, const Rectf &screenRect, GUIContent &content, int first, int last, const GUIStyleState *gss) const +{ + if (!state.m_OnGUIState.m_Enabled) + return; + + Rectf visibleRect = state.m_CanvasGUIState.m_GUIClipState.GetVisibleRect(); + + SetGUIClipRect(visibleRect); + + Texture *tex = builtintex::GetWhiteTexture(); + Font& currentFont = GetCurrentFont(); + + float lineHeight = currentFont.GetLineSpacing (m_FontSize); + Material *material = GetGUIBlendMaterial(); + + // If the style uses clipping, just apply it - there's only ever one of these anyways + Rectf innerRect = m_Padding.Remove (screenRect); + Rectf clipRect; + + if (m_Clipping) { + clipRect = visibleRect; + innerRect.Clamp (visibleRect); + innerRect.x += (m_ContentOffset.x + m_ClipOffset.x); + innerRect.y += (m_ContentOffset.y + m_ClipOffset.y); + + SetGUIClipRect (innerRect); + } + + { + ColorRGBA32 textColor = gss->textColor * state.m_OnGUIState.m_Color * state.m_OnGUIState.m_ContentColor; + + int min = first < last ? first : last; + int max = first > last ? first : last; + + Vector2f pos = GetCursorPixelPosition (screenRect, content, min) - m_ClipOffset; + Vector2f maxPos = GetCursorPixelPosition (screenRect, content, max) - m_ClipOffset; + + float underlineSize = std::max(1.0f, lineHeight*0.03f); + float underlineOffset = lineHeight*0.95f-underlineSize; + + while(pos.y < maxPos.y - 0.01) + { +#if UNITY_EDITOR + OptimizedGUIBlock *block = GetCaptureGUIBlock (); + if (block) + { + block->QueueTexture (Rectf (pos.x, pos.y+underlineOffset, innerRect.GetRight() - pos.x + 1, underlineSize), tex, textColor, visibleRect); + } else +#endif + DrawGUITexture (Rectf (pos.x, pos.y+underlineOffset, innerRect.GetRight() - pos.x + 1, underlineSize), tex, textColor, material); + pos.y += ceilf(lineHeight); + pos.x = innerRect.x; + } +#if UNITY_EDITOR + OptimizedGUIBlock *block = GetCaptureGUIBlock (); + if (block) + { + block->QueueTexture (Rectf (pos.x, pos.y+underlineOffset, maxPos.x - pos.x + 1, underlineSize), tex, textColor, visibleRect); + } else +#endif + DrawGUITexture (Rectf (pos.x, pos.y+underlineOffset, maxPos.x - pos.x + 1, underlineSize), tex, textColor, material); + } + if (m_Clipping) + SetGUIClipRect (clipRect); +} + +void GUIStyle::SetStyleState (int stateIndex, ColorRGBAf textColor, Texture2D *background) { + GUIStyleState *gss = &m_Normal; + gss += stateIndex; + gss->background = background; + gss->textColor = textColor; +} + +void GUIStyle::SetGUIClipRect (const Rectf &screenRect) +{ + s_GUIClipRect = screenRect; + Matrix4x4f clipMatrix; + clipMatrix.SetIdentity(); + // In a divide-by-zero case, set these to infinity, so we don't render anything. + if (screenRect.width > 0.0f) + clipMatrix.Get (0,0) = (kGUIClipTextureSize-2.0f)/kGUIClipTextureSize / screenRect.width; + else + clipMatrix.Get (0,0) = numeric_limits<float>::infinity (); + if (screenRect.height > 0.0f) + clipMatrix.Get (1,1) = (kGUIClipTextureSize-2.0f)/kGUIClipTextureSize / screenRect.height; + else + clipMatrix.Get (1,1) = numeric_limits<float>::infinity (); + clipMatrix.Get (0,3) = -screenRect.x * clipMatrix.Get (0,0) + 1.0f/kGUIClipTextureSize; + clipMatrix.Get (1,3) = -screenRect.y * clipMatrix.Get (1,1) + 1.0f/kGUIClipTextureSize; + clipMatrix.Get (2,2) = clipMatrix.Get (3,3) = 0; // Kill all perspective/depth: Just put 1 in ZW texcoords + clipMatrix.Get (2,3) = clipMatrix.Get (3,3) = 1; // Kill all perspective/depth: Just put 1 in ZW texcoords + GetGfxDevice().GetBuiltinParamValues().SetMatrixParam(kShaderMatGUIClip, clipMatrix); +} + +Rectf GUIStyle::GetGUIClipRect () +{ + return s_GUIClipRect; +} + +MonoBehaviour* GetBuiltinSkin (int skin) +{ + // 0 == Game, 1 == Editor Light, 2 = Editor Dark + static PPtr<MonoBehaviour> skinObject[3] = { NULL, NULL, NULL }; + +#if UNITY_EDITOR + // Load skins (editor version) + if (!skinObject[0] || !skinObject[1] || !skinObject[2]) + { + // If this is a devel buildwe want to try and load the skins from the opened project + // (super useful when skinning the app). + if (IsDeveloperBuild ()) + { + // Try to load the skins from the current project + skinObject[0] = dynamic_pptr_cast<MonoBehaviour*> (GetMainAsset ("Assets/DefaultResources/GameSkin/GameSkin.GUISkin")); + skinObject[1] = dynamic_pptr_cast<MonoBehaviour*> (GetMainAsset (AppendPathName ("Assets/Editor Default Resources/", EditorResources::kLightSkinPath))); + skinObject[2] = dynamic_pptr_cast<MonoBehaviour*> (GetMainAsset (AppendPathName ("Assets/Editor Default Resources/", EditorResources::kDarkSkinPath))); + } + + // Load the game skin. + // We can not mark this object as dont save, because that will make it not be unloaded when unloading the player. + // When that happens in the player the serialized state will be lost. So instead we let it be unloaded and on next + // load it will be reloaded from disk. + if (!skinObject[0]) + { + Object *obj = GetBuiltinResourceManager ().GetResource (ClassID(MonoBehaviour), "GameSkin/GameSkin.guiskin"); + skinObject[0] = static_cast<MonoBehaviour*> (obj); + } + + // Load the light inspector skin. + if (!skinObject[1]) + skinObject[1] = GetEditorAssetBundle()->Get<MonoBehaviour>(EditorResources::kLightSkinPath); + + // Load the dark inspector skin. + if (!skinObject[2]) + skinObject[2] = GetEditorAssetBundle()->Get<MonoBehaviour>(EditorResources::kDarkSkinPath); + } +#else + // Players are much easier: we just load the game skin. + if (!skinObject[0]) + { + Object *obj = GetBuiltinResourceManager ().GetResource (ClassID(MonoBehaviour), "GameSkin/GameSkin.guiskin"); + skinObject[0] = static_cast<MonoBehaviour*> (obj); + } +#endif + + return skinObject[skin]; +} + +MonoBehaviour* GetDefaultSkin (int skinMode) +{ +#if UNITY_EDITOR + if (skinMode == 0) + return GetBuiltinSkin (0); + return GetBuiltinSkin (GetEditorResources().GetSkinIdx () + 1); +#else + return GetBuiltinSkin (0); +#endif +} diff --git a/Runtime/IMGUI/GUIStyle.h b/Runtime/IMGUI/GUIStyle.h new file mode 100644 index 0000000..d1af3ac --- /dev/null +++ b/Runtime/IMGUI/GUIStyle.h @@ -0,0 +1,297 @@ +#ifndef GUISTYLE_H +#define GUISTYLE_H + +#include "Runtime/Serialize/SerializeUtility.h" +#include "Runtime/Math/Vector2.h" +#include "Runtime/Math/Rect.h" +#include "Runtime/Graphics/Texture2D.h" +#include "Runtime/Math/Color.h" +#include "Runtime/IMGUI/TextUtil.h" +#include "Runtime/Mono/MonoBehaviour.h" + +class Font; +struct MonoString; +struct GUIState; +struct GUIContent; +class TextMeshGenerator2; +struct GUIGraphicsCacheBlock; +class GUIClipRegion; + +struct GUIStyleState { + DECLARE_SERIALIZE (GUIStyleState) + + /// Background image used by this style + PPtr<Texture2D> background; + /// The color of the text + ColorRGBAf textColor; + + GUIStyleState () : textColor (0,0,0,1) {} + GUIStyleState (const GUIStyleState &other) : background (other.background), textColor (other.textColor) {} +}; + +template<class TransferFunc> +void GUIStyleState::Transfer (TransferFunc& transfer) +{ + transfer.Transfer (background, "m_Background"); + transfer.Transfer (textColor, "m_TextColor"); +} + +/// Positioning of the image and the text withing a GUIStyle +enum ImagePosition { + /// Image is to the left of the text. + kImageLeft = 0, + /// Image is above the text. + kImageAbove = 1, + /// Only the image is displayed. + kImageOnly = 2, + /// Only the text is displayed. + kTextOnly = 3 +}; + + + +struct RectOffset { + DECLARE_SERIALIZE (RectOffset) + + int left, right, top, bottom; + + RectOffset () { left = right = top = bottom = 0; } + RectOffset (const RectOffset &other) + { + left = other.left; + right = other.right; + top = other.top; + bottom = other.bottom; + } + + int GetHorizontal () { return left + right; } + int GetVertical () { return top + bottom; } + + Rectf Add (const Rectf &r) const + { + return MinMaxRect (r.x - left, r.y - top, r.GetRight() + right, r.GetBottom() + bottom); + } + + Vector2f Size () const { + return Vector2f (left + right, top + bottom); + } + Rectf Remove (const Rectf &r) const + { + return MinMaxRect (r.x + left, r.y + top, r.GetRight() - right, r.GetBottom() - bottom); + } +}; + +template<class TransferFunc> +void RectOffset::Transfer (TransferFunc& transfer) +{ + transfer.Transfer (left, "m_Left"); + transfer.Transfer (right, "m_Right"); + transfer.Transfer (top, "m_Top"); + transfer.Transfer (bottom, "m_Bottom"); +} + +class GUIStyle { + public: + DECLARE_SERIALIZE (GUIStyle); + GUIStyle (); + GUIStyle (const GUIStyle &other); + UnityStr m_Name; + + GUIStyleState m_Normal; + GUIStyleState m_Hover; + GUIStyleState m_Active; + GUIStyleState m_Focused; + GUIStyleState m_OnNormal; + GUIStyleState m_OnHover; + GUIStyleState m_OnActive; + GUIStyleState m_OnFocused; + + /// Border of the background images + RectOffset m_Border; + + /// Spacing between this element and ones next to it + RectOffset m_Margin; + + /// Distance from outer edge to contents + RectOffset m_Padding; + + /// Extra size to use for the background images. + RectOffset m_Overflow; + + /// The font to use. If not set, the font is read from the main GUISkin + PPtr<Font> m_Font; + + /// Text alignment. + int m_Alignment; ///< enum { Upper Left = 0, Upper Center = 1, Upper Right = 2, Middle Left = 3, Middle Center = 4, Middle Right = 5, Lower Left = 6, Lower Center = 7, Lower Right = 8 } How is the content placed inside the control. + + /// Word wrap the text? + bool m_WordWrap; + + /// Use HTML-style markup + bool m_RichText; + + /// Clipping mode to use. + int m_Clipping; ///< enum { Overflow = 0, Clip = 1 } What happens with content that goes outside the control + + /// How image and text is combined. + int m_ImagePosition; ///< enum { Image Left = 0, Image Above = 1, Image Only = 2, Text Only = 3 } How text and image is placed in relation to each other. + + /// Pixel offset to apply to the content of this GUIstyle. + Vector2f m_ContentOffset; + + /// Clip offset + Vector2f m_ClipOffset; + + float m_FixedWidth; ///< If non-0, that axis is always draw at the specified size. + float m_FixedHeight; ///< If non-0, that axis is always draw at the specified size. + + /// The font size to use. Set to 0 to use default font size. Only applicable for dynamic fonts. + int m_FontSize; + + /// The font style to use. Only applicable for dynamic fonts. + int m_FontStyle; ///< enum { Normal = 0, Bold = 1, Italic = 2, Bold and Italic = 3 } Only applicable for dynamic fonts. + + bool m_StretchWidth, m_StretchHeight; + + /// Draw this GUI style + /// screenRect: Rectangle for the border + /// content: The text/image to stuff inside + /// isHover: Is the mouse over the element + /// isActive: Does the element have keyboard focus + /// on: Is the element on (as in a togglebutton) + void Draw (GUIState &state, const Rectf &screenRect, GUIContent &content, bool isHover, bool isActive, bool on, bool hasKeyboardFocus) const ; + + /// Draw this GUI style + /// screenRect: Rectangle for the border + /// content: The text/image to stuff inside + /// id: The controlID mouse of the element + /// isActive: Does the element have keyboard focus + /// on: Is the element on (as in a togglebutton) + void Draw (GUIState &state, const Rectf &screenRect, GUIContent &content, int controlID, bool on) const ; + + + /// screenRect: Rectangle for the border + /// content: The text/image to stuff inside + /// isHover: Is the mouse over the element + /// isActive: Does the element have keyboard focus + /// on: Is the element on (as in a togglebutton) + /// cursorFirst, last Where is the text selection + void DrawWithTextSelection (GUIState &state, const Rectf &screenRect, GUIContent &content, bool isHover, bool isActive, bool on, bool hasKeyboardFocus, bool drawSelectionAsComposition, int cursorFirst, int cursorLast, const ColorRGBAf &cursorColor, const ColorRGBAf &selectionColor) const; + + /// screenRect: Rectangle for the border + /// text/image: The text/image to stuff inside + /// position Where is the text selection + void DrawCursor(GUIState &state, const Rectf &screenRect, GUIContent &content, int position, const ColorRGBAf &cursorColor) const; + + /// Calculate the min & max widths of this element to correctly render the content + void CalcMinMaxWidth (GUIContent &content, float *minWidth, float *maxWidth) const; + /// Calculate the height of a component given a specific width + float CalcHeight (GUIContent &content, float width) const; + /// Calculate the size + Vector2f CalcSize (GUIContent &content) const; + /// Get the position of a specific character (used for finding out where to draw the cursor) + Vector2f GetCursorPixelPosition (const Rectf &screenRect, GUIContent &content, int cursorStringIndex) const; + /// Get the index of a given pixel position (used for finding out where in the string of a textfield the user clicked) + int GetCursorStringIndex (const Rectf &screenRect, GUIContent &content, const Vector2f &cursorPixelPosition) const; + + /// Get the height of one line, in pixels... + float GetLineHeight () const; + + /// returns number of characters that can fit within width, returns -1 if fails to shorten string + int GetNumCharactersThatFitWithinWidth (const UTF16String &text, float width) const; + + /// Set the default font (used by GUISkin) + static void SetDefaultFont (Font *font); + static Font*GetDefaultFont (); + + static Texture2D* GetClipTexture (); + + Font& GetCurrentFont () const; + static Font &GetBuiltinFont (); + + /// Set a specific style state. Used when changing style states from mono + void SetStyleState (int stateIndex, ColorRGBAf textColor, Texture2D *background); + + // Calculate where text and image go inside screenRect in order to fit imageSize and textSize + static void CalcContentRects (const Rectf &contentRect, Vector2f imageSize, Vector2f textSize, Rectf &imageRect, Rectf &textRect, float &width, float &height, int imagePosition, int alignment, Vector2f contentOffset); + + // Used by OptimizedGUIBlock + static Rectf GetGUIClipRect (); + static void SetGUIClipRect (const Rectf& rect); + + static void SetMouseTooltip (GUIState& state, const UTF16String& tooltip, const Rectf& screenRect); + private: + + /// Figure out which GUIStyle to use + const GUIStyleState *GetGUIStyleState (GUIState &state, bool isHover, bool isActive, bool on, bool hasKeyboardFocus) const; + /// Draw the background GUIStyle without any contents + void DrawBackground (GUIState &state, const Rectf &screenRect, const GUIStyleState *gss) const; + + + /// Draw the contents + void DrawContent (GUIState &state, const Rectf &screenRect, GUIContent &content, const GUIStyleState *gss) const; + /// Draw the text selection highlight. + void DrawTextSelection (GUIState &state, const Rectf &screenRect, GUIContent &content, int first, int last, const ColorRGBAf &cursorColor, const ColorRGBAf &selectionColor) const; + void DrawTextUnderline (GUIState &state, const Rectf &screenRect, GUIContent &content, int first, int last, const GUIStyleState *gss) const; + + // set up all the static text-rendering vars from this settings. + void RenderText (const Rectf &screenRect, TextMeshGenerator2 &tmgen, ColorRGBAf color) const; + + TextMeshGenerator2 *GetGenerator (const Rectf &screenRect, GUIContent &content, ColorRGBA32 color) const; + TextMeshGenerator2 *GetGenerator (const Rectf &screenRect, GUIContent &content) const; + + // Clamp a screenRect to be set to fixedWidth & height + Rectf ClampRect (const Rectf &screenRect) const; + +}; +template<class TransferFunc> +void GUIStyle::Transfer (TransferFunc& transfer) +{ + TRANSFER (m_Name); + transfer.Align (); + TRANSFER (m_Normal); + TRANSFER (m_Hover); + TRANSFER (m_Active); + TRANSFER (m_Focused); + TRANSFER (m_OnNormal); + TRANSFER (m_OnHover); + TRANSFER (m_OnActive); + TRANSFER (m_OnFocused); + + TRANSFER (m_Border); + TRANSFER (m_Margin); + TRANSFER (m_Padding); + TRANSFER (m_Overflow); + TRANSFER (m_Font); + TRANSFER (m_FontSize); + TRANSFER (m_FontStyle); + TRANSFER (m_Alignment); + TRANSFER (m_WordWrap); + TRANSFER (m_RichText); + transfer.Align(); + transfer.Transfer (m_Clipping, "m_TextClipping"); + TRANSFER (m_ImagePosition); + TRANSFER (m_ContentOffset); + TRANSFER (m_FixedWidth); + TRANSFER (m_FixedHeight); + TRANSFER (m_StretchWidth); + TRANSFER (m_StretchHeight); + transfer.Align(); +}; + +Material* GetGUIBlitMaterial (); +Material* GetGUIBlendMaterial (); + +void InitializeGUIClipTexture(); + +// skin: +// Game = 0 +// Light Skin = 1 +// Dark Skin = 2 +MonoBehaviour* GetBuiltinSkin (int skin); +// skinMode: +// Game = 0 +// Editor = 1 +MonoBehaviour* GetDefaultSkin (int skinMode); + +#endif diff --git a/Runtime/IMGUI/GUITest.cpp b/Runtime/IMGUI/GUITest.cpp new file mode 100644 index 0000000..2214097 --- /dev/null +++ b/Runtime/IMGUI/GUITest.cpp @@ -0,0 +1,209 @@ +#include "UnityPrefix.h" +#include "Configuration/UnityConfigure.h" + +// Disabled GUITests for lack of acceptable framework for native tests hitting scripting invocations (GetControlID). Rene is on the case! +#if 0 +//#if ENABLE_UNIT_TESTS + +#include "External/UnitTest++/src/UnitTest++.h" + +#include "Runtime/IMGUI/GUIState.h" +#include "Runtime/Misc/InputEvent.h" +#include "Runtime/IMGUI/IDList.h" + +static ObjectGUIState* gObjectGUIState; + +// Set up a clean GUIState for testing purposes. Delete it with DeleteTestGUIState +static GUIState &MakeTestGUIState () +{ + GUIState *state = new GUIState (); + state->m_EternalGUIState = new EternalGUIState (); + state->m_CurrentEvent = new InputEvent(); + state->m_CurrentEvent->Init(); + gObjectGUIState = new ObjectGUIState(); + return *state; +} + +static void DeleteTestGUIState (GUIState &state) +{ + delete gObjectGUIState; + delete &state; +} + +static void BeginOnGUI (GUIState &state, InputEvent evt) +{ + *state.m_CurrentEvent = evt; + state.BeginOnGUI (*gObjectGUIState); +} + +static void EndOnGUI (GUIState &state) +{ + state.EndOnGUI (); +} + +static InputEvent MakeLayoutEvent () +{ + InputEvent e; + e.type = InputEvent::kLayout; + return e; +} + +static InputEvent MakeRepaintEvent () +{ + InputEvent e; + e.type = InputEvent::kRepaint; + return e; +} + +static InputEvent MakeKeyDownEvent (char c) +{ + InputEvent e; + e.type = InputEvent::kKeyDown; + e.character = c; + return e; +} + +SUITE ( GUITests ) +{ +TEST (GUITests_IDListGeneration) +{ + GUIState &state = MakeTestGUIState (); + state.BeginFrame (); + BeginOnGUI (state, MakeLayoutEvent ()); + int v1 = state.GetControlID (1, kPassive); + int v2 = state.GetControlID (1, kPassive); + int v3 = state.GetControlID (2, kPassive); + EndOnGUI (state); + + // Check we get the same IDs next event + BeginOnGUI (state, MakeRepaintEvent ()); + CHECK_EQUAL (v1, state.GetControlID (1, kPassive)); + CHECK_EQUAL (v2, state.GetControlID (1, kPassive)); + CHECK_EQUAL (v3, state.GetControlID (2, kPassive)); + EndOnGUI (state); + + //check we correctly handle something going away. + BeginOnGUI (state, MakeRepaintEvent ()); + CHECK_EQUAL (v1, state.GetControlID (1, kPassive)); + CHECK_EQUAL (v3, state.GetControlID (2, kPassive)); + EndOnGUI (state); + + state.EndFrame (); + DeleteTestGUIState(state); +} + +TEST (GUITests_IDListTabFinding) +{ + GUIState &state = MakeTestGUIState (); + state.BeginFrame (); + + // Init & set up keycontrol + BeginOnGUI (state, MakeLayoutEvent ()); + int v1 = state.GetControlID (1, kKeyboard); + state.GetControlID (1, kPassive); + int v2 = state.GetControlID (1, kKeyboard); + int v3 = state.GetControlID (2, kKeyboard); + EndOnGUI (state); + state.m_MultiFrameGUIState.m_KeyboardControl = v2; + + // Run again to make sure we have the values - they are only recorded on keydown events + BeginOnGUI (state, MakeKeyDownEvent ('\t')); + state.GetControlID (1, kKeyboard); + state.GetControlID (1, kPassive); + state.GetControlID (1, kKeyboard); + state.GetControlID (2, kKeyboard); + + CHECK (state.m_ObjectGUIState->m_IDList.HasKeyboardControl()); + CHECK_EQUAL (v1, state.m_ObjectGUIState->m_IDList.GetPreviousKeyboardControlID()); + CHECK_EQUAL (v3, state.m_ObjectGUIState->m_IDList.GetNextKeyboardControlID()); + CHECK_EQUAL (v1, state.m_ObjectGUIState->m_IDList.GetFirstKeyboardControlID()); + CHECK_EQUAL (v3, state.m_ObjectGUIState->m_IDList.GetLastKeyboardControlID()); + + EndOnGUI (state); + state.EndFrame (); + DeleteTestGUIState(state); +} + +TEST (GUITests_IDListNamedKeyControls) +{ + GUIState &state = MakeTestGUIState (); + state.BeginFrame (); + + BeginOnGUI (state, MakeLayoutEvent ()); + state.GetControlID (1, kKeyboard); + state.SetNameOfNextKeyboardControl ("v1"); + int v1 = state.GetControlID (1, kKeyboard); + state.SetNameOfNextKeyboardControl ("v2"); + state.GetControlID (1, kPassive); + int v2 = state.GetControlID (1, kKeyboard); + state.GetControlID (2, kKeyboard); + state.SetNameOfNextKeyboardControl ("v3fake"); + EndOnGUI (state); + + // Run event chain so we can move to past events + BeginOnGUI (state, MakeRepaintEvent ()); + state.GetControlID (1, kKeyboard); + state.SetNameOfNextKeyboardControl ("v1"); + state.GetControlID (1, kKeyboard); + state.SetNameOfNextKeyboardControl ("v2"); + state.GetControlID (1, kPassive); + state.GetControlID (1, kKeyboard); + state.GetControlID (2, kKeyboard); + state.SetNameOfNextKeyboardControl ("v3fake"); + + CHECK_EQUAL (v1, state.GetIDOfNamedControl ("v1")); + CHECK_EQUAL (v2, state.GetIDOfNamedControl ("v2")); + EndOnGUI (state); + + state.EndFrame (); + DeleteTestGUIState(state); +} + +TEST (GUITests_IDListNativeGetsKBControl) +{ + GUIState &state = MakeTestGUIState (); + state.BeginFrame (); + + // First pass: Layout... get all the control ID's + BeginOnGUI (state, MakeLayoutEvent ()); + + // padding control at start + int id1 = state.GetControlID (1, kNative); + + // first named control + state.SetNameOfNextKeyboardControl ("named1"); + int id2 = state.GetControlID (1, kNative); + + // second named control... passive should be ignored! + state.SetNameOfNextKeyboardControl ("named2"); + int id3 = state.GetControlID (1, kPassive); + int id4 = state.GetControlID (1, kNative); + + // extra trailing controls + int id5 = state.GetControlID (1, kNative); + + // check the named ID's + CHECK_EQUAL (id2, state.GetIDOfNamedControl ("named1")); + CHECK_EQUAL (id4, state.GetIDOfNamedControl ("named2")); + EndOnGUI (state); + + // Now simulate a repaint... the control ID's should be the same as before! + BeginOnGUI (state, MakeRepaintEvent ()); + CHECK_EQUAL (id1, state.GetControlID (1, kNative)); + state.SetNameOfNextKeyboardControl ("v1"); + CHECK_EQUAL (id2, state.GetControlID (1, kNative)); + state.SetNameOfNextKeyboardControl ("v2"); + CHECK_EQUAL (id3, state.GetControlID (1, kPassive)); + CHECK_EQUAL (id4, state.GetControlID (1, kNative)); + CHECK_EQUAL (id5, state.GetControlID (1, kNative)); + + // also check that the named events STILL have the same ID's + CHECK_EQUAL (id2, state.GetIDOfNamedControl ("named1")); + CHECK_EQUAL (id4, state.GetIDOfNamedControl ("named2")); + EndOnGUI (state); + + state.EndFrame (); + DeleteTestGUIState(state); +} +} +#endif diff --git a/Runtime/IMGUI/GUIToggle.cpp b/Runtime/IMGUI/GUIToggle.cpp new file mode 100644 index 0000000..a8fde5a --- /dev/null +++ b/Runtime/IMGUI/GUIToggle.cpp @@ -0,0 +1,76 @@ +#include "UnityPrefix.h" +#include "Runtime/IMGUI/GUIToggle.h" +#include "Runtime/IMGUI/GUIStyle.h" +#include "Runtime/IMGUI/GUIState.h" +#include "Runtime/IMGUI/IMGUIUtils.h" + +namespace IMGUI +{ + +static const int kGUIToggleHash = -1784436876; + +bool GUIToggle (GUIState &state, const Rectf &position, bool value, GUIContent &content, GUIStyle &style, int id) +{ + InputEvent &evt (*state.m_CurrentEvent); + switch (GetEventTypeForControl (state, evt, id)) + { + case InputEvent::kMouseDown: + // If the mouse is inside the button, we say that we're the hot control +#if ENABLE_NEW_EVENT_SYSTEM + if (position.Contains (evt.touch.pos)) +#else + if (position.Contains (evt.mousePosition)) +#endif + { + GrabMouseControl (state, id); + evt.Use (); + } + break; + case InputEvent::kKeyDown: + if (evt.character == 32 && state.m_MultiFrameGUIState.m_KeyboardControl == id) + { + evt.Use (); + state.m_OnGUIState.m_Changed = true; + return !value; + } + break; + case InputEvent::kMouseUp: + if (HasMouseControl (state, id)) + { + ReleaseMouseControl (state); + + // If we got the mousedown, the mouseup is ours as well + // (no matter if the click was in the button or not) + evt.Use (); + + // toggle the passed-in value if the mouse was over the button & return true +#if ENABLE_NEW_EVENT_SYSTEM + if (position.Contains (evt.touch.pos)) +#else + if (position.Contains (evt.mousePosition)) +#endif + { + state.m_OnGUIState.m_Changed = true; + return !value; + } + } + break; + case InputEvent::kMouseDrag: + if (HasMouseControl (state, id)) + evt.Use (); + break; + + case InputEvent::kRepaint: + style.Draw (state, position, content, id, value); + break; + } + return value; +} + +bool GUIToggle (GUIState &state, const Rectf &position, bool value, GUIContent &content, GUIStyle &style) +{ + int id = GetControlID (state, kGUIToggleHash, kNative, position); + return GUIToggle (state, position, value, content, style, id); +} + +}
\ No newline at end of file diff --git a/Runtime/IMGUI/GUIToggle.h b/Runtime/IMGUI/GUIToggle.h new file mode 100644 index 0000000..3f9d619 --- /dev/null +++ b/Runtime/IMGUI/GUIToggle.h @@ -0,0 +1,16 @@ +#ifndef GUITOGGLE_H +#define GUITOGGLE_H + +#include "Runtime/Math/Rect.h" + +struct GUIState; +struct GUIContent; +class GUIStyle; + +namespace IMGUI +{ + bool GUIToggle (GUIState &state, const Rectf &position, bool value, GUIContent &content, GUIStyle &style, int id); + bool GUIToggle (GUIState &state, const Rectf &position, bool value, GUIContent &content, GUIStyle &style); +} + +#endif diff --git a/Runtime/IMGUI/GUIWindows.cpp b/Runtime/IMGUI/GUIWindows.cpp new file mode 100644 index 0000000..30a3f9b --- /dev/null +++ b/Runtime/IMGUI/GUIWindows.cpp @@ -0,0 +1,748 @@ +#include "UnityPrefix.h" +#include "Runtime/Misc/BuildSettings.h" + +#if ENABLE_UNITYGUI +#include "Runtime/IMGUI/GUIWindows.h" +#include "Runtime/IMGUI/IMGUIUtils.h" +#include "Runtime/IMGUI/GUIState.h" +#include "Runtime/IMGUI/IMGUIUtils.h" +#include "Runtime/IMGUI/GUIStyle.h" +#include "Runtime/Scripting/ScriptingUtility.h" +#include "Runtime/Scripting/ScriptingObjectWithIntPtrField.h" +#include "Runtime/Scripting/ScriptingManager.h" +#include "Runtime/Scripting/CommonScriptingClasses.h" +#include "Runtime/Scripting/Backend/ScriptingBackendApi.h" + + +#include <algorithm> // for std::sort +namespace IMGUI +{ + static Vector2f s_DragStartPos (0,0); // Start of the drag (mousePosition) + static Vector2f s_DragStartSize (0,0); // Value at start of drag. + + GUIWindow::GUIWindow () + { + m_Delegate = m_Skin = 0; + m_Moved = m_ForceRect = false; + m_ID = 0; + m_Style = 0; + } + + GUIWindow::~GUIWindow () + { + ReleaseScriptingObjects (); + } + + + void GUIWindow::ReleaseScriptingObjects () + { + if (m_Delegate) + { + scripting_gchandle_free (m_Delegate); + m_Delegate = 0; + } + if (m_Skin) + { + scripting_gchandle_free (m_Skin); + m_Skin = 0; + } + if (m_Style) + { + scripting_gchandle_free (m_Style); + m_Style = 0; + } + } + + void GUIWindow::OnGUI (GUIState& state) + { + InputEvent& evt (*state.m_CurrentEvent); + // Set up the state that was recorded for this window + state.m_OnGUIState.m_Color = m_Color; + state.m_OnGUIState.m_BackgroundColor = m_BackgroundColor; + state.m_OnGUIState.m_ContentColor = m_ContentColor; + state.m_OnGUIState.m_Enabled = m_Enabled; + state.m_CanvasGUIState.m_GUIClipState.SetMatrix (evt, m_Matrix); + state.m_MultiFrameGUIState.m_Windows->m_CurrentWindow = this; + + // Block OnHover calls into the scene if the window contains the mouse +#if ENABLE_NEW_EVENT_SYSTEM + if (evt.type == InputEvent::kRepaint && m_Position.Contains (evt.touch.pos)) +#else + if (evt.type == InputEvent::kRepaint && m_Position.Contains (evt.mousePosition)) +#endif + state.m_CanvasGUIState.m_IsMouseUsed = true; + + // Disable drawing keyboard focus if window doesn't have focus. + int hadShowKeyboardControl = state.m_OnGUIState.m_ShowKeyboardControl; + GUIWindowState* winState = state.m_MultiFrameGUIState.m_Windows; + state.m_OnGUIState.m_ShowKeyboardControl &= winState->m_FocusedWindow == m_ID; + + // If it's a repaint event, draw the background + ScriptingObjectPtr style = scripting_gchandle_get_target (m_Style); + if (style && evt.type == InputEvent::kRepaint) + { + GUIStyle* _style = ScriptingObjectWithIntPtrField<GUIStyle> (style).GetPtr(); +#if ENABLE_NEW_EVENT_SYSTEM + _style->Draw (state, m_Position, m_Title, m_Position.Contains (evt.touch.pos), false, state.m_MultiFrameGUIState.m_Windows->m_FocusedWindow == m_ID, false); +#else + _style->Draw (state, m_Position, m_Title, m_Position.Contains (evt.mousePosition), false, state.m_MultiFrameGUIState.m_Windows->m_FocusedWindow == m_ID, false); +#endif + } + + state.m_CanvasGUIState.m_GUIClipState.Push (*state.m_CurrentEvent, m_Position, Vector2f::zero, Vector2f::zero, false); + ObjectGUIState* old = state.m_ObjectGUIState; + state.BeginOnGUI (m_ObjectGUIState); + + // No exception handling here on purpose. + ScriptingInvocation invocation(MONO_COMMON.callGUIWindowDelegate); + invocation.AddObject(scripting_gchandle_get_target (m_Delegate)); + invocation.AddInt(m_ID); + invocation.AddObject(scripting_gchandle_get_target (m_Skin)); + invocation.AddInt((int)m_ForceRect); + invocation.AddFloat(m_Position.width); + invocation.AddFloat(m_Position.height); + invocation.AddObject(style); + + state.m_OnGUIState.m_ShowKeyboardControl = winState->m_FocusedWindow == m_ID; + + // we need to catch a log our own exceptions to properly handle ExitGUIException + ScriptingExceptionPtr exception = NULL; + invocation.logException = false; + + invocation.Invoke (&exception); + + if (exception) + { + // TODO: Kill GUI all the way down to the MonoBehaviour +#if ENABLE_MONO + void* excparams[] = {exception}; + MonoObject* res = CallStaticMonoMethod("GUIUtility", "EndGUIFromException", excparams); + if (!MonoObjectToBool(res)) + ::Scripting::LogException(exception, 0); +#endif + } + + state.EndOnGUI (); + state.m_ObjectGUIState = old; + state.m_CanvasGUIState.m_GUIClipState.Pop (evt); + state.m_MultiFrameGUIState.m_Windows->m_CurrentWindow = NULL; + + // make sure that the rest of the script shows keyboard focus + state.m_OnGUIState.m_ShowKeyboardControl = hadShowKeyboardControl; + } + + Rectf DoWindow (GUIState& state, int id, const Rectf &clientRect, ScriptingObjectPtr delegate, GUIContent& title, ScriptingObjectPtr style, ScriptingObjectPtr guiSkin, bool forceRectOnLayout, bool isModal) + { + GUIWindowState* winState = state.m_MultiFrameGUIState.m_Windows; + if (winState == NULL) + state.m_MultiFrameGUIState.m_Windows = winState = new GUIWindowState (); + + GUIWindow* win = winState->GetWindow (id); + if (!win) + { + if(isModal && winState->m_ModalWindow != NULL) + { + DebugStringToFile ("You cannot show two modal windows at once", 0, __FILE__, __LINE__, kError); + return clientRect; + } + win = new GUIWindow(); + win->m_ID = id; + win->m_Depth = -1; + + if(isModal) + { + winState->m_ModalWindow = win; + } + else + { + winState->m_WindowList.push_back(win); + winState->m_LayersChanged = true; + } + } + + if(isModal) + { + if(winState->m_ModalWindow == NULL) + { + winState->m_ModalWindow = win; + + // If window is in the window list, remove it. + GUIWindowState::WindowList::iterator i = std::find(winState->m_WindowList.begin(), + winState->m_WindowList.end(), + win); + if(i != winState->m_WindowList.end()) + { + winState->m_WindowList.erase(i); + winState->m_LayersChanged = true; + } + } + else if(winState->m_ModalWindow != win) + { + // This can happen if you already have a modal window open, and attempt + // to show an already-created window as a modal window. + DebugStringToFile ("Attempting to show modal windows at once; the newer windows will not be modal", 0, __FILE__, __LINE__, kError); + } + } + + if (!win->m_Moved) + win->m_Position = clientRect; + else + win->m_Moved = false; + + win->m_Title = title; + + win->ReleaseScriptingObjects (); + win->m_Style = scripting_gchandle_new (style); + win->m_Delegate = scripting_gchandle_new (delegate); + win->m_Skin = scripting_gchandle_new (guiSkin); + + win->m_Used = true; + win->m_Enabled = state.m_OnGUIState.m_Enabled; + win->m_Color = state.m_OnGUIState.m_Color; + win->m_BackgroundColor = state.m_OnGUIState.m_BackgroundColor; + win->m_ContentColor = state.m_OnGUIState.m_ContentColor; + win->m_Matrix = state.m_CanvasGUIState.m_GUIClipState.GetMatrix(); + win->m_ForceRect = forceRectOnLayout; + + #if !GAMERELEASE + if (state.m_MultiFrameGUIState.m_Windows->m_CurrentWindow) + ErrorString("GUI Error: You called GUI.Window inside a another window's function. Ensure to call it in a OnGUI code path."); + #endif + + return win->m_Position; + + } + + void DragWindow (GUIState &state, const Rectf &position) + { + GUIWindowState* winState = state.m_MultiFrameGUIState.m_Windows; + GUIWindow* win = winState ? winState->m_CurrentWindow : NULL; + if (win == NULL) + { + ErrorString ("Dragwindow can only be called within a window callback"); + return; + } + + int id = IMGUI::GetControlID (state, 0, kPassive); + + InputEvent& evt (*state.m_CurrentEvent); + + switch (GetEventTypeForControl (state, evt, id)) { + case InputEvent::kMouseDown: + // If the mouse is inside the button, we say that we're the hot control +#if ENABLE_NEW_EVENT_SYSTEM + if (position.Contains (evt.touch.pos)) +#else + if (position.Contains (evt.mousePosition)) +#endif + { + GrabMouseControl (state, id); + evt.Use (); + //Matrix4x4f mat = win->m_Matrix; + Vector2f mouseAbs = state.m_CanvasGUIState.m_GUIClipState.GetAbsoluteMousePosition(); + Vector3f windowAbs = win->m_Matrix.MultiplyPoint3 (Vector3f (win->m_Position.x, win->m_Position.y, 0)); + s_DragStartPos = mouseAbs - Vector2f (windowAbs.x, windowAbs.y); + s_DragStartSize = Vector2f (win->m_Position.width, win->m_Position.height); + } + break; + case InputEvent::kMouseUp: + if (GetHotControl (state) == id) + { + ReleaseMouseControl (state); + evt.Use (); + } + break; + case InputEvent::kMouseDrag: + if (GetHotControl (state) == id) + { + Matrix4x4f mat; + Matrix4x4f::Invert_Full (win->m_Matrix, mat); + Vector2f mouseAbs = state.m_CanvasGUIState.m_GUIClipState.GetAbsoluteMousePosition(); + + Vector3f deltaPos (mouseAbs.x - s_DragStartPos.x, mouseAbs.y - s_DragStartPos.y, 0); + deltaPos = mat.MultiplyPoint3 (deltaPos); + win->m_Position = Rectf (deltaPos.x, deltaPos.y, s_DragStartSize.x, s_DragStartSize.y); + + win->m_Moved = true; + evt.Use (); + } + break; + } + + } + + struct GUIStatePropertiesCache + { + Matrix4x4f mat; + ColorRGBAf color; + ColorRGBAf contentColor; + ColorRGBAf backgroundColor; + bool enabled; + }; + + void CacheGUIStateProperties (GUIState &state, GUIStatePropertiesCache &cache) + { + // Cache some GUIState properties to restore after we're done doing windows + cache.mat = state.m_CanvasGUIState.m_GUIClipState.GetMatrix(); + cache.color = state.m_OnGUIState.m_Color; + cache.contentColor = state.m_OnGUIState.m_ContentColor; + cache.backgroundColor = state.m_OnGUIState.m_BackgroundColor; + cache.enabled = state.m_OnGUIState.m_Enabled; + } + + void RestoreGUIStateProperties (GUIState &state, InputEvent &evt, GUIStatePropertiesCache &cache) + { + // Restore previous GUIState properties + state.m_CanvasGUIState.m_GUIClipState.SetMatrix (evt, cache.mat); + state.m_OnGUIState.m_Color = cache.color; + state.m_OnGUIState.m_ContentColor = cache.contentColor; + state.m_OnGUIState.m_BackgroundColor = cache.backgroundColor; + state.m_OnGUIState.m_Enabled = cache.enabled; + } + + void BeginWindows (GUIState &state, bool setupClipping, bool ignoreModalWindow) + { + GUIWindowState* winState = state.m_MultiFrameGUIState.m_Windows; + InputEvent& evt (*state.m_CurrentEvent); + if (winState == NULL) + return; + + GUIStatePropertiesCache oldProperties; + CacheGUIStateProperties (state, oldProperties); + + if (setupClipping) + state.m_CanvasGUIState.m_GUIClipState.BeginOnGUI (evt); + + if (winState->m_LayersChanged) + winState->SortWindows(); + + // The window we want to pass the event on to (mouse & key mainly) + GUIWindow* win = NULL; + + switch (evt.type) { + case InputEvent::kLayout: + // Before we process any events... Mark all windows as unused (we mark them as used when doing the layout event for all scripts) + for (GUIWindowState::WindowList::iterator i = winState->m_WindowList.begin(); i != winState->m_WindowList.end(); i++) + (*i)->m_Used = false; + + if(!ignoreModalWindow && winState->m_ModalWindow != NULL) + winState->m_ModalWindow->m_Used = false; + break; + + // Dragging events go to the window UNDER the mouse + case InputEvent::kDragUpdated: + case InputEvent::kDragPerform: + case InputEvent::kDragExited: + if (!ignoreModalWindow && winState->m_ModalWindow != NULL) + win = winState->m_ModalWindow; + else + win = winState->FindWindowUnderMouse (state); + break; + + // If we have a hot control, we send mouseUp/mouseDrag event to the active window. + // If not, we send it to window under mouse. + case InputEvent::kMouseUp: + case InputEvent::kMouseDrag: + case InputEvent::kMouseMove: + if (!ignoreModalWindow && winState->m_ModalWindow != NULL) + win = winState->m_ModalWindow; + else if (GetHotControl (state) == 0) + win = winState->FindWindowUnderMouse (state); + else + win = winState->GetWindow (winState->m_FocusedWindow); + break; + + // Scroll wheel goes to window under mouse + // TODO: Maybe not the same for windows + case InputEvent::kScrollWheel: + if (!ignoreModalWindow && winState->m_ModalWindow != NULL) + win = winState->m_ModalWindow; + else + win = winState->FindWindowUnderMouse (state); + break; + // mouse events should pick a specific window & bring that forwards... + case InputEvent::kMouseDown: + winState->m_FocusedWindow = -1; + if(!ignoreModalWindow && winState->m_ModalWindow != NULL) + win = winState->m_ModalWindow; + else + win = winState->FindWindowUnderMouse (state); + + // If somebody got moved to front - we need to go over all windows and reset the window depth + if (win) { + win->m_Depth = -1; + winState->m_FocusedWindow = win->m_ID; + winState->SortWindows (); + } + break; + case InputEvent::kRepaint: + if (IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_1_1_a1)) + state.m_EternalGUIState->m_AllowHover = ((ignoreModalWindow || winState->m_ModalWindow == NULL) && winState->FindWindowUnderMouse (state) == NULL); + // We handle all repainting in EndWindows (so we can draw in reverse order on top of other stuff) + return; + default: + if(!ignoreModalWindow && winState->m_ModalWindow != NULL) + win = winState->m_ModalWindow; + else + win = winState->GetWindow (winState->m_FocusedWindow); + break; + } + + // Pass the event on to the window + if (win != NULL && win->m_Delegate != 0) + { + win->OnGUI (state); + + // Some events should not be passed down to the GUI or windows underneath the handled window + if(!ignoreModalWindow && winState->m_ModalWindow != NULL) + { + // If this is a scrollwheel or mousedown event, OR + // If this is a mouse move/drag or mouseup event AND we have no active control + // Ignore the event. + if(evt.type == InputEvent::kScrollWheel || evt.type == InputEvent::kMouseDown) + { + evt.type = InputEvent::kIgnore; + } + else if( (evt.type == InputEvent::kMouseMove + || evt.type == InputEvent::kMouseDrag + || evt.type == InputEvent::kMouseUp) + && IMGUI::GetHotControl (state) == 0) + { + evt.type = InputEvent::kIgnore; + } + } + } + + RestoreGUIStateProperties (state, evt, oldProperties); + + if (setupClipping) + state.m_CanvasGUIState.m_GUIClipState.EndOnGUI (*state.m_CurrentEvent); + } + + void EndWindows (GUIState &state, bool ignoreModalWindow) + { + GUIWindowState* winState = state.m_MultiFrameGUIState.m_Windows; + if (winState == NULL) + return; + + GUIStatePropertiesCache oldProperties; + CacheGUIStateProperties (state, oldProperties); + + InputEvent& evt (*state.m_CurrentEvent); + switch (evt.type) { + case InputEvent::kLayout: + { + // Remove unused windows (they have to be marked as Used during the Layout event. + bool clearFocus = true; + for (int i = winState->m_WindowList.size(); i--;) + { + GUIWindow* win = winState->m_WindowList[i]; + if (!win->m_Used) + { + delete win; + winState->m_WindowList.erase(winState->m_WindowList.begin() + i); + winState->m_LayersChanged = true; + } else { + if (win->m_ID == winState->m_FocusedWindow) + clearFocus = false; + } + } + + if(!ignoreModalWindow && winState->m_ModalWindow != NULL && !winState->m_ModalWindow->m_Used) + { + delete winState->m_ModalWindow; + winState->m_ModalWindow = NULL; + } + + if (clearFocus) + winState->m_FocusedWindow = -1; + + if (winState->m_LayersChanged) + winState->SortWindows (); + + // Always run modal windows first + if(!ignoreModalWindow && winState->m_ModalWindow != NULL) + winState->m_ModalWindow->OnGUI(state); + + for (GUIWindowState::WindowList::iterator i = winState->m_WindowList.begin(); i != winState->m_WindowList.end(); i++) + { + // Send the layout event to the user's input code (also does the layouting from the C# delegate wrapper) + (*i)->OnGUI (state); + } + break; + } + case InputEvent::kRepaint: + GUIWindow* windowUnderMouse; + + if(winState->m_ModalWindow != NULL) + windowUnderMouse = winState->m_ModalWindow; + else + windowUnderMouse = winState->FindWindowUnderMouse (state); + + for (int i = winState->m_WindowList.size(); i--;) + { + GUIWindow* win = winState->m_WindowList[i]; + if (IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_1_1_a1)) + state.m_EternalGUIState->m_AllowHover = (win == windowUnderMouse && winState->m_ModalWindow == NULL); + win->OnGUI (state); + } + + if(ignoreModalWindow || winState->m_ModalWindow == NULL) + { + // Re-enable hovering for always-on-top normal GUIs when there's no modal GUI + state.m_EternalGUIState->m_AllowHover = true; + } + else + { + // Disable hovering for the non-modal GUI when we have one. + // Repainting happens in RepaintModalWindow. + state.m_EternalGUIState->m_AllowHover = false; + } + + break; + } + + RestoreGUIStateProperties (state, evt, oldProperties); + + // Release objects if we don't have a modal window. If we do, this will be done later. + if (evt.type != InputEvent::kLayout && (ignoreModalWindow || winState->m_ModalWindow == NULL)) + winState->ReleaseScriptingObjects(); + } + + void RepaintModalWindow(GUIState& state) + { + GUIWindowState* winState = state.m_MultiFrameGUIState.m_Windows; + if (winState == NULL) + return; + + GUIStatePropertiesCache oldProperties; + CacheGUIStateProperties (state, oldProperties); + + InputEvent& evt (*state.m_CurrentEvent); + if(evt.type == InputEvent::kRepaint) + { + state.m_EternalGUIState->m_AllowHover = true; + + // Always run modal windows last so they paint on top. + if(winState->m_ModalWindow != NULL) + winState->m_ModalWindow->OnGUI(state); + } + + if(evt.type != InputEvent::kLayout) + { + winState->ReleaseScriptingObjects(); + } + } + + GUIWindow *GetFocusedWindow (GUIState &state) + { + if (state.m_MultiFrameGUIState.m_Windows) + return state.m_MultiFrameGUIState.m_Windows->GetWindow (state.m_MultiFrameGUIState.m_Windows->m_FocusedWindow); + return NULL; + } + + + GUIWindowState::GUIWindowState () + { + m_FocusedWindow = -1; + m_LayersChanged = false; + m_CurrentWindow = NULL; + m_ModalWindow = NULL; + } + + + GUIWindowState::~GUIWindowState () + { + for (GUIWindowState::WindowList::iterator i = m_WindowList.begin(); i != m_WindowList.end(); i++) + delete *i; + + if(m_ModalWindow != NULL) + { + delete m_ModalWindow; + m_ModalWindow = NULL; + } + } + + GUIWindow* GUIWindowState::GetWindow (int windowId) + { + for (GUIWindowState::WindowList::iterator i = m_WindowList.begin(); i != m_WindowList.end(); i++) + { + if ((*i)->m_ID == windowId) + return *i; + } + + if(m_ModalWindow != NULL && m_ModalWindow->m_ID == windowId) + { + return m_ModalWindow; + } + + return NULL; + } + + static bool SortTwoWindows(const GUIWindow* a, const GUIWindow* b) + { + return a->m_Depth < b->m_Depth; + } + + void GUIWindowState::SortWindows () + { + std::sort (m_WindowList.begin(), m_WindowList.end(), SortTwoWindows); + for (int i = 0; i < m_WindowList.size(); i++) + m_WindowList[i]->m_Depth = i; + } + + + GUIWindow* GUIWindowState::FindWindowUnderMouse (GUIState &state) + { + InputEvent evt (*state.m_CurrentEvent); + +#if ENABLE_NEW_EVENT_SYSTEM + if(m_ModalWindow != NULL && m_ModalWindow->m_Position.Contains(evt.touch.pos)) +#else + if(m_ModalWindow != NULL && m_ModalWindow->m_Position.Contains(evt.mousePosition)) +#endif + { + return m_ModalWindow; + } + + for (GUIWindowState::WindowList::iterator i = m_WindowList.begin(); i != m_WindowList.end(); i++) + { + state.m_CanvasGUIState.m_GUIClipState.SetMatrix (evt, (*i)->m_Matrix); +#if ENABLE_NEW_EVENT_SYSTEM + if ((*i)->m_Position.Contains (evt.touch.pos)) +#else + if ((*i)->m_Position.Contains (evt.mousePosition)) +#endif + return *i; + } + return NULL; + } + + void GUIWindowState::ReleaseScriptingObjects () + { + for (WindowList::iterator i = m_WindowList.begin(); i != m_WindowList.end(); i++) + (*i)->ReleaseScriptingObjects (); + + if(m_ModalWindow != NULL) + { + m_ModalWindow->ReleaseScriptingObjects(); + } + } + + void MoveWindowFromLayout (GUIState &state, int windowID, const Rectf &rect) + { + GUIWindowState* winState = state.m_MultiFrameGUIState.m_Windows; + AssertIf (!winState); + + GUIWindow* win = winState->GetWindow(windowID); + if (win && win->m_Position != rect) + { + win->m_Position = rect; + win->m_Moved = true; + } + } + + Rectf GetWindowRect (GUIState &state, int windowID) + { + GUIWindowState* winState = state.m_MultiFrameGUIState.m_Windows; + AssertIf (!winState); + + GUIWindow* win = winState->GetWindow (windowID); + if (win) + return win->m_Position; + return Rectf (0,0,0,0); + } + + Rectf GetWindowsBounds (GUIState &state) + { + GUIWindowState* winState = state.m_MultiFrameGUIState.m_Windows; + if (!winState) + return Rectf(0,0,0,0); + + GUIWindowState::WindowList &windows = winState->m_WindowList; + + Rectf bounds (std::numeric_limits<float>::max (), std::numeric_limits<float>::max (), -std::numeric_limits<float>::max (), -std::numeric_limits<float>::max ()); + for (GUIWindowState::WindowList::const_iterator i = windows.begin (); i != windows.end (); i++) + { + Rectf& windowRect = (*i)->m_Position; + + bounds.SetLeft (std::min (bounds.x, windowRect.x)); + bounds.SetTop (std::min (bounds.y, windowRect.y)); + bounds.SetRight (std::max (bounds.GetXMax (), windowRect.GetXMax ())); + bounds.SetBottom (std::max (bounds.GetYMax (), windowRect.GetYMax ())); + } + + if(winState->m_ModalWindow != NULL) + { + Rectf& windowRect = winState->m_ModalWindow->m_Position; + bounds.SetLeft (std::min (bounds.x, windowRect.x)); + bounds.SetTop (std::min (bounds.y, windowRect.y)); + bounds.SetRight (std::max (bounds.GetXMax (), windowRect.GetXMax ())); + bounds.SetBottom (std::max (bounds.GetYMax (), windowRect.GetYMax ())); + } + + return bounds; + } + + void BringWindowToFront (GUIState &state, int windowID) + { + GUIWindowState* winState = state.m_MultiFrameGUIState.m_Windows; + if (!winState) + return; + + // Modal windows are on top by definition. + if(winState->m_ModalWindow != NULL && winState->m_ModalWindow->m_ID == windowID) + return; + + GUIWindow* win = winState->GetWindow (windowID); + if (win) + { + int minDepth = 0; + for (GUIWindowState::WindowList::iterator i = winState->m_WindowList.begin(); i != winState->m_WindowList.end(); i++) + { + if ((*i)->m_Depth < minDepth) + minDepth = (*i)->m_Depth; + } + win->m_Depth = minDepth - 1; + winState->m_LayersChanged = true; + } + } + + void BringWindowToBack (GUIState &state, int windowID) + { + GUIWindowState* winState = state.m_MultiFrameGUIState.m_Windows; + if (!winState) + return; + + // Modal windows are on top by definition. + if(winState->m_ModalWindow != NULL && winState->m_ModalWindow->m_ID == windowID) + return; + + GUIWindow* win = winState->GetWindow (windowID); + if (win) + { + int maxDepth = 0; + for (GUIWindowState::WindowList::iterator i = winState->m_WindowList.begin(); i != winState->m_WindowList.end(); i++) + { + if ((*i)->m_Depth > maxDepth) + maxDepth = (*i)->m_Depth; + } + win->m_Depth = maxDepth + 1; + winState->m_LayersChanged = true; + } + } + + void FocusWindow (GUIState &state, int windowID) + { + GUIWindowState* winState = state.m_MultiFrameGUIState.m_Windows; + if (!winState) + return; + + // Modal windows must be focused if they exist. + if(winState->m_ModalWindow != NULL && winState->m_ModalWindow->m_ID != windowID) + return; + + winState->m_FocusedWindow = windowID; + } +} +#endif diff --git a/Runtime/IMGUI/GUIWindows.h b/Runtime/IMGUI/GUIWindows.h new file mode 100644 index 0000000..1519d43 --- /dev/null +++ b/Runtime/IMGUI/GUIWindows.h @@ -0,0 +1,95 @@ +#ifndef GUIWINDOWS_H +#define GUIWINDOWS_H + +#include "Runtime/Math/Rect.h" +#include "Runtime/IMGUI/GUIContent.h" +#include "Runtime/IMGUI/IMGUIUtils.h" + +struct GUIContent; +class GUIStyle; + +namespace IMGUI +{ + struct GUIWindow + { + int m_ID; + // The ID list used by this window + ObjectGUIState m_ObjectGUIState; + Rectf m_Position; + // Sorting depth + int m_Depth; + // What's the title? + GUIContent m_Title; + // Was this window referenced this frame? (used to clean up unused windows at end-of-frame) + bool m_Used; + // Was it moved with a DragWindow? If so, we need to use our internal rect instead of the one passed in to us + bool m_Moved; + bool m_ForceRect; + + // Mono Object handles + int m_Delegate; + int m_Skin; + int m_Style; + + // GUIState GUI.window time: + ColorRGBAf m_Color, m_BackgroundColor, m_ContentColor; + Matrix4x4f m_Matrix; + bool m_Enabled; + + void LoadFromGUIState (GUIState &state); + void SetupGUIValues (GUIState &state); + void OnGUI (GUIState &state); + void ReleaseScriptingObjects (); + + GUIWindow (); + ~GUIWindow (); + }; + + Rectf DoWindow (GUIState &state, int windowId, const Rectf &clientRect, ScriptingObjectPtr delegate, GUIContent& title, ScriptingObjectPtr style, ScriptingObjectPtr guiSkin, bool forceRectOnLayout, bool isModal = false); + void DragWindow (GUIState &state, const Rectf &rect); + + void BeginWindows (GUIState &state, bool setupClipping, bool ignoreModalWindow = true); + void EndWindows (GUIState &state, bool ignoreModalWindow = true); + void RepaintModalWindow(GUIState &state); + + void MoveWindowFromLayout (GUIState &state, int windowID, const Rectf &rect); + Rectf GetWindowRect (GUIState &state, int windowID); + + Rectf GetWindowsBounds (GUIState &state); + + void BringWindowToFront (GUIState &state, int windowID); + void BringWindowToBack (GUIState &state, int windowID); + void FocusWindow (GUIState &state, int windowID); + + // Get the window that has focus, or NULL + GUIWindow *GetFocusedWindow (GUIState &state); + + struct GUIWindowState + { + GUIWindowState (); + ~GUIWindowState (); + typedef std::vector<GUIWindow*> WindowList; + WindowList m_WindowList; + int m_FocusedWindow; + bool m_LayersChanged; + + // The window we're currently calling OnGUI on, or NULL + GUIWindow* m_CurrentWindow; + + // The current modal window being displayed, or NULL if there are no modal windows this frame + GUIWindow* m_ModalWindow; + + GUIWindow* GetWindow (int windowId); + void SortWindows (); + GUIWindow* FindWindowUnderMouse (GUIState &state); + + // Release all GC handles. We call this at the end of every frame in order to make sure we don't leak anything + // (they only need to get remembered WITHIN one layout/event cycle) + void ReleaseScriptingObjects (); + }; +} + + + + +#endif diff --git a/Runtime/IMGUI/IDList.cpp b/Runtime/IMGUI/IDList.cpp new file mode 100644 index 0000000..896c498 --- /dev/null +++ b/Runtime/IMGUI/IDList.cpp @@ -0,0 +1,164 @@ +#include "UnityPrefix.h" +#include "Runtime/IMGUI/IDList.h" +#include "Runtime/IMGUI/GUIState.h" +#include "Runtime/Misc/InputEvent.h" + +IDList::IDList () +{ + m_Idx = 0; +} + +int IDList::CalculateNextFromHintList (GUIState &state, int hint, bool isKeyboard) +{ + int retval = 0; + // Ok - we're searching: start at idx and search to end. + for (int searchIdx = m_Idx; searchIdx < m_IDs.size(); searchIdx++) + { + if (m_IDs[searchIdx].hint == hint) + { + m_Idx = searchIdx + 1; + retval = m_IDs[searchIdx].value; + break; + } + } + + // We still couldn't find it, so we just add to end... + if (retval == 0) + { + retval = state.m_EternalGUIState->GetNextUniqueID(); + m_IDs.push_back (ID (hint, retval, isKeyboard)); + m_Idx = m_IDs.size(); + } + + return retval; +} + +void IDList::BeginOnGUI () +{ + m_Idx = 0; + m_FirstKeyControl = -1; + m_LastKeyControl = -1; + m_PreviousKeyControl = -1; + m_NextKeyControl = -1; + m_HasKeyboardControl = false; + m_TabControlSearchStatus = kLookingForPrevious; +} + +int IDList::GetNext (GUIState &state, int hint, FocusType focusType, const Rectf &rect) +{ + int retval = GetNext (state, hint, focusType); + if (state.m_CurrentEvent->type != InputEvent::kLayout && state.m_CurrentEvent->type != InputEvent::kUsed && ShouldBeKeyboardControl(focusType)) + { + Assert (m_Idx > 0); + m_IDs[m_Idx-1].rect = rect; + } + return retval; +} + +int IDList::GetNext (GUIState &state, int hint, FocusType focusType) +{ + InputEvent::Type type = state.m_CurrentEvent->type; + + bool isKeyboard = ShouldBeKeyboardControl(focusType); + int retval = 0; + if (type != InputEvent::kUsed) + retval = CalculateNextFromHintList(state, hint, isKeyboard); + else + { + return -1; + } + + if (type != InputEvent::kLayout) + { + if (type == InputEvent::kKeyDown && state.m_OnGUIState.m_Enabled == (int)true) + { + if (isKeyboard) + { + switch (m_TabControlSearchStatus) + { + case kNotActive: + break; + case kLookingForPrevious: + if (m_FirstKeyControl == -1) + m_FirstKeyControl = retval; + if (retval != state.m_MultiFrameGUIState.m_KeyboardControl) + m_PreviousKeyControl = retval; + else + { + m_TabControlSearchStatus = kLookingForNext; + m_HasKeyboardControl = true; + } + break; + case kLookingForNext: + m_NextKeyControl = retval; + m_TabControlSearchStatus = kFound; + break; + default: + break; + } + m_LastKeyControl = retval; + } + } + } + return retval; +} + +bool IDList::GetRectOfControl (int id, Rectf &out) const +{ + for (dynamic_array<ID>::const_iterator i = m_IDs.begin(); i != m_IDs.end(); i++) + { + if (i->value == id && i->rect.width != -1.0f) + { + out = i->rect; + return true; + } + } + return false; +} + +bool IDList::CanHaveKeyboardFocus (int id) const +{ + for (dynamic_array<ID>::const_iterator i = m_IDs.begin(); i != m_IDs.end(); i++) + { + if (i->value == id) + { + return i->isKeyboard; + } + } + return false; +} + +bool IDList::ShouldBeKeyboardControl (FocusType focus) { + switch (focus) { + case kPassive: + return false; + case kKeyboard: + return true; + case kNative: + return false; + // TODO: Move this back in during 2.x when we accept keyboard input on the various GUI controls. + /* PlatformSelection platform = GUI.skin.settings.keyboardFocus; + if (platform == PlatformSelection.Native) { + if (Application.platform == RuntimePlatform.WindowsPlayer || Application.platform == RuntimePlatform.WindowsWebPlayer || Application.platform == RuntimePlatform.WindowsEditor) + platform = PlatformSelection.Windows; + else + platform = PlatformSelection.Mac; + } + return platform == PlatformSelection.Windows; + */ } + return true; +} + +void IDList::SetSearchIndex (int index) +{ + if (index >= 0 && index < m_IDs.size()) + m_Idx = index; + else + AssertString (Format("Invalid index %d (size is %zd)", index, m_IDs.size())); +} + +int IDList::GetSearchIndex () const +{ + return m_Idx; +} + diff --git a/Runtime/IMGUI/IDList.h b/Runtime/IMGUI/IDList.h new file mode 100644 index 0000000..40c1a3d --- /dev/null +++ b/Runtime/IMGUI/IDList.h @@ -0,0 +1,69 @@ +#ifndef IDLIST_H +#define IDLIST_H + +struct GUIState; +#include "Runtime/Math/Rect.h" +#include "Runtime/Utilities/dynamic_array.h" + +/// Used by GUIUtility.GetcontrolID to inform the UnityGUI system if a given control can get keyboard focus. +/// MUST MATCH FocusType in GUIUtility.txt +enum FocusType { + /// This control can get keyboard focus on Windows, but not on Mac. Used for buttons, checkboxes and other "pressable" things. + kNative = 0, + /// This is a proper keyboard control. It can have input focus on all platforms. Used for TextField and TextArea controls + kKeyboard = 1, + /// This control can never recieve keyboard focus. + kPassive = 2, +}; + +/// Manages the list of IDs that are returned from GUIUtility.GetControlID (); +class IDList +{ +public: + IDList (); + int GetNext (GUIState &state, int hint, FocusType focusType); + int GetNext (GUIState &state, int hint, FocusType focusType, const Rectf &rect); + void BeginOnGUI (); + bool HasKeyboardControl () { return m_HasKeyboardControl; } + + // Get the ID of the keyboard control BEFORE the one that has keyboard focus (used to implement shift-tab) -1 if not found + int GetPreviousKeyboardControlID () { return m_PreviousKeyControl; } + // Get the ID of the keyboard control AFTER the one that has keyboard focus (used to implement tab) -1 if not found + int GetNextKeyboardControlID () { return m_NextKeyControl; } + // Get the ID of the first keyboard control - used when tabbing into this script -1 if not found + int GetFirstKeyboardControlID () { return m_FirstKeyControl; } + // Get the ID of the last keyboard control - used when tabbing out of this script -1 if not found + int GetLastKeyboardControlID () { return m_LastKeyControl; } + + bool GetRectOfControl (int id, Rectf &out) const; + bool CanHaveKeyboardFocus (int id) const; + void SetSearchIndex (int index); + int GetSearchIndex () const; +private: + // Enum and vars for handling searching for next and previous fields when tabbing through controls. + enum TabControlSearchStatus + { + kNotActive = 0, kLookingForPrevious = 1, kLookingForNext = 2, kFound = 3 + }; + TabControlSearchStatus m_TabControlSearchStatus; + int m_FirstKeyControl, m_LastKeyControl, m_PreviousKeyControl, m_NextKeyControl; + bool m_HasKeyboardControl; + /// Determine if a given focusType should result in keyboard control + static bool ShouldBeKeyboardControl (FocusType focus); + + int CalculateNextFromHintList (GUIState &state, int hint, bool isKeyboard); + struct ID + { + int hint, value; + bool isKeyboard; + Rectf rect; + ID (int _hint, int _value, bool _isKeyboard) : hint (_hint), value (_value), isKeyboard (_isKeyboard), rect (-1.0f, -1.0f, -1.0f, -1.0f) {} + ID (int _hint, int _value, bool _isKeyboard, Rectf _rect) : hint (_hint), value (_value), isKeyboard (_isKeyboard), rect (_rect) {} + }; + + dynamic_array<ID> m_IDs; + int m_Idx; +}; + + +#endif diff --git a/Runtime/IMGUI/IMGUIUtils.cpp b/Runtime/IMGUI/IMGUIUtils.cpp new file mode 100644 index 0000000..2ce0596 --- /dev/null +++ b/Runtime/IMGUI/IMGUIUtils.cpp @@ -0,0 +1,101 @@ +#include "UnityPrefix.h" +#include "Runtime/IMGUI/IMGUIUtils.h" +#include "Runtime/IMGUI/TextMeshGenerator2.h" + +const float s_TabWidth = 16; +extern float s_GUIStyleIconSizeX; + +namespace IMGUI +{ + InputEvent::Type GetEventType (const GUIState &state, const InputEvent &event) + { + InputEvent::Type type = event.type; + if (!state.m_OnGUIState.m_Enabled) + { + if (type == InputEvent::kRepaint || type == InputEvent::kLayout || type == InputEvent::kUsed) + return type; + return InputEvent::kIgnore; + } + + if (state.m_CanvasGUIState.m_GUIClipState.GetEnabled()) + return type; + + if (type == InputEvent::kMouseDown || type == InputEvent::kMouseUp || type == InputEvent::kDragPerform || type == InputEvent::kDragUpdated) + return InputEvent::kIgnore; + + return type; + } + + + InputEvent::Type GetEventTypeForControl (const GUIState &state, const InputEvent &event, int controlID) + { + InputEvent::Type m_Type = event.type; + + // if we have no hot control, just return the usual + if (GetHotControl(state) == 0) + return GetEventType (state, event); + switch (m_Type) + { + // Mouse events follow GUIUtility.hotControl + case InputEvent::kMouseDown: + case InputEvent::kMouseUp: + case InputEvent::kMouseMove: + case InputEvent::kMouseDrag: + if (!GetEnabled(state)) + return InputEvent::kIgnore; + if (GetGUIClipEnabled(state) || HasMouseControl (state, controlID)) + return m_Type; + return InputEvent::kIgnore; + + // Key events follow keyboard control + case InputEvent::kKeyDown: + case InputEvent::kKeyUp: + case InputEvent::kScrollWheel: // Not sure about scrollwheel. For now we map it to behave like keyboard events. + + if (!GetEnabled(state)) + return InputEvent::kIgnore; + if (GetGUIClipEnabled(state) || HasMouseControl (state, controlID) || GetKeyboardControl(state) == controlID) + return m_Type; + return InputEvent::kIgnore; + + // Repaint, Layout, Used, DragUpdated, DragPerform, Ignore + default: + return m_Type; + } + } + + + TextMeshGenerator2* GetGenerator (const Rectf &contentRect, const GUIContent &content, Font& font, TextAnchor alignment, bool wordWrap, bool richText, ColorRGBA32 color, int fontSize, int fontStyle, ImagePosition imagePosition) + { + if (!wordWrap) + return &TextMeshGenerator2::Get (content.m_Text, &font, alignment, kAuto, 0.0f, s_TabWidth, 1.0f, richText, true, color, fontSize, fontStyle); + + Texture *image = content.m_Image; + float textWidth = contentRect.Width(); + switch (imagePosition) { + case kImageLeft: + // Todo: Subtract width of the icon + if (image != NULL) + { + Vector2f imageSize = Vector2f (image->GetDataWidth(), image->GetDataHeight()); + + //float imageScale = clamp (min (contentRect.Width() / imageSize.x, contentRect.Height() / imageSize.y), 0.0f, 1.0f); + //contentRect.width -= Roundf (imageSize.x * imageScale); + + if (s_GUIStyleIconSizeX == 0) + textWidth -= Roundf (imageSize.x * clamp (std::min (contentRect.Width() / imageSize.x, contentRect.Height() / imageSize.y), 0.0f, 1.0f)); + else + textWidth -= s_GUIStyleIconSizeX; + } + break; + case kImageOnly: + return NULL; + case kImageAbove: + case kTextOnly: + // These two require no special handling + break; + } + + return &TextMeshGenerator2::Get (content.m_Text, &font, alignment, kAuto, textWidth, s_TabWidth, 1.0f, richText, true, color, fontSize, fontStyle); + } +} // namespace IMGUI diff --git a/Runtime/IMGUI/IMGUIUtils.h b/Runtime/IMGUI/IMGUIUtils.h new file mode 100644 index 0000000..486b2fe --- /dev/null +++ b/Runtime/IMGUI/IMGUIUtils.h @@ -0,0 +1,64 @@ +#ifndef IMGUIUtils_H +#define IMGUIUtils_H + +#include "Runtime/Math/Color.h" +#include "Runtime/IMGUI/GUIState.h" +#include "Runtime/IMGUI/GUIContent.h" +#include "Runtime/IMGUI/GUIStyle.h" + +struct InputEvent; +struct GUIState; +class TextMeshGenerator2; +namespace IMGUI +{ + inline ColorRGBAf GetColor (const GUIState &state) { return state.m_OnGUIState.m_Color; } + inline void SetColor (GUIState &state, const ColorRGBAf &color) { state.m_OnGUIState.m_Color = color; } + + inline ColorRGBAf GetBackgroundColor (const GUIState &state) { return state.m_OnGUIState.m_BackgroundColor; } + inline void SetBackgroundColor (GUIState &state, const ColorRGBAf &color) { state.m_OnGUIState.m_BackgroundColor = color; } + + inline ColorRGBAf GetContentColor (const GUIState &state) { return state.m_OnGUIState.m_ContentColor; } + inline void SetContentColor (GUIState &state, const ColorRGBAf &color) { state.m_OnGUIState.m_ContentColor = color; } + + inline bool GetEnabled (const GUIState &state) { return state.m_OnGUIState.m_Enabled; } + inline void SetEnabled (GUIState &state, bool enab) { state.m_OnGUIState.m_Enabled = enab; } + + inline bool GetChanged (const GUIState &state) { return state.m_OnGUIState.m_Changed; } + inline void SetChanged (GUIState &state, bool changed) { state.m_OnGUIState.m_Changed = changed; } + + inline int GetDepth (const GUIState &state) { return state.m_OnGUIState.m_Depth; } + inline void SetDepth (GUIState &state, int depth) { state.m_OnGUIState.m_Depth = depth; } + + inline int GetHotControl (const GUIState &state) { return state.m_EternalGUIState->m_HotControl; } + inline void SetHotControl (GUIState &state, int hotControl) { state.m_EternalGUIState->m_HotControl = hotControl; } + + inline int GetKeyboardControl (const GUIState &state) { return state.m_MultiFrameGUIState.m_KeyboardControl; } + inline void SetKeyboardControl (GUIState &state, int keyControl) { state.m_MultiFrameGUIState.m_KeyboardControl = keyControl; } + + inline InputEvent &GetCurrentEvent (GUIState &state) { return *state.m_CurrentEvent; } + + inline bool GetGUIClipEnabled (const GUIState &state) { return state.m_CanvasGUIState.m_GUIClipState.GetEnabled();} + + /// Get the type of the current event taking clipping and enabled flag into account from GUIState. + InputEvent::Type GetEventType (const GUIState &state, const InputEvent &event); + + /// Get the type of the current event considering that a specific control is asking. + /// This is more agressive than the old C# one as it will also cull mouse events, assuming that hotControl is being used. + InputEvent::Type GetEventTypeForControl (const GUIState &state, const InputEvent &event, int controlID); + + inline int GetControlID (GUIState &state, int hash, FocusType focusType) + { return state.GetControlID (hash, focusType); } + + inline int GetControlID (GUIState &state, int hash, FocusType focusType, const Rectf& rect) + { return state.GetControlID (hash, focusType, rect); } + + + inline void GrabMouseControl (GUIState &state, int controlID) { state.m_EternalGUIState->m_HotControl = controlID; } + inline void ReleaseMouseControl (GUIState &state) { state.m_EternalGUIState->m_HotControl = 0; } + inline bool HasMouseControl (const GUIState &state, int controlID) { return state.m_EternalGUIState->m_HotControl == controlID; } + + inline bool AreWeInOnGUI (const GUIState &state) { return state.m_OnGUIDepth > 0; } + + TextMeshGenerator2* GetGenerator (const Rectf &contentRect, const GUIContent &content, Font& font, TextAnchor alignment, bool wordWrap, bool richText, ColorRGBA32 color, int fontSize, int fontStyle, ImagePosition imagePosition); +} +#endif diff --git a/Runtime/IMGUI/NamedKeyControlList.cpp b/Runtime/IMGUI/NamedKeyControlList.cpp new file mode 100644 index 0000000..6574a13 --- /dev/null +++ b/Runtime/IMGUI/NamedKeyControlList.cpp @@ -0,0 +1,27 @@ +#include "UnityPrefix.h" +#include "Runtime/IMGUI/NamedKeyControlList.h" +namespace IMGUI +{ + +void NamedKeyControlList::AddNamedControl (const std::string &name, int id, int windowID) +{ + m_NamedControls[name] = NamedControl (id, windowID); +} + +std::string NamedKeyControlList::GetNameOfControl (int id) +{ + for (std::map<std::string, NamedControl>::const_iterator i = m_NamedControls.begin(); i != m_NamedControls.end(); i++) + if (i->second.ID == id) + return i->first; + return std::string (""); +} + +NamedControl* NamedKeyControlList::GetControlNamed (const std::string &name) +{ + std::map<std::string, NamedControl>::iterator i = m_NamedControls.find (name); + if (i != m_NamedControls.end ()) + return &i->second; + return NULL; +} + +} // namespace
\ No newline at end of file diff --git a/Runtime/IMGUI/NamedKeyControlList.h b/Runtime/IMGUI/NamedKeyControlList.h new file mode 100644 index 0000000..dc4dff7 --- /dev/null +++ b/Runtime/IMGUI/NamedKeyControlList.h @@ -0,0 +1,40 @@ +#ifndef NAMEDKEYCONTROLLIST_H +#define NAMEDKEYCONTROLLIST_H + +namespace IMGUI +{ + struct NamedControl + { + int ID; + int windowID; + NamedControl () + { + ID = 0; + windowID = -1; + } + NamedControl (int _ID, int _windowID) + { + ID = _ID; + windowID = _windowID; + } + }; + + class NamedKeyControlList + { + public: + void AddNamedControl (const std::string &str, int id, int windowID); + + // Return ptr name of a given control, NULL if none. + std::string GetNameOfControl (int id); + + // Return the ID of a named control. + // Used in GUIState::FocusControl + NamedControl* GetControlNamed (const std::string &name); + + void Clear () { m_NamedControls.clear (); } + private: + std::map<std::string, NamedControl> m_NamedControls; + }; +}; + +#endif
\ No newline at end of file diff --git a/Runtime/IMGUI/TextFormatting.cpp b/Runtime/IMGUI/TextFormatting.cpp new file mode 100644 index 0000000..7495326 --- /dev/null +++ b/Runtime/IMGUI/TextFormatting.cpp @@ -0,0 +1,337 @@ +#include "UnityPrefix.h" +#include "TextFormatting.h" + +enum FormattingTag { + kTagBold, + kTagItalic, + kTagColor, + kTagSize, + kTagMaterial, + kTagImage, + kTagX, + kTagY, + kTagWidth, + kTagHeight, + kNumTags, +}; + +const char* kFormattingTagStrings[] = +{ + "b", + "i", + "color", + "size", + "material", + "quad", + "x", + "y", + "width", + "height", +}; + + +const char* kFormattingHTMLColorStrings[] = +{ + "red", + "cyan", + "blue", + "darkblue", + "lightblue", + "purple", + "yellow", + "lime", + "fuchsia", + "white", + "silver", + "grey", + "black", + "orange", + "brown", + "maroon", + "green", + "olive", + "navy", + "teal", + "aqua", + "magenta", +}; + +#define kNumHTMLColors (sizeof(kFormattingHTMLColorStrings)/sizeof(kFormattingHTMLColorStrings[0])) + +const UInt8 kFormattingHTMLColorValues[kNumHTMLColors*4] = +{ + 0xff,0x00,0x00,0xff, + 0x00,0xff,0xff,0xff, + 0x00,0x00,0xff,0xff, + 0x00,0x00,0xa0,0xff, + 0xad,0xd8,0xe6,0xff, + 0x80,0x00,0x80,0xff, + 0xff,0xff,0x00,0xff, + 0x00,0xff,0x00,0xff, + 0xff,0x00,0xff,0xff, + 0xff,0xff,0xff,0xff, + 0xc0,0xc0,0xc0,0xff, + 0x80,0x80,0x80,0xff, + 0x00,0x00,0x00,0xff, + 0xff,0xa5,0x00,0xff, + 0xa5,0x2a,0x2a,0xff, + 0x80,0x00,0x00,0xff, + 0x00,0x80,0x00,0xff, + 0x80,0x80,0x00,0xff, + 0x00,0x00,0x80,0xff, + 0x00,0x80,0x80,0xff, + 0x00,0xff,0xff,0xff, + 0xff,0x00,0xff,0xff, +}; + +FormattingTag GetTag(UTF16String& input, int &pos, bool &closing) +{ + if (input[pos] == '<') + { + int outpos = pos + 1; + if (outpos == input.length) + return (FormattingTag)-1; + + closing = input[outpos] == '/'; + if (closing) + outpos++; + + for (int i=0;i<kNumTags;i++) + { + bool match = true; + for (int j=0;kFormattingTagStrings[i][j] != '\0'; j++) + { + if (outpos+j == input.length || ToLower((char)input[outpos+j]) != kFormattingTagStrings[i][j]) + { + match = false; + break; + } + } + if (match) + { + int paramPos = outpos + strlen(kFormattingTagStrings[i]); + if ((!closing && input[paramPos] == '=') || (input[paramPos] == ' ' && i == kTagImage)) + { + while (input[paramPos] != '>' && paramPos < input.length) + paramPos++; + } + else if (input[paramPos] != '>') + continue; + outpos += strlen(kFormattingTagStrings[i]); + pos = outpos; + return (FormattingTag)i; + } + } + } + return (FormattingTag)-1; +} + +FormattingTag GetImageTag(UTF16String& input, int &pos) +{ + for (int i=0;i<kNumTags;i++) + { + bool match = true; + for (int j=0;kFormattingTagStrings[i][j] != '\0'; j++) + { + if (pos+j == input.length || ToLower((char)input[pos+j]) != kFormattingTagStrings[i][j]) + { + match = false; + break; + } + } + if (match) + { + int paramPos = pos + strlen(kFormattingTagStrings[i]); + if (input[paramPos] == '=') + { + pos = paramPos; + return (FormattingTag)i; + } + else + continue; + } + } + return (FormattingTag)-1; +} + +std::string GetParameter(UTF16String& input, int &pos, bool allowMultipleParamaters = false) +{ + std::string parameter; + if (input[pos] == '=') + { + pos++; + while (input[pos] != '>' && (input[pos] != ' ' || !allowMultipleParamaters) && pos < input.length) + parameter.push_back(input[pos++]); + } + if (parameter.size() > 2 && parameter[0] == parameter[parameter.size()-1]) + { + if (parameter[0] == '\'' || parameter[0] == '"') + parameter = parameter.substr(1,parameter.size()-2); + } + return parameter; +} + +ColorRGBA32 ParseHTMLColor(std::string colorstring) +{ + ColorRGBA32 color = ColorRGBA32(0xffffffff); + if (colorstring[0] == '#') + { + if (colorstring.size() == 4 || colorstring.size() == 5) + { + std::string longcolorstring = "#"; + for (int i=1;i<colorstring.size();i++) + { + longcolorstring += colorstring[i]; + longcolorstring += colorstring[i]; + } + colorstring = longcolorstring; + } + if (colorstring.size() == 7 || colorstring.size() == 9) + HexStringToBytes (&colorstring[1], colorstring.size()/2, &color); + } + else for (int i=0; i<kNumHTMLColors; i++) + { + if (StrICmp(colorstring, kFormattingHTMLColorStrings[i]) == 0) + return ColorRGBA32 (*(UInt32*)(kFormattingHTMLColorValues+i*4)); + } + return color; +} + +bool ValidateFormat (std::vector<TextFormatChange> &format) +{ + std::vector<int> stack; + for (std::vector<TextFormatChange>::iterator i = format.begin(); i != format.end(); i++) + { + int flags = i->flags; + if (flags & kFormatPop) + { + if (stack.empty()) + // closing tag without opening tag. + return false; + flags &= ~kFormatPop; + if (stack.back() != flags) + // closing does not match opening tag. + return false; + stack.pop_back(); + } + else + stack.push_back(flags); + } + if (!stack.empty()) + // unclosed tags. + return false; + return true; +} + +void ParseImageParameters (UTF16String& input, int &pos, TextFormatChange &change) +{ + while (pos < input.length && input[pos] != '>') + { + FormattingTag tag = GetImageTag(input, pos); + if (tag != -1) + { + switch (tag) + { + case kTagMaterial: + change.flags |= kFormatMaterial; + change.format.material = StringToInt(GetParameter(input, pos, true)); + break; + case kTagSize: + change.flags |= kFormatSize; + change.format.size = StringToInt(GetParameter(input, pos, true)); + break; + case kTagColor: + change.flags |= kFormatColor; + change.format.color = ParseHTMLColor(GetParameter(input, pos, true)); + break; + case kTagX: + sscanf(GetParameter(input, pos, true).c_str(), "%f", &change.format.imageRect.x); + break; + case kTagY: + sscanf(GetParameter(input, pos, true).c_str(), "%f", &change.format.imageRect.y); + break; + case kTagWidth: + sscanf(GetParameter(input, pos, true).c_str(), "%f", &change.format.imageRect.width); + break; + case kTagHeight: + sscanf(GetParameter(input, pos, true).c_str(), "%f", &change.format.imageRect.height); + break; + default: + break; + } + } + else + pos++; + } +} + +void GetFormatString (UTF16String& input, std::vector<TextFormatChange> &format) +{ + int pos = 0; + while (pos < input.length) + { + int oldpos = pos; + bool closing; + FormattingTag tag = GetTag(input, pos, closing); + if (tag != -1) + { + TextFormatChange change; + change.flags = kFormatPop; + switch (tag) + { + case kTagBold: + change.flags = kFormatBold; + break; + case kTagItalic: + change.flags = kFormatItalic; + break; + case kTagSize: + change.flags = kFormatSize; + break; + case kTagColor: + change.flags = kFormatColor; + break; + case kTagMaterial: + change.flags = kFormatMaterial; + break; + case kTagImage: + change.flags = kFormatImage; + break; + default: + break; + } + if (closing) + change.flags |= kFormatPop; + else switch (tag) + { + case kTagSize: + change.format.size = StringToInt(GetParameter(input, pos)); + break; + case kTagColor: + change.format.color = ParseHTMLColor(GetParameter(input, pos)); + break; + case kTagMaterial: + change.format.material = StringToInt(GetParameter(input, pos)); + break; + case kTagImage: + ParseImageParameters(input, pos, change); + break; + default: + break; + } + change.skipCharacters = pos + 1 - oldpos; + change.startPosition = oldpos; + format.push_back(change); + if (tag == kTagImage) + { + change.flags |= kFormatPop; + change.skipCharacters = 0; + format.push_back(change); + } + } + pos ++; + } + if (!ValidateFormat(format)) + // Fail silently here rather then spamming the console with errors while typing unfinished markup. + format.clear(); +}
\ No newline at end of file diff --git a/Runtime/IMGUI/TextFormatting.h b/Runtime/IMGUI/TextFormatting.h new file mode 100644 index 0000000..3646692 --- /dev/null +++ b/Runtime/IMGUI/TextFormatting.h @@ -0,0 +1,85 @@ +#ifndef TEXTFORMATTING_H +#define TEXTFORMATTING_H + +#include "TextUtil.h" +#include "Runtime/Math/Rect.h" + +enum { + kStyleDefault = 0, + kStyleFlagBold = 1 << 0, + kStyleFlagItalic = 1 << 1, +}; + +struct TextFormat +{ + int style; + ColorRGBA32 color; + int size; + int material; + Rectf imageRect; + + TextFormat () : + style(0), + color(0xff,0xff,0xff,0xff), + size(0), + material(0), + imageRect(0,0,1,1) + { + } +}; + +enum FormatFlags { + kFormatBold = 1 << 0, + kFormatItalic = 1 << 1, + kFormatColor = 1 << 2, + kFormatSize = 1 << 3, + kFormatMaterial = 1 << 4, + kFormatImage = 1 << 5, + kFormatPop = 1 << 15, +}; + +struct TextFormatChange +{ + int startPosition; + int skipCharacters; + TextFormat format; + int flags; +}; + +class FormatStack : std::vector<TextFormat> +{ +public: + FormatStack (ColorRGBA32 _color, int _size, int _style) + { + push_back (TextFormat()); + back().color = _color; + back().size = _size; + back().style = _style; + } + + void PushFormat (TextFormatChange &change) + { + if (change.flags & kFormatPop) + pop_back(); + else + { + push_back(back()); + if (change.flags & kFormatBold) + back().style |= kStyleFlagBold; + if (change.flags & kFormatItalic) + back().style |= kStyleFlagItalic; + if (change.flags & kFormatColor) + back().color = change.format.color; + if (change.flags & kFormatSize) + back().size = change.format.size; + if (change.flags & kFormatMaterial) + back().material = change.format.material; + } + } + + TextFormat& Current() { return back(); } +}; + +void GetFormatString (UTF16String& input, std::vector<TextFormatChange> &format); + +#endif
\ No newline at end of file diff --git a/Runtime/IMGUI/TextMeshGenerator2.cpp b/Runtime/IMGUI/TextMeshGenerator2.cpp new file mode 100644 index 0000000..c5fafb0 --- /dev/null +++ b/Runtime/IMGUI/TextMeshGenerator2.cpp @@ -0,0 +1,871 @@ +#include "UnityPrefix.h" +#include "TextMeshGenerator2.h" +#include "TextFormatting.h" +#include "Runtime/Filters/Misc/Font.h" +#include "Runtime/Misc/BuildSettings.h" +#include "Runtime/Misc/ResourceManager.h" +#include "Runtime/Graphics/DrawUtil.h" +#include "Runtime/GfxDevice/GfxDevice.h" +#include "Runtime/Input/TimeManager.h" +#include "Runtime/Allocator/LinearAllocator.h" +#include "Runtime/Profiler/Profiler.h" +#include "Runtime/Utilities/dynamic_array.h" +#include <limits> + +#define TEXTDEBUG 0 + +#define kMaxMaterials 8 + +using std::pair; +typedef std::vector<TextMeshGenerator2*/*, main_thread_allocator<TextMeshGenerator2*> */> Generators; +static Generators s_Generators; +static Font *gDefaultFont = NULL; +const int kKillFrames = 5; + +static const TextAlignment kTextAnchorToAlignment[] = { + kLeft, kCenter, kRight, kLeft, kCenter, kRight, kLeft, kCenter, kRight +}; + +// The global getter function +TextMeshGenerator2 &TextMeshGenerator2::Get (const UTF16String &text, Font *font, TextAnchor anchor, TextAlignment alignment, float wordWrapWidth, float tabSize, float lineSpacing, bool richText, bool pixelCorrect, ColorRGBA32 color, int fontSize, int fontStyle) { + if (font == NULL) { + if (!gDefaultFont) + gDefaultFont = GetBuiltinResource<Font> (kDefaultFontName); + font = gDefaultFont; + } + + // maybe not the best way around, but we want to report error if changing size/style for non-dynamic fonts + // but in the same time don't want to spam console because we'll continue to work just fine + bool reportNonDynamicFontError = false; + + bool fontDynamic = font->GetConvertCase() == Font::kDynamicFont; + if( !fontDynamic ) + { + if( fontSize != 0 || fontStyle != kStyleDefault ) + reportNonDynamicFontError = true; + + fontSize = 0; + fontStyle = kStyleDefault; + } + + if (alignment == kAuto) + alignment = kTextAnchorToAlignment[anchor]; + // TODO: Use some sort of hash to quickly find candidate meshes + // in practice a lot of the parameters will be the same, with only the string really differing + for (Generators::const_iterator i = s_Generators.begin(); i != s_Generators.end(); i++) { + TextMeshGenerator2 *gen = *i; + if ( gen->m_Font.GetInstanceID() == font->GetInstanceID() && + (anchor == kDontCare || (gen->m_Anchor == anchor && gen->m_Alignment == alignment)) && + gen->m_WordWrapWidth == wordWrapWidth && + gen->m_TabSize == tabSize && + gen->m_LineSpacing == lineSpacing && + gen->m_UTF16Text == text && + gen->m_FontSize == fontSize && + gen->m_FontStyle == fontStyle && + gen->m_RichText == richText && + gen->m_PixelCorrect == pixelCorrect && + gen->m_Color == color + ) { + // we mark it as dirty the moment we look at it. + // Doing it during rendering only makes us regenerate strings only for wordwrap & scrollview size negotiations. + gen->m_LastUsedFrame = GetTimeManager().GetRenderFrameCount(); + return *gen; + } + } + + // report error only now - it will fire up the single time we create text mesh + if(reportNonDynamicFontError) + { + // TODO: font name? + WarningString("Font size and style overrides are only supported for dynamic fonts."); + } + + // We don't have one already so we need to generate it. + // Sanitize anchor if we don't care. We just pick one for the generation. + // If it gets rendered with another, this mesh will get garbage collected later and the real one used instead. + if (anchor == kDontCare) + anchor = kUpperLeft; + + TextMeshGenerator2 *gen = new TextMeshGenerator2 (text, font, anchor, alignment, wordWrapWidth, tabSize, lineSpacing, richText, pixelCorrect, color, fontSize, fontStyle); + gen->Generate (); + s_Generators.push_back (gen); + return *gen; +} + +float TextMeshGenerator2::Roundf (float x) +{ + if (m_PixelCorrect) + return ::Roundf(x); + else + return x; +} +/// Get the cursor position +Vector2f TextMeshGenerator2::GetCursorPosition (const Rectf &screenRect, int textPosition) { + // clamp the text position to valid ranges + if (textPosition < 0) + textPosition = 0; + else if (textPosition > m_UTF16Text.length) + textPosition = m_UTF16Text.length; + + // make sure we don't access out of bounds position when the string is too large to be processed completely. + if(textPosition * 4 + 4 > std::numeric_limits<UInt16>::max()) + textPosition = (std::numeric_limits<UInt16>::max() / 4) - 1; + + return m_CursorPos[textPosition] + GetTextOffset (screenRect); +} + + +// Compare 2 vertex positions. +// this function correctly handles rounding so you can click on the first half of a letter and be taken to the front... +inline int ComparePositions2 (Vector2f *arr, int maxCount, int idx1, const Vector2f &p2, float lineHeight) { + // Find out if we're line(s) above or below + Vector2f p = arr[idx1]; + if (p.y <= p2.y - lineHeight) + return -1; + if (p.y > p2.y) + return 1; + // ok, we're on the same line here, so compare x positions... + + // We need to get the middle of letters + Vector2f curr = arr[idx1]; + Vector2f nextPoint = arr[idx1 != maxCount ? idx1+ 1 : maxCount]; + // If the next letter is on the line after, we cheat by moving the point way to the right, so hit detection will work... + float next; + if (nextPoint.y == curr.y) + next = nextPoint.x; + else + next = 10000; + + + float rightBorder = (next + curr.x) * .5f; + if (rightBorder < p2.x) + return -1; + + Vector2f prevPoint = arr[idx1 != 0 ? idx1 - 1 : 0]; + float prev; + // If the previous letter is on the line before, we cheat by moving the point way to the left, so hit detection will work... + if (prevPoint.y == curr.y) + prev = prevPoint.x; + else + prev = -10000; + float leftBorder = (prev + curr.x) * .5f; + if (leftBorder > p2.x) + return 1; + return 0; +} + +int TextMeshGenerator2::GetCursorIndexAtPosition (const Rectf &screenRect, const Vector2f &pos) { + int start = 0; + int maxCount = m_CursorPos.size () - 1; + int end = maxCount; + Vector2f localPos = pos - GetTextOffset (screenRect); + Vector2f *arr = &m_CursorPos[0]; + + Font *font = GetFont(); + float lineSize = Roundf (font->GetLineSpacing(m_FontSize)); + + // Binary search to find out where we clicked + while (start <= end) { + int mid = ((start + end) >> 1); + switch (ComparePositions2 (arr, maxCount, mid, localPos, lineSize)) { + case 0: + return mid; + case -1: + start = mid + 1; + break; + case 1: + end = mid - 1; + break; + } + } + // We didn't find anything, this is the closest match + if (end < 0) + end = 0; // Sanity. + return end; +} + +PROFILER_INFORMATION(gTextMeshGenerator, "TextRendering.Cleanup", kProfilerRender) + + // clear the cache for anything that wasn't rendered in the last frame. + void TextMeshGenerator2::GarbageCollect () +{ + int currentFrame = GetTimeManager().GetRenderFrameCount(); + +#if TEXTDEBUG + printf_console ("TextMesh GarbageCollect\n"); +#endif + + // We're removing from the vector, so we need to iterate backwards through it + for (int i = s_Generators.size(); i--;) + { + PROFILER_AUTO(gTextMeshGenerator, NULL) + TextMeshGenerator2 *gen = s_Generators[i]; + if (currentFrame - gen->m_LastUsedFrame > kKillFrames) + { +#if TEXTDEBUG + printf_console ("\tTextMesh killed - %d chars\n", gen->m_CursorPos.size() - 1); +#endif + delete gen; + s_Generators.erase(s_Generators.begin() + i); + } + } +} + +void TextMeshGenerator2::Flush () +{ + for (int i = s_Generators.size(); i--;) + { + TextMeshGenerator2 *gen = s_Generators[i]; + delete gen; + } + s_Generators.clear (); +} + + +TextMeshGenerator2::TextMeshGenerator2 (const UTF16String &text, Font *font, TextAnchor anchor, TextAlignment alignment, float wordWrapWidth, float tabSize, float lineSpacing, bool richText, bool pixelCorrect, ColorRGBA32 color, int fontSize, int fontStyle) : +m_UTF16Text (text) +{ + m_Font = font; + m_FontSize = fontSize; + m_FontStyle = fontStyle; + + m_Anchor = anchor; + m_Alignment = alignment; + m_WordWrapWidth = wordWrapWidth; + m_TabSize = tabSize; + m_LastUsedFrame = 0; + m_LineSpacing = lineSpacing; + m_Mesh = NULL; + m_RichText = richText; + m_PixelCorrect = pixelCorrect; + m_Color = color; + +#if TEXTDEBUG + printf_console ("Creating textMesh\n"); +#endif +} + + +TextMeshGenerator2::~TextMeshGenerator2 () +{ + if (m_Mesh) + DestroySingleObject (&*m_Mesh); +} + +Vector2f TextMeshGenerator2::GetTextOffset (const Rectf &rect) +{ + Vector2f offset; + switch (m_Anchor) { + case kUpperLeft: + offset.x = Roundf (rect.x); + offset.y = Roundf (rect.y); + break; + case kUpperCenter: + offset.x = Roundf (rect.x + rect.width *.5f); + offset.y = Roundf (rect.y); + break; + case kUpperRight: + offset.x = Roundf (rect.GetRight()); + offset.y = Roundf (rect.y); + break; + case kMiddleLeft: + offset.x = Roundf (rect.x); + offset.y = Roundf ((rect.GetBottom() + rect.y - m_Rect.Height ()) * .5f); + break; + case kMiddleCenter: + offset.x = Roundf (rect.x + rect.width *.5f); + offset.y = Roundf ((rect.GetBottom() + rect.y - m_Rect.Height ()) * .5f); + break; + case kMiddleRight: + offset.x = Roundf (rect.GetRight()); + offset.y = Roundf ((rect.GetBottom() + rect.y - m_Rect.Height ()) * .5f); + break; + case kLowerLeft: + offset.x = Roundf (rect.x); + offset.y = Roundf (rect.GetBottom() - m_Rect.Height ()); + break; + case kLowerCenter: + offset.x = Roundf ((rect.x + rect.GetRight()) *.5f); + offset.y = Roundf (rect.GetBottom() - m_Rect.Height ()); + break; + case kLowerRight: + offset.x = Roundf (rect.GetRight()); + offset.y = Roundf (rect.GetBottom() - m_Rect.Height ()); + break; + default: + offset.x = offset.y = 0.0F; + break; + } + return offset; +} + +// Draw the text mesh +// TODO: clip +void TextMeshGenerator2::Render (const Rectf &rect, const ChannelAssigns& channels) +{ + GfxDevice &device = GetGfxDevice(); + + float matWorld[16], matView[16]; + + CopyMatrix(device.GetViewMatrix(), matView); + CopyMatrix(device.GetWorldMatrix(), matWorld); + + Matrix4x4f textMatrix; + + Vector2f offset = GetTextOffset (rect); + textMatrix.SetTranslate (Vector3f (offset.x, offset.y, 0.0f)); + + device.SetViewMatrix (textMatrix.GetPtr()); + + DrawUtil::DrawMeshRaw (channels, *m_Mesh, 0); + + device.SetViewMatrix(matView); + device.SetWorldMatrix(matWorld); + + // YES, we have actually used this TextMesh + m_LastUsedFrame = GetTimeManager().GetRenderFrameCount(); +} + +void TextMeshGenerator2::RenderRaw (const ChannelAssigns& channels) +{ + DrawUtil::DrawMeshRaw (channels, *m_Mesh, 0); + m_LastUsedFrame = GetTimeManager().GetRenderFrameCount(); +} + +// Will offset count*4 vertices horizontally - used for doing center & rightaligned +void OffsetCharacters (Vector2f offset, TextMeshGenerator2::Vertex *firstIndex, Vector2f *cursor, int count) { + int vertexCount = count * 4; + while (vertexCount--) { + firstIndex->vert.x += offset.x; + firstIndex->vert.y += offset.y; + firstIndex++; + } + while (count--) { + cursor->x += offset.x; + cursor->y += offset.y; + cursor++; + } +} + +void TextMeshGenerator2::FixLineOffset (float lineWidth, TextMeshGenerator2::Vertex *firstChar, Vector2f *firstCursor, int count) { + // If we're centre or right-aligned, handle that. + // Right now charOffset.x contains the width of the line. + switch (m_Alignment) { + // if we're right-aligned, move everything backl + case kRight: + OffsetCharacters (Vector2f (-lineWidth, 0), firstChar, firstCursor, count); + break; + case kCenter: + OffsetCharacters (Vector2f (Roundf (-lineWidth * .5f), 0), firstChar, firstCursor, count); + break; + default: + break; + } +} + +class TextMeshGenerationData +{ + TextMeshGenerator2 &tmgen; + std::vector<TextFormatChange> format; + TextMeshGenerator2::Vertex *vertex, *vertexBegin; + dynamic_array<UInt16> materialTriangles[kMaxMaterials]; + Font *font; + int formatChange; + int characterPos; + int lastChar; + int startOfWord, startOfLine; + int stringlen; + int materialCount; + float lineSize; + float lineLength, wordLength; + float endOfLastWord, startOfWordPos; + float spacesLength; + float maxLineLength; + bool wordWrap; + Vector3f charOffset; + float startY; + FormatStack formatStack; + + inline float Roundf (float x) + { + return tmgen.Roundf(x); + } + +public: + int GetStringLength () const { return stringlen; } + dynamic_array<UInt16> *GetTriangles () { return materialTriangles; } + + TextMeshGenerationData(TextMeshGenerator2 &_tmgen) + : tmgen (_tmgen), + formatStack(tmgen.m_Color, tmgen.m_FontSize, tmgen.m_FontStyle), + formatChange (0), + characterPos (0), + startOfWord (0), // Character index at start of current word (used for wordwrap) + startOfLine (0), // character index at start of current line (used for right/center-aligning text). + lineLength (0), // Length of current line without trailing spaces (in pixels) + wordLength (0), // Length of current word (in pixels) + endOfLastWord (0), // position of the last completed word's right bounds + startOfWordPos (0), // Pixel position of the current word start. + spacesLength (0), // Size of spaces (used to not make trailing spaces affect alignment) + maxLineLength (0), // The max line length - used for calculating the rect at the end. + lineSize (0), + startY (0), + lastChar (-1) + { + font = tmgen.GetFont(); + wordWrap = tmgen.m_WordWrapWidth != 0.0f; + } + + void ParseFormatAndCacheFont () + { + if (tmgen.m_RichText && IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_0_a1)) + GetFormatString (tmgen.m_UTF16Text, format); + int maxFontSize = tmgen.m_FontSize ? tmgen.m_FontSize : font->GetFontSize(); + + materialCount = 1; + for (std::vector<TextFormatChange>::iterator i = format.begin(); i != format.end(); i++) + { + if (i->flags & kFormatSize) + { + float size = i->format.size ? i->format.size : font->GetFontSize(); + if (size > maxFontSize) + maxFontSize = (int)size; + } + if (i->flags & (kFormatMaterial | kFormatImage)) + { + if (i->format.material >= kMaxMaterials || i->format.material < 0) + { + WarningStringMsg ("Only %d materials are allowed per TextMesh.", kMaxMaterials); + i->format.material = 0; + } + if (i->format.material+1 > materialCount) + materialCount = i->format.material+1; + } + } + + AssertIf (font == NULL); + font->CacheFontForText (tmgen.m_UTF16Text.text, tmgen.m_UTF16Text.length, tmgen.m_FontSize, tmgen.m_FontStyle, format); + + if (font->GetFontSize() != 0 && IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_3_a1) || font->GetConvertCase() == Font::kDynamicFont) + startY = Roundf(font->GetAscent() * ((float)maxFontSize/font->GetFontSize() - 1.0f)); + + lineSize = Roundf (font->GetLineSpacing(maxFontSize) * tmgen.m_LineSpacing); + + charOffset = Vector3f (0, startY, 0); + } + + void SetupBuffers () + { + stringlen = tmgen.m_UTF16Text.length; + + if(stringlen * 4 + 4 > std::numeric_limits<UInt16>::max()) + { + stringlen = (std::numeric_limits<UInt16>::max() / 4) - 1; + Assert("String too long for TextMeshGenerator. Cutting off characters."); + } + + Mesh *mesh = tmgen.m_Mesh; + if (!mesh) + { + tmgen.m_Mesh = NEW_OBJECT_MAIN_THREAD (Mesh); + mesh = tmgen.m_Mesh; + mesh->Reset(); + mesh->AwakeFromLoad(kInstantiateOrCreateFromCodeAwakeFromLoad); + + mesh->SetHideFlags (Object::kHideAndDontSave); + mesh->SetHideFromRuntimeStats(true); +#if UNITY_EDITOR + mesh->SetName("TextMesh"); +#endif + } + else + mesh->Clear (true); + + size_t vCount = stringlen * 4 + 4; + mesh->ResizeVertices (vCount, TextMeshGenerator2::Vertex::kFormat); + mesh->SetVertexColorsSwizzled (gGraphicsCaps.needsToSwizzleVertexColors); + + vertexBegin = (TextMeshGenerator2::Vertex*)mesh->GetVertexDataPointer (); + vertex = vertexBegin; + + // Set up the array & pointer for cursor positions. + // This is 1 longer than the string, as asking for a cursor position AFTER the last character is a valid request + tmgen.m_CursorPos.resize (stringlen + 1); + } + + void InsertSpace () + { + vertex[0].vert = vertex[1].vert = vertex[2].vert = vertex[3].vert = charOffset; + vertex += 4; + float w = font->GetCharacterWidth (' ', formatStack.Current().size, formatStack.Current().style); + // If this is the first space following a word, we record the last word position for correct wrapping + if (spacesLength == 0) + endOfLastWord = charOffset.x; + + spacesLength += w; + + // all the vars that makes this a word delimiter + startOfWord = characterPos + 1; + wordLength = 0; + startOfWordPos = charOffset.x + w; + // we never wordwrap on a space (no matter how many spaces you have, if it doesn't fit the cursor just stops at the rightmost position) + + charOffset.x += w; + } + + void InsertLineBreak () + { + // Move the line down + vertex[0].vert = vertex[1].vert = vertex[2].vert = vertex[3].vert = charOffset; + vertex += 4; + + // if we're right or center aligned, move all character in this line to the right place. + tmgen.FixLineOffset (lineLength, &vertexBegin[startOfLine * 4], &tmgen.m_CursorPos[startOfLine], characterPos - startOfLine + 1); + + charOffset.x = 0; + charOffset.y += lineSize; + maxLineLength = std::max(maxLineLength, lineLength); + lineLength = 0; + spacesLength = 0; + startOfLine = startOfWord = characterPos + 1; + } + + void InsertTab () + { + if (spacesLength == 0) + endOfLastWord = charOffset.x; + spacesLength = 0; + + int tab = FloorfToInt (charOffset.x / tmgen.m_TabSize) + 1; + if (wordWrap && tab * tmgen.m_TabSize > tmgen.m_WordWrapWidth) { + // if we're right or center aligned, move all character in this line to the right place. + tmgen.FixLineOffset (lineLength, &vertexBegin[startOfLine * 4], &tmgen.m_CursorPos[startOfLine], characterPos - startOfLine); + charOffset.y += lineSize; + charOffset.x = lineLength = tmgen.m_TabSize; + startOfLine = startOfWord = characterPos + 1; + maxLineLength = std::max(maxLineLength, lineLength); + } else { + // float newPos = tab * m_TabSize; + + if (wordWrap && tab * tmgen.m_TabSize > tmgen.m_WordWrapWidth) { + // if we're right or center aligned, move all character in this line to the right place. + tmgen.FixLineOffset (lineLength, &vertexBegin[startOfLine * 4], &tmgen.m_CursorPos[startOfLine], characterPos - startOfLine); + charOffset.y -= lineSize; + charOffset.x = lineLength = tmgen.m_TabSize; + startOfLine = startOfWord = characterPos + 1; + maxLineLength = std::max(maxLineLength, lineLength); + } else { + float newPos = tab * tmgen.m_TabSize; + + lineLength = charOffset.x = newPos; + } + vertex[0].vert = vertex[1].vert = vertex[2].vert = vertex[3].vert = charOffset; + vertex += 4; + + // all the vars that makes this a word delimiter + startOfWord = characterPos + 1; + wordLength = 0; + startOfWordPos = charOffset.x; + } + } + + void InsertCharacter (int c) + { + Rectf vert, charUv; // rendering info + bool flipped; + float w; // character width + + font->GetCharacterRenderInfo( c, formatStack.Current().size, formatStack.Current().style, vert, charUv, flipped ); + w = Roundf (font->GetCharacterWidth (c, formatStack.Current().size, formatStack.Current().style)); + + // TODO: can these be non-floats? If they can't shouldn't we be doing this at load rather than when getting each character? + // Possibly during serialization as well to conserve size (assume 100 characters * 4 floats = 1600 bytes/font) + vert.y = Roundf (vert.y); + vert.x = Roundf (vert.x); + vert.width = Roundf (vert.width); + vert.height = Roundf (vert.height); + + // Add kerning information if present. + if( !font->GetKerningValues().empty() && lastChar != -1 ) + { + pair<UnicodeChar, UnicodeChar> kerningPair = pair<UnicodeChar, UnicodeChar>(lastChar, c); + Font::KerningValues::iterator foundKerning = font->GetKerningValues().find(kerningPair); + + if (foundKerning != font->GetKerningValues().end()) + { + float kerning = foundKerning->second; + if (tmgen.m_FontSize != 0) + kerning = Roundf(kerning * tmgen.m_FontSize/(float)font->GetFontSize()); + charOffset.x += kerning; + } + } + + // Add quad vertices and UVs + vertex[0].vert = charOffset + Vector3f(vert.x, -vert.y, 0); + vertex[flipped?2:0].uv = Vector2f (charUv.x, charUv.GetBottom()); + vertex[1].vert = charOffset + Vector3f(vert.GetRight(), -vert.y, 0); + vertex[1].uv = Vector2f (charUv.GetRight(), charUv.GetBottom()); + vertex[2].vert = charOffset + Vector3f(vert.GetRight(), -vert.GetBottom(), 0); + vertex[flipped?0:2].uv = Vector2f (charUv.GetRight(), charUv.y); + vertex[3].vert = charOffset + Vector3f(vert.x, -vert.GetBottom(), 0); + vertex[3].uv = Vector2f (charUv.x, charUv.y); + ColorRGBA32 deviceColor = GfxDevice::ConvertToDeviceVertexColor(formatStack.Current().color); + vertex[0].color = vertex[1].color = vertex[2].color = vertex[3].color = deviceColor; + vertex += 4; + + // Add two triangles to indices + int vertexIndex = characterPos * 4; // since we always emit vertices we know the triangle index from the character index + int material = formatStack.Current().material; + materialTriangles[material].push_back(vertexIndex + 1); + materialTriangles[material].push_back(vertexIndex + 2); + materialTriangles[material].push_back(vertexIndex); + + materialTriangles[material].push_back(vertexIndex + 2); + materialTriangles[material].push_back(vertexIndex + 3); + materialTriangles[material].push_back(vertexIndex); + + // word-wrapping: If we're too large with this letter, we wrap the word to the next line by moving the verts + if (wordWrap && charOffset.x + w > tmgen.m_WordWrapWidth /* && wordLength != 0.0f*/) { + + // Special case: if the word we're wrapping is longer than one line alone, we split the word at the offending + // character. + if (startOfWord == startOfLine) { + startOfWord = characterPos; + wordLength = 0; + startOfWordPos = charOffset.x; + endOfLastWord = charOffset.x; + } + + // Horizontally align everything up to the last word (the part before the inserted newline) + tmgen.FixLineOffset (endOfLastWord, &vertexBegin[startOfLine * 4], &tmgen.m_CursorPos[startOfLine], startOfWord - startOfLine); + + // Move all letters after the inserted newline one line down and to 0 in Xoffset. + OffsetCharacters (Vector2f (-startOfWordPos, +lineSize), &vertexBegin[startOfWord * 4], &tmgen.m_CursorPos[startOfWord], characterPos - startOfWord + 1); + maxLineLength = std::max(maxLineLength, lineLength); + + // Move the cursor + charOffset.x -= startOfWordPos; + // The length of the current line is the length of the word we just wrapped down + lineLength = wordLength; + + charOffset.y += lineSize; + + // The new line starts at this word + startOfLine = startOfWord; + startOfWordPos = 0; + + // If we had any accumulated spaces, they don't apply now (as we just word-wrapped) + spacesLength = 0; + } + wordLength += w; + // advance the cursor + charOffset.x += w; + lineLength += w + spacesLength; // Add this letter + any accumulated spaces to the line length + spacesLength = 0; // We have now used the accumulated spaces + lastChar = c; + } + + void ProcessFormatForPosition () + { + while (formatChange < format.size() && characterPos >= format[formatChange].startPosition) + { + characterPos += format[formatChange].skipCharacters; + + // Make sure we don't overflow if the string is capped. + if (characterPos > stringlen) + { + characterPos = stringlen; + return; + } + + for (int j=0; j<format[formatChange].skipCharacters; j++) + { + vertex[0].vert = vertex[1].vert = vertex[2].vert = vertex[3].vert = charOffset; + vertex += 4; + } + + formatStack.PushFormat(format[formatChange]); + if ((format[formatChange].flags & (kFormatImage | kFormatPop)) == kFormatImage) + { + vertex -= 4; + float size = formatStack.Current().size; + if (font->GetFontSize() != 0 && IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_3_a1) || font->GetConvertCase() == Font::kDynamicFont) + { + if (size == 0) + size = font->GetFontSize(); + size = size / font->GetFontSize() * font->GetAscent(); + } + else + size = font->GetAscent(); + Vector3f imageBase = charOffset + Vector3f(0, font->GetAscent(), 0); + Rectf& r = format[formatChange].format.imageRect; + float aspect; + if (IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_3_a1)) + aspect = r.width / r.height; + else + aspect = 1.0f; + vertex[0].vert = imageBase + Vector3f(0, -size, 0); + vertex[0].uv = Vector2f (r.x, r.GetYMax()); + vertex[1].vert = imageBase + Vector3f(size * aspect, -size, 0); + vertex[1].uv = Vector2f (r.GetXMax(), r.GetYMax()); + vertex[2].vert = imageBase + Vector3f(size * aspect, 0, 0); + vertex[2].uv = Vector2f (r.GetXMax(), r.y); + vertex[3].vert = imageBase + Vector3f(0, 0, 0); + vertex[3].uv = Vector2f (r.x, r.y); + ColorRGBA32 deviceColor = GfxDevice::ConvertToDeviceVertexColor(formatStack.Current().color); + vertex[0].color = vertex[1].color = vertex[2].color = vertex[3].color = deviceColor; + charOffset += Vector3f(Roundf(size * aspect), 0, 0); + + if (IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_3_a1)) + lineLength = charOffset.x; + + vertex += 4; + + int vertexIndex = (characterPos-1) * 4; // since we always emit vertices we know the triangle index from the character index + int material = formatStack.Current().material; + materialTriangles[material].push_back(vertexIndex + 1); + materialTriangles[material].push_back(vertexIndex + 2); + materialTriangles[material].push_back(vertexIndex); + + materialTriangles[material].push_back(vertexIndex + 2); + materialTriangles[material].push_back(vertexIndex + 3); + materialTriangles[material].push_back(vertexIndex); + } + formatChange++; + } + } + + void ProcessString () + { + for (characterPos=0; characterPos <= stringlen; characterPos++) + { + ProcessFormatForPosition (); + + // all the code becomes a lot simpler if we pretend we have a newline at the end. + // If not, the last line becomes a special-case requiring lots of code duplication. + int c; + if (characterPos < stringlen) + c = tmgen.m_UTF16Text[characterPos]; + else + c = '\n'; + +#if TEXTDEBUG + printf_console ("%c", c); +#endif + + // store the character offset into the cursor position array + tmgen.m_CursorPos[characterPos] = Vector2f (charOffset.x, charOffset.y - startY); + + switch (c) { + case '\n': + InsertLineBreak(); + break; + case ' ': + InsertSpace(); + break; + case '\t': + InsertTab(); + break; + default: + InsertCharacter(c); + break; + } + } +#if TEXTDEBUG + printf_console ("\n"); +#endif + } + + Rectf GetBounds () + { + // Get the bounding volume. + // Y coordinates are 0 - charOffset.y (which has been moved down one line due to the fake \n we inserted. + Rectf r; + r.y = 0; + r.SetBottom(charOffset.y-startY); + switch (tmgen.m_Alignment) { + case kLeft: + r.x = 0; + r.width = Roundf (maxLineLength); + break; + case kRight: + r.x = -Roundf (maxLineLength); + r.width = Abs(r.x); + break; + case kCenter: + r.x = -Roundf (maxLineLength * .5f); + r.width = Roundf (maxLineLength); + break; + default: + break; + } + return r; + } + + void SetMeshData () + { + Mesh* mesh = tmgen.m_Mesh; + mesh->SetSubMeshCount(materialCount); + for (int i=0; i<materialCount; i++) + { + if (materialTriangles[i].size()) + mesh->SetIndicesComplex(&materialTriangles[i][0], materialTriangles[i].size(), i, kPrimitiveTriangles, Mesh::k16BitIndices); + } + + AABB bounds; + bounds.SetCenterAndExtent(Vector3f(tmgen.m_Rect.x, tmgen.m_Rect.y, 0.0f), Vector3f::zero); + bounds.Encapsulate (Vector3f(tmgen.m_Rect.GetXMax(), tmgen.m_Rect.GetYMax(), 0.0f)); + mesh->SetLocalAABB (bounds); + + mesh->SetChannelsDirty (mesh->GetAvailableChannels (), false ); + } +}; + +void TextMeshGenerator2::Generate () +{ + TextMeshGenerationData data (*this); + data.ParseFormatAndCacheFont (); + data.SetupBuffers (); + + // Allocate temp memory for the first material (which contains all triangles by default), + // to avoid unnecessary allocations. + UInt16 *triangles = NULL; + int bufferSize = data.GetStringLength() * 6; + ALLOC_TEMP(triangles, UInt16, bufferSize); + data.GetTriangles()[0].assign_external (triangles, triangles + bufferSize); + data.GetTriangles()[0].resize_uninitialized(0); + + data.ProcessString (); + + m_Rect = data.GetBounds (); + data.SetMeshData (); +} + +Font *TextMeshGenerator2::GetFont () const { + Font *f = m_Font; + if (!f) { + if (!gDefaultFont) + gDefaultFont = GetBuiltinResource<Font> (kDefaultFontName); + return gDefaultFont; + } + else { + return f; + } +} + +#if UNITY_EDITOR +void TextMeshGenerator2::CleanCache (const UTF16String &text) +{ + for (Generators::iterator i = s_Generators.begin(); i != s_Generators.end();) + { + TextMeshGenerator2 *gen = *i; + if (gen->m_UTF16Text == text) + { + delete gen; + i = s_Generators.erase(i); + } + else + i++; + } +} +#endif diff --git a/Runtime/IMGUI/TextMeshGenerator2.h b/Runtime/IMGUI/TextMeshGenerator2.h new file mode 100644 index 0000000..71b5cc6 --- /dev/null +++ b/Runtime/IMGUI/TextMeshGenerator2.h @@ -0,0 +1,96 @@ +#ifndef TEXTMESH +#define TEXTMESH + +#include "TextUtil.h" +#include "Runtime/Filters/Mesh/LodMesh.h" +#include "Runtime/Math/Rect.h" + +class ChannelAssigns; + +#if UNITY_LINUX +class Font; +#endif + +//#include "DLAllocator.h" + +// Pixel-correct textmesh generator +class TextMeshGenerator2 { + public: +// DECLARE_FORWARD_MAINTHREAD_ALLOCATOR_MEMBER_NEW_DELETE + + struct Vertex { + enum { kFormat = VERTEX_FORMAT3(Vertex, Color, TexCoord0) }; + Vector3f vert; + ColorRGBA32 color; + Vector2f uv; + }; + + // The global getter function + static TextMeshGenerator2 &Get (const UTF16String &text, Font *font, TextAnchor anchor, TextAlignment alignment, float wordWrapWidth, float tabSize, float lineSpacing, bool richText, bool pixelcorrect, ColorRGBA32 color, int fontSize = 0, int fontStyle = 0); + + /// Global update function - this is called once per frame & cleans up any meshes not used since then. + static void GarbageCollect (); + + /// Delete all text meshes + static void Flush (); + + static void CleanCache (const UTF16String &text); + // Individual textmesh objects here on down: + + /// Render this text thing inside Rect. + // See this code for the correct stup instructions for rendering text with RenderRaw + void Render (const Rectf &rect, const ChannelAssigns& channels); + + /// Just call the rendering on the GPU - don't set up a matrix to position the text or any such crap. Just submit the mesh. + void RenderRaw (const ChannelAssigns& channels); + + /// Get the modelview offset to position this object's text within rect. + Vector2f GetTextOffset (const Rectf &rect); + // Get the font to use - this gets the default font if none set + Font *GetFont () const; + // Get the g + Mesh *GetMesh () const { return m_Mesh; } + + /// Get the cursor position + Vector2f GetCursorPosition (const Rectf &screenRect, int cursor); + + int GetCursorIndexAtPosition (const Rectf &screenRect, const Vector2f &pos); + + /// Get the size. + Vector2f GetSize () const { return Vector2f (m_Rect.width, m_Rect.height); } + + private: + + TextMeshGenerator2 (const UTF16String &text, Font *font, TextAnchor anchor, TextAlignment alignment, float wordWrapWidth, float tabSize, float lineSpacing, bool richText, bool pixelcorrect, ColorRGBA32 color, int fontSize, int fontStyle); + ~TextMeshGenerator2 (); + void FixLineOffset (float lineWidth, Vertex *firstChar, Vector2f *firstCursor, int count); + + // Generate the mesh stuff from the stored values + void Generate (); + + float Roundf (float x); + + // Width to word-wrap against. If 0, no word-wrapping is applied. + float m_WordWrapWidth; + + TextAnchor m_Anchor; //< The text anchor. + TextAlignment m_Alignment; + float m_LineSpacing; + float m_TabSize; //< The pixel tab size + bool m_TabUsed; //< Is the tab used in the string? + bool m_RichText; + PPtr<Font> m_Font; //< font to use + int m_FontSize; + int m_FontStyle; + ColorRGBA32 m_Color; + bool m_PixelCorrect; + Rectf m_Rect; + UTF16String m_UTF16Text; + + Mesh* m_Mesh; //< The mesh that contains the generated characters + std::vector<Vector2f/*, main_thread_allocator<Vector2f>*/ > m_CursorPos; //< Cursor positions for each character + int m_LastUsedFrame; //< Has this textmeshgenerator been used this frame + + friend class TextMeshGenerationData; +}; +#endif diff --git a/Runtime/IMGUI/TextUtil.cpp b/Runtime/IMGUI/TextUtil.cpp new file mode 100644 index 0000000..4d8289a --- /dev/null +++ b/Runtime/IMGUI/TextUtil.cpp @@ -0,0 +1,277 @@ +#include "UnityPrefix.h" +#include "TextUtil.h" +#include "Runtime/Filters/Misc/Font.h" +#include "Runtime/Misc/ResourceManager.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Shaders/Material.h" +#include "Runtime/Shaders/Shader.h" +#include "Runtime/GfxDevice/GfxDevice.h" +#include "Runtime/Shaders/VBO.h" +#include "Runtime/Scripting/ScriptingUtility.h" +#include "Runtime/Shaders/Shader.h" +#include "Runtime/Scripting/ScriptingUtility.h" + +using namespace std; + +class Font; + +namespace TextUtil_Static +{ +static Font *s_DefaultFont = NULL; +} // namespace TextUtil_Static +static PPtr <Font> s_CurrentFont = NULL; +static PPtr<Material> s_CurrentMaterial = NULL; + +void SetTextFont (Font *font) { s_CurrentFont = font; } +void SetDrawTextMaterial (Material *m) { s_CurrentMaterial = m; } +Material *GetDrawTextMaterial () { return s_CurrentMaterial; } + +extern "C" UInt16* AllocateUTF16Memory(int bytes) +{ + return (UInt16*)UNITY_MALLOC(kMemUTF16String,bytes); +} + +#if ENABLE_SCRIPTING + +#if ENABLE_MONO +UTF16String::UTF16String (MonoString *sourceString) { + if (sourceString != SCRIPTING_NULL && mono_string_length (sourceString) != 0) + { + text = mono_string_chars (sourceString); + length = mono_string_length (sourceString); + owns = false; + } + else + { + owns = false; + text = NULL; + length = 0; + } +} +#elif UNITY_WINRT +UTF16String::UTF16String (ScriptingStringPtr sourceString) +{ + if (sourceString != SCRIPTING_NULL) + { + Platform::String^ utf16String = safe_cast<Platform::String^>(sourceString); + length = utf16String->Length(); + int size = length * sizeof(UInt16); + text = (UInt16*)UNITY_MALLOC(kMemUTF16String, size); + memcpy(text, utf16String->Data(), size); + owns = true; + } + else + { + owns = false; + text = NULL; + length = 0; + } +} +#else +UTF16String::UTF16String (ScriptingStringPtr sourceString) +{ + if (sourceString != SCRIPTING_NULL) + { + std::string str = scripting_cpp_string_for(sourceString); + text = AllocateUTF16Memory(str.size()*sizeof(UInt16)); + ConvertUTF8toUTF16 (str.c_str(), str.size(), text, length); + owns = true; + } + else + { + owns = false; + text = NULL; + length = 0; + } +} +#endif + +#endif + +extern "C" void UTF16String_TakeOverPreAllocatedUTF16Bytes(UTF16String* utfString, UInt16* bytes, int length) +{ + utfString->TakeOverPreAllocatedUTF16Bytes(bytes,length); +} + +void UTF16String::TakeOverPreAllocatedUTF16Bytes(UInt16* bytes, int size) +{ + if (owns) + UNITY_FREE(kMemUTF16String,text); + text = bytes; + length = size; + owns = true; +} + +void UTF16String::InitFromCharPointer (const char* str) +{ + int slen = strlen (str); + if (slen != 0) { + text = AllocateUTF16Memory(slen*sizeof(UInt16)); + ConvertUTF8toUTF16 (str, slen, text, length); + owns = true; + } + else { + text = NULL; + length = 0; + owns = false; + } +} + +UTF16String::UTF16String (const char* str) { + InitFromCharPointer(str); +} + +UTF16String::~UTF16String () { + if (owns) + UNITY_FREE(kMemUTF16String,text); +} + +UTF16String::UTF16String (const UTF16String &other) { + if (other.length != 0) + { + length = other.length; + text =(UInt16*)UNITY_MALLOC(kMemUTF16String,length*sizeof(UInt16)); + memcpy(text,other.text,sizeof (UInt16) * length); + owns = true; + } + else + { + length = 0; + text = NULL; + owns = false; + } +} + + +void UTF16String::CopyString (const UTF16String &other) +{ + if (owns) + UNITY_FREE(kMemUTF16String,text); + if (other.length != 0) + { + length = other.length; + text =(UInt16*)UNITY_MALLOC(kMemUTF16String,length*sizeof(UInt16)); + memcpy(text,other.text,sizeof (UInt16) * length); + owns = true; + } + else + { + length = 0; + text = NULL; + owns = false; + } +} + +#if ENABLE_SCRIPTING + +#if ENABLE_MONO +void UTF16String::BorrowString( ScriptingString *sourceString ) +{ + if (owns) + UNITY_FREE(kMemUTF16String,text); + + if (sourceString != NULL && mono_string_length (sourceString) != 0) + { + text = mono_string_chars (sourceString); + length = mono_string_length (sourceString); + owns = false; + } + else + { + owns = false; + text = NULL; + length = 0; + } +} +#elif UNITY_WINRT +void UTF16String::BorrowString(ScriptingStringPtr sourceString) +{ + if (owns) + UNITY_FREE(kMemUTF16String,text); + + if (sourceString != SCRIPTING_NULL) + { + Platform::String^ utf16String = safe_cast<Platform::String^>(sourceString); + length = utf16String->Length(); + int size = length * sizeof(UInt16); + text = (UInt16*)UNITY_MALLOC(kMemUTF16String, size); + memcpy(text, utf16String->Data(), size); + owns = true; + } + else + { + owns = false; + text = NULL; + length = 0; + } +} +#endif + +void UTF8ToUTF16String (const char* src, UTF16String& dst) +{ + UTF16String tmp(src); + dst.TakeOverPreAllocatedUTF16Bytes(tmp.text, tmp.length); + tmp.owns = false; +} + +ScriptingStringPtr UTF16String::GetScriptingString () +{ + if (!length || !text) + return SCRIPTING_NULL; + +#if ENABLE_MONO + UInt16* buffer = new UInt16[length+1]; + memcpy(buffer, text, length * 2); + buffer[length] = 0; + MonoString* retval = MonoStringNewUTF16 ((const wchar_t*) buffer); + delete[] buffer; + return retval; +#elif UNITY_FLASH || UNITY_WINRT + std::vector<char> tempStr; + tempStr.resize(length * 4); + int outLength; + ConvertUTF16toUTF8 (text, length, &tempStr[0], outLength); + tempStr[outLength]=0; + return scripting_string_new((char const*)&tempStr[0]); +#endif +} + +#endif + +Font* +GetTextFont () +{ + using namespace TextUtil_Static; + Font* font = s_CurrentFont; + if (font == NULL) { + // Make sure we have default resource + if (s_DefaultFont == NULL) { + s_DefaultFont = GetBuiltinResource<Font> (kDefaultFontName); + if (!s_DefaultFont || !s_DefaultFont->GetMaterial()) { + LogString ("Couldn't load default font or font material!"); + } + } + font = s_DefaultFont; + } + return font; +} + + +#include "TextMeshGenerator2.h" +static TextMeshGenerator2 &GetMeshGen (const UTF16String &text, Font *font, TextAnchor align, float width) { + const float tabSize = 16; + return TextMeshGenerator2::Get (text, font, align, kAuto, width, tabSize, 1.0f, false, true, ColorRGBA32(0xffffffff)); +} +float CalcTextHeight (const UTF16String &text, float width) { + TextMeshGenerator2 &gen = GetMeshGen (text, (Font*)GetTextFont(), kDontCare, width); + return gen.GetSize().y; +} +Vector2f CalcTextSize (const UTF16String &text) { + TextMeshGenerator2 &gen = GetMeshGen (text, (Font*)GetTextFont(), kDontCare, 0); + return gen.GetSize (); +} +Vector2f GetCursorPosition (const Rectf &screenRect, const UTF16String &text, TextAnchor align, bool wordWrap, int textPosition) { + float width = wordWrap ? screenRect.Width() : 0; + TextMeshGenerator2 &gen = GetMeshGen (text, (Font*)GetTextFont(), align, width); + return gen.GetCursorPosition(screenRect, textPosition); +} diff --git a/Runtime/IMGUI/TextUtil.h b/Runtime/IMGUI/TextUtil.h new file mode 100644 index 0000000..2ddc8be --- /dev/null +++ b/Runtime/IMGUI/TextUtil.h @@ -0,0 +1,110 @@ +#ifndef TEXTUTIL_H +#define TEXTUTIL_H + +namespace Unity { class Material; } +class Transform; +class Camera; +class Vector3f; +class Matrix4x4f; +class Object; +class Texture; +class Shader; +class Vector2f; +class Font; + +//#include <string> +#include <set> +#include <vector> +#include "Runtime/BaseClasses/GameObject.h" +#include "Runtime/Math/Color.h" +#include "Runtime/Math/Rect.h" + +using namespace Unity; + +enum TextAlignment { + kLeft, + kCenter, + kRight, + kAuto, +}; + +enum TextAnchor { + kUpperLeft, + kUpperCenter, + kUpperRight, + kMiddleLeft, + kMiddleCenter, + kMiddleRight, + kLowerLeft, + kLowerCenter, + kLowerRight, + kDontCare ///< Special case for getting text mesh generators: The anchoring used for the text doesn't modify the size of the generated text, so if you just want to query for it you don't care about anchoring +}; + +enum TextClipping { + /// Text flows freely outside the element. + kOverflow = 0, + /// Text gets clipped to be inside the element. + kClip = 1, +}; + +struct MonoString; + +// Opaque UTF16 string representation +struct UTF16String +{ +#if ENABLE_SCRIPTING + explicit UTF16String (ScriptingStringPtr sourceSting); +#endif + UTF16String () {text = NULL; length = 0; owns = false;} + explicit UTF16String (const char* str); + UTF16String (const UTF16String &other); + ~UTF16String (); + + UInt16 operator [] (int index) const { return text[index]; } + friend bool operator == (const UTF16String& lhs, const UTF16String& rhs) + { + using namespace std; + if (lhs.length != rhs.length) + return false; + return (rhs.text == NULL || memcmp(lhs.text, rhs.text,lhs.length * sizeof (UInt16)) == 0); + } + + UInt16* text; + int length; + bool owns; + + ScriptingStringPtr GetScriptingString (); + void CopyString (const UTF16String &other); +#if ENABLE_MONO || UNITY_WINRT + void BorrowString (ScriptingStringPtr sourceString); +#endif + void TakeOverPreAllocatedUTF16Bytes(UInt16* bytes, int size); +private: + void InitFromCharPointer(const char* str); +}; + +void UTF8ToUTF16String (const char* src, UTF16String& dst); + +Vector2f GetCursorPosition (const Rectf &screenRect, const UTF16String &text, TextAnchor align, bool wordWrap, int textPosition); + +// Set a custom font to use. +// If null, it will use the default resource font +void SetTextFont (Font *font); +Font *GetTextFont (); + +// Calculate size of a piece of text +Vector2f CalcTextSize (const UTF16String &text); + +/// Calculate height of text word-wrapped to width +float CalcTextHeight (const UTF16String &, float width); + +/// Set a custom shader to use for drawing text. +/// If null, the material set in the font will be used +void SetDrawTextMaterial (Material *m); +Material *GetDrawTextMaterial (); + + + + +#endif |