diff --git a/BeefLibs/Beefy2D/src/events/DragEvent.bf b/BeefLibs/Beefy2D/src/events/DragEvent.bf index 31f2ace3..6cf06991 100644 --- a/BeefLibs/Beefy2D/src/events/DragEvent.bf +++ b/BeefLibs/Beefy2D/src/events/DragEvent.bf @@ -4,12 +4,19 @@ using System.Text; namespace Beefy.events { + public enum DragKind + { + None, + Inside, + Before, + After + } + public class DragEvent : Event { public float mX; public float mY; public Object mDragTarget; - public int32 mDragTargetDir; - public bool mDragAllowed = true; + public DragKind mDragKind = .Inside; } } diff --git a/BeefLibs/Beefy2D/src/theme/dark/DarkListView.bf b/BeefLibs/Beefy2D/src/theme/dark/DarkListView.bf index a86390a3..ee86eb66 100644 --- a/BeefLibs/Beefy2D/src/theme/dark/DarkListView.bf +++ b/BeefLibs/Beefy2D/src/theme/dark/DarkListView.bf @@ -101,7 +101,7 @@ namespace Beefy.theme.dark public DragEvent mCurDragEvent ~ delete _; public DragHelper mDragHelper ~ delete _; public DarkListViewItem mDragTarget; - public int32 mDragTargetInsertDir; + public DragKind mDragKind; public bool mOpenOnDoubleClick = true; public bool mIsBold; @@ -174,6 +174,14 @@ namespace Beefy.theme.dark } } + public override bool IsDragging + { + get + { + return (mDragTarget != null) && (mDragHelper.mIsDragging); + } + } + public this() { @@ -596,8 +604,52 @@ namespace Beefy.theme.dark DrawLinesGrid(g); DrawChildren(g); - - if (mDragTarget != null) + + if (mDragTarget != null) + { + listView.mOnPostDraw.Add(new (g) => + { + float targetX; + float targetY; + mDragTarget.SelfToOtherTranslate(mListView, 0, 0, out targetX, out targetY); + + //using (g.PushTranslate(targetX, targetY)) + { + /*if ((mUpdateCnt % 60) == 0) + Debug.WriteLine(String.Format("{0} indent {1}", mDragTarget.mLabel, mDragTarget.mDepth));*/ + + if (mDragKind == .Inside) + { + using (g.PushColor(0xFF6f9761)) + { + //g.FillRect(targetX + GS!(4), targetY, mListView.mWidth - targetX - GS!(28), GS!(22)); + //g.OutlineRect(targetX + GS!(4), targetY, mListView.mWidth - targetX - GS!(28), GS!(20)); + g.DrawButton(DarkTheme.sDarkTheme.GetImage(Focused ? DarkTheme.ImageIdx.MenuSelect : DarkTheme.ImageIdx.MenuNonFocusSelect), + targetX + GS!(2), targetY, listView.mWidth - targetX - GS!(24)); + } + } + else + { + if ((mDragKind == .Inside) || (mDragKind == .After)) // Inside or after + targetY += mDragTarget.mSelfHeight; + + if (mDragKind == .After) // After + targetY += mDragTarget.mChildAreaHeight + mDragTarget.mBottomPadding; + + if (mDragKind == .Inside) // Inside + targetX += ((DarkListView)mListView).mChildIndent + mDragTarget.mChildIndent; + + /*if (-curY + targetY > mHeight) + wasTargetBelowBottom = true;*/ + + using (g.PushColor(0xFF95A68F)) + g.FillRect(targetX + GS!(4), targetY, mListView.mWidth - targetX - GS!(28), GS!(2)); + } + } + }); + } + + /*if (mDragTarget != null) { float targetX; float targetY; @@ -614,20 +666,28 @@ namespace Beefy.theme.dark /*if ((mUpdateCnt % 60) == 0) Debug.WriteLine(String.Format("{0} indent {1}", mDragTarget.mLabel, mDragTarget.mDepth));*/ - if (mDragTargetInsertDir >= 0) // Inside or after - targetY += mDragTarget.mSelfHeight; - - if (mDragTargetInsertDir > 0) // After - targetY += mDragTarget.mChildAreaHeight + mDragTarget.mBottomPadding; + if (mDragKind == .Inside) + { + using (g.PushColor(0xFF95A68F)) + g.FillRect(targetX + GS!(4), targetY, mListView.mWidth - targetX - GS!(28), GS!(22)); + } + else + { + if ((mDragKind == .Inside) || (mDragKind == .After)) // Inside or after + targetY += mDragTarget.mSelfHeight; + + if (mDragKind == .After) // After + targetY += mDragTarget.mChildAreaHeight + mDragTarget.mBottomPadding; - if (mDragTargetInsertDir == 0) // Inside - targetX += ((DarkListView)mListView).mChildIndent + mDragTarget.mChildIndent; + if (mDragKind == .Inside) // Inside + targetX += ((DarkListView)mListView).mChildIndent + mDragTarget.mChildIndent; - if (-curY + targetY > mHeight) - wasTargetBelowBottom = true; + if (-curY + targetY > mHeight) + wasTargetBelowBottom = true; - using (g.PushColor(0xFF95A68F)) - g.FillRect(targetX + 4, targetY, mListView.mWidth - targetX - 28, 2); + using (g.PushColor(0xFF95A68F)) + g.FillRect(targetX + GS!(4), targetY, mListView.mWidth - targetX - GS!(28), GS!(2)); + } } if (wasTargetBelowBottom) @@ -635,7 +695,7 @@ namespace Beefy.theme.dark /*Image img = DarkTheme.sDarkTheme.GetImage(DarkTheme.ImageIdx.MoveDownArrow); g.Draw(img, mWidth / 2 - img.mWidth / 2, mHeight - img.mHeight);*/ } - } + }*/ } public override bool Open(bool open, bool immediate = false) @@ -662,7 +722,7 @@ namespace Beefy.theme.dark e.mSender = GetMainItem(); e.mDragTarget = mDragTarget; if (mDragHelper.mAborted) - e.mDragAllowed = false; + e.mDragKind = .None; if (listView.mOnDragEnd.HasListeners) listView.mOnDragEnd(e); } @@ -723,12 +783,12 @@ namespace Beefy.theme.dark float yOfs = aY - childY; if (yOfs < mHeight / 2) - mDragTargetInsertDir = -1; + mDragKind = .Before; else { - mDragTargetInsertDir = 1; + mDragKind = .After; if ((listViewItem.mOpenButton != null) && (listViewItem.mOpenButton.mIsOpen)) - mDragTargetInsertDir = 0; + mDragKind = .None; } delete mCurDragEvent; @@ -737,12 +797,12 @@ namespace Beefy.theme.dark mCurDragEvent.mY = y; mCurDragEvent.mSender = head; mCurDragEvent.mDragTarget = foundWidget; - mCurDragEvent.mDragTargetDir = mDragTargetInsertDir; + mCurDragEvent.mDragKind = mDragKind; listView.mOnDragUpdate(mCurDragEvent); - mDragTargetInsertDir = mCurDragEvent.mDragTargetDir; + mDragKind = mCurDragEvent.mDragKind; mDragTarget = mCurDragEvent.mDragTarget as DarkListViewItem; - if (!mCurDragEvent.mDragAllowed) + if (mCurDragEvent.mDragKind == .None) mDragTarget = null; if (mDragTarget != null) @@ -847,6 +907,7 @@ namespace Beefy.theme.dark public Event mOnDragUpdate ~ _.Dispose(); public Event mOnDragEnd ~ _.Dispose(); + public Event mOnPostDraw ~ _.Dispose(); public this() { @@ -1004,6 +1065,13 @@ namespace Beefy.theme.dark } } + public override void DrawAll(Graphics g) + { + base.DrawAll(g); + mOnPostDraw(g); + mOnPostDraw.Dispose(); + } + public override void MouseMove(float x, float y) { base.MouseMove(x, y); diff --git a/BeefLibs/Beefy2D/src/widgets/DragHelper.bf b/BeefLibs/Beefy2D/src/widgets/DragHelper.bf index f013c1cb..a609cd47 100644 --- a/BeefLibs/Beefy2D/src/widgets/DragHelper.bf +++ b/BeefLibs/Beefy2D/src/widgets/DragHelper.bf @@ -151,20 +151,23 @@ namespace Beefy.widgets public void Update() { - int32 ticksDown = mWidget.mUpdateCnt - mDownUpdateCnt; + //int32 ticksDown = mWidget.mUpdateCnt - mDownUpdateCnt; - if (((mMouseFlags & MouseFlag.Left) != 0) && (ticksDown >= mMinDownTicks)) + if ((mMouseFlags & MouseFlag.Left) != 0) { - if ((!mIsDragging) && (mAllowDrag)) + bool isTriggered = false; + /*if ((mMinDownTicks > 0) && (ticksDown >= mMinDownTicks)) + isTriggered = true;*/ + if ((Math.Abs(mMouseX - mMouseDownX) >= mTriggerDist) || (Math.Abs(mMouseY - mMouseDownY) >= mTriggerDist)) + isTriggered = true; + + if ((!mIsDragging) && (mAllowDrag) && (isTriggered)) { - if ((Math.Abs(mMouseX - mMouseDownX) >= mTriggerDist) || (Math.Abs(mMouseY - mMouseDownY) >= mTriggerDist)) - { - SetHooks(true); - mPreparingForWidgetMove = false; - mAborted = false; - mIsDragging = true; - mDragInterface.DragStart(); - } + SetHooks(true); + mPreparingForWidgetMove = false; + mAborted = false; + mIsDragging = true; + mDragInterface.DragStart(); } if (mIsDragging) diff --git a/BeefLibs/Beefy2D/src/widgets/ListView.bf b/BeefLibs/Beefy2D/src/widgets/ListView.bf index 6b985ca8..e4360ee8 100644 --- a/BeefLibs/Beefy2D/src/widgets/ListView.bf +++ b/BeefLibs/Beefy2D/src/widgets/ListView.bf @@ -4,6 +4,7 @@ using System.Collections; using System.Text; using System.Diagnostics; using Beefy.gfx; +using Beefy.events; namespace Beefy.widgets { @@ -111,6 +112,14 @@ namespace Beefy.widgets } } + public virtual bool IsDragging + { + get + { + return false; + } + } + public virtual float LabelX { get { return 0; } } public virtual float LabelWidth { get { return mWidth; } } @@ -185,6 +194,16 @@ namespace Beefy.widgets return null; } + public int32 GetSelectedItemCount() + { + int32 count = 0; + WithSelectedItems(scope [&] (item) => + { + count++; + }); + return count; + } + public ListViewItem FindLastSelectedItem(bool filterToVisible = false) { ListViewItem selected = null; @@ -320,13 +339,44 @@ namespace Beefy.widgets } else { - SelectItemExclusively(item); - if (item.Selected) - item.Focused = true; + if (item.Selected) + { + item.mOnMouseUp.AddFront(new => ItemMouseUpHandler); + + var focusedItem = FindFocusedItem(); + if (focusedItem != null) + { + focusedItem.Focused = false; + focusedItem.Selected = true; + } + item.Focused = true; + } + else + { + SelectItemExclusively(item); + if (item.Selected) + item.Focused = true; + } + //SelectItemExclusively(item); + //if (item.Selected) + //item.Focused = true; } } } + void ItemMouseUpHandler(MouseEvent evt) + { + var item = evt.mSender as ListViewItem; + if ((item.mMouseOver) && (!item.IsDragging)) + { + SelectItemExclusively(item); + if (item.Selected) + item.Focused = true; + } + + item.mOnMouseUp.Remove(scope => ItemMouseUpHandler, true); + } + public virtual void Clear() { if (mChildItems == null) @@ -673,11 +723,6 @@ namespace Beefy.widgets } } - public class ListViewDragData - { - List mDragItems; - } - public abstract class ListView : ScrollableWidget { public List mColumns = new List() ~ DeleteContainerAndItems!(_); diff --git a/BeefLibs/corlib/src/Event.bf b/BeefLibs/corlib/src/Event.bf index dc59fae9..dd0bb466 100644 --- a/BeefLibs/corlib/src/Event.bf +++ b/BeefLibs/corlib/src/Event.bf @@ -114,6 +114,28 @@ namespace System } } + public void AddFront(T ownDelegate) mut + { + Object data = Target; + if (data == null) + { + Target = ownDelegate; + return; + } + + if (var list = data as List) + { + list.Insert(0, ownDelegate); + } + else + { + var list = new List(); + list.Add(ownDelegate); + list.Add((T)data); + Target = list; + } + } + public void Remove(T compareDelegate, bool deleteDelegate = false) mut { Object data = Target; diff --git a/IDE/src/Project.bf b/IDE/src/Project.bf index 15cddccc..0a09ac4e 100644 --- a/IDE/src/Project.bf +++ b/IDE/src/Project.bf @@ -455,6 +455,19 @@ namespace IDE } } + public void GetFullDisplayName(String displayName) + { + if (mParentFolder == null) + return; + + if (mParentFolder.mParentFolder != null) + { + mParentFolder.mParentFolder.GetFullDisplayName(displayName); + displayName.Append("/"); + } + displayName.Append(mName); + } + public void GetRelDir(String path) { if (mPath != null) diff --git a/IDE/src/ui/ProjectPanel.bf b/IDE/src/ui/ProjectPanel.bf index 81bad10b..56f196f7 100644 --- a/IDE/src/ui/ProjectPanel.bf +++ b/IDE/src/ui/ProjectPanel.bf @@ -145,8 +145,8 @@ namespace IDE.ui mListView.UpdateScrollbars(); mListView.mOnFocusChanged.Add(new => FocusChangedHandler); - /*mListView.mOnDragEnd.Add(new => HandleDragEnd); - mListView.mOnDragUpdate.Add(new => HandleDragUpdate);*/ + mListView.mOnDragEnd.Add(new => HandleDragEnd); + mListView.mOnDragUpdate.Add(new => HandleDragUpdate); AddWidget(mListView); @@ -217,24 +217,175 @@ namespace IDE.ui DarkListViewItem source = (DarkListViewItem)theEvent.mSender; DarkListViewItem target = (DarkListViewItem)theEvent.mDragTarget; + theEvent.mDragKind = .None; + + int validCount = 0; + int invalidCount = 0; if (source.mListView == target.mListView) { - ProjectItem sourceProjectItem = mListViewToProjectMap[source]; - ProjectItem targetProjectItem = mListViewToProjectMap[target]; - if (((sourceProjectItem.mParentFolder == null) || (targetProjectItem.mParentFolder == null)) || - (sourceProjectItem.mProject != targetProjectItem.mProject)) - theEvent.mDragAllowed = false; - } + if (mListViewToProjectMap.GetValue(target) case .Ok(var targetProjectItem)) + { + mListView.GetRoot().WithSelectedItems(scope [&] (selectedItem) => + { + if (mListViewToProjectMap.GetValue(selectedItem) case .Ok(var sourceProjectItem)) + { + if ((sourceProjectItem.mParentFolder != null) && (sourceProjectItem.mProject == targetProjectItem.mProject)) + validCount++; + else + invalidCount++; + } + }); + } + } + + if ((validCount > 0) && (invalidCount == 0)) + theEvent.mDragKind = .Inside; } void HandleDragEnd(DragEvent theEvent) { - if (!theEvent.mDragAllowed) + if (theEvent.mDragKind == .None) return; - if (theEvent.mDragTarget is DarkListViewItem) + if (theEvent.mDragTarget is ProjectListViewItem) { - DarkListViewItem source = (DarkListViewItem)theEvent.mSender; + ProjectListViewItem source = (ProjectListViewItem)theEvent.mSender; + ProjectListViewItem target = (ProjectListViewItem)theEvent.mDragTarget; + + theEvent.mDragKind = .None; + + if (source.mListView == target.mListView) + { + if (mListViewToProjectMap.GetValue(target) case .Ok(var targetProjectItem)) + { + if (targetProjectItem == null) + return; + + if (source == target) + return; + + var targetProjectFolder = targetProjectItem as ProjectFolder; + if (targetProjectFolder == null) + targetProjectFolder = targetProjectItem.mParentFolder; + var targetListItem = mProjectToListViewMap[targetProjectFolder]; + + int moveCount = 0; + int selectCount = 0; + mListView.GetRoot().WithSelectedItems(scope [&] (selectedItem) => + { + if (mListViewToProjectMap.GetValue(selectedItem) case .Ok(var sourceProjectItem)) + { + if (sourceProjectItem.mParentFolder != targetProjectFolder) + moveCount++; + } + selectCount++; + }); + + if (moveCount == 0) + return; + + var targetDisplayName = targetProjectFolder.GetFullDisplayName(.. scope .()); + if (targetDisplayName.IsEmpty) + targetDisplayName = "src"; + + Dialog dialog; + if (selectCount == 1) + { + dialog = ThemeFactory.mDefault.CreateDialog("Move file to a new location?", + scope $"Are you sure you want to move this file to '{targetDisplayName}'?"); + } + else + { + dialog = ThemeFactory.mDefault.CreateDialog("Move files to a new location?", + scope $"Are you sure you want to move these files to '{targetDisplayName}'?"); + } + dialog.AddButton("Yes", new (evt) => + { + List fileErrors = scope .(); + + List selectedItems = scope .(); + mListView.GetRoot().WithSelectedItems(scope [&] (selectedItem) => + { + selectedItems.Add(selectedItem); + }); + + for (var selectedItem in selectedItems) + { + if (mListViewToProjectMap.GetValue(selectedItem) case .Ok(var sourceProjectItem)) + { + var sourceProjectFileItem = sourceProjectItem as ProjectFileItem; + if (sourceProjectFileItem == null) + return; + + var sourcePath = sourceProjectFileItem.GetFullImportPath(.. scope .()); + var destPath = targetProjectFolder.GetFullImportPath(.. scope .()); + destPath.AppendF($"{Path.DirectorySeparatorChar}{sourceProjectFileItem.mName}"); + + if (File.Move(sourcePath, destPath) case .Ok) + { + gApp.FileRenamed(sourceProjectFileItem, sourcePath, destPath); + } + else + fileErrors.Add(scope:: .(destPath)); + + if (targetProjectFolder != sourceProjectItem.mParentFolder) + { + source.mParentItem.RemoveChildItem(source, false); + sourceProjectItem.mParentFolder.RemoveChild(sourceProjectItem); + + targetListItem.AddChildAtIndex(0, source); + targetListItem.mOpenButton.Open(true, false); + targetProjectFolder.AddChildAtIndex(0, sourceProjectItem); + } + } + } + + QueueSortItem(targetListItem); + DoSortItem(targetListItem); + + if (!fileErrors.IsEmpty) + { + var errorStr = scope String(); + if (fileErrors.Count == 1) + errorStr.AppendF("Failed to move file to: {0}", fileErrors[0]); + else + { + errorStr.AppendF("Failed to move {0} files:", fileErrors.Count); + for (var file in fileErrors) + errorStr.Append("\n {}", file); + } + gApp.Fail(errorStr); + } + }); + dialog.AddButton("No", new (evt) => + { + + }); + dialog.PopupWindow(gApp.GetActiveWindow()); + + + //RehupFolder(targetProjectFolder.mProject.mRootFolder, .FullTraversal); + + /*if (theEvent.mDragTargetDir == -1) // Before + { + target.mParentItem.InsertChild(source, target); + targetProjectItem.mParentFolder.InsertChild(sourceProjectItem, targetProjectItem); + } + else if (theEvent.mDragTargetDir == 0) // Inside + { + target.AddChildAtIndex(0, source); + target.mOpenButton.Open(true, false); + ((ProjectFolder)targetProjectItem).AddChildAtIndex(0, sourceProjectItem); + } + else if (theEvent.mDragTargetDir == 1) // After + { + target.mParentItem.AddChild(source, target); + targetProjectItem.mParentFolder.AddChild(sourceProjectItem, targetProjectItem); + }*/ + } + } + + /*DarkListViewItem source = (DarkListViewItem)theEvent.mSender; DarkListViewItem target = (DarkListViewItem)theEvent.mDragTarget; if (source.mListView == target.mListView) @@ -242,32 +393,8 @@ namespace IDE.ui ProjectItem targetProjectItem = mListViewToProjectMap[target]; ProjectItem sourceProjectItem = mListViewToProjectMap[source]; - if ((targetProjectItem == null) || (sourceProjectItem == null)) - return; - - if (source == target) - return; - - source.mParentItem.RemoveChildItem(source); - sourceProjectItem.mParentFolder.RemoveChild(sourceProjectItem); - - if (theEvent.mDragTargetDir == -1) // Before - { - target.mParentItem.InsertChild(source, target); - targetProjectItem.mParentFolder.InsertChild(sourceProjectItem, targetProjectItem); - } - else if (theEvent.mDragTargetDir == 0) // Inside - { - target.AddChildAtIndex(0, source); - target.mOpenButton.Open(true, false); - ((ProjectFolder)targetProjectItem).AddChildAtIndex(0, sourceProjectItem); - } - else if (theEvent.mDragTargetDir == 1) // After - { - target.mParentItem.AddChild(source, target); - targetProjectItem.mParentFolder.AddChild(sourceProjectItem, targetProjectItem); - } - } + + }*/ } } @@ -342,6 +469,7 @@ namespace IDE.ui item.mOnMouseClick.Add(new => ListViewItemClicked); UpdateColors(); DarkListViewItem listViewItem = (DarkListViewItem)item; + listViewItem.AllowDragging = true; listViewItem.mFocusColor = gApp.mSettings.mUISettings.mColors.mWorkspaceDisabledText; listViewItem.mSelectColor = gApp.mSettings.mUISettings.mColors.mWorkspaceDisabledText; } @@ -1136,10 +1264,13 @@ namespace IDE.ui { if (item.Focused) { - // Just rehup focus handler - handles case of closing file then clicking on it again - // even though it's already selected. We want that to re-open it. - FocusChangedHandler(item); - return; + if (mListView.GetRoot().GetSelectedItemCount() <= 1) + { + // Just rehup focus handler - handles case of closing file then clicking on it again + // even though it's already selected. We want that to re-open it. + FocusChangedHandler(item); + return; + } } mListView.GetRoot().SelectItem(item, checkKeyStates); diff --git a/IDE/src/ui/WatchPanel.bf b/IDE/src/ui/WatchPanel.bf index 980ce1b4..60cc666a 100644 --- a/IDE/src/ui/WatchPanel.bf +++ b/IDE/src/ui/WatchPanel.bf @@ -1719,7 +1719,8 @@ namespace IDE.ui void HandleDragUpdate(DragEvent evt) { - evt.mDragAllowed = false; + var dragKind = evt.mDragKind; + evt.mDragKind = .None; if (mIsAuto) return; @@ -1736,20 +1737,20 @@ namespace IDE.ui // Check for if we're dragging after the last open child item. If so, treat it as if we're dragging to after the topmost parent /*if ((evt.mDragTargetDir != 1) || (dragTarget != dragTarget.mParentItem.mChildItems[dragTarget.mParentItem.mChildItems.Count - 1])) return;*/ - evt.mDragTargetDir = 1; + evt.mDragKind = .After; dragTarget = (WatchListViewItem)dragTarget.mParentItem; evt.mDragTarget = dragTarget; } - if ((dragTarget.mLabel == "") && (evt.mDragTargetDir >= 0)) - evt.mDragTargetDir = -1; - if (evt.mDragTargetDir == 0) + if ((dragTarget.mLabel == "") && (dragKind == .After)) + dragKind = .Before; + if (dragKind == .None) return; - evt.mDragAllowed = true; + evt.mDragKind = dragKind; } void HandleDragEnd(DragEvent theEvent) { - if (!theEvent.mDragAllowed) + if (theEvent.mDragKind == .None) return; if (theEvent.mDragTarget is IDEListViewItem) @@ -1766,9 +1767,9 @@ namespace IDE.ui { // We're dragging a top-level item into a new position source.mParentItem.RemoveChildItem(source, false); - if (theEvent.mDragTargetDir == -1) // Before + if (theEvent.mDragKind == .Before) // Before target.mParentItem.InsertChild(source, target); - else if (theEvent.mDragTargetDir == 1) // After + else if (theEvent.mDragKind == .After) // After target.mParentItem.AddChild(source, target); } else @@ -1778,8 +1779,8 @@ namespace IDE.ui var rootItem = mListView.GetRoot(); int idx = rootItem.mChildItems.IndexOf(target); - if (theEvent.mDragTargetDir > 0) - idx += theEvent.mDragTargetDir; + if (theEvent.mDragKind == .After) + idx++; var listViewItem = (WatchListViewItem)rootItem.CreateChildItemAtIndex(idx); listViewItem.mVisible = false; SetupListViewItem(listViewItem, compactEvalStr, compactEvalStr);