374 lines
12 KiB
C#
374 lines
12 KiB
C#
|
using System.Collections.Generic;
|
||
|
using UnityEditor.Experimental.GraphView;
|
||
|
using UnityEditor.ShaderGraph.Drawing.Interfaces;
|
||
|
using UnityEngine;
|
||
|
using UnityEngine.UIElements;
|
||
|
|
||
|
namespace UnityEditor.ShaderGraph.Drawing.Views
|
||
|
{
|
||
|
interface ISelectionProvider
|
||
|
{
|
||
|
List<ISelectable> GetSelection { get; }
|
||
|
}
|
||
|
|
||
|
class GraphSubWindow : GraphElement, ISGResizable
|
||
|
{
|
||
|
ISGViewModel m_ViewModel;
|
||
|
|
||
|
ISGViewModel ViewModel
|
||
|
{
|
||
|
get => m_ViewModel;
|
||
|
set => m_ViewModel = value;
|
||
|
}
|
||
|
|
||
|
Dragger m_Dragger;
|
||
|
|
||
|
// This needs to be something that each subclass defines for itself at creation time
|
||
|
// if they all use the same they'll be stacked on top of each other at SG window creation
|
||
|
protected WindowDockingLayout windowDockingLayout { get; private set; } = new WindowDockingLayout
|
||
|
{
|
||
|
dockingTop = true,
|
||
|
dockingLeft = false,
|
||
|
verticalOffset = 8,
|
||
|
horizontalOffset = 8,
|
||
|
};
|
||
|
|
||
|
// Used to cache the window docking layout between resizing operations as it interferes with window resizing operations
|
||
|
private IStyle cachedWindowDockingStyle;
|
||
|
|
||
|
protected VisualElement m_MainContainer;
|
||
|
protected VisualElement m_Root;
|
||
|
protected Label m_TitleLabel;
|
||
|
protected Label m_SubTitleLabel;
|
||
|
protected ScrollView m_ScrollView;
|
||
|
protected VisualElement m_ContentContainer;
|
||
|
protected VisualElement m_HeaderItem;
|
||
|
protected VisualElement m_ParentView;
|
||
|
|
||
|
// These are used as default values for styling and layout purposes
|
||
|
// They can be overriden if a child class wants to roll its own style and layout behavior
|
||
|
public virtual string layoutKey => "UnityEditor.ShaderGraph.SubWindow";
|
||
|
public virtual string styleName => "GraphSubWindow";
|
||
|
public virtual string UxmlName => "GraphSubWindow";
|
||
|
|
||
|
// Each sub-window will override these if they need to
|
||
|
public virtual string elementName => "";
|
||
|
public virtual string windowTitle => "";
|
||
|
|
||
|
public VisualElement ParentView
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (!isWindowed && m_ParentView == null)
|
||
|
m_ParentView = GetFirstAncestorOfType<GraphView>();
|
||
|
return m_ParentView;
|
||
|
}
|
||
|
|
||
|
set
|
||
|
{
|
||
|
if (!isWindowed)
|
||
|
return;
|
||
|
m_ParentView = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public List<ISelectable> selection
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (ParentView is ISelectionProvider selectionProvider)
|
||
|
return selectionProvider.GetSelection;
|
||
|
|
||
|
AssertHelpers.Fail("GraphSubWindow was unable to find a selection provider. Please check if parent view of: " + name + " implements ISelectionProvider::GetSelection");
|
||
|
return new List<ISelectable>();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public override string title
|
||
|
{
|
||
|
get { return m_TitleLabel.text; }
|
||
|
set { m_TitleLabel.text = value; }
|
||
|
}
|
||
|
|
||
|
public string subTitle
|
||
|
{
|
||
|
get { return m_SubTitleLabel.text; }
|
||
|
set { m_SubTitleLabel.text = value; }
|
||
|
}
|
||
|
|
||
|
// Intended for future handling of docking to sides of the shader graph window
|
||
|
bool m_IsWindowed;
|
||
|
public bool isWindowed
|
||
|
{
|
||
|
get { return m_IsWindowed; }
|
||
|
set
|
||
|
{
|
||
|
if (m_IsWindowed == value) return;
|
||
|
|
||
|
if (value)
|
||
|
{
|
||
|
capabilities &= ~Capabilities.Movable;
|
||
|
AddToClassList("windowed");
|
||
|
this.RemoveManipulator(m_Dragger);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
capabilities |= Capabilities.Movable;
|
||
|
RemoveFromClassList("windowed");
|
||
|
this.AddManipulator(m_Dragger);
|
||
|
}
|
||
|
m_IsWindowed = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public override VisualElement contentContainer => m_ContentContainer;
|
||
|
|
||
|
private bool m_IsResizable = false;
|
||
|
|
||
|
// Can be set by child classes as needed
|
||
|
protected bool isWindowResizable
|
||
|
{
|
||
|
get => m_IsResizable;
|
||
|
set
|
||
|
{
|
||
|
if (m_IsResizable != value)
|
||
|
{
|
||
|
m_IsResizable = value;
|
||
|
HandleResizingBehavior(m_IsResizable);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void HandleResizingBehavior(bool isResizable)
|
||
|
{
|
||
|
if (isResizable)
|
||
|
{
|
||
|
var resizeElement = this.Q<ResizableElement>();
|
||
|
resizeElement.BindOnResizeCallback(OnWindowResize);
|
||
|
hierarchy.Add(resizeElement);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
var resizeElement = this.Q<ResizableElement>();
|
||
|
resizeElement.SetResizeRules(ResizableElement.Resizer.None);
|
||
|
hierarchy.Remove(resizeElement);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected void SetResizingRules(ResizableElement.Resizer resizeDirections)
|
||
|
{
|
||
|
var resizeElement = this.Q<ResizableElement>();
|
||
|
resizeElement.SetResizeRules(resizeDirections);
|
||
|
}
|
||
|
|
||
|
private bool m_IsScrollable = false;
|
||
|
|
||
|
// Can be set by child classes as needed
|
||
|
protected bool isWindowScrollable
|
||
|
{
|
||
|
get => m_IsScrollable;
|
||
|
set
|
||
|
{
|
||
|
if (m_IsScrollable != value)
|
||
|
{
|
||
|
m_IsScrollable = value;
|
||
|
HandleScrollingBehavior(m_IsScrollable);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected float scrollableWidth
|
||
|
{
|
||
|
get { return m_ScrollView.contentContainer.layout.width - m_ScrollView.contentViewport.layout.width; }
|
||
|
}
|
||
|
|
||
|
protected float scrollableHeight
|
||
|
{
|
||
|
get { return contentContainer.layout.height - m_ScrollView.contentViewport.layout.height; }
|
||
|
}
|
||
|
|
||
|
void HandleScrollingBehavior(bool scrollable)
|
||
|
{
|
||
|
if (scrollable)
|
||
|
{
|
||
|
// Remove the categories container from the content item and add it to the scrollview
|
||
|
m_ContentContainer.RemoveFromHierarchy();
|
||
|
m_ScrollView.Add(m_ContentContainer);
|
||
|
AddToClassList("scrollable");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Remove the categories container from the scrollview and add it to the content item
|
||
|
m_ContentContainer.RemoveFromHierarchy();
|
||
|
m_Root.Add(m_ContentContainer);
|
||
|
|
||
|
RemoveFromClassList("scrollable");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected GraphSubWindow(ISGViewModel viewModel)
|
||
|
{
|
||
|
ViewModel = viewModel;
|
||
|
m_ParentView = ViewModel.parentView;
|
||
|
ParentView.Add(this);
|
||
|
|
||
|
var styleSheet = Resources.Load<StyleSheet>($"Styles/{styleName}");
|
||
|
// Setup VisualElement from Stylesheet and UXML file
|
||
|
styleSheets.Add(styleSheet);
|
||
|
var uxml = Resources.Load<VisualTreeAsset>($"UXML/{UxmlName}");
|
||
|
m_MainContainer = uxml.Instantiate();
|
||
|
m_MainContainer.AddToClassList("mainContainer");
|
||
|
|
||
|
m_Root = m_MainContainer.Q("content");
|
||
|
m_HeaderItem = m_MainContainer.Q("header");
|
||
|
m_HeaderItem.AddToClassList("subWindowHeader");
|
||
|
m_ScrollView = m_MainContainer.Q<ScrollView>("scrollView");
|
||
|
m_TitleLabel = m_MainContainer.Q<Label>(name: "titleLabel");
|
||
|
m_SubTitleLabel = m_MainContainer.Q<Label>(name: "subTitleLabel");
|
||
|
m_ContentContainer = m_MainContainer.Q(name: "contentContainer");
|
||
|
|
||
|
hierarchy.Add(m_MainContainer);
|
||
|
|
||
|
capabilities |= Capabilities.Movable | Capabilities.Resizable;
|
||
|
style.overflow = Overflow.Hidden;
|
||
|
focusable = false;
|
||
|
|
||
|
name = elementName;
|
||
|
title = windowTitle;
|
||
|
|
||
|
ClearClassList();
|
||
|
AddToClassList(name);
|
||
|
|
||
|
BuildManipulators();
|
||
|
|
||
|
/* Event interception to prevent GraphView manipulators from being triggered */
|
||
|
//RegisterCallback<DragUpdatedEvent>(e =>
|
||
|
//{
|
||
|
// e.StopPropagation();
|
||
|
//});
|
||
|
|
||
|
// prevent Zoomer manipulator
|
||
|
RegisterCallback<WheelEvent>(e =>
|
||
|
{
|
||
|
e.StopPropagation();
|
||
|
});
|
||
|
|
||
|
//RegisterCallback<MouseDownEvent>(e =>
|
||
|
//{
|
||
|
// // prevent ContentDragger manipulator
|
||
|
// e.StopPropagation();
|
||
|
//});
|
||
|
}
|
||
|
|
||
|
public void ShowWindow()
|
||
|
{
|
||
|
this.style.visibility = Visibility.Visible;
|
||
|
this.m_ScrollView.style.display = DisplayStyle.Flex;
|
||
|
this.MarkDirtyRepaint();
|
||
|
}
|
||
|
|
||
|
public void HideWindow()
|
||
|
{
|
||
|
this.style.visibility = Visibility.Hidden;
|
||
|
this.m_ScrollView.style.display = DisplayStyle.None;
|
||
|
this.MarkDirtyRepaint();
|
||
|
}
|
||
|
|
||
|
void BuildManipulators()
|
||
|
{
|
||
|
m_Dragger = new Dragger { clampToParentEdges = true };
|
||
|
RegisterCallback<MouseUpEvent>(OnMoveEnd);
|
||
|
this.AddManipulator(m_Dragger);
|
||
|
}
|
||
|
|
||
|
#region Layout
|
||
|
public void ClampToParentLayout(Rect parentLayout)
|
||
|
{
|
||
|
windowDockingLayout.CalculateDockingCornerAndOffset(layout, parentLayout);
|
||
|
windowDockingLayout.ClampToParentWindow();
|
||
|
|
||
|
// If the parent shader graph window is being resized smaller than this window on either axis
|
||
|
if (parentLayout.width < this.layout.width || parentLayout.height < this.layout.height)
|
||
|
{
|
||
|
// Don't adjust the sub window in this case as it causes flickering errors and looks broken
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
windowDockingLayout.ApplyPosition(this);
|
||
|
}
|
||
|
|
||
|
SerializeLayout();
|
||
|
}
|
||
|
|
||
|
public void OnStartResize()
|
||
|
{
|
||
|
cachedWindowDockingStyle = this.style;
|
||
|
}
|
||
|
|
||
|
public void OnResized()
|
||
|
{
|
||
|
if (cachedWindowDockingStyle != null)
|
||
|
{
|
||
|
this.style.left = cachedWindowDockingStyle.left;
|
||
|
this.style.right = cachedWindowDockingStyle.right;
|
||
|
this.style.bottom = cachedWindowDockingStyle.bottom;
|
||
|
this.style.top = cachedWindowDockingStyle.top;
|
||
|
}
|
||
|
windowDockingLayout.size = layout.size;
|
||
|
SerializeLayout();
|
||
|
}
|
||
|
|
||
|
public void DeserializeLayout()
|
||
|
{
|
||
|
var serializedLayout = EditorUserSettings.GetConfigValue(layoutKey);
|
||
|
if (!string.IsNullOrEmpty(serializedLayout))
|
||
|
windowDockingLayout = JsonUtility.FromJson<WindowDockingLayout>(serializedLayout);
|
||
|
else
|
||
|
{
|
||
|
// The window size needs to come from the stylesheet or UXML as opposed to being defined in code
|
||
|
windowDockingLayout.size = layout.size;
|
||
|
}
|
||
|
|
||
|
windowDockingLayout.ApplySize(this);
|
||
|
windowDockingLayout.ApplyPosition(this);
|
||
|
}
|
||
|
|
||
|
protected void AddStyleSheetFromPath(string styleSheetPath)
|
||
|
{
|
||
|
StyleSheet sheetAsset = Resources.Load<StyleSheet>(styleSheetPath); ;
|
||
|
|
||
|
if (sheetAsset == null)
|
||
|
{
|
||
|
Debug.LogWarning(string.Format("Style sheet not found for path \"{0}\"", styleSheetPath));
|
||
|
return;
|
||
|
}
|
||
|
styleSheets.Add(sheetAsset);
|
||
|
}
|
||
|
|
||
|
void SerializeLayout()
|
||
|
{
|
||
|
windowDockingLayout.size = layout.size;
|
||
|
var serializedLayout = JsonUtility.ToJson(windowDockingLayout);
|
||
|
EditorUserSettings.SetConfigValue(layoutKey, serializedLayout);
|
||
|
}
|
||
|
|
||
|
void OnMoveEnd(MouseUpEvent upEvent)
|
||
|
{
|
||
|
windowDockingLayout.CalculateDockingCornerAndOffset(layout, ParentView.layout);
|
||
|
windowDockingLayout.ClampToParentWindow();
|
||
|
|
||
|
SerializeLayout();
|
||
|
}
|
||
|
|
||
|
public bool CanResizePastParentBounds()
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
void OnWindowResize(MouseUpEvent upEvent)
|
||
|
{
|
||
|
}
|
||
|
}
|
||
|
#endregion
|
||
|
}
|