committed by
GitHub
31 changed files with 1233 additions and 806 deletions
@ -0,0 +1,89 @@ |
|||||
|
#include "common.h" |
||||
|
|
||||
|
extern AvnDragDropEffects ConvertDragDropEffects(NSDragOperation nsop) |
||||
|
{ |
||||
|
int effects = 0; |
||||
|
if((nsop & NSDragOperationCopy) != 0) |
||||
|
effects |= (int)AvnDragDropEffects::Copy; |
||||
|
if((nsop & NSDragOperationMove) != 0) |
||||
|
effects |= (int)AvnDragDropEffects::Move; |
||||
|
if((nsop & NSDragOperationLink) != 0) |
||||
|
effects |= (int)AvnDragDropEffects::Link; |
||||
|
return (AvnDragDropEffects)effects; |
||||
|
}; |
||||
|
|
||||
|
extern NSString* GetAvnCustomDataType() |
||||
|
{ |
||||
|
char buffer[256]; |
||||
|
sprintf(buffer, "net.avaloniaui.inproc.uti.n%in", getpid()); |
||||
|
return [NSString stringWithUTF8String:buffer]; |
||||
|
} |
||||
|
|
||||
|
@interface AvnDndSource : NSObject<NSDraggingSource> |
||||
|
|
||||
|
@end |
||||
|
|
||||
|
@implementation AvnDndSource |
||||
|
{ |
||||
|
NSDragOperation _operation; |
||||
|
ComPtr<IAvnDndResultCallback> _cb; |
||||
|
void* _sourceHandle; |
||||
|
}; |
||||
|
|
||||
|
- (NSDragOperation)draggingSession:(nonnull NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context |
||||
|
{ |
||||
|
return NSDragOperationCopy; |
||||
|
} |
||||
|
|
||||
|
- (AvnDndSource*) initWithOperation: (NSDragOperation)operation |
||||
|
andCallback: (IAvnDndResultCallback*) cb |
||||
|
andSourceHandle: (void*) handle |
||||
|
{ |
||||
|
self = [super init]; |
||||
|
_operation = operation; |
||||
|
_cb = cb; |
||||
|
_sourceHandle = handle; |
||||
|
return self; |
||||
|
} |
||||
|
|
||||
|
- (void)draggingSession:(NSDraggingSession *)session |
||||
|
endedAtPoint:(NSPoint)screenPoint |
||||
|
operation:(NSDragOperation)operation |
||||
|
{ |
||||
|
if(_cb != nil) |
||||
|
{ |
||||
|
auto cb = _cb; |
||||
|
_cb = nil; |
||||
|
cb->OnDragAndDropComplete(ConvertDragDropEffects(operation)); |
||||
|
} |
||||
|
if(_sourceHandle != nil) |
||||
|
{ |
||||
|
FreeAvnGCHandle(_sourceHandle); |
||||
|
_sourceHandle = nil; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
- (void*) gcHandle |
||||
|
{ |
||||
|
return _sourceHandle; |
||||
|
} |
||||
|
|
||||
|
@end |
||||
|
|
||||
|
extern NSObject<NSDraggingSource>* CreateDraggingSource(NSDragOperation op, IAvnDndResultCallback* cb, void* handle) |
||||
|
{ |
||||
|
return [[AvnDndSource alloc] initWithOperation:op andCallback:cb andSourceHandle:handle]; |
||||
|
}; |
||||
|
|
||||
|
extern void* GetAvnDataObjectHandleFromDraggingInfo(NSObject<NSDraggingInfo>* info) |
||||
|
{ |
||||
|
id obj = [info draggingSource]; |
||||
|
if(obj == nil) |
||||
|
return nil; |
||||
|
if([obj isKindOfClass: [AvnDndSource class]]) |
||||
|
{ |
||||
|
auto src = (AvnDndSource*)obj; |
||||
|
return [src gcHandle]; |
||||
|
} |
||||
|
return nil; |
||||
|
} |
||||
@ -0,0 +1,115 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Collections.ObjectModel; |
||||
|
using System.Linq; |
||||
|
using System.Reactive; |
||||
|
using Avalonia.Controls; |
||||
|
using ReactiveUI; |
||||
|
|
||||
|
namespace ControlCatalog.ViewModels |
||||
|
{ |
||||
|
public class TreeViewPageViewModel : ReactiveObject |
||||
|
{ |
||||
|
private readonly Node _root; |
||||
|
private SelectionMode _selectionMode; |
||||
|
|
||||
|
public TreeViewPageViewModel() |
||||
|
{ |
||||
|
_root = new Node(); |
||||
|
|
||||
|
Items = _root.Children; |
||||
|
Selection = new SelectionModel(); |
||||
|
Selection.SelectionChanged += SelectionChanged; |
||||
|
|
||||
|
AddItemCommand = ReactiveCommand.Create(AddItem); |
||||
|
RemoveItemCommand = ReactiveCommand.Create(RemoveItem); |
||||
|
} |
||||
|
|
||||
|
public ObservableCollection<Node> Items { get; } |
||||
|
public SelectionModel Selection { get; } |
||||
|
public ReactiveCommand<Unit, Unit> AddItemCommand { get; } |
||||
|
public ReactiveCommand<Unit, Unit> RemoveItemCommand { get; } |
||||
|
|
||||
|
public SelectionMode SelectionMode |
||||
|
{ |
||||
|
get => _selectionMode; |
||||
|
set |
||||
|
{ |
||||
|
Selection.ClearSelection(); |
||||
|
this.RaiseAndSetIfChanged(ref _selectionMode, value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void AddItem() |
||||
|
{ |
||||
|
var parentItem = Selection.SelectedItems.Count > 0 ? (Node)Selection.SelectedItems[0] : _root; |
||||
|
parentItem.AddItem(); |
||||
|
} |
||||
|
|
||||
|
private void RemoveItem() |
||||
|
{ |
||||
|
while (Selection.SelectedItems.Count > 0) |
||||
|
{ |
||||
|
Node lastItem = (Node)Selection.SelectedItems[0]; |
||||
|
RecursiveRemove(Items, lastItem); |
||||
|
Selection.DeselectAt(Selection.SelectedIndices[0]); |
||||
|
} |
||||
|
|
||||
|
bool RecursiveRemove(ObservableCollection<Node> items, Node selectedItem) |
||||
|
{ |
||||
|
if (items.Remove(selectedItem)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
foreach (Node item in items) |
||||
|
{ |
||||
|
if (item.AreChildrenInitialized && RecursiveRemove(item.Children, selectedItem)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void SelectionChanged(object sender, SelectionModelSelectionChangedEventArgs e) |
||||
|
{ |
||||
|
var selected = string.Join(",", e.SelectedIndices); |
||||
|
var deselected = string.Join(",", e.DeselectedIndices); |
||||
|
System.Diagnostics.Debug.WriteLine($"Selected '{selected}', Deselected '{deselected}'"); |
||||
|
} |
||||
|
|
||||
|
public class Node |
||||
|
{ |
||||
|
private ObservableCollection<Node> _children; |
||||
|
private int _childIndex = 10; |
||||
|
|
||||
|
public Node() |
||||
|
{ |
||||
|
Header = "Item"; |
||||
|
} |
||||
|
|
||||
|
public Node(Node parent, int index) |
||||
|
{ |
||||
|
Parent = parent; |
||||
|
Header = parent.Header + ' ' + index; |
||||
|
} |
||||
|
|
||||
|
public Node Parent { get; } |
||||
|
public string Header { get; } |
||||
|
public bool AreChildrenInitialized => _children != null; |
||||
|
public ObservableCollection<Node> Children => _children ??= CreateChildren(); |
||||
|
public void AddItem() => Children.Add(new Node(this, _childIndex++)); |
||||
|
public void RemoveItem(Node child) => Children.Remove(child); |
||||
|
public override string ToString() => Header; |
||||
|
|
||||
|
private ObservableCollection<Node> CreateChildren() |
||||
|
{ |
||||
|
return new ObservableCollection<Node>( |
||||
|
Enumerable.Range(0, 10).Select(i => new Node(this, i))); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,76 @@ |
|||||
|
using System; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Input.Platform; |
||||
|
using Avalonia.Interactivity; |
||||
|
using Avalonia.Native.Interop; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.VisualTree; |
||||
|
|
||||
|
namespace Avalonia.Native |
||||
|
{ |
||||
|
class AvaloniaNativeDragSource : IPlatformDragSource |
||||
|
{ |
||||
|
private readonly IAvaloniaNativeFactory _factory; |
||||
|
|
||||
|
public AvaloniaNativeDragSource(IAvaloniaNativeFactory factory) |
||||
|
{ |
||||
|
_factory = factory; |
||||
|
} |
||||
|
|
||||
|
TopLevel FindRoot(IInteractive interactive) |
||||
|
{ |
||||
|
while (interactive != null && !(interactive is IVisual)) |
||||
|
interactive = interactive.InteractiveParent; |
||||
|
if (interactive == null) |
||||
|
return null; |
||||
|
var visual = (IVisual)interactive; |
||||
|
return visual.VisualRoot as TopLevel; |
||||
|
} |
||||
|
|
||||
|
class DndCallback : CallbackBase, IAvnDndResultCallback |
||||
|
{ |
||||
|
private TaskCompletionSource<DragDropEffects> _tcs; |
||||
|
|
||||
|
public DndCallback(TaskCompletionSource<DragDropEffects> tcs) |
||||
|
{ |
||||
|
_tcs = tcs; |
||||
|
} |
||||
|
public void OnDragAndDropComplete(AvnDragDropEffects effect) |
||||
|
{ |
||||
|
_tcs?.TrySetResult((DragDropEffects)effect); |
||||
|
_tcs = null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public Task<DragDropEffects> DoDragDrop(PointerEventArgs triggerEvent, IDataObject data, DragDropEffects allowedEffects) |
||||
|
{ |
||||
|
// Sanity check
|
||||
|
var tl = FindRoot(triggerEvent.Source); |
||||
|
var view = tl?.PlatformImpl as WindowBaseImpl; |
||||
|
if (view == null) |
||||
|
throw new ArgumentException(); |
||||
|
|
||||
|
triggerEvent.Pointer.Capture(null); |
||||
|
|
||||
|
var tcs = new TaskCompletionSource<DragDropEffects>(); |
||||
|
|
||||
|
var clipboardImpl = _factory.CreateDndClipboard(); |
||||
|
using (var clipboard = new ClipboardImpl(clipboardImpl)) |
||||
|
using (var cb = new DndCallback(tcs)) |
||||
|
{ |
||||
|
if (data.Contains(DataFormats.Text)) |
||||
|
// API is synchronous, so it's OK
|
||||
|
clipboard.SetTextAsync(data.GetText()).Wait(); |
||||
|
|
||||
|
view.BeginDraggingSession((AvnDragDropEffects)allowedEffects, |
||||
|
triggerEvent.GetPosition(tl).ToAvnPoint(), clipboardImpl, cb, |
||||
|
GCHandle.ToIntPtr(GCHandle.Alloc(data))); |
||||
|
} |
||||
|
|
||||
|
return tcs.Task; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
using System.Runtime.InteropServices; |
||||
|
|
||||
|
namespace Avalonia.Native.Interop |
||||
|
{ |
||||
|
unsafe partial class IAvnString |
||||
|
{ |
||||
|
private string _managed; |
||||
|
|
||||
|
public string String |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
if (_managed == null) |
||||
|
{ |
||||
|
var ptr = Pointer(); |
||||
|
if (ptr == null) |
||||
|
return null; |
||||
|
_managed = System.Text.Encoding.UTF8.GetString((byte*)ptr.ToPointer(), Length()); |
||||
|
} |
||||
|
|
||||
|
return _managed; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public override string ToString() => String; |
||||
|
} |
||||
|
|
||||
|
partial class IAvnStringArray |
||||
|
{ |
||||
|
public string[] ToStringArray() |
||||
|
{ |
||||
|
var arr = new string[Count]; |
||||
|
for(uint c = 0; c<arr.Length;c++) |
||||
|
using (var s = Get(c)) |
||||
|
arr[c] = s.String; |
||||
|
return arr; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
File diff suppressed because it is too large
Loading…
Reference in new issue