// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. using System; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Input; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Utilities; namespace FlaxEditor.Surface { /// /// Visject Surface comment control. /// /// [HideInEditor] public class SurfaceComment : SurfaceNode { private Rectangle _colorButtonRect; private Rectangle _resizeButtonRect; private Float2 _startResizingSize; private readonly TextBox _renameTextBox; /// /// True if sizing tool is in use. /// protected bool _isResizing; /// /// True if rename textbox is active in order to rename comment /// protected bool _isRenaming; /// /// Gets or sets the color of the comment. /// public Color Color { get => BackgroundColor; set => BackgroundColor = value; } private string TitleValue { get => (string)Values[0]; set => SetValue(0, value, false); } private Color ColorValue { get => (Color)Values[1]; set => SetValue(1, value, false); } private Float2 SizeValue { get => (Float2)Values[2]; set => SetValue(2, value, false); } private int OrderValue { get => (int)Values[3]; set => SetValue(3, value, false); } /// public SurfaceComment(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) : base(id, context, nodeArch, groupArch) { _renameTextBox = new TextBox(false, 0, 0, Width) { Height = Constants.NodeHeaderSize, Visible = false, Parent = this, EndEditOnClick = false, // We have to handle this ourselves, otherwise the textbox instantly loses focus when double-clicking the header HorizontalAlignment = TextAlignment.Center, }; } /// public override void OnSurfaceLoaded(SurfaceNodeActions action) { base.OnSurfaceLoaded(action); // Read node data Title = TitleValue; Color = ColorValue; var size = SizeValue; if (Surface.GridSnappingEnabled) size = Surface.SnapToGrid(size, true); Size = size; // Order // Backwards compatibility - When opening with an older version send the old comments to the back if (Values.Length < 4) { if (IndexInParent > 0) IndexInParent = 0; OrderValue = IndexInParent; } else if (OrderValue != -1) { IndexInParent = OrderValue; } } /// public override void OnSpawned(SurfaceNodeActions action) { base.OnSpawned(action); // Randomize color Color = ColorValue = Color.FromHSV(new Random().NextFloat(0, 360), 0.7f, 0.25f, 0.8f); if (OrderValue == -1) OrderValue = Context.CommentCount - 1; IndexInParent = OrderValue; } /// public override void OnValuesChanged() { base.OnValuesChanged(); // Read node data Title = TitleValue; Color = ColorValue; Size = SizeValue; } private void EndResizing() { // Clear state _isResizing = false; if (_startResizingSize != Size) { SizeValue = Size; Surface.MarkAsEdited(false); } EndMouseCapture(); } /// public override bool CanSelect(ref Float2 location) { return _headerRect.MakeOffsetted(Location).Contains(ref location) && !_resizeButtonRect.MakeOffsetted(Location).Contains(ref location); } /// public override bool IsSelectionIntersecting(ref Rectangle selectionRect) { return _headerRect.MakeOffsetted(Location).Intersects(ref selectionRect); } /// protected override void UpdateRectangles() { const float headerSize = Constants.NodeHeaderSize; const float buttonMargin = Constants.NodeCloseButtonMargin; const float buttonSize = Constants.NodeCloseButtonSize; _headerRect = new Rectangle(0, 0, Width, headerSize); _closeButtonRect = new Rectangle(Width - buttonSize - buttonMargin, buttonMargin, buttonSize, buttonSize); _colorButtonRect = new Rectangle(_closeButtonRect.Left - buttonSize - buttonMargin, buttonMargin, buttonSize, buttonSize); _resizeButtonRect = new Rectangle(_closeButtonRect.Left, Height - buttonSize - buttonMargin, buttonSize, buttonSize); _renameTextBox.Width = Width; _renameTextBox.Height = headerSize; } /// public override void Update(float deltaTime) { if (_isRenaming && (!_renameTextBox.IsFocused || !RootWindow.IsFocused)) { Rename(_renameTextBox.Text); StopRenaming(); } base.Update(deltaTime); } /// public override void Draw() { var style = Style.Current; var color = Color; var backgroundRect = new Rectangle(Float2.Zero, Size); var headerColor = new Color(Mathf.Clamp(color.R, 0.1f, 0.3f), Mathf.Clamp(color.G, 0.1f, 0.3f), Mathf.Clamp(color.B, 0.1f, 0.3f), 0.4f); if (IsSelected && !_isRenaming) headerColor *= 2.0f; // Paint background Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), BackgroundColor); // Draw child controls DrawChildren(); // Header Render2D.FillRectangle(_headerRect, headerColor); if (!_isRenaming) Render2D.DrawText(style.FontLarge, Title, _headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); // Close button Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey); // Color button Render2D.DrawSprite(style.Settings, _colorButtonRect, _colorButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey); // Check if is resizing if (_isResizing) { // Draw overlay Render2D.FillRectangle(_resizeButtonRect, style.Selection); Render2D.DrawRectangle(_resizeButtonRect, style.SelectionBorder); } // Resize button Render2D.DrawSprite(style.Scale, _resizeButtonRect, _resizeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey); // Selection outline if (_isSelected) { backgroundRect.Expand(1.5f); var colorTop = Color.Orange; var colorBottom = Color.OrangeRed; Render2D.DrawRectangle(backgroundRect, colorTop, colorTop, colorBottom, colorBottom); } } /// protected override Float2 CalculateNodeSize(float width, float height) { return Size; } /// public override void OnLostFocus() { // Check if was resizing if (_isResizing) { EndResizing(); } // Check if was renaming if (_isRenaming) { Rename(_renameTextBox.Text); StopRenaming(); } // Base base.OnLostFocus(); } /// public override void OnEndMouseCapture() { // Check if was resizing if (_isResizing) { EndResizing(); } else { base.OnEndMouseCapture(); } } /// public override bool ContainsPoint(ref Float2 location, bool precise) { return _headerRect.Contains(ref location) || _resizeButtonRect.Contains(ref location); } /// public override bool OnMouseDown(Float2 location, MouseButton button) { if (base.OnMouseDown(location, button)) return true; // Check if can start resizing if (button == MouseButton.Left && _resizeButtonRect.Contains(ref location) && Surface.CanEdit) { // Start sliding _isResizing = true; _startResizingSize = Size; StartMouseCapture(); return true; } return false; } /// public override void OnMouseMove(Float2 location) { // Check if is resizing if (_isResizing) { // Update size var size = Float2.Max(location, new Float2(140.0f, _headerRect.Bottom)); if (Surface.GridSnappingEnabled) size = Surface.SnapToGrid(size, true); Size = size; } else { // Base base.OnMouseMove(location); } } /// public override bool OnMouseDoubleClick(Float2 location, MouseButton button) { if (base.OnMouseDoubleClick(location, button)) return true; // Rename if (_headerRect.Contains(ref location) && Surface.CanEdit) { StartRenaming(); return true; } return false; } /// /// Starts the renaming of the comment. Shows the UI for the user. /// public void StartRenaming() { _isRenaming = true; _renameTextBox.Visible = true; _renameTextBox.SetText(Title); _renameTextBox.Focus(); _renameTextBox.SelectAll(); } private void StopRenaming() { _isRenaming = false; _renameTextBox.Visible = false; } private void Rename(string newTitle) { if (string.Equals(Title, newTitle, StringComparison.Ordinal)) return; Title = TitleValue = newTitle; Surface.MarkAsEdited(false); } /// public override bool OnKeyDown(KeyboardKeys key) { if (key == KeyboardKeys.Return) { Rename(_renameTextBox.Text); StopRenaming(); return true; } if (key == KeyboardKeys.Escape) { StopRenaming(); return true; } return base.OnKeyDown(key); } /// public override bool OnMouseUp(Float2 location, MouseButton button) { if (button == MouseButton.Left && _isResizing) { EndResizing(); return true; } if (base.OnMouseUp(location, button)) return true; // Close if (_closeButtonRect.Contains(ref location) && Surface.CanEdit) { Surface.Delete(this); return true; } // Color if (_colorButtonRect.Contains(ref location) && Surface.CanEdit) { ColorValueBox.ShowPickColorDialog?.Invoke(this, Color, OnColorChanged); return true; } return false; } private void OnColorChanged(Color color, bool sliding) { Color = ColorValue = color; Surface.MarkAsEdited(false); } /// public override void OnShowSecondaryContextMenu(FlaxEditor.GUI.ContextMenu.ContextMenu menu, Float2 location) { base.OnShowSecondaryContextMenu(menu, location); menu.AddSeparator(); ContextMenuChildMenu cmOrder = menu.AddChildMenu("Order"); { cmOrder.ContextMenu.AddButton("Bring Forward", () => { if (IndexInParent < Context.CommentCount - 1) IndexInParent++; OrderValue = IndexInParent; }); cmOrder.ContextMenu.AddButton("Bring to Front", () => { IndexInParent = Context.CommentCount - 1; OrderValue = IndexInParent; }); cmOrder.ContextMenu.AddButton("Send Backward", () => { if (IndexInParent > 0) IndexInParent--; OrderValue = IndexInParent; }); cmOrder.ContextMenu.AddButton("Send to Back", () => { IndexInParent = 0; OrderValue = IndexInParent; }); } } } }