using System.Collections.Generic; using UnityEditor.Experimental.GraphView; using UnityEngine; using UnityEngine.UIElements; using System.Reflection; using System.Linq; namespace UnityEditor.VFX.UI { class ResizableElementFactory : UxmlFactory {} class ElementResizer : Manipulator { public readonly ResizableElement.Resizer direction; public readonly VisualElement resizedElement; public ElementResizer(VisualElement resizedElement, ResizableElement.Resizer direction) { this.direction = direction; this.resizedElement = resizedElement; } protected override void RegisterCallbacksOnTarget() { target.RegisterCallback(OnMouseDown); target.RegisterCallback(OnMouseUp); } protected override void UnregisterCallbacksFromTarget() { target.UnregisterCallback(OnMouseDown); target.UnregisterCallback(OnMouseUp); } Vector2 m_StartMouse; Vector2 m_StartSize; Vector2 m_MinSize; Vector2 m_MaxSize; Vector2 m_StartPosition; bool m_DragStarted = false; void OnMouseDown(MouseDownEvent e) { if (e.button == 0 && e.clickCount == 1) { VisualElement resizedTarget = resizedElement.parent; if (resizedTarget != null) { VisualElement resizedBase = resizedTarget.parent; if (resizedBase != null) { target.RegisterCallback(OnMouseMove); e.StopPropagation(); target.CaptureMouse(); m_StartMouse = resizedBase.WorldToLocal(e.mousePosition); m_StartSize = new Vector2(resizedTarget.resolvedStyle.width, resizedTarget.resolvedStyle.height); m_StartPosition = new Vector2(resizedTarget.resolvedStyle.left, resizedTarget.resolvedStyle.top); bool minWidthDefined = resizedTarget.resolvedStyle.minWidth != StyleKeyword.Auto; bool maxWidthDefined = resizedTarget.resolvedStyle.maxWidth != StyleKeyword.None; bool minHeightDefined = resizedTarget.resolvedStyle.minHeight != StyleKeyword.Auto; bool maxHeightDefined = resizedTarget.resolvedStyle.maxHeight != StyleKeyword.None; m_MinSize = new Vector2( minWidthDefined ? resizedTarget.resolvedStyle.minWidth.value : Mathf.NegativeInfinity, minHeightDefined ? resizedTarget.resolvedStyle.minHeight.value : Mathf.NegativeInfinity); m_MaxSize = new Vector2( maxWidthDefined ? resizedTarget.resolvedStyle.maxWidth.value : Mathf.Infinity, maxHeightDefined ? resizedTarget.resolvedStyle.maxHeight.value : Mathf.Infinity); m_DragStarted = false; } } } } void OnMouseMove(MouseMoveEvent e) { VisualElement resizedTarget = resizedElement.parent; VisualElement resizedBase = resizedTarget.parent; Vector2 mousePos = resizedBase.WorldToLocal(e.mousePosition); if (!m_DragStarted) { if (resizedTarget is IVFXResizable) { (resizedTarget as IVFXResizable).OnStartResize(); } m_DragStarted = true; } if ((direction & ResizableElement.Resizer.Right) != 0) { resizedTarget.style.width = Mathf.Min(m_MaxSize.x, Mathf.Max(m_MinSize.x, m_StartSize.x + mousePos.x - m_StartMouse.x)); } else if ((direction & ResizableElement.Resizer.Left) != 0) { float delta = mousePos.x - m_StartMouse.x; if (m_StartSize.x - delta < m_MinSize.x) { delta = -m_MinSize.x + m_StartSize.x; } else if (m_StartSize.x - delta > m_MaxSize.x) { delta = -m_MaxSize.x + m_StartSize.x; } resizedTarget.style.left = delta + m_StartPosition.x; resizedTarget.style.width = -delta + m_StartSize.x; } if ((direction & ResizableElement.Resizer.Bottom) != 0) { resizedTarget.style.height = Mathf.Min(m_MaxSize.y, Mathf.Max(m_MinSize.y, m_StartSize.y + mousePos.y - m_StartMouse.y)); } else if ((direction & ResizableElement.Resizer.Top) != 0) { float delta = mousePos.y - m_StartMouse.y; if (m_StartSize.y - delta < m_MinSize.y) { delta = -m_MinSize.y + m_StartSize.y; } else if (m_StartSize.y - delta > m_MaxSize.y) { delta = -m_MaxSize.y + m_StartSize.y; } resizedTarget.style.top = delta + m_StartPosition.y; resizedTarget.style.height = -delta + m_StartSize.y; } e.StopPropagation(); } void OnMouseUp(MouseUpEvent e) { if (e.button == 0) { VisualElement resizedTarget = resizedElement.parent; if (resizedTarget.style.width != m_StartSize.x || resizedTarget.style.height != m_StartSize.y) { if (resizedTarget is IVFXResizable) { (resizedTarget as IVFXResizable).OnResized(); } } target.UnregisterCallback(OnMouseMove); target.ReleaseMouse(); e.StopPropagation(); } } } public class ResizableElement : VisualElement { public ResizableElement() : this("uxml/Resizable") { pickingMode = PickingMode.Ignore; } public ResizableElement(string uiFile) { var tpl = Resources.Load(uiFile); var sheet = Resources.Load("Resizable"); styleSheets.Add(sheet); tpl.CloneTree(this); foreach (Resizer value in System.Enum.GetValues(typeof(Resizer))) { VisualElement resizer = this.Q(value.ToString().ToLower() + "-resize"); if (resizer != null) resizer.AddManipulator(new ElementResizer(this, value)); m_Resizers[value] = resizer; } foreach (Resizer vertical in new[] {Resizer.Top, Resizer.Bottom}) foreach (Resizer horizontal in new[] {Resizer.Left, Resizer.Right}) { VisualElement resizer = this.Q(vertical.ToString().ToLower() + "-" + horizontal.ToString().ToLower() + "-resize"); if (resizer != null) resizer.AddManipulator(new ElementResizer(this, vertical | horizontal)); m_Resizers[vertical | horizontal] = resizer; } } public enum Resizer { Top = 1 << 0, Bottom = 1 << 1, Left = 1 << 2, Right = 1 << 3, } Dictionary m_Resizers = new Dictionary(); } class StickyNodeChangeEvent : EventBase { public static StickyNodeChangeEvent GetPooled(StickyNote target, Change change) { var evt = GetPooled(); evt.target = target; evt.change = change; return evt; } public enum Change { title, contents, theme, textSize } public Change change {get; protected set; } } class StickyNote : GraphElement, IVFXResizable { public enum Theme { Classic, Black, Orange, Green, Blue, Red, Purple, Teal } Theme m_Theme = Theme.Classic; public Theme theme { get { return m_Theme; } set { if (m_Theme != value) { m_Theme = value; UpdateThemeClasses(); } } } public enum TextSize { Small, Medium, Large, Huge } TextSize m_TextSize = TextSize.Medium; public TextSize textSize { get {return m_TextSize; } set { if (m_TextSize != value) { m_TextSize = value; UpdateSizeClasses(); } } } public virtual void OnStartResize() { } public virtual void OnResized() { } Vector2 AllExtraSpace(VisualElement element) { return new Vector2( element.resolvedStyle.marginLeft + element.resolvedStyle.marginRight + element.resolvedStyle.paddingLeft + element.resolvedStyle.paddingRight + element.resolvedStyle.borderRightWidth + element.resolvedStyle.borderLeftWidth, element.resolvedStyle.marginTop + element.resolvedStyle.marginBottom + element.resolvedStyle.paddingTop + element.resolvedStyle.paddingBottom + element.resolvedStyle.borderBottomWidth + element.resolvedStyle.borderTopWidth ); } void OnFitToText(DropdownMenuAction a) { FitText(false); } public void FitText(bool onlyIfSmaller) { Vector2 preferredTitleSize = Vector2.zero; if (!string.IsNullOrEmpty(m_Title.text)) preferredTitleSize = m_Title.MeasureTextSize(m_Title.text, 0, MeasureMode.Undefined, 0, MeasureMode.Undefined); // This is the size of the string with the current title font and such preferredTitleSize += AllExtraSpace(m_Title); preferredTitleSize.x += m_Title.ChangeCoordinatesTo(this, Vector2.zero).x + resolvedStyle.width - m_Title.ChangeCoordinatesTo(this, new Vector2(m_Title.layout.width, 0)).x; Vector2 preferredContentsSizeOneLine = m_Contents.MeasureTextSize(m_Contents.text, 0, MeasureMode.Undefined, 0, MeasureMode.Undefined); Vector2 contentExtraSpace = AllExtraSpace(m_Contents); preferredContentsSizeOneLine += contentExtraSpace; Vector2 extraSpace = new Vector2(resolvedStyle.width, resolvedStyle.height) - m_Contents.ChangeCoordinatesTo(this, new Vector2(m_Contents.layout.width, m_Contents.layout.height)); extraSpace += m_Title.ChangeCoordinatesTo(this, Vector2.zero); preferredContentsSizeOneLine += extraSpace; float width = 0; float height = 0; // The content in one line is smaller than the current width. // Set the width to fit both title and content. // Set the height to have only one line in the content if (preferredContentsSizeOneLine.x < Mathf.Max(preferredTitleSize.x, resolvedStyle.width)) { width = Mathf.Max(preferredContentsSizeOneLine.x, preferredTitleSize.x); height = preferredContentsSizeOneLine.y + preferredTitleSize.y; } else // The width is not enough for the content: keep the width or use the title width if bigger. { width = Mathf.Max(preferredTitleSize.x + extraSpace.x, resolvedStyle.width); float contextWidth = width - extraSpace.x - contentExtraSpace.x; Vector2 preferredContentsSize = m_Contents.MeasureTextSize(m_Contents.text, contextWidth, MeasureMode.Exactly, 0, MeasureMode.Undefined); preferredContentsSize += contentExtraSpace; height = preferredTitleSize.y + preferredContentsSize.y + extraSpace.y; } if (!onlyIfSmaller || resolvedStyle.width < width) style.width = width; if (!onlyIfSmaller || resolvedStyle.height < height) style.height = height; OnResized(); } void UpdateThemeClasses() { foreach (Theme value in System.Enum.GetValues(typeof(Theme))) { if (m_Theme != value) { RemoveFromClassList("theme-" + value.ToString().ToLower()); } else { AddToClassList("theme-" + value.ToString().ToLower()); } } } void UpdateSizeClasses() { foreach (TextSize value in System.Enum.GetValues(typeof(TextSize))) { if (m_TextSize != value) { RemoveFromClassList("size-" + value.ToString().ToLower()); } else { AddToClassList("size-" + value.ToString().ToLower()); } } } public static readonly Vector2 defaultSize = new Vector2(200, 160); public StickyNote(Vector2 position) : this("uxml/StickyNote", position) { styleSheets.Add(Resources.Load("Selectable")); styleSheets.Add(Resources.Load("StickyNote")); RegisterCallback(OnAttachToPanel); } public StickyNote(string uiFile, Vector2 position) { var tpl = Resources.Load(uiFile); tpl.CloneTree(this); capabilities = Capabilities.Movable | Capabilities.Deletable | Capabilities.Ascendable | Capabilities.Selectable; m_Title = this.Q