57 changed files with 1269 additions and 863 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