remove tiling frames for now and add a more solid approach to extensions with fast compiled linq expressions
This commit is contained in:
parent
e41b1df0ba
commit
2b850168f1
|
|
@ -0,0 +1,5 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*.cs]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 3
|
||||||
|
|
@ -77,9 +77,11 @@ public static class ExpressionParser
|
||||||
digester.Emit(OpCode.BitwiseRight);
|
digester.Emit(OpCode.BitwiseRight);
|
||||||
break;
|
break;
|
||||||
case TokenType.Increment:
|
case TokenType.Increment:
|
||||||
|
digester.EmitConstant(new Value(1L));
|
||||||
digester.Emit(OpCode.Increment);
|
digester.Emit(OpCode.Increment);
|
||||||
break;
|
break;
|
||||||
case TokenType.Decrement:
|
case TokenType.Decrement:
|
||||||
|
digester.EmitConstant(new Value(1L));
|
||||||
digester.Emit(OpCode.Decrement);
|
digester.Emit(OpCode.Decrement);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
@ -148,17 +150,23 @@ public static class ExpressionParser
|
||||||
digester.EmitDynamic(OpCode.Array, length); // digester.MakeConstant(new Value((long)length)));
|
digester.EmitDynamic(OpCode.Array, length); // digester.MakeConstant(new Value((long)length)));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void AddItem(Digester digester, bool canAssign)
|
static void Increment(Digester digester, bool canAssign)
|
||||||
{
|
{
|
||||||
digester.ParseExpression();
|
digester.ParseExpression();
|
||||||
digester.Emit(OpCode.AddItem);
|
digester.Emit(OpCode.Increment);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Decrement(Digester digester, bool canAssign)
|
||||||
|
{
|
||||||
|
digester.ParseExpression();
|
||||||
|
digester.Emit(OpCode.Decrement);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Accessor(Digester digester, bool canAssign)
|
static void Accessor(Digester digester, bool canAssign)
|
||||||
{
|
{
|
||||||
digester.ParseExpression();
|
digester.ParseExpression();
|
||||||
digester.Consume(TokenType.ArrayClose, "expected ] after array accessor");
|
digester.Consume(TokenType.ArrayClose, "expected ] after array accessor");
|
||||||
if (canAssign && digester.Match(TokenType.AddItem)) {
|
if (canAssign && digester.Match(TokenType.PlusEqual)) {
|
||||||
digester.ParseExpression();
|
digester.ParseExpression();
|
||||||
digester.Emit(OpCode.AddItem);
|
digester.Emit(OpCode.AddItem);
|
||||||
} else if (canAssign && digester.Match(TokenType.Equal)) {
|
} else if (canAssign && digester.Match(TokenType.Equal)) {
|
||||||
|
|
@ -295,8 +303,8 @@ public static class ExpressionParser
|
||||||
_rules[TokenType.ContextClose] = new Rule(null, null, Weight.None);
|
_rules[TokenType.ContextClose] = new Rule(null, null, Weight.None);
|
||||||
_rules[TokenType.ArrayOpen] = new Rule(Array, Accessor, Weight.Call);
|
_rules[TokenType.ArrayOpen] = new Rule(Array, Accessor, Weight.Call);
|
||||||
_rules[TokenType.ArrayClose] = new Rule(null, null, Weight.None);
|
_rules[TokenType.ArrayClose] = new Rule(null, null, Weight.None);
|
||||||
_rules[TokenType.AddItem] = new Rule(null, AddItem, Weight.None);
|
_rules[TokenType.PlusEqual] = new Rule(null, Increment, Weight.None); // this is its own compilation operation as x++ calls binary and makes up the atomic change (1) on the spot.
|
||||||
_rules[TokenType.RemoveItem] = new Rule(null, null, Weight.None);
|
_rules[TokenType.MinusEqual] = new Rule(null, Decrement, Weight.None); // same as above
|
||||||
_rules[TokenType.Colon] = new Rule(null, Dot, Weight.Call);
|
_rules[TokenType.Colon] = new Rule(null, Dot, Weight.Call);
|
||||||
_rules[TokenType.Comma] = new Rule(null, null, Weight.None);
|
_rules[TokenType.Comma] = new Rule(null, null, Weight.None);
|
||||||
_rules[TokenType.Dot] = new Rule(null, Dot, Weight.Call);
|
_rules[TokenType.Dot] = new Rule(null, Dot, Weight.Call);
|
||||||
|
|
|
||||||
|
|
@ -52,8 +52,8 @@ public enum OpCode
|
||||||
Greater = 0x51,
|
Greater = 0x51,
|
||||||
Less = 0x52,
|
Less = 0x52,
|
||||||
|
|
||||||
Increment = 0x70,
|
Increment = 0x70, // basically increment by, ++ simply 'makes up' the 1.
|
||||||
Decrement = 0x71,
|
Decrement = 0x71, // same here broren min
|
||||||
|
|
||||||
Array = 0xc0,
|
Array = 0xc0,
|
||||||
List = 0xc1,
|
List = 0xc1,
|
||||||
|
|
|
||||||
|
|
@ -456,7 +456,7 @@ public class Runner : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool InvokeNative(Value target, NativeExtension extension, int argumentCount)
|
private bool InvokeExtension(Value target, ExtensionMethod extension, int argumentCount)
|
||||||
{
|
{
|
||||||
#if LOG
|
#if LOG
|
||||||
_logger.Method(extension);
|
_logger.Method(extension);
|
||||||
|
|
@ -472,7 +472,7 @@ public class Runner : IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
Stack.Decimate(Stack.Position - argumentCount - 1);
|
Stack.Decimate(Stack.Position - argumentCount - 1);
|
||||||
Value result = extension.Callback(target, values);
|
Value result = extension.Call(target, values);
|
||||||
Push(result);
|
Push(result);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -500,13 +500,14 @@ public class Runner : IDisposable
|
||||||
private bool Invoke(string methodName, int argumentCount)
|
private bool Invoke(string methodName, int argumentCount)
|
||||||
{
|
{
|
||||||
Value value = Peek(-argumentCount - 1);
|
Value value = Peek(-argumentCount - 1);
|
||||||
|
TypeInfo typeInfo = value.TypeInfo;
|
||||||
if (!value.Is(T.Instance)) {
|
if (!value.Is(T.Instance)) {
|
||||||
NativeExtension? extension = NativeExtension.Get(value.Type, methodName);
|
ExtensionMethod? extension = ExtensionMethod.Get(typeInfo, methodName, argumentCount);
|
||||||
if (extension == null) {
|
if (extension == null) {
|
||||||
Error($"Could not invoke {methodName} on {value}, no method or extension by that name found for this type.");
|
Error($"Could not invoke {methodName} on {value}, no method or extension by that name found for {typeInfo}.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return InvokeNative(value, extension, argumentCount);
|
return InvokeExtension(value, extension, argumentCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
Instance instance = value.Ptr.As<Instance>()!;
|
Instance instance = value.Ptr.As<Instance>()!;
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,9 @@ public static class Extensions
|
||||||
return match.Success;
|
return match.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static T[] Subset<T>(this T[] array, long from, long length)
|
public static T[] Subset<T>(this T[] array, long from, long length = -1)
|
||||||
{
|
{
|
||||||
|
length = length < 0 ? array.Length - from : length;
|
||||||
var result = new T[length];
|
var result = new T[length];
|
||||||
Array.Copy(array, from, result, 0, length);
|
Array.Copy(array, from, result, 0, length);
|
||||||
return result;
|
return result;
|
||||||
|
|
|
||||||
|
|
@ -273,77 +273,79 @@ public class Reader : IReader<Token>, IDisposable
|
||||||
'(' => MakeToken(GroupOpen, buffer),
|
'(' => MakeToken(GroupOpen, buffer),
|
||||||
')' => MakeToken(GroupClose, buffer),
|
')' => MakeToken(GroupClose, buffer),
|
||||||
'.' => Check('~') ?
|
'.' => Check('~') ?
|
||||||
MakeToken(This, buffer + Next()) :
|
MakeToken(This, buffer + Next()) :
|
||||||
MakeToken(Dot, buffer),
|
MakeToken(Dot, buffer),
|
||||||
',' => MakeToken(Comma, buffer),
|
',' => MakeToken(Comma, buffer),
|
||||||
';' => MakeToken(Semicolon, buffer),
|
';' => MakeToken(Semicolon, buffer),
|
||||||
':' => Check(':') ?
|
':' => Check(':') ?
|
||||||
MakeToken(Print, buffer + Next()) :
|
MakeToken(Print, buffer + Next()) :
|
||||||
Check('[') ?
|
Check('[') ?
|
||||||
MakeToken(ListOpen, buffer + Next()) :
|
MakeToken(ListOpen, buffer + Next()) :
|
||||||
MakeToken(Colon, buffer),
|
MakeToken(Colon, buffer),
|
||||||
'&' => Check('&') ?
|
'&' => Check('&') ?
|
||||||
MakeToken(And, buffer + Next()) :
|
MakeToken(And, buffer + Next()) :
|
||||||
MakeToken(BitwiseAnd, buffer),
|
MakeToken(BitwiseAnd, buffer),
|
||||||
'^' => Check('~') ?
|
'^' => Check('~') ?
|
||||||
MakeToken(Base, buffer + Next()) :
|
MakeToken(Base, buffer + Next()) :
|
||||||
MakeToken(BitwiseXor, buffer),
|
MakeToken(BitwiseXor, buffer),
|
||||||
'%' => Check('=') ?
|
'%' => Check('=') ?
|
||||||
MakeToken(ModuloEqual, buffer + Next()) :
|
MakeToken(ModuloEqual, buffer + Next()) :
|
||||||
MakeToken(Modulo, buffer),
|
MakeToken(Modulo, buffer),
|
||||||
'|' => Check('|') ?
|
'|' => Check('|') ?
|
||||||
MakeToken(Or, buffer + Next()) :
|
MakeToken(Or, buffer + Next()) :
|
||||||
MakeToken(BitwiseOr, buffer),
|
MakeToken(BitwiseOr, buffer),
|
||||||
'!' => Check('=') ?
|
'!' => Check('=') ?
|
||||||
MakeToken(BangEqual, buffer + Next()) :
|
MakeToken(BangEqual, buffer + Next()) :
|
||||||
MakeToken(Bang, buffer),
|
MakeToken(Bang, buffer),
|
||||||
'+' => Check('+') ?
|
'+' => Check('+') ?
|
||||||
MakeToken(Increment, buffer + Next()) :
|
MakeToken(Increment, buffer + Next()) :
|
||||||
Check('=') ?
|
Check('=') ?
|
||||||
MakeToken(PlusEqual, buffer + Next()) :
|
MakeToken(PlusEqual, buffer + Next()) :
|
||||||
MakeToken(Plus, buffer),
|
MakeToken(Plus, buffer),
|
||||||
'-' => Check('-') ?
|
'-' => Check('-') ?
|
||||||
MakeToken(Decrement, buffer + Next()) :
|
MakeToken(Decrement, buffer + Next()) :
|
||||||
Check('=') ?
|
Check('=') ?
|
||||||
MakeToken(MinusEqual, buffer + Next()) :
|
MakeToken(MinusEqual, buffer + Next()) :
|
||||||
MakeToken(Minus, buffer),
|
MakeToken(Minus, buffer),
|
||||||
'/' => Check('=') ?
|
'/' => Check('=') ?
|
||||||
MakeToken(SlashEqual, buffer + Next()) :
|
MakeToken(SlashEqual, buffer + Next()) :
|
||||||
MakeToken(Slash, buffer),
|
MakeToken(Slash, buffer),
|
||||||
'*' => Check('=') ?
|
'*' => Check('=') ?
|
||||||
MakeToken(StarEqual, buffer + Next()) :
|
MakeToken(StarEqual, buffer + Next()) :
|
||||||
Check('~') ?
|
Check('~') ?
|
||||||
MakeToken(Var, buffer + Next()) :
|
MakeToken(Var, buffer + Next()) :
|
||||||
MakeToken(Star, buffer),
|
MakeToken(Star, buffer),
|
||||||
'=' => Check('=') ?
|
'=' => Check('=') ?
|
||||||
MakeToken(EqualEqual, buffer + Next()) :
|
MakeToken(EqualEqual, buffer + Next()) :
|
||||||
MakeToken(Equal, buffer),
|
MakeToken(Equal, buffer),
|
||||||
'<' => Check('~') ?
|
'<' => Check('~') ?
|
||||||
MakeToken(Equal, buffer + Next()) :
|
MakeToken(Equal, buffer + Next()) :
|
||||||
Check(':') ?
|
Check(':') ?
|
||||||
MakeToken(Return, buffer + Next()) :
|
MakeToken(Return, buffer + Next()) :
|
||||||
Check('+') ?
|
Check('+') ?
|
||||||
MakeToken(AddItem, buffer + Next()) :
|
MakeToken(PlusEqual, buffer + Next()) :
|
||||||
Check('<') ?
|
Check('-') ?
|
||||||
MakeToken(BitwiseLeft, buffer + Next()) :
|
MakeToken(MinusEqual, buffer + Next()) :
|
||||||
Check('=') ?
|
Check('<') ?
|
||||||
|
MakeToken(BitwiseLeft, buffer + Next()) :
|
||||||
|
Check('=') ?
|
||||||
MakeToken(LessEqual, buffer + Next()) :
|
MakeToken(LessEqual, buffer + Next()) :
|
||||||
Check('>') ?
|
Check('>') ?
|
||||||
MakeToken(TernaryElse, buffer + Next()) :
|
MakeToken(TernaryElse, buffer + Next()) :
|
||||||
MakeToken(Less, buffer),
|
MakeToken(Less, buffer),
|
||||||
'>' => Check('>') ?
|
'>' => Check('>') ?
|
||||||
MakeToken(BitwiseRight, buffer + Next()) :
|
MakeToken(BitwiseRight, buffer + Next()) :
|
||||||
Check('=') ?
|
Check('=') ?
|
||||||
MakeToken(GreaterEqual, buffer + Next()) :
|
MakeToken(GreaterEqual, buffer + Next()) :
|
||||||
MakeToken(Greater, buffer),
|
MakeToken(Greater, buffer),
|
||||||
'~' => Check('(') ?
|
'~' => Check('(') ?
|
||||||
MakeToken(Lambda, buffer) :
|
MakeToken(Lambda, buffer) :
|
||||||
MakeToken(BitwiseNot, buffer),
|
MakeToken(BitwiseNot, buffer),
|
||||||
'?' => Check('?') ?
|
'?' => Check('?') ?
|
||||||
MakeToken(DoubleQuestion, buffer + Next()) :
|
MakeToken(DoubleQuestion, buffer + Next()) :
|
||||||
Check(':') ?
|
Check(':') ?
|
||||||
MakeToken(TypeOf, buffer + Next()) :
|
MakeToken(TypeOf, buffer + Next()) :
|
||||||
MakeToken(Question, buffer),
|
MakeToken(Question, buffer),
|
||||||
_ => MakeToken(Error, buffer) //throw new ReaderException($"Could not identify operator <{buffer}>", this)
|
_ => MakeToken(Error, buffer) //throw new ReaderException($"Could not identify operator <{buffer}>", this)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -351,23 +353,26 @@ public class Reader : IReader<Token>, IDisposable
|
||||||
private Token MakeToken(TokenType type, string buffer)
|
private Token MakeToken(TokenType type, string buffer)
|
||||||
{
|
{
|
||||||
return new Token(
|
return new Token(
|
||||||
type,
|
type,
|
||||||
buffer,
|
buffer,
|
||||||
_startPosition,
|
_startPosition,
|
||||||
new StreamSpan(_startIndex, _current - _startIndex));
|
new StreamSpan(_startIndex, _current - _startIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Continue()
|
private void Continue()
|
||||||
{
|
{
|
||||||
if (Done)
|
if (Done)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
long size = _stream.Length - _stream.Position;
|
long size = _stream.Length - _stream.Position;
|
||||||
if (size > ChunkSize)
|
if (size > ChunkSize)
|
||||||
size = ChunkSize;
|
size = ChunkSize;
|
||||||
|
|
||||||
byte[] buffer = new byte[size];
|
byte[] buffer = new byte[size];
|
||||||
int read = _stream.Read(buffer, 0, (int)size);
|
int read = _stream.Read(buffer, 0, (int)size);
|
||||||
if (read < 0)
|
if (read < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_buffer.AddRange(Encoding.UTF8.GetChars(buffer));
|
_buffer.AddRange(Encoding.UTF8.GetChars(buffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,8 +57,6 @@ public enum TokenType
|
||||||
SlashEqual = Assignment | 4,
|
SlashEqual = Assignment | 4,
|
||||||
StarEqual = Assignment | 5,
|
StarEqual = Assignment | 5,
|
||||||
ModuloEqual = Assignment | 6,
|
ModuloEqual = Assignment | 6,
|
||||||
AddItem = Assignment | 7,
|
|
||||||
RemoveItem = Assignment | 8,
|
|
||||||
|
|
||||||
Condition = 1 << 10,
|
Condition = 1 << 10,
|
||||||
Bang = Condition | 1,
|
Bang = Condition | 1,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,249 @@
|
||||||
|
using Qrakhen.Qamp.Core.Collections;
|
||||||
|
using Qrakhen.Qamp.Core.Logging;
|
||||||
|
using Qrakhen.Qamp.Core.Values.Objects;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Qrakhen.Qamp.Core.Values;
|
||||||
|
|
||||||
|
public delegate Value ExtensionDelegate(Value target, Value[] parameters);
|
||||||
|
|
||||||
|
public class ExtensionException(string message, object? context = null) : QampException(message, context);
|
||||||
|
|
||||||
|
public abstract class ExtensionAttribute : Attribute;
|
||||||
|
|
||||||
|
public abstract class TypedExtensionAttribute : ExtensionAttribute
|
||||||
|
{
|
||||||
|
public ValueType ValueType { get; set; } = ValueType.Undefined;
|
||||||
|
public Type? ValueObjectType { get; set; } = null;
|
||||||
|
public string? ValueObjectName { get; set; }
|
||||||
|
public bool Nullable { get; set; } = false;
|
||||||
|
public TypeInfo TypeInfo => new TypeInfo(ValueType, ValueObjectType, ValueObjectName, Nullable);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
public class ExtensionMethodAttribute : ExtensionAttribute
|
||||||
|
{
|
||||||
|
public string? Name { get; set; } = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
public class ReturnsAttribute : TypedExtensionAttribute;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Parameter)]
|
||||||
|
public class SelfAttribute : TypedExtensionAttribute;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
public class ExtensionClassAttribute : SelfAttribute
|
||||||
|
{
|
||||||
|
public string? Name { get; set; } = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Parameter)]
|
||||||
|
public class ParameterAttribute : TypedExtensionAttribute
|
||||||
|
{
|
||||||
|
public string? Name { get; set; } = null;
|
||||||
|
public bool Optional { get; set; } = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class ExtensionMethod(
|
||||||
|
TypeInfo targetType,
|
||||||
|
TypeInfo returnType,
|
||||||
|
string name,
|
||||||
|
ExtensionDelegate callback,
|
||||||
|
string[] parameters)
|
||||||
|
{
|
||||||
|
private static readonly Register<TypeInfo, Register<string, ExtensionMethod>> _register = [];
|
||||||
|
|
||||||
|
private static readonly ILogger _logger = LoggerService.Get<ExtensionMethod>();
|
||||||
|
|
||||||
|
public readonly string Name = name;
|
||||||
|
public readonly TypeInfo TargetType = targetType;
|
||||||
|
public readonly TypeInfo ReturnType = returnType;
|
||||||
|
public readonly ExtensionDelegate Callback = callback;
|
||||||
|
public readonly string[] Parameters = parameters.ToArray();
|
||||||
|
|
||||||
|
public readonly string Key = parameters.Length > 0 ? $"{name}_{parameters.Length}" : name;
|
||||||
|
|
||||||
|
public static ExtensionMethod? Get(TypeInfo targetType, string name, int parameters = 0)
|
||||||
|
{
|
||||||
|
if (parameters > 0)
|
||||||
|
name = $"{name}_{parameters}";
|
||||||
|
|
||||||
|
if (!_register.Has(targetType))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (!_register[targetType].Has(name))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return _register[targetType][name];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Register(IEnumerable<ExtensionMethod> extensions)
|
||||||
|
{
|
||||||
|
foreach (var extension in extensions)
|
||||||
|
Register(extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Register(ExtensionMethod extension)
|
||||||
|
{
|
||||||
|
if (!_register.Has(extension.TargetType))
|
||||||
|
_register.Add(extension.TargetType, []);
|
||||||
|
|
||||||
|
if (_register[extension.TargetType].Has(extension.Key))
|
||||||
|
throw new ExtensionException($"Extension {extension.Key} already exists for type {extension.TargetType}.");
|
||||||
|
|
||||||
|
_register[extension.TargetType].Add(extension.Key, extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Register(TypeInfo targetType,
|
||||||
|
TypeInfo returnType,
|
||||||
|
string name,
|
||||||
|
ExtensionDelegate callback,
|
||||||
|
params string[] parameters)
|
||||||
|
=> Register(new ExtensionMethod(targetType, returnType, name, callback, parameters));
|
||||||
|
|
||||||
|
private static ExtensionMethod RenderExtension(MethodInfo method)
|
||||||
|
{
|
||||||
|
_logger.Verbose($"Rendering extension from {method}...");
|
||||||
|
var attr = method.GetCustomAttribute<ExtensionMethodAttribute>();
|
||||||
|
if (attr is null)
|
||||||
|
throw new ExtensionException($"Expected an [ExtensionMethod] attribute on method {method} for extension compilation.", method);
|
||||||
|
|
||||||
|
var parameters = method.GetParameters();
|
||||||
|
if (parameters.Length < 1)
|
||||||
|
throw new ExtensionException($"Tried to define method {method} as extension, but extensions require their first parameter to be <self>, the target value of the extension.", method);
|
||||||
|
|
||||||
|
var selfAttr = parameters[0].GetCustomAttribute<SelfAttribute>() ?? method.DeclaringType?.GetCustomAttribute<ExtensionClassAttribute>();
|
||||||
|
if (selfAttr is null)
|
||||||
|
throw new ExtensionException($"Expected first parameter of {method} to have a [Self], or its declaring type to have a [ExtensionClass] attribute in order to define the extension's target type.");
|
||||||
|
|
||||||
|
TypeInfo anyType = TypeInfo.Any;
|
||||||
|
|
||||||
|
string name = attr.Name ?? method.Name;
|
||||||
|
TypeInfo selfType = selfAttr.TypeInfo;
|
||||||
|
TypeInfo returnType = method.GetCustomAttribute<ReturnsAttribute>()?.TypeInfo ?? anyType; // todo: assume returned type from actual return value
|
||||||
|
string[] parameterNames = parameters.Subset(1).Select(p => p.Name!).ToArray() ?? []; // todo: use the actual parameter attribute for this.
|
||||||
|
|
||||||
|
ExtensionDelegate callback = RenderDelegate(method);
|
||||||
|
|
||||||
|
return new ExtensionMethod(selfType, returnType, name, callback, parameterNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ExtensionDelegate RenderDelegate(MethodInfo method)
|
||||||
|
{
|
||||||
|
_logger.Verbose($"Rendering delegate expression...");
|
||||||
|
var selfParameter = Expression.Parameter(typeof(Value), "self");
|
||||||
|
var argsParameter = Expression.Parameter(typeof(Value[]), "args");
|
||||||
|
ParameterInfo[] parameterInfos = method.GetParameters();
|
||||||
|
|
||||||
|
var parameterExpressions = new Expression[parameterInfos.Length - 1];
|
||||||
|
|
||||||
|
for (int i = 0; i < parameterInfos.Length; i++) {
|
||||||
|
ConstantExpression index = Expression.Constant(i);
|
||||||
|
BinaryExpression indexExpression = Expression.ArrayIndex(argsParameter, index);
|
||||||
|
|
||||||
|
/* todo: unsure
|
||||||
|
Type requiredType = parameterInfos[i].ParameterType;
|
||||||
|
if (indexExpression.Type != requiredType) {
|
||||||
|
parameterExpressions[i] = Expression.Convert(indexExpression, requiredType);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
parameterExpressions[i] = indexExpression;
|
||||||
|
}
|
||||||
|
|
||||||
|
Expression[] arguments = [selfParameter, .. parameterExpressions];
|
||||||
|
MethodCallExpression callExpression = Expression.Call(method, arguments);
|
||||||
|
Expression final;
|
||||||
|
|
||||||
|
if (method.ReturnType != typeof(Value)) {
|
||||||
|
ConstructorInfo? ctor = typeof(Value).GetConstructor([ method.ReturnType ]);
|
||||||
|
if (ctor is null)
|
||||||
|
throw new ExtensionException($"Can not compile extension from method {method}, its return type '{method.ReturnType}' is not accepted by any Value constructors.", method);
|
||||||
|
final = Expression.New(ctor, callExpression);
|
||||||
|
} else {
|
||||||
|
final = callExpression;
|
||||||
|
}
|
||||||
|
|
||||||
|
LambdaExpression lambda = Expression.Lambda(final, selfParameter, argsParameter);
|
||||||
|
|
||||||
|
_logger.Verbose($"Done: ", lambda);
|
||||||
|
return (ExtensionDelegate)lambda.Compile();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<ExtensionMethod> CompileExtensionsFromType(Type type)
|
||||||
|
{
|
||||||
|
_logger.Debug($"Compiling static extension methods from {type}...");
|
||||||
|
List<ExtensionMethod> extensions = [];
|
||||||
|
|
||||||
|
var methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public)
|
||||||
|
.Where(method => method.GetCustomAttribute<ExtensionMethodAttribute>() is not null)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
if (methods.Length == 0) {
|
||||||
|
_logger.Warn($"Tried to compile static extension methods from {type}, but no public static methods were found declared inside it.");
|
||||||
|
return extensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var method in methods) {
|
||||||
|
extensions.Add(RenderExtension(method));
|
||||||
|
}
|
||||||
|
|
||||||
|
return extensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ExtensionMethod()
|
||||||
|
{
|
||||||
|
Register(CompileExtensionsFromType(typeof(StringExtensions)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Value Call(Value self, Value[] parameters)
|
||||||
|
{
|
||||||
|
return Callback(self, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{TargetType}.{Name}({string.Join(", ", Parameters)}) (NativeExtension)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ExtensionClass(ValueType = ValueType.String)]
|
||||||
|
public static class StringExtensions
|
||||||
|
{
|
||||||
|
[ExtensionMethod]
|
||||||
|
public static long Length(Objects.String self)
|
||||||
|
{
|
||||||
|
return self.Value?.Length ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ExtensionMethod]
|
||||||
|
public static long IndexOf(Objects.String self, Objects.String needle)
|
||||||
|
{
|
||||||
|
if (needle.Value is null)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return self.Value?.IndexOf(needle.Value) ?? -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ExtensionMethod]
|
||||||
|
public static Objects.Array Split(Objects.String self, Objects.String splitter)
|
||||||
|
{
|
||||||
|
string? _splitter = splitter.Value;
|
||||||
|
string? _string = self.Value;
|
||||||
|
|
||||||
|
if (_string is null)
|
||||||
|
return new Objects.Array([]);
|
||||||
|
|
||||||
|
if (_splitter is null)
|
||||||
|
return new Objects.Array([ Obj.Create(self) ]);
|
||||||
|
|
||||||
|
string[] parts = _string.Split(_splitter);
|
||||||
|
Value[] values = new Value[parts.Length];
|
||||||
|
for (int i = 0; i < parts.Length; i++)
|
||||||
|
values[i] = Objects.String.Make(parts[i]);
|
||||||
|
|
||||||
|
return new Objects.Array(values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
using Qrakhen.Qamp.Core.Collections;
|
|
||||||
using Qrakhen.Qamp.Core.Values.Objects;
|
|
||||||
|
|
||||||
namespace Qrakhen.Qamp.Core.Values;
|
|
||||||
|
|
||||||
public delegate Value ExtensionDelegate(Value target, Value[] parameters);
|
|
||||||
|
|
||||||
public class NativeExtension(
|
|
||||||
ValueType targetType,
|
|
||||||
string name,
|
|
||||||
ExtensionDelegate callback,
|
|
||||||
string[] parameters)
|
|
||||||
{
|
|
||||||
private static readonly Register<ValueType, Register<string, NativeExtension>> _register = new();
|
|
||||||
|
|
||||||
public readonly string Name = name;
|
|
||||||
public readonly ValueType TargetType = targetType;
|
|
||||||
public readonly ExtensionDelegate Callback = callback;
|
|
||||||
public readonly string[] Parameters = parameters.ToArray();
|
|
||||||
|
|
||||||
public static NativeExtension? Get(ValueType targetType, string name)
|
|
||||||
{
|
|
||||||
if (!_register.Has(targetType))
|
|
||||||
return null;
|
|
||||||
if (!_register[targetType].Has(name))
|
|
||||||
return null;
|
|
||||||
return _register[targetType][name];
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Register(ValueType targetType,
|
|
||||||
string name,
|
|
||||||
ExtensionDelegate callback,
|
|
||||||
params string[] parameters)
|
|
||||||
{
|
|
||||||
if (!_register.Has(targetType))
|
|
||||||
_register.Add(targetType, new());
|
|
||||||
if (_register[targetType].Has(name))
|
|
||||||
throw new QampException($"Extension {name} already exists for type {targetType}.");
|
|
||||||
_register[targetType].Add(name, new NativeExtension(targetType, name, callback, parameters));
|
|
||||||
}
|
|
||||||
|
|
||||||
static NativeExtension()
|
|
||||||
{
|
|
||||||
Register(ValueType.String, "Length", (v, p) => new Value((long)v.Ptr.As<Objects.String>()!.Value!.Length), []);
|
|
||||||
Register(ValueType.String, "IndexOf", (v, p) => new Value((long)v.Ptr.As<Objects.String>()!.Value!.IndexOf(p[0].Ptr.As<Objects.String>()!.Value!)), ["needle"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"{TargetType}.{Name}({string.Join(", ", Parameters)}) (NativeExtension)";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
public class Obj(ValueType type) : IValue
|
public class Obj(ValueType type) : IValue
|
||||||
{
|
{
|
||||||
public bool __gcMarked { get; private set; }
|
public bool __gcMarked = false;
|
||||||
public int __gcCount { get; private set; } = 1;
|
public int __gcCount = 1;
|
||||||
|
|
||||||
public readonly ValueType Type = type;
|
public readonly ValueType Type = type;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,25 @@ public readonly struct Ptr
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Obj? As(Type type, bool throwWhenNull = true)
|
||||||
|
{
|
||||||
|
Obj? value = Convert.ChangeType(Value, type) as Obj;
|
||||||
|
if (value == null && throwWhenNull)
|
||||||
|
throw new RuntimeException($"Could not convert Object {Value} to target type {type}.", Value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
public override string ToString() => $"0x{Value}";
|
public override string ToString() => $"0x{Value}";
|
||||||
|
|
||||||
|
public static async Task __GC_Prepare()
|
||||||
|
{
|
||||||
|
await Task.Run(() => {
|
||||||
|
foreach (var obj in _register) {
|
||||||
|
obj.__gcCount = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public static byte[] SerializeFunctions()
|
public static byte[] SerializeFunctions()
|
||||||
{
|
{
|
||||||
List<byte> result = new();
|
List<byte> result = new();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
namespace Qrakhen.Qamp.Core.Values;
|
||||||
|
|
||||||
|
public readonly record struct TypeInfo(ValueType Type, Type? ObjectType = null, string? TypeName = null, bool Nullable = false)
|
||||||
|
{
|
||||||
|
public static readonly TypeInfo Any = new(ValueType.Undefined, null, null, true);
|
||||||
|
public static readonly TypeInfo Void = new(ValueType.Void, null, null, false);
|
||||||
|
|
||||||
|
public bool Match(TypeInfo other)
|
||||||
|
{
|
||||||
|
if (Type != ValueType.Undefined && other.Type != ValueType.Undefined && Type != other.Type)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (ObjectType != null && other.ObjectType != null && ObjectType != other.ObjectType)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (TypeName != null && other.TypeName != null && TypeName != other.TypeName)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TypeInfo FromValue(Value value)
|
||||||
|
{
|
||||||
|
return new TypeInfo(value.Type, value.IsObj ? value.Ptr.Value?.GetType() : null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -43,10 +43,12 @@ public readonly struct Value : IValue, ISerialize<Value>, IDebug<string>
|
||||||
public Value(Address address) : this(T.Address) => Address = address;
|
public Value(Address address) : this(T.Address) => Address = address;
|
||||||
public Value(Ptr ptr) : this(ptr.Value?.Type ?? T.Pointer) => Ptr = ptr;
|
public Value(Ptr ptr) : this(ptr.Value?.Type ?? T.Pointer) => Ptr = ptr;
|
||||||
public Value(Ref @ref) : this(T.Reference) => Ref = @ref;
|
public Value(Ref @ref) : this(T.Reference) => Ref = @ref;
|
||||||
|
public Value(Obj obj) : this(Ptr.Create(obj)) { } // todo: very spicy, let's see if this doesn't fuck up the GC all too bad.
|
||||||
|
|
||||||
public dynamic? Dynamic => AsDynamic();
|
public dynamic? Dynamic => AsDynamic();
|
||||||
|
|
||||||
public T ValueType => Type;
|
public T ValueType => Type;
|
||||||
|
public TypeInfo TypeInfo => TypeInfo.FromValue(this);
|
||||||
|
|
||||||
public bool IsNumber => Type <= T.Decimal;
|
public bool IsNumber => Type <= T.Decimal;
|
||||||
public bool IsSigned => Is(T.Signed);
|
public bool IsSigned => Is(T.Signed);
|
||||||
|
|
|
||||||
|
|
@ -42,4 +42,4 @@ public enum ValueType
|
||||||
Structure = ItemProvider | 0x0004,
|
Structure = ItemProvider | 0x0004,
|
||||||
|
|
||||||
Module = 0xFFFF
|
Module = 0xFFFF
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -140,17 +140,6 @@ public class EditorFrameViewModel : ObservableObject
|
||||||
CurrentSelection = BufferRegion.Void;
|
CurrentSelection = BufferRegion.Void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
asdasdy
|
|
||||||
klöklöklökl
|
|
||||||
köäläälööx
|
|
||||||
cyxc
|
|
||||||
|
|
||||||
asdasd
|
|
||||||
|
|
||||||
* */
|
|
||||||
|
|
||||||
public void Insert(string data, bool follow = true)
|
public void Insert(string data, bool follow = true)
|
||||||
{
|
{
|
||||||
data = data.Replace("\t", " ");
|
data = data.Replace("\t", " ");
|
||||||
|
|
|
||||||
|
|
@ -1,256 +0,0 @@
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
using System.Windows.Input;
|
|
||||||
using System.Windows.Media;
|
|
||||||
using System.Xml.Linq;
|
|
||||||
|
|
||||||
namespace CopaData.FileInspector.GUI.TilingPanels.Controls.DragAndDrop;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Used to handle drag and drop callbacks via the abstract methods <see cref="OnDragStart(object, UIElement)"/> and <see cref="OnDrop(object, UIElement)"/>.
|
|
||||||
/// Can be used as a parent to any amount of <see cref="UIElement"/>s that refer to the their handler via <see cref="HandlerProperty"/>.
|
|
||||||
/// If no Handler is provided, the element itself will be considered its own handler, as long as it is a <see cref="DragAndDropControl"/>.
|
|
||||||
/// In order to make an element draggable, set <see cref="IsDraggableProperty"/> to true within the element's XAML attributes.
|
|
||||||
/// To mark a FrameworkElement as a drop zone, set <see cref="IsDropZoneProperty"/> to true.
|
|
||||||
/// If a draggable element is dropped on a drop zone, the handler's <see cref="DragAndDropControl.OnDrop(object, UIElement)" /> is called.
|
|
||||||
/// </summary>
|
|
||||||
public abstract class DragAndDropControl : Control
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Called when a DragDrop operation starts.
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void OnElementDragStart(UIElement draggedElement) { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called during a DragDrop operation in order to update the cursor, if needed.
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void OnElementDragGiveCursorFeedback(object sender, GiveFeedbackEventArgs args) { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called during a DragDrop operation.
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void OnElementDrag(UIElement draggedElement, MouseEventArgs args) { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when an element was dropped without being on an element marked as a DropZone.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="draggedElement"></param>
|
|
||||||
protected virtual void OnElementDragStop(UIElement draggedElement) { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when this element is the potential target of a DragDrop operation.
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void OnElementDragOver(DragAndDropData data, UIElement targetElement) { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when this element stops being a potential target of a DragDrop operation.
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void OnElementDragLeave(UIElement targetElement) { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Required callback to handle <paramref name="targetElement"/>,
|
|
||||||
/// provided with <see cref="DragAndDropData"/> containing both the source element being dragged and it's original data context.
|
|
||||||
/// </summary>
|
|
||||||
protected abstract void OnElementDropped(DragAndDropData data, UIElement targetElement);
|
|
||||||
|
|
||||||
#region Attached Properties
|
|
||||||
|
|
||||||
/* === IsDraggable === */
|
|
||||||
|
|
||||||
public static readonly DependencyProperty IsDraggableProperty = DependencyProperty
|
|
||||||
.RegisterAttached(
|
|
||||||
"IsDraggable",
|
|
||||||
typeof(bool),
|
|
||||||
typeof(DragAndDropControl),
|
|
||||||
new PropertyMetadata(false, OnIsDraggableChanged));
|
|
||||||
|
|
||||||
public static bool GetIsDraggable(DependencyObject obj) => (bool)obj.GetValue(IsDraggableProperty);
|
|
||||||
public static void SetIsDraggable(DependencyObject obj, bool value) => obj.SetValue(IsDraggableProperty, value);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds event listeners to the element's GiveFeedback, MouseLeftButtonUp and MouseMove events.
|
|
||||||
/// </summary>
|
|
||||||
private static void OnIsDraggableChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (sender is not FrameworkElement element)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"IsDraggable may only be attached to FrameworkElements, got {sender.GetType()} instead.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((bool)e.NewValue)
|
|
||||||
{
|
|
||||||
element.GiveFeedback += HandleGiveFeedback;
|
|
||||||
element.MouseLeftButtonUp += HandleLeftMouseButtonUp;
|
|
||||||
element.MouseMove += HandleMouseMove;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
element.GiveFeedback -= HandleGiveFeedback;
|
|
||||||
element.MouseLeftButtonUp -= HandleLeftMouseButtonUp;
|
|
||||||
element.MouseMove -= HandleMouseMove;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* === IsDropZone === */
|
|
||||||
|
|
||||||
public static readonly DependencyProperty IsDropZoneProperty = DependencyProperty
|
|
||||||
.RegisterAttached(
|
|
||||||
"IsDropZone",
|
|
||||||
typeof(bool),
|
|
||||||
typeof(DragAndDropControl),
|
|
||||||
new PropertyMetadata(false, OnIsDropZoneChanged));
|
|
||||||
|
|
||||||
public static bool GetIsDropZone(DependencyObject obj) => (bool)obj.GetValue(IsDropZoneProperty);
|
|
||||||
public static void SetIsDropZone(DependencyObject obj, bool value) => obj.SetValue(IsDropZoneProperty, value);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds an event listener to this element's <see cref="UIElement.Drop"/> event.
|
|
||||||
/// </summary>
|
|
||||||
private static void OnIsDropZoneChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (sender is not FrameworkElement element)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"IsDraggable may only be attached to FrameworkElements, got {sender.GetType()} instead.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((bool)e.NewValue)
|
|
||||||
{
|
|
||||||
element.AllowDrop = true;
|
|
||||||
element.DragOver += HandleDragOver;
|
|
||||||
element.DragLeave += HandleDragLeave;
|
|
||||||
element.Drop += HandleDrop;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
element.DragOver -= HandleDragOver;
|
|
||||||
element.DragLeave -= HandleDragLeave;
|
|
||||||
element.Drop -= HandleDrop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* === Handler === */
|
|
||||||
|
|
||||||
public static readonly DependencyProperty HandlerProperty = DependencyProperty
|
|
||||||
.RegisterAttached(
|
|
||||||
"Handler",
|
|
||||||
typeof(DependencyObject),
|
|
||||||
typeof(DragAndDropControl),
|
|
||||||
new PropertyMetadata(null, OnHandlerChanged));
|
|
||||||
|
|
||||||
public static DependencyObject GetHandler(DependencyObject obj) => (DependencyObject)obj.GetValue(HandlerProperty);
|
|
||||||
public static void SetHandler(DependencyObject obj, bool value) => obj.SetValue(HandlerProperty, value);
|
|
||||||
|
|
||||||
private static void OnHandlerChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
// Simple validation
|
|
||||||
if (sender is not UIElement element || e.NewValue is not DragAndDropControl dndControl)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Can not set {e.NewValue} as handler. Handler needs to be of type DragAndDropControl.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attempts to retrieve the correct <see cref="DragAndDropControl"/> handler for the provided element.<br/>
|
|
||||||
/// This may either be via the defined Handler property, or the element itself if no handler property was attached.
|
|
||||||
/// </summary>
|
|
||||||
private static DragAndDropControl RetrieveHandler(DependencyObject obj)
|
|
||||||
{
|
|
||||||
if (obj.GetValue(HandlerProperty) is not DragAndDropControl handler)
|
|
||||||
{
|
|
||||||
if (obj is DragAndDropControl)
|
|
||||||
{
|
|
||||||
handler = (DragAndDropControl)obj;
|
|
||||||
}
|
|
||||||
else // Todo: last resort if ((handler = FindVisualParent<DragAndDropControl>(obj)!) == null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Could not find a DragAndDropControl for element {obj}. You may explicitly provide one using the DragAndDropControl.Handler attached property.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Dynamic handler for the <see cref="UIElement.MouseMove"/> event.
|
|
||||||
/// Called when the mouse cursor moves while on an element.
|
|
||||||
/// This also calls the <see cref="OnElementDrag(UIElement, MouseEventArgs)"/> method in order to handle any specific behaviour <b>during</b> the operation.
|
|
||||||
/// </summary>
|
|
||||||
private static void HandleMouseMove(object sender, MouseEventArgs args)
|
|
||||||
{
|
|
||||||
if (args.LeftButton == MouseButtonState.Pressed)
|
|
||||||
{
|
|
||||||
FrameworkElement element = (FrameworkElement)sender;
|
|
||||||
DragAndDropControl handler = RetrieveHandler(element);
|
|
||||||
DragAndDropData data = new(element, element.DataContext);
|
|
||||||
DragDrop.DoDragDrop(element, data, DragDropEffects.All);
|
|
||||||
handler.OnElementDrag(element, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Dynamic handler for the <see cref="UIElement.GiveFeedback"/> event.
|
|
||||||
/// </summary>
|
|
||||||
private static void HandleGiveFeedback(object sender, GiveFeedbackEventArgs args)
|
|
||||||
{
|
|
||||||
FrameworkElement element = (FrameworkElement)sender;
|
|
||||||
DragAndDropControl handler = RetrieveHandler(element);
|
|
||||||
handler.OnElementDragGiveCursorFeedback(element, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Dynamic handler for the <see cref="UIElement.MouseLeftButtonUp"/> event.
|
|
||||||
/// </summary>
|
|
||||||
private static void HandleLeftMouseButtonUp(object sender, MouseButtonEventArgs args)
|
|
||||||
{
|
|
||||||
FrameworkElement element = (FrameworkElement)sender;
|
|
||||||
DragAndDropControl handler = RetrieveHandler(element);
|
|
||||||
handler.OnElementDragStop(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Dynamic handler for the <see cref="UIElement.DragOver"/> event.
|
|
||||||
/// </summary>
|
|
||||||
private static void HandleDragOver(object sender, DragEventArgs args)
|
|
||||||
{
|
|
||||||
FrameworkElement element = (FrameworkElement)sender;
|
|
||||||
DragAndDropControl handler = RetrieveHandler(element);
|
|
||||||
handler.OnElementDragOver((DragAndDropData)args.Data.GetData(typeof(DragAndDropData)), element);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Dynamic handler for the <see cref="UIElement.DragLeave"/> event.
|
|
||||||
/// </summary>
|
|
||||||
private static void HandleDragLeave(object sender, DragEventArgs args)
|
|
||||||
{
|
|
||||||
FrameworkElement element = (FrameworkElement)sender;
|
|
||||||
DragAndDropControl handler = RetrieveHandler(element);
|
|
||||||
handler.OnElementDragLeave(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Dynamic handler for the <see cref="UIElement.Drop"/> event.
|
|
||||||
/// </summary>
|
|
||||||
private static void HandleDrop(object sender, DragEventArgs args)
|
|
||||||
{
|
|
||||||
FrameworkElement element = (FrameworkElement)sender;
|
|
||||||
DragAndDropControl handler = RetrieveHandler(element);
|
|
||||||
handler.OnElementDropped((DragAndDropData)args.Data.GetData(typeof(DragAndDropData)), element);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static T? FindVisualParent<T>(DependencyObject child) where T : DependencyObject
|
|
||||||
{
|
|
||||||
// Todo: find a better solution to this, if there even is one.
|
|
||||||
// There are, in fact, many controls even in the WPF standard that do similar lookups,
|
|
||||||
// so I assume it can't be _that_ bad, but every clean solution kind of seems to need some ugly spot in it. :(
|
|
||||||
|
|
||||||
DependencyObject parent = VisualTreeHelper.GetParent(child);
|
|
||||||
while (parent != null)
|
|
||||||
{
|
|
||||||
if (parent is T expected) return expected;
|
|
||||||
parent = VisualTreeHelper.GetParent(parent);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
using System.Windows;
|
|
||||||
|
|
||||||
namespace CopaData.FileInspector.GUI.TilingPanels.Controls.DragAndDrop;
|
|
||||||
|
|
||||||
public class DragAndDropData
|
|
||||||
{
|
|
||||||
public FrameworkElement DraggedElement { get; }
|
|
||||||
public object DataModel { get; }
|
|
||||||
|
|
||||||
public DragAndDropData(FrameworkElement draggedElement, object dataModel)
|
|
||||||
{
|
|
||||||
DraggedElement = draggedElement;
|
|
||||||
DataModel = dataModel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
|
|
||||||
namespace CopaData.FileInspector.GUI.TilingPanels.Controls.DropArea;
|
|
||||||
|
|
||||||
public class DropArea : Control
|
|
||||||
{
|
|
||||||
public static readonly DependencyProperty DropDecisionProperty = DependencyProperty
|
|
||||||
.RegisterAttached(
|
|
||||||
"Decision",
|
|
||||||
typeof(DropDecision),
|
|
||||||
typeof(DropArea));
|
|
||||||
|
|
||||||
public static DropDecision GetDropDecision(DependencyObject obj) => (DropDecision)obj.GetValue(DropDecisionProperty);
|
|
||||||
public static void SetDropDecision(DependencyObject obj, bool value) => obj.SetValue(DropDecisionProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
namespace CopaData.FileInspector.GUI.TilingPanels.Controls.DropArea;
|
|
||||||
|
|
||||||
public delegate void DropAreaDecisionEvent(DropArea sender, TilingHostControl host, DropDecision decision);
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
namespace CopaData.FileInspector.GUI.TilingPanels.Controls.DropArea;
|
|
||||||
|
|
||||||
public enum DropDecision
|
|
||||||
{
|
|
||||||
Center = 0,
|
|
||||||
Left = 1,
|
|
||||||
Top = 2,
|
|
||||||
Right = 3,
|
|
||||||
Bottom = 4,
|
|
||||||
Cancel = 5
|
|
||||||
}
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
using Qrakhen.TilingFrames.Controls.DragAndDrop;
|
|
||||||
using Qrakhen.TilingFrames.Models;
|
|
||||||
|
|
||||||
namespace Qrakhen.TilingFrames.Controls;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Control that represents the state of a <see cref="Models.TilingHost"/>,
|
|
||||||
/// with all the necessary interaction logic tied to it.
|
|
||||||
/// </summary>
|
|
||||||
public class HostControl : DragAndDropControl
|
|
||||||
{
|
|
||||||
public TilingHost? Host => DataContext as TilingHost;
|
|
||||||
|
|
||||||
static HostControl()
|
|
||||||
{
|
|
||||||
DefaultStyleKeyProperty.OverrideMetadata(
|
|
||||||
typeof(HostControl),
|
|
||||||
new FrameworkPropertyMetadata(typeof(HostControl)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public HostControl()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#region DependencyProperties
|
|
||||||
|
|
||||||
/*public Host Host
|
|
||||||
{
|
|
||||||
get => (Host)GetValue(HostProperty);
|
|
||||||
set => SetValue(HostProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DependencyProperty HostProperty = DependencyProperty
|
|
||||||
.Register(nameof(Host),
|
|
||||||
typeof(Host),
|
|
||||||
typeof(HostControl),
|
|
||||||
new PropertyMetadata(null));*/
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
using Qrakhen.TilingFrames.Models;
|
|
||||||
|
|
||||||
namespace Qrakhen.TilingFrames.Controls;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Control that represents the state of a <see cref="HostFrame"/>,
|
|
||||||
/// with all the necessary interaction logic tied to it.
|
|
||||||
/// </summary>
|
|
||||||
public class HostFrameControl : ContentControl
|
|
||||||
{
|
|
||||||
static HostFrameControl()
|
|
||||||
{
|
|
||||||
DefaultStyleKeyProperty.OverrideMetadata(
|
|
||||||
typeof(HostFrameControl),
|
|
||||||
new FrameworkPropertyMetadata(typeof(HostFrameControl)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public HostFrameControl()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#region DependencyProperties
|
|
||||||
|
|
||||||
public HostFrame Frame {
|
|
||||||
get => (HostFrame)GetValue(FrameProperty);
|
|
||||||
set => SetValue(FrameProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DependencyProperty FrameProperty = DependencyProperty
|
|
||||||
.Register(nameof(Frame),
|
|
||||||
typeof(HostFrame),
|
|
||||||
typeof(HostFrameControl),
|
|
||||||
new PropertyMetadata(null));
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
using Qrakhen.TilingFrames.Models;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
|
|
||||||
namespace Qrakhen.TilingFrames.Controls;
|
|
||||||
|
|
||||||
public class PanelControl : Control
|
|
||||||
{
|
|
||||||
static PanelControl()
|
|
||||||
{
|
|
||||||
DefaultStyleKeyProperty.OverrideMetadata(
|
|
||||||
typeof(PanelControl),
|
|
||||||
new FrameworkPropertyMetadata(typeof(PanelControl)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public PanelControl()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#region DependencyProperties
|
|
||||||
|
|
||||||
public Orientation SplitOrientation => Panel.SplitOrientation;
|
|
||||||
|
|
||||||
public TilingNode Alpha => Panel.Alpha;
|
|
||||||
public TilingNode? Beta => Panel.Beta;
|
|
||||||
|
|
||||||
public Models.TilingPanel Panel {
|
|
||||||
get => (Models.TilingPanel)GetValue(PanelProperty);
|
|
||||||
set => SetValue(PanelProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DependencyProperty PanelProperty = DependencyProperty
|
|
||||||
.Register(nameof(Panel),
|
|
||||||
typeof(Models.TilingPanel),
|
|
||||||
typeof(PanelControl),
|
|
||||||
new PropertyMetadata(null));
|
|
||||||
|
|
||||||
public double SplitterWidth {
|
|
||||||
get => (double)GetValue(SplitterWidthProperty);
|
|
||||||
set => SetValue(SplitterWidthProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DependencyProperty SplitterWidthProperty = DependencyProperty
|
|
||||||
.Register(
|
|
||||||
nameof(SplitterWidth),
|
|
||||||
typeof(double),
|
|
||||||
typeof(PanelControl),
|
|
||||||
new PropertyMetadata(8.0));
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
|
|
||||||
namespace CopaData.FileInspector.GUI.TilingPanels.Controls
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Used to force-update columns into rows when orientation is vertical
|
|
||||||
/// </summary>
|
|
||||||
public static class TilingGridAttachment
|
|
||||||
{
|
|
||||||
#region AlphaRow
|
|
||||||
public static readonly DependencyProperty AlphaRowProperty =
|
|
||||||
DependencyProperty.RegisterAttached(
|
|
||||||
"AlphaRow",
|
|
||||||
typeof(int),
|
|
||||||
typeof(TilingGridAttachment),
|
|
||||||
new PropertyMetadata(0, OnAlphaRowChanged));
|
|
||||||
|
|
||||||
public static int GetAlphaRow(DependencyObject obj)
|
|
||||||
=> (int)obj.GetValue(AlphaRowProperty);
|
|
||||||
public static void SetAlphaRow(DependencyObject obj, int value)
|
|
||||||
=> obj.SetValue(AlphaRowProperty, value);
|
|
||||||
|
|
||||||
private static void OnAlphaRowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (d is ContentPresenter presenter)
|
|
||||||
{
|
|
||||||
Grid.SetRow(presenter, (int)e.NewValue);
|
|
||||||
Grid.SetColumn(presenter, 0); // Always Column 0 for vertical split
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region BetaRow
|
|
||||||
public static readonly DependencyProperty BetaRowProperty =
|
|
||||||
DependencyProperty.RegisterAttached(
|
|
||||||
"BetaRow",
|
|
||||||
typeof(int),
|
|
||||||
typeof(TilingGridAttachment),
|
|
||||||
new PropertyMetadata(0, OnBetaRowChanged));
|
|
||||||
|
|
||||||
public static int GetBetaRow(DependencyObject obj)
|
|
||||||
=> (int)obj.GetValue(BetaRowProperty);
|
|
||||||
public static void SetBetaRow(DependencyObject obj, int value)
|
|
||||||
=> obj.SetValue(BetaRowProperty, value);
|
|
||||||
|
|
||||||
private static void OnBetaRowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (d is ContentPresenter presenter)
|
|
||||||
{
|
|
||||||
Grid.SetRow(presenter, (int)e.NewValue);
|
|
||||||
Grid.SetColumn(presenter, 0); // Always Column 0 for vertical split
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
using CopaData.FileInspector.GUI.TilingPanels.Controls.DragAndDrop;
|
|
||||||
using CopaData.FileInspector.GUI.TilingPanels.Controls.DropArea;
|
|
||||||
using CopaData.FileInspector.GUI.TilingPanels.Models;
|
|
||||||
using CopaData.FileInspector.GUI.ViewModels;
|
|
||||||
|
|
||||||
namespace CopaData.FileInspector.GUI.TilingPanels.Controls;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Control that represents the state of a <see cref="Models.TilingHost"/>,
|
|
||||||
/// with all the necessary interaction logic tied to it.
|
|
||||||
/// </summary>
|
|
||||||
public class TilingHostControl : DragAndDropControl
|
|
||||||
{
|
|
||||||
public TilingHost? Host => DataContext as TilingHost;
|
|
||||||
|
|
||||||
static TilingHostControl()
|
|
||||||
{
|
|
||||||
DefaultStyleKeyProperty.OverrideMetadata(
|
|
||||||
typeof(TilingHostControl),
|
|
||||||
new FrameworkPropertyMetadata(typeof(TilingHostControl)));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnElementDragOver(DragAndDropData data, UIElement targetElement)
|
|
||||||
{
|
|
||||||
SetValue(IsDragTargetPropertyKey, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnElementDragLeave(UIElement targetElement)
|
|
||||||
{
|
|
||||||
SetValue(IsDragTargetPropertyKey, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnElementDropped(DragAndDropData data, UIElement targetElement)
|
|
||||||
{
|
|
||||||
if (data.DataModel is not TilingFrame frame)
|
|
||||||
{
|
|
||||||
// not for us
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DropDecision decision = (DropDecision)targetElement.GetValue(DropArea.DropArea.DropDecisionProperty);
|
|
||||||
if (decision == DropDecision.Cancel)
|
|
||||||
return;
|
|
||||||
|
|
||||||
TilingDirection direction = (TilingDirection)decision; // simple cast suffices here
|
|
||||||
if (direction == TilingDirection.None) {
|
|
||||||
TilingHost.Insert(TilingManagerViewModel.GlobalRoot, Host!, frame);
|
|
||||||
} else {
|
|
||||||
TilingPanel.Attach(TilingManagerViewModel.GlobalRoot, Host!, frame, direction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Dependency Properties
|
|
||||||
|
|
||||||
/// <inheritdoc cref="IsDragTarget" />
|
|
||||||
private static readonly DependencyPropertyKey IsDragTargetPropertyKey =
|
|
||||||
DependencyProperty.RegisterReadOnly(
|
|
||||||
nameof(IsDragTarget),
|
|
||||||
typeof(bool),
|
|
||||||
typeof(TilingHostControl),
|
|
||||||
new PropertyMetadata(false));
|
|
||||||
|
|
||||||
/// <inheritdoc cref="IsDragTarget" />
|
|
||||||
public static readonly DependencyProperty IsDragTargetProperty = IsDragTargetPropertyKey.DependencyProperty;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether this <see cref="TilingHostControl"/> is being a potential drop target at the moment.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsDragTarget => (bool)GetValue(IsDragTargetProperty);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
using CopaData.FileInspector.GUI.TilingPanels.Models;
|
|
||||||
|
|
||||||
namespace CopaData.FileInspector.GUI.TilingPanels.Controls;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Control that represents the state of a <see cref="TilingFrame"/>,
|
|
||||||
/// with all the necessary interaction logic tied to it.
|
|
||||||
/// </summary>
|
|
||||||
public class TilingHostFrameControl : ContentControl
|
|
||||||
{
|
|
||||||
static TilingHostFrameControl()
|
|
||||||
{
|
|
||||||
DefaultStyleKeyProperty.OverrideMetadata(
|
|
||||||
typeof(TilingHostFrameControl),
|
|
||||||
new FrameworkPropertyMetadata(typeof(TilingHostFrameControl)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public TilingHostFrameControl()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#region DependencyProperties
|
|
||||||
|
|
||||||
public TilingFrame Frame
|
|
||||||
{
|
|
||||||
get => (TilingFrame)GetValue(FrameProperty);
|
|
||||||
set => SetValue(FrameProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DependencyProperty FrameProperty = DependencyProperty
|
|
||||||
.Register(nameof(Frame),
|
|
||||||
typeof(TilingFrame),
|
|
||||||
typeof(TilingHostFrameControl),
|
|
||||||
new PropertyMetadata(null));
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
using CopaData.FileInspector.GUI.TilingPanels.Models;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
|
|
||||||
namespace CopaData.FileInspector.GUI.TilingPanels.Controls;
|
|
||||||
|
|
||||||
public class TilingPanelControl : Control
|
|
||||||
{
|
|
||||||
static TilingPanelControl()
|
|
||||||
{
|
|
||||||
DefaultStyleKeyProperty.OverrideMetadata(
|
|
||||||
typeof(TilingPanelControl),
|
|
||||||
new FrameworkPropertyMetadata(typeof(TilingPanelControl)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public TilingPanelControl()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#region DependencyProperties
|
|
||||||
|
|
||||||
public Orientation SplitOrientation => Panel.SplitOrientation;
|
|
||||||
|
|
||||||
public TilingNode Alpha => Panel.Alpha;
|
|
||||||
public TilingNode? Beta => Panel.Beta;
|
|
||||||
|
|
||||||
public TilingPanel Panel
|
|
||||||
{
|
|
||||||
get => (TilingPanel)GetValue(PanelProperty);
|
|
||||||
set => SetValue(PanelProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DependencyProperty PanelProperty = DependencyProperty
|
|
||||||
.Register(nameof(Panel),
|
|
||||||
typeof(TilingPanel),
|
|
||||||
typeof(TilingPanelControl),
|
|
||||||
new PropertyMetadata(null));
|
|
||||||
|
|
||||||
public double SplitterWidth
|
|
||||||
{
|
|
||||||
get => (double)GetValue(SplitterWidthProperty);
|
|
||||||
set => SetValue(SplitterWidthProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DependencyProperty SplitterWidthProperty = DependencyProperty
|
|
||||||
.Register(
|
|
||||||
nameof(SplitterWidth),
|
|
||||||
typeof(double),
|
|
||||||
typeof(TilingPanelControl),
|
|
||||||
new PropertyMetadata(6.0));
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
|
|
||||||
namespace Qrakhen.TilingFrames.Controls;
|
|
||||||
|
|
||||||
public class TilingRoot : Control
|
|
||||||
{
|
|
||||||
static TilingRoot()
|
|
||||||
{
|
|
||||||
DefaultStyleKeyProperty.OverrideMetadata(
|
|
||||||
typeof(TilingRoot),
|
|
||||||
new FrameworkPropertyMetadata(typeof(TilingRoot)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Dependency Properties
|
|
||||||
|
|
||||||
public double SplitterWidth {
|
|
||||||
get => (double)GetValue(SplitterWidthProperty);
|
|
||||||
set => SetValue(SplitterWidthProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DependencyProperty SplitterWidthProperty = DependencyProperty
|
|
||||||
.Register(
|
|
||||||
nameof(SplitterWidth),
|
|
||||||
typeof(double),
|
|
||||||
typeof(TilingRoot),
|
|
||||||
new PropertyMetadata(8.0));
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
using CopaData.FileInspector.GUI.TilingPanels.Models;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
|
|
||||||
namespace CopaData.FileInspector.GUI.TilingPanels.Controls;
|
|
||||||
|
|
||||||
public class TilingRootControl : Control
|
|
||||||
{
|
|
||||||
static TilingRootControl()
|
|
||||||
{
|
|
||||||
DefaultStyleKeyProperty.OverrideMetadata(
|
|
||||||
typeof(TilingRootControl),
|
|
||||||
new FrameworkPropertyMetadata(typeof(TilingRootControl)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Dependency Properties
|
|
||||||
|
|
||||||
public double SplitterWidth
|
|
||||||
{
|
|
||||||
get => (double)GetValue(SplitterWidthProperty);
|
|
||||||
set => SetValue(SplitterWidthProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DependencyProperty SplitterWidthProperty = DependencyProperty
|
|
||||||
.Register(
|
|
||||||
nameof(SplitterWidth),
|
|
||||||
typeof(double),
|
|
||||||
typeof(TilingRootControl),
|
|
||||||
new PropertyMetadata(6.0));
|
|
||||||
|
|
||||||
public bool IsPrimaryRoot
|
|
||||||
{
|
|
||||||
get => (bool)GetValue(IsPrimaryRootProperty);
|
|
||||||
set => SetValue(IsPrimaryRootProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DependencyProperty IsPrimaryRootProperty = DependencyProperty
|
|
||||||
.Register(
|
|
||||||
nameof(IsPrimaryRoot),
|
|
||||||
typeof(bool),
|
|
||||||
typeof(TilingRootControl),
|
|
||||||
new PropertyMetadata(true));
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
using System.Windows;
|
|
||||||
|
|
||||||
namespace CopaData.FileInspector.GUI.TilingPanels.Controls;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A dedicated window for hosting its own <see cref="TilingRoot"/>, to be used for pop-out interactions.
|
|
||||||
/// </summary>
|
|
||||||
public class TilingWindow : Window
|
|
||||||
{
|
|
||||||
static TilingWindow()
|
|
||||||
{
|
|
||||||
DefaultStyleKeyProperty.OverrideMetadata(
|
|
||||||
typeof(TilingWindow),
|
|
||||||
new FrameworkPropertyMetadata(typeof(TilingWindow)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Dependency Properties
|
|
||||||
|
|
||||||
public double SplitterWidth
|
|
||||||
{
|
|
||||||
get => (double)GetValue(SplitterWidthProperty);
|
|
||||||
set => SetValue(SplitterWidthProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DependencyProperty SplitterWidthProperty = DependencyProperty
|
|
||||||
.Register(
|
|
||||||
nameof(SplitterWidth),
|
|
||||||
typeof(double),
|
|
||||||
typeof(TilingWindow),
|
|
||||||
new PropertyMetadata(6.0));
|
|
||||||
|
|
||||||
public TilingRootControl Root
|
|
||||||
{
|
|
||||||
get => (TilingRootControl)GetValue(RootProperty);
|
|
||||||
set => SetValue(RootProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DependencyProperty RootProperty = DependencyProperty
|
|
||||||
.Register(
|
|
||||||
nameof(Root),
|
|
||||||
typeof(TilingRootControl),
|
|
||||||
typeof(TilingWindow));
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
using System.Globalization;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Data;
|
|
||||||
using System.Windows.Markup;
|
|
||||||
|
|
||||||
namespace CopaData.FileInspector.GUI.TilingPanels.Converters;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts a double between 0 and 1 to a star-length representation,
|
|
||||||
/// to be used in conjunction with <see cref="BetaRatioConverter"/>.
|
|
||||||
/// </summary>
|
|
||||||
public class AlphaRatioConverter : MarkupExtension, IValueConverter
|
|
||||||
{
|
|
||||||
public override object ProvideValue(IServiceProvider serviceProvider) => this;
|
|
||||||
|
|
||||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
if (value is double ratio)
|
|
||||||
{
|
|
||||||
if (ratio <= double.Epsilon)
|
|
||||||
{
|
|
||||||
// If a ratio near zero, alpha is essentially hidden.
|
|
||||||
return new GridLength(0, GridUnitType.Star);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ratio <= .5)
|
|
||||||
{
|
|
||||||
// With a ratio less than or equal to .5, alpha will be calculated as the minor component
|
|
||||||
return new GridLength(1, GridUnitType.Star);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ratio >= 1)
|
|
||||||
{
|
|
||||||
// With a ratio near 1, alpha will be 1.
|
|
||||||
return new GridLength(1, GridUnitType.Star);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the parts-per-ratio that alpha represents
|
|
||||||
return new GridLength((1 - ratio) / ratio, GridUnitType.Star);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ArgumentException($"Expected value to be ratio (double), god {value} instead.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
|
||||||
=> throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
using System.Globalization;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Data;
|
|
||||||
using System.Windows.Markup;
|
|
||||||
|
|
||||||
namespace CopaData.FileInspector.GUI.TilingPanels.Converters;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts a double between 0 and 1 to a star-length representation,
|
|
||||||
/// to be used in conjunction with <see cref="AlphaRatioConverter"/>.
|
|
||||||
/// </summary>
|
|
||||||
public class BetaRatioConverter : MarkupExtension, IValueConverter
|
|
||||||
{
|
|
||||||
public override object ProvideValue(IServiceProvider serviceProvider) => this;
|
|
||||||
|
|
||||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
if (value is double ratio)
|
|
||||||
{
|
|
||||||
if (ratio >= 1)
|
|
||||||
{
|
|
||||||
// If near 1, beta is essentially hidden.
|
|
||||||
return new GridLength(0, GridUnitType.Star);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ratio >= .5)
|
|
||||||
{
|
|
||||||
// With a ratio larger than or equal to 0.5, beta is the lesser component of the two.
|
|
||||||
return new GridLength(1, GridUnitType.Star);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ratio <= double.Epsilon)
|
|
||||||
{
|
|
||||||
// If near zero, beta covers the entire panel.
|
|
||||||
return new GridLength(1, GridUnitType.Star);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the parts-per-ratio that beta represents.
|
|
||||||
return new GridLength((1 - ratio) / ratio, GridUnitType.Star);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ArgumentException($"Expected value to be ratio (double), god {value} instead.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
|
||||||
=> throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
namespace CopaData.FileInspector.GUI.TilingPanels.Models;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initial empty frame, can be turned of by setting <see cref="TilingRoot.ShowEmptyInitial"/> to false.
|
|
||||||
/// </summary>
|
|
||||||
public class EmptyFrame : TilingFrame
|
|
||||||
{
|
|
||||||
public EmptyFrame()
|
|
||||||
{
|
|
||||||
HeaderText = "Empty";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Todo: remove this later, only used to test the non-overriden case of a data template
|
|
||||||
/// </summary>
|
|
||||||
public class TestOverrideFrame : TilingFrame { public TestOverrideFrame() { HeaderText = "Testerinho"; } }
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
namespace CopaData.FileInspector.GUI.TilingPanels.Models;
|
|
||||||
|
|
||||||
public class EmptyHost : TilingHost
|
|
||||||
{
|
|
||||||
public EmptyHost() : base(new EmptyFrame(), new TestOverrideFrame())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
namespace CopaData.FileInspector.GUI.TilingPanels.Models;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Flags for declaring the buttons on a <see cref="TilingHost"/>
|
|
||||||
/// </summary>
|
|
||||||
[Flags]
|
|
||||||
public enum FrameButtons
|
|
||||||
{
|
|
||||||
None = 0,
|
|
||||||
Close = 1 << 0,
|
|
||||||
Minimize = 1 << 1,
|
|
||||||
Pin = 1 << 2,
|
|
||||||
Split = 1 << 3,
|
|
||||||
PopOut = 1 << 4,
|
|
||||||
Options = 1 << 5,
|
|
||||||
Help = 1 << 6,
|
|
||||||
|
|
||||||
Default = Close | Pin | PopOut
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
namespace Qrakhen.TilingFrames.Models;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Base data class for content that is displayed for a <see cref="TilingHost"/>'s tabs or stand-alone pop-out windows.<br/>
|
|
||||||
/// Anything that you can physically see from the root <see cref="TilingPanel"/> is a <see cref="HostFrame"/>.<br/>
|
|
||||||
/// Serving a header text, pin state and button configuration properties.
|
|
||||||
/// </summary>
|
|
||||||
public abstract class HostFrame : ObservableObject
|
|
||||||
{
|
|
||||||
/// <inheritdoc cref="HeaderText"/>
|
|
||||||
private string _headerText = string.Empty;
|
|
||||||
/// <summary>
|
|
||||||
/// The title of the tab that will be displayed inside the tab button.
|
|
||||||
/// </summary>
|
|
||||||
public string HeaderText {
|
|
||||||
get => _headerText;
|
|
||||||
set => SetField(ref _headerText, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc cref="FrameButtons"/>
|
|
||||||
private FrameButtons _frameButtons = FrameButtons.Default;
|
|
||||||
/// <summary>
|
|
||||||
/// The buttons to be displayed next to the header.
|
|
||||||
/// </summary>
|
|
||||||
public FrameButtons FrameButtons {
|
|
||||||
get => _frameButtons;
|
|
||||||
set => SetField(ref _frameButtons, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc cref="IsPinned"/>
|
|
||||||
private bool _isPinned = false;
|
|
||||||
/// <summary>
|
|
||||||
/// The title of the tab that will be displayed inside the tab button.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsPinned {
|
|
||||||
get => _isPinned;
|
|
||||||
set => SetField(ref _isPinned, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual object? GetOptions() => null;
|
|
||||||
public virtual void SetOptions(object? options) { }
|
|
||||||
|
|
||||||
public virtual object? GetHelp() => null;
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace Qrakhen.TilingFrames.Models;
|
|
||||||
|
|
||||||
public abstract class ObservableObject : INotifyPropertyChanged
|
|
||||||
{
|
|
||||||
public event PropertyChangedEventHandler? PropertyChanged;
|
|
||||||
|
|
||||||
protected bool SetField<T>([NotNullIfNotNull("newValue")] ref T field, T newValue, [CallerMemberName] string? propertyName = null)
|
|
||||||
{
|
|
||||||
if (EqualityComparer<T>.Default.Equals(field, newValue))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
field = newValue;
|
|
||||||
OnPropertyChanged(propertyName);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
|
||||||
{
|
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
namespace CopaData.FileInspector.GUI.TilingPanels.Models;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Used to declare where a new <see cref="TilingHost"/> shall be placed when tiling.
|
|
||||||
/// </summary>
|
|
||||||
public enum TilingDirection
|
|
||||||
{
|
|
||||||
None = 0,
|
|
||||||
/// <summary>
|
|
||||||
/// Left side of the target host, the new child becomes alpha and the target host moves to beta.
|
|
||||||
/// </summary>
|
|
||||||
Left = 1,
|
|
||||||
/// <summary>
|
|
||||||
/// The top of the target host, the new child becomes alpha and the target host moves to beta.
|
|
||||||
/// </summary>
|
|
||||||
Top = 2,
|
|
||||||
/// <summary>
|
|
||||||
/// Right side of the target host, the new child becomes beta.
|
|
||||||
/// </summary>
|
|
||||||
Right = 3,
|
|
||||||
/// <summary>
|
|
||||||
/// The bottom of the target host, the new child becomes beta.
|
|
||||||
/// </summary>
|
|
||||||
Bottom = 4
|
|
||||||
}
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
using CopaData.FileInspector.GUI.Models;
|
|
||||||
|
|
||||||
namespace CopaData.FileInspector.GUI.TilingPanels.Models;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Base data class for content that is displayed for a <see cref="TilingHost"/>'s tabs or stand-alone pop-out windows.<br/>
|
|
||||||
/// Anything that you can physically see from the root <see cref="TilingPanel"/> is a <see cref="TilingFrame"/>.<br/>
|
|
||||||
/// Serving a header text, pin state and button configuration properties.
|
|
||||||
/// </summary>
|
|
||||||
public abstract class TilingFrame : Observable
|
|
||||||
{
|
|
||||||
/// <inheritdoc cref="HeaderText"/>
|
|
||||||
private string _headerText = string.Empty;
|
|
||||||
/// <summary>
|
|
||||||
/// The title of the tab that will be displayed inside the tab button.
|
|
||||||
/// </summary>
|
|
||||||
public string HeaderText
|
|
||||||
{
|
|
||||||
get => _headerText;
|
|
||||||
set => SetField(ref _headerText, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc cref="FrameButtons"/>
|
|
||||||
private FrameButtons _frameButtons = FrameButtons.Default;
|
|
||||||
/// <summary>
|
|
||||||
/// The buttons to be displayed next to the header.
|
|
||||||
/// </summary>
|
|
||||||
public FrameButtons FrameButtons
|
|
||||||
{
|
|
||||||
get => _frameButtons;
|
|
||||||
set => SetField(ref _frameButtons, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc cref="IsPinned"/>
|
|
||||||
private bool _isPinned = false;
|
|
||||||
/// <summary>
|
|
||||||
/// The title of the tab that will be displayed inside the tab button.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsPinned
|
|
||||||
{
|
|
||||||
get => _isPinned;
|
|
||||||
set => SetField(ref _isPinned, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc cref="Parent" />
|
|
||||||
private TilingHost? _parent;
|
|
||||||
/// <summary>
|
|
||||||
/// The parent hosting this frame.
|
|
||||||
/// </summary>
|
|
||||||
public TilingHost? Parent
|
|
||||||
{
|
|
||||||
get => _parent;
|
|
||||||
set => SetField(ref _parent, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual object? GetOptions() => null;
|
|
||||||
public virtual void SetOptions(object? options) { }
|
|
||||||
|
|
||||||
public virtual object? GetHelp() => null;
|
|
||||||
}
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
using CopaData.FileInspector.GUI.Models;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Collections.Specialized;
|
|
||||||
|
|
||||||
namespace CopaData.FileInspector.GUI.TilingPanels.Models;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Host node that by paradigm is located only at the very end of the tree's branches.<br/>
|
|
||||||
/// Hosts may contain at least one central control, or multiple tabs of controls.<br/>
|
|
||||||
/// Note that tab contents must _not_ be any tiling controls, as that would break hierarchy and branching logic.<br/>
|
|
||||||
/// Everything above a <see cref="TilingHost"/> will be a <see cref="TilingPanel"/> by definition.
|
|
||||||
/// </summary>
|
|
||||||
public class TilingHost : TilingNode
|
|
||||||
{
|
|
||||||
public ObservableCollection<TilingFrame> Frames { get; } = [];
|
|
||||||
|
|
||||||
private TilingFrame? _activeFrame;
|
|
||||||
public TilingFrame? ActiveFrame
|
|
||||||
{
|
|
||||||
get => _activeFrame;
|
|
||||||
set => SetField(ref _activeFrame, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ShowTabs => Frames.Count > 1;
|
|
||||||
|
|
||||||
public bool IsEmpty => Frames.Count == 0;
|
|
||||||
|
|
||||||
public TilingHost(params TilingFrame[] frames)
|
|
||||||
{
|
|
||||||
if (frames != null && frames.Length > 0)
|
|
||||||
{
|
|
||||||
Frames = new ObservableCollection<TilingFrame>(frames);
|
|
||||||
ActiveFrame = Frames[^1];
|
|
||||||
foreach (var frame in Frames)
|
|
||||||
{
|
|
||||||
frame.Parent = this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Frames.CollectionChanged += OnFramesItemsChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnFramesItemsChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.NewItems != null && e.NewItems.Count > 0)
|
|
||||||
{
|
|
||||||
// Make the last item active
|
|
||||||
ActiveFrame = e.NewItems[^1] as TilingFrame;
|
|
||||||
foreach (var item in e.NewItems)
|
|
||||||
{
|
|
||||||
if (item is TilingFrame frame)
|
|
||||||
{
|
|
||||||
// We're using a reference here as this is the only place that always listens to updates.
|
|
||||||
frame.Parent = this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (e.OldItems != null && e.OldItems.Count > 0)
|
|
||||||
{
|
|
||||||
if (ActiveFrame != null && !Frames.Contains(ActiveFrame))
|
|
||||||
{
|
|
||||||
// Previous active tab got removed, revert back to the most recent tab or set ActiveTab to null if tabs are empty.
|
|
||||||
if (Frames.Count > 0)
|
|
||||||
{
|
|
||||||
ActiveFrame = Frames[^1];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ActiveFrame = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Inserts <paramref name="newHost"/> into <paramref name="targetHost"/> as a new tab
|
|
||||||
/// after being dropped in the center region by the user.<br/>
|
|
||||||
/// If the previous host only had one frame inside it, it will be detached from its parent.
|
|
||||||
/// Otherwise, only the frame will be removed and inserted into <paramref name="targetHost"/>'s frames.
|
|
||||||
/// </summary>
|
|
||||||
public static void Insert(TilingPanel rootPanel, TilingHost targetHost, TilingFrame frame)
|
|
||||||
{
|
|
||||||
if (frame.Parent?.Frames.Count < 2)
|
|
||||||
{
|
|
||||||
TilingPanel.Detach(rootPanel, frame.Parent);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
frame.Parent?.Frames.Remove(frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
targetHost.Frames.Add(frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
namespace CopaData.FileInspector.GUI.TilingPanels.Models;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Declares the intent of dropping a host into another.
|
|
||||||
/// </summary>
|
|
||||||
public enum TilingMethod
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// In the case of the host being placed as a split next to the target host.
|
|
||||||
/// </summary>
|
|
||||||
Split = 0,
|
|
||||||
/// <summary>
|
|
||||||
/// In case of the new host becoming a tab of the target host, rather than splitting.
|
|
||||||
/// </summary>
|
|
||||||
Drop = 1
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
using CopaData.FileInspector.GUI.Models;
|
|
||||||
|
|
||||||
namespace CopaData.FileInspector.GUI.TilingPanels.Models;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Base node for the binary tree structure to work.<br/>
|
|
||||||
/// A tiling node is either a <see cref="TilingPanel"/>, which has a reference to two <see cref="TilingNode"/>s.<br/>
|
|
||||||
/// Those nodes may then be additional <see cref="TilingPanel"/>s, or, when at the end of the branch, a <see cref="TilingHost"/>.
|
|
||||||
/// </summary>
|
|
||||||
public abstract class TilingNode : Observable;
|
|
||||||
|
|
@ -1,259 +0,0 @@
|
||||||
using System.Windows.Controls;
|
|
||||||
|
|
||||||
namespace CopaData.FileInspector.GUI.TilingPanels.Models;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This class represents a branch in the tree structure, having two children,<br/>
|
|
||||||
/// which can be either another <see cref="TilingPanel"/> - in the case of the tree branching onwards -<br/>
|
|
||||||
/// or a <see cref="TilingHost"/>, if we're at the end of a branch (a so-called leaf node).<br/>
|
|
||||||
/// A panel contains information about how its children <see cref="Alpha"/> and <see cref="Beta"/>
|
|
||||||
/// are separated by exposing an <see cref="Orientation"/> and split ratio.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// The entire attaching and detaching logic is handled from static methods in this class for brevity.<br/>
|
|
||||||
/// Those methods are <see cref="Attach(TilingPanel, TilingHost, TilingHost, TilingDirection)"/> and <see cref="Detach(TilingPanel, TilingNode)"/>.
|
|
||||||
/// </remarks>
|
|
||||||
public class TilingPanel : TilingNode
|
|
||||||
{
|
|
||||||
/// <inheritdoc cref="SplitOrientation" />
|
|
||||||
private Orientation _splitOrientation = Orientation.Horizontal;
|
|
||||||
/// <summary>
|
|
||||||
/// The orientation of the split, so either vertical or horizontal.
|
|
||||||
/// </summary>
|
|
||||||
public Orientation SplitOrientation
|
|
||||||
{
|
|
||||||
get => _splitOrientation;
|
|
||||||
set => SetField(ref _splitOrientation, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc cref="SplitRatio" />
|
|
||||||
private double _splitRatio = 0.5;
|
|
||||||
/// <summary>
|
|
||||||
/// The width or height ratio of the split, with 0.5 being the exact middle.
|
|
||||||
/// </summary>
|
|
||||||
public double SplitRatio
|
|
||||||
{
|
|
||||||
get => _splitRatio;
|
|
||||||
set => SetField(ref _splitRatio, Math.Min(1, Math.Max(0, value)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc cref="CutoffLength" />
|
|
||||||
private double _cutoffLength = 256;
|
|
||||||
/// <summary>
|
|
||||||
/// TODO: Move this into the view/control, it really got nothing in common with the model i think.
|
|
||||||
/// The width or height of alpha in pixels, will be initialized with 50% of the panel's available width.
|
|
||||||
/// </summary>
|
|
||||||
public double CutoffLength
|
|
||||||
{
|
|
||||||
get => _cutoffLength;
|
|
||||||
set => SetField(ref _cutoffLength, Math.Min(1, Math.Max(0, value)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc cref="Alpha" />
|
|
||||||
private TilingNode _alpha;
|
|
||||||
/// <summary>
|
|
||||||
/// 'Alpha is always set' is an enforced paradigm, so it will always point to an existing node.
|
|
||||||
/// </summary>
|
|
||||||
public TilingNode Alpha
|
|
||||||
{
|
|
||||||
get => _alpha;
|
|
||||||
private set => SetField(ref _alpha, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc cref="Beta" />
|
|
||||||
private TilingNode? _beta;
|
|
||||||
/// <summary>
|
|
||||||
/// The beta node may be null, for example if a panel is not yet split.
|
|
||||||
/// </summary>
|
|
||||||
public TilingNode? Beta
|
|
||||||
{
|
|
||||||
get => _beta;
|
|
||||||
private set => SetField(ref _beta, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether this panel is split in two, so whether beta is not null.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsSplit => Beta != null;
|
|
||||||
|
|
||||||
public TilingPanel(TilingNode alpha, TilingNode? beta = null)
|
|
||||||
: base()
|
|
||||||
{
|
|
||||||
_alpha = alpha;
|
|
||||||
_beta = beta;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Traverses down the entire tree from <paramref name="rootPanel"/> until
|
|
||||||
/// <paramref name="node"/> is encountered, returning its direct parent panel.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This may not be the quickest approach, but we're looking at UI tiling panels with perhaps 20 children at most.
|
|
||||||
/// </remarks>
|
|
||||||
public static TilingPanel? GetParentPanel(TilingPanel rootPanel, TilingNode node)
|
|
||||||
{
|
|
||||||
if (rootPanel.Alpha == node || rootPanel.Beta == node)
|
|
||||||
{
|
|
||||||
return rootPanel;
|
|
||||||
}
|
|
||||||
|
|
||||||
TilingPanel? parent = null;
|
|
||||||
if (rootPanel.Alpha is TilingPanel alphaPanel)
|
|
||||||
{
|
|
||||||
parent = GetParentPanel(alphaPanel, node);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parent == null && rootPanel.Beta is TilingPanel betaPanel)
|
|
||||||
{
|
|
||||||
parent = GetParentPanel(betaPanel, node);
|
|
||||||
}
|
|
||||||
|
|
||||||
return parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// <para>
|
|
||||||
/// Attaches <paramref name="newFrame"/> to <paramref name="targetHost"/>'s parent as a new <see cref="TilingHost"/>.<br/>
|
|
||||||
/// The parent will be looked up by traversing the tree from <paramref name="rootPanel"/>.<br/>
|
|
||||||
/// The following ordered rules apply when attaching:
|
|
||||||
/// </para>
|
|
||||||
/// <para>
|
|
||||||
/// 1. If <paramref name="targetHost"/>'s parent has an open beta slot, <paramref name="newFrame"/> will become the beta node, wrappped in a new host.<br/>
|
|
||||||
/// 2. If one of the <paramref name="targetHost"/> parent's child nodes is an <see cref="EmptyHost"/>, <paramref name="newFrame"/> will be inserted<br/>
|
|
||||||
/// 3. If none of the above apply, the <paramref name="targetHost"/> parent's beta node will be split into a new tiling panel.
|
|
||||||
/// </para>
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// All of these actions will automatically split the parent panel.<br/>
|
|
||||||
/// If <paramref name="newFrame"/> had a parent assigned previously, that parent will be detached from the branch first.<br/>
|
|
||||||
/// If more frames are present in <paramref name="newFrame"/>'s parent, only that frame will be removed from it,<br/>
|
|
||||||
/// and a new <see cref="TilingHost"/> is created, containing only that frame.<br/>
|
|
||||||
/// Note that all passed elements have to be children to the same <see cref="TilingNode"/> tree.
|
|
||||||
/// In order to attach a <see cref="TilingFrame"/> to a foreign tree, <see cref="Detach"/> it first and insert it to the foreign host.
|
|
||||||
/// </remarks>
|
|
||||||
/// <param name="rootPanel">The root to start branch traversal from.</param>
|
|
||||||
/// <param name="targetHost">The host to be split.</param>
|
|
||||||
/// <param name="newFrame">
|
|
||||||
/// The frame to be attached inside a new host to <paramref name="targetHost"/>'s parent.
|
|
||||||
/// If set to null, an <see cref="EmptyHost"/> will be created in its place.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="direction">The direction at which to drop <paramref name="newHost"/> relative to <paramref name="targetHost"/>.</param>
|
|
||||||
/// <returns>The <see cref="TilingPanel"/> that now contains <paramref name="newHost"/> and <paramref name="targetHost"/>.</returns>
|
|
||||||
public static TilingPanel Attach(TilingPanel rootPanel,
|
|
||||||
TilingHost targetHost,
|
|
||||||
TilingFrame? newFrame,
|
|
||||||
TilingDirection direction)
|
|
||||||
{
|
|
||||||
// This method looks way more complicated than it actually is,
|
|
||||||
// all I wanted to achieve is one centralized place where branching and child assignments happen,
|
|
||||||
// as tree structures are notoriously hard to navigate and handle when putting references everywhere.
|
|
||||||
// It really boils down to a simple three-step process:
|
|
||||||
// 1. Get or create correct host to attach,
|
|
||||||
// 2. Find out where to place the new host (Alpha or Beta, depending on tiling direction),
|
|
||||||
// 3. Create branch to be attached, or attach to free slot of parent.
|
|
||||||
// Todo: This note is in place for the reviewer to have a bit more context. Remove when done.
|
|
||||||
|
|
||||||
TilingPanel? parent = GetParentPanel(rootPanel, targetHost);
|
|
||||||
if (parent == null)
|
|
||||||
{
|
|
||||||
throw new NullReferenceException($"Detached target: Could not find a parent for {targetHost} in any of {rootPanel}'s child nodes.");
|
|
||||||
}
|
|
||||||
|
|
||||||
TilingHost? newHost;
|
|
||||||
if (newFrame == null)
|
|
||||||
{
|
|
||||||
// Substitute for the split.
|
|
||||||
newHost = new EmptyHost();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (newFrame.Parent?.Frames.Count < 2)
|
|
||||||
{
|
|
||||||
// Detach newFrame's host from its parent.
|
|
||||||
Detach(rootPanel, newFrame.Parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
newHost = new TilingHost([ newFrame ]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// I tried formulating an explanation for this step but I simply can't.
|
|
||||||
// "Left or Top go alpha, Bottom or Right go beta."
|
|
||||||
bool newHostIsAlpha = direction is TilingDirection.Left or TilingDirection.Top;
|
|
||||||
|
|
||||||
// Check whether there's a free beta slot in the parent to use
|
|
||||||
if (parent.Beta == null || parent.Beta is EmptyHost emptyHost)
|
|
||||||
{
|
|
||||||
if (newHostIsAlpha)
|
|
||||||
{
|
|
||||||
// Open beta slot but newHost wants to be in alpha slot, simply swap and attach.
|
|
||||||
parent.Beta = parent.Alpha;
|
|
||||||
parent.Alpha = newHost;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Open beta slot means we just put newHost there, no branching needed.
|
|
||||||
parent.Beta = newHost;
|
|
||||||
}
|
|
||||||
return parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new branch to hold both target- and newHost.
|
|
||||||
TilingPanel branch;
|
|
||||||
if (direction is TilingDirection.Left or TilingDirection.Top)
|
|
||||||
{
|
|
||||||
branch = new TilingPanel(newHost, targetHost);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
branch = new TilingPanel(targetHost, newHost);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assign new branch to correct chíld node.
|
|
||||||
if (parent.Alpha == targetHost)
|
|
||||||
{
|
|
||||||
parent.Alpha = branch;
|
|
||||||
}
|
|
||||||
else if (parent.Beta == targetHost)
|
|
||||||
{
|
|
||||||
parent.Beta = branch;
|
|
||||||
}
|
|
||||||
|
|
||||||
return branch;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Detaches <paramref name="child"/> from its parent node, which will be looked up by traversing the <paramref name="rootPanel"/>.<br/>
|
|
||||||
/// If the child happened to be the alpha value, the beta value will be moved into the alpha slot to ensure alpha always having a value.<br/>
|
|
||||||
/// If the child's parent node ends up having no children left, which would violate the 'alpha is always set' rule,<br/>
|
|
||||||
/// that node itself will also be detached in order to prevent trailing zombie nodes.
|
|
||||||
/// </summary>
|
|
||||||
public static void Detach(TilingPanel rootPanel, TilingNode child)
|
|
||||||
{
|
|
||||||
TilingPanel? parent = GetParentPanel(rootPanel, child);
|
|
||||||
if (parent == null)
|
|
||||||
{
|
|
||||||
return; // Already an orphan.
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parent.Alpha == child)
|
|
||||||
{
|
|
||||||
if (parent.Beta != null)
|
|
||||||
{
|
|
||||||
// Move beta over to alpha if beta has a value.
|
|
||||||
parent.Alpha = parent.Beta;
|
|
||||||
parent.Beta = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Both branches have detached, which means we'll detach the parent itself
|
|
||||||
// in order to prevent empty, trailing panels with no children.
|
|
||||||
Detach(rootPanel, child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Child was the beta node, so we simply null it.
|
|
||||||
parent.Beta = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# TilingPanels
|
# TilingFrames
|
||||||
## Tiling window manager control library for WPF
|
## Tiling (Docking) window manager library for WPF
|
||||||
### CopaData.Ui.Wpf.TilingPanels
|
### Qrakhen.TilingFramess
|
||||||
|
|
||||||
Cool Library to have tiling panels.
|
Cool Library to have tiling panels.
|
||||||
Is that not cool?
|
Is that not cool?
|
||||||
|
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
|
||||||
|
|
||||||
<Color x:Key="C_Back_Primary">#242527</Color>
|
|
||||||
<Color x:Key="C_Back_Primary_Tint">#28292c</Color>
|
|
||||||
<Color x:Key="C_Back_Secondary">#161718</Color>
|
|
||||||
<Color x:Key="C_Back_Secondary_Tint">#1c1d1f</Color>
|
|
||||||
<Color x:Key="C_Back_Tertiary">#323336</Color>
|
|
||||||
<Color x:Key="C_Back_Tertiary_Tint">#37393c</Color>
|
|
||||||
<Color x:Key="C_Border_Primary">#727478</Color>
|
|
||||||
<Color x:Key="C_Border_Primary_Tint">#84868b</Color>
|
|
||||||
<Color x:Key="C_Text_Primary">#fcfeff</Color>
|
|
||||||
<Color x:Key="C_Text_Grey">#97999c</Color>
|
|
||||||
<Color x:Key="C_Text_Accent">#32ce96</Color>
|
|
||||||
<Color x:Key="C_Text_Accent_Tint">#48faaf</Color>
|
|
||||||
<Color x:Key="C_Text_Accent_Dark">#249672</Color>
|
|
||||||
<Color x:Key="C_Text_Accent_Dark">#249672</Color>
|
|
||||||
<Color x:Key="C_Text_Error">#ef4232</Color>
|
|
||||||
|
|
||||||
<SolidColorBrush x:Key="B_Back_Primary" Color="{StaticResource C_Back_Primary}" />
|
|
||||||
<SolidColorBrush x:Key="B_Back_Primary_Tint" Color="{StaticResource C_Back_Primary_Tint}" />
|
|
||||||
<SolidColorBrush x:Key="B_Back_Secondary" Color="{StaticResource C_Back_Secondary}" />
|
|
||||||
<SolidColorBrush x:Key="B_Back_Secondary_Tint" Color="{StaticResource C_Back_Secondary_Tint}" />
|
|
||||||
<SolidColorBrush x:Key="B_Back_Tertiary" Color="{StaticResource C_Back_Tertiary}" />
|
|
||||||
<SolidColorBrush x:Key="B_Back_Tertiary_Tint" Color="{StaticResource C_Back_Tertiary_Tint}" />
|
|
||||||
<SolidColorBrush x:Key="B_Border_Primary" Color="{StaticResource C_Border_Primary}" />
|
|
||||||
<SolidColorBrush x:Key="B_Border_Primary_Tint" Color="{StaticResource C_Border_Primary_Tint}" />
|
|
||||||
<SolidColorBrush x:Key="B_Text_Primary" Color="{StaticResource C_Text_Primary}" />
|
|
||||||
<SolidColorBrush x:Key="B_Text_Grey" Color="{StaticResource C_Text_Grey}" />
|
|
||||||
<SolidColorBrush x:Key="B_Text_Accent" Color="{StaticResource C_Text_Accent}" />
|
|
||||||
<SolidColorBrush x:Key="B_Text_Accent_Tint" Color="{StaticResource C_Text_Accent_Tint}" />
|
|
||||||
<SolidColorBrush x:Key="B_Text_Accent_Dark" Color="{StaticResource C_Text_Accent_Dark}" />
|
|
||||||
<SolidColorBrush x:Key="B_Text_Accent_Dark" Color="{StaticResource C_Text_Accent_Dark}" />
|
|
||||||
<SolidColorBrush x:Key="B_Text_Error" Color="{StaticResource C_Text_Error}" />
|
|
||||||
|
|
||||||
</ResourceDictionary>
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:converters="clr-namespace:CopaData.FileInspector.GUI.TilingPanels.Converters"
|
|
||||||
xmlns:local="clr-namespace:CopaData.FileInspector.GUI.TilingPanels.Controls.DropArea"
|
|
||||||
xmlns:dnd="clr-namespace:CopaData.FileInspector.GUI.TilingPanels.Controls.DragAndDrop"
|
|
||||||
xmlns:models="clr-namespace:CopaData.FileInspector.GUI.TilingPanels.Models">
|
|
||||||
|
|
||||||
<Style x:Key="DropAreaButton" TargetType="{x:Type Button}">
|
|
||||||
<Setter Property="BorderThickness" Value="0" />
|
|
||||||
<Setter Property="AllowDrop" Value="True" />
|
|
||||||
<Setter Property="Background" Value="#10affefe" />
|
|
||||||
<Setter Property="Margin" Value="3" />
|
|
||||||
<Setter Property="Padding" Value="0" />
|
|
||||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
|
||||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
|
||||||
<Style.Triggers>
|
|
||||||
<Trigger Property="IsMouseOver" Value="True">
|
|
||||||
<Trigger.Setters>
|
|
||||||
<Setter Property="Background" Value="#40affefe" />
|
|
||||||
</Trigger.Setters>
|
|
||||||
</Trigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<Style TargetType="{x:Type local:DropArea}">
|
|
||||||
<Setter Property="Template">
|
|
||||||
<Setter.Value>
|
|
||||||
<ControlTemplate TargetType="{x:Type local:DropArea}">
|
|
||||||
<Grid>
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="48" />
|
|
||||||
<ColumnDefinition Width="72" />
|
|
||||||
<ColumnDefinition Width="48" />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="48" />
|
|
||||||
<RowDefinition Height="72" />
|
|
||||||
<RowDefinition Height="48" />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
|
|
||||||
<Button x:Name="CenterDrop"
|
|
||||||
dnd:DragAndDropControl.IsDropZone="True"
|
|
||||||
dnd:DragAndDropControl.Handler="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}}"
|
|
||||||
Style="{StaticResource DropAreaButton}"
|
|
||||||
Grid.Row="1"
|
|
||||||
Grid.Column="1" />
|
|
||||||
|
|
||||||
<Button x:Name="LeftDrop"
|
|
||||||
Style="{StaticResource DropAreaButton}"
|
|
||||||
Grid.Row="1"
|
|
||||||
Grid.Column="0" />
|
|
||||||
|
|
||||||
<Button x:Name="TopDrop"
|
|
||||||
Style="{StaticResource DropAreaButton}"
|
|
||||||
Grid.Row="0"
|
|
||||||
Grid.Column="1" />
|
|
||||||
|
|
||||||
<Button x:Name="RightDrop"
|
|
||||||
Style="{StaticResource DropAreaButton}"
|
|
||||||
Grid.Row="1"
|
|
||||||
Grid.Column="2" />
|
|
||||||
|
|
||||||
<Button x:Name="BottomDrop"
|
|
||||||
Style="{StaticResource DropAreaButton}"
|
|
||||||
Grid.Row="2"
|
|
||||||
Grid.Column="1" />
|
|
||||||
|
|
||||||
<Border BorderBrush="#affefe"
|
|
||||||
BorderThickness="1.28"
|
|
||||||
Grid.Column="1"
|
|
||||||
Grid.RowSpan="3" />
|
|
||||||
|
|
||||||
<Border BorderBrush="#affefe"
|
|
||||||
BorderThickness="1.28"
|
|
||||||
Grid.ColumnSpan="3"
|
|
||||||
Grid.Row="1" />
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
</ControlTemplate>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
</ResourceDictionary>
|
|
||||||
|
|
@ -1,129 +0,0 @@
|
||||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:converters="clr-namespace:CopaData.FileInspector.GUI.TilingPanels.Converters"
|
|
||||||
xmlns:local="clr-namespace:CopaData.FileInspector.GUI.TilingPanels.Controls"
|
|
||||||
xmlns:models="clr-namespace:CopaData.FileInspector.GUI.TilingPanels.Models"
|
|
||||||
xmlns:dnd="clr-namespace:CopaData.FileInspector.GUI.TilingPanels.Controls.DragAndDrop"
|
|
||||||
xmlns:droparea="clr-namespace:CopaData.FileInspector.GUI.TilingPanels.Controls.DropArea">
|
|
||||||
|
|
||||||
|
|
||||||
<Style TargetType="{x:Type local:TilingHostControl}">
|
|
||||||
<Setter Property="Template">
|
|
||||||
<Setter.Value>
|
|
||||||
<ControlTemplate TargetType="{x:Type local:TilingHostControl}">
|
|
||||||
<Border Padding="0"
|
|
||||||
CornerRadius="4"
|
|
||||||
BorderBrush="{DynamicResource Brush_Border_Primary}"
|
|
||||||
BorderThickness="1"
|
|
||||||
dnd:DragAndDropControl.Handler="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}}"
|
|
||||||
dnd:DragAndDropControl.IsDropZone="True">
|
|
||||||
<TabControl ItemsSource="{Binding Frames}"
|
|
||||||
SelectedItem="{Binding ActiveFrame, Mode=TwoWay}">
|
|
||||||
<TabControl.ItemTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<Grid Background="Transparent"
|
|
||||||
dnd:DragAndDropControl.Handler="{Binding RelativeSource={
|
|
||||||
RelativeSource Mode=FindAncestor,
|
|
||||||
AncestorType={x:Type dnd:DragAndDropControl}}}"
|
|
||||||
dnd:DragAndDropControl.IsDraggable="True">
|
|
||||||
<TextBlock Text="{Binding HeaderText}" />
|
|
||||||
</Grid>
|
|
||||||
</DataTemplate>
|
|
||||||
</TabControl.ItemTemplate>
|
|
||||||
|
|
||||||
<TabControl.ContentTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<ContentPresenter Content="{Binding}" />
|
|
||||||
</DataTemplate>
|
|
||||||
</TabControl.ContentTemplate>
|
|
||||||
|
|
||||||
<TabControl.Style>
|
|
||||||
<Style TargetType="TabControl">
|
|
||||||
<Setter Property="Template">
|
|
||||||
<Setter.Value>
|
|
||||||
<ControlTemplate TargetType="TabControl">
|
|
||||||
<Border CornerRadius="4">
|
|
||||||
<Grid KeyboardNavigation.TabNavigation="Local"
|
|
||||||
Background="{DynamicResource Brush_Background_Primary}">
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="*" />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
|
|
||||||
<Border CornerRadius="4"
|
|
||||||
Background="{DynamicResource Brush_Background_Secondary}">
|
|
||||||
<TabPanel Grid.Row="0"
|
|
||||||
IsItemsHost="True"
|
|
||||||
KeyboardNavigation.TabIndex="1"
|
|
||||||
Margin="0,0,0,-1" />
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
<Border x:Name="ActiveContent"
|
|
||||||
Grid.Row="1"
|
|
||||||
BorderThickness="0"
|
|
||||||
AllowDrop="True"
|
|
||||||
Padding="4"
|
|
||||||
BorderBrush="{DynamicResource Brush_Border_Primary}"
|
|
||||||
Background="Transparent">
|
|
||||||
<Grid>
|
|
||||||
<ContentPresenter ContentSource="SelectedContent"
|
|
||||||
KeyboardNavigation.TabNavigation="Cycle"
|
|
||||||
KeyboardNavigation.DirectionalNavigation="Contained" />
|
|
||||||
<droparea:DropArea />
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
|
||||||
</ControlTemplate>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
</Style>
|
|
||||||
</TabControl.Style>
|
|
||||||
</TabControl>
|
|
||||||
</Border>
|
|
||||||
</ControlTemplate>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<DataTemplate DataType="{x:Type models:TilingHost}">
|
|
||||||
<local:TilingHostControl />
|
|
||||||
</DataTemplate>
|
|
||||||
|
|
||||||
<Style TargetType="TabItem">
|
|
||||||
<Setter Property="Template">
|
|
||||||
<Setter.Value>
|
|
||||||
<ControlTemplate TargetType="TabItem">
|
|
||||||
<Border x:Name="TemplateBorder"
|
|
||||||
Background="Transparent"
|
|
||||||
BorderBrush="Transparent"
|
|
||||||
Cursor="Hand"
|
|
||||||
Margin="0"
|
|
||||||
Padding="8,4"
|
|
||||||
MinWidth="48"
|
|
||||||
CornerRadius="4"
|
|
||||||
BorderThickness="1">
|
|
||||||
|
|
||||||
<ContentPresenter x:Name="ContentSite"
|
|
||||||
ContentSource="Header"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
RecognizesAccessKey="True" />
|
|
||||||
</Border>
|
|
||||||
<ControlTemplate.Triggers>
|
|
||||||
<Trigger Property="IsSelected" Value="True">
|
|
||||||
<Setter TargetName="ContentSite" Property="TextElement.Foreground" Value="{DynamicResource Brush_Text_Primary}" />
|
|
||||||
<Setter TargetName="TemplateBorder" Property="BorderBrush" Value="{DynamicResource Brush_Accent_Primary}" />
|
|
||||||
<Setter TargetName="TemplateBorder" Property="Background" Value="Transparent" />
|
|
||||||
</Trigger>
|
|
||||||
<Trigger Property="IsMouseOver" Value="True">
|
|
||||||
<Setter TargetName="TemplateBorder" Property="Background" Value="{DynamicResource Brush_Background_Primary_Tint}" />
|
|
||||||
</Trigger>
|
|
||||||
</ControlTemplate.Triggers>
|
|
||||||
</ControlTemplate>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
<Setter Property="Foreground" Value="{DynamicResource Brush_Text_Grey}" />
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
</ResourceDictionary>
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:converters="clr-namespace:CopaData.FileInspector.GUI.TilingPanels.Converters"
|
|
||||||
xmlns:local="clr-namespace:CopaData.FileInspector.GUI.TilingPanels.Controls"
|
|
||||||
xmlns:models="clr-namespace:CopaData.FileInspector.GUI.TilingPanels.Models">
|
|
||||||
|
|
||||||
<!-- NO TEMPLATE OVERRIDE (FALLBACK) -->
|
|
||||||
<DataTemplate DataType="{x:Type models:TilingFrame}">
|
|
||||||
<Border BorderBrush="{DynamicResource Brush_Text_Error}">
|
|
||||||
<StackPanel>
|
|
||||||
<TextBlock FontSize="14"
|
|
||||||
Foreground="{DynamicResource Brush_Text_Error}">
|
|
||||||
No Template override for the provided HostFrame model found.<LineBreak/>
|
|
||||||
You need to override the data template of your host frame like so:
|
|
||||||
</TextBlock>
|
|
||||||
<TextBlock xml:space="preserve"
|
|
||||||
FontSize="13"
|
|
||||||
FontFamily="Consolas"
|
|
||||||
Foreground="{DynamicResource Brush_Text_Primary}">
|
|
||||||
<DataTemplate DataType="{x:Type YourFrameModel}">
|
|
||||||
<TextBlock Text="This is my model view! :)" />
|
|
||||||
</DataTemplate>
|
|
||||||
</TextBlock>
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
|
||||||
</DataTemplate>
|
|
||||||
|
|
||||||
<!-- EMPTY TEMPLATE -->
|
|
||||||
<DataTemplate DataType="{x:Type models:EmptyFrame}">
|
|
||||||
<Border Padding="8"
|
|
||||||
MaxHeight="64"
|
|
||||||
MaxWidth="256"
|
|
||||||
Margin="24"
|
|
||||||
CornerRadius="4"
|
|
||||||
Background="#10fefefe"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
VerticalAlignment="Stretch">
|
|
||||||
<TextBlock FontSize="14"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
Foreground="{DynamicResource Brush_Text_Primary}">
|
|
||||||
Drag or Drop something here :)
|
|
||||||
</TextBlock>
|
|
||||||
</Border>
|
|
||||||
</DataTemplate>
|
|
||||||
|
|
||||||
</ResourceDictionary>
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:converters="clr-namespace:CopaData.FileInspector.GUI.TilingPanels.Converters"
|
|
||||||
xmlns:local="clr-namespace:CopaData.FileInspector.GUI.TilingPanels.Controls"
|
|
||||||
xmlns:models="clr-namespace:CopaData.FileInspector.GUI.TilingPanels.Models">
|
|
||||||
|
|
||||||
<converters:AlphaRatioConverter x:Key="AlphaRatioConverter" />
|
|
||||||
<converters:BetaRatioConverter x:Key="BetaRatioConverter" />
|
|
||||||
|
|
||||||
<Style TargetType="{x:Type local:TilingPanelControl}">
|
|
||||||
<Style.Setters>
|
|
||||||
<Setter Property="SplitterWidth" Value="{Binding SplitterWidth,
|
|
||||||
RelativeSource={RelativeSource
|
|
||||||
AncestorType={x:Type local:TilingRoot},
|
|
||||||
Mode=FindAncestor}}" />
|
|
||||||
</Style.Setters>
|
|
||||||
<Style.Triggers>
|
|
||||||
|
|
||||||
<!-- VERTICAL LAYOUT -->
|
|
||||||
<DataTrigger Binding="{Binding SplitOrientation}" Value="{x:Static Orientation.Vertical}">
|
|
||||||
<Setter Property="Template">
|
|
||||||
<Setter.Value>
|
|
||||||
<ControlTemplate TargetType="{x:Type local:TilingPanelControl}">
|
|
||||||
<Border CornerRadius="4">
|
|
||||||
<Grid>
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="*" />
|
|
||||||
<RowDefinition Height="{TemplateBinding SplitterWidth}" />
|
|
||||||
<RowDefinition Height="*" />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
|
|
||||||
<ContentPresenter Grid.Row="0"
|
|
||||||
Content="{Binding Alpha}" />
|
|
||||||
|
|
||||||
<GridSplitter Grid.Row="1"
|
|
||||||
Height="{TemplateBinding SplitterWidth}"
|
|
||||||
Background="Transparent"
|
|
||||||
Margin="0"
|
|
||||||
ResizeBehavior="PreviousAndNext"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
HorizontalAlignment="Stretch" />
|
|
||||||
|
|
||||||
<ContentPresenter Grid.Row="2"
|
|
||||||
Content="{Binding Beta}" />
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
|
||||||
</ControlTemplate>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
</DataTrigger>
|
|
||||||
|
|
||||||
<!-- HORIZONTAL LAYOUT -->
|
|
||||||
<DataTrigger Binding="{Binding SplitOrientation}" Value="{x:Static Orientation.Horizontal}">
|
|
||||||
<Setter Property="Template">
|
|
||||||
<Setter.Value>
|
|
||||||
<ControlTemplate TargetType="{x:Type local:TilingPanelControl}">
|
|
||||||
<Border CornerRadius="4">
|
|
||||||
<Grid>
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="*" />
|
|
||||||
<ColumnDefinition Width="{TemplateBinding SplitterWidth}" />
|
|
||||||
<ColumnDefinition Width="*" />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
|
|
||||||
<ContentPresenter Grid.Column="0"
|
|
||||||
Content="{Binding Alpha}" />
|
|
||||||
|
|
||||||
<GridSplitter Grid.Column="1"
|
|
||||||
Width="{TemplateBinding SplitterWidth}"
|
|
||||||
Background="Transparent"
|
|
||||||
Margin="0"
|
|
||||||
ResizeBehavior="PreviousAndNext"
|
|
||||||
VerticalAlignment="Stretch"
|
|
||||||
HorizontalAlignment="Center" />
|
|
||||||
|
|
||||||
<ContentPresenter Grid.Column="2"
|
|
||||||
Content="{Binding Beta}" />
|
|
||||||
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
|
||||||
</ControlTemplate>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
</DataTrigger>
|
|
||||||
|
|
||||||
<!-- OVERRIDE FOR NO SPLIT -->
|
|
||||||
<DataTrigger Binding="{Binding Beta}" Value="{x:Null}">
|
|
||||||
<Setter Property="Template">
|
|
||||||
<Setter.Value>
|
|
||||||
<ControlTemplate TargetType="{x:Type local:TilingPanelControl}">
|
|
||||||
<Border CornerRadius="4">
|
|
||||||
<Grid>
|
|
||||||
<ContentPresenter Content="{Binding Alpha}" />
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
|
||||||
</ControlTemplate>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<DataTemplate DataType="{x:Type models:TilingPanel}">
|
|
||||||
<local:TilingPanelControl />
|
|
||||||
</DataTemplate>
|
|
||||||
|
|
||||||
</ResourceDictionary>
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:converters="clr-namespace:CopaData.FileInspector.GUI.TilingPanels.Converters"
|
|
||||||
xmlns:local="clr-namespace:CopaData.FileInspector.GUI.TilingPanels.Controls"
|
|
||||||
xmlns:models="clr-namespace:CopaData.FileInspector.GUI.TilingPanels.Models">
|
|
||||||
|
|
||||||
<Style TargetType="{x:Type local:TilingRoot}">
|
|
||||||
<Setter Property="Template">
|
|
||||||
<Setter.Value>
|
|
||||||
<ControlTemplate TargetType="{x:Type local:TilingRoot}">
|
|
||||||
<Border Padding="{TemplateBinding Padding}"
|
|
||||||
Margin="{TemplateBinding Margin}">
|
|
||||||
<local:TilingPanelControl DataContext="{Binding}" />
|
|
||||||
</Border>
|
|
||||||
</ControlTemplate>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<DataTemplate DataType="{x:Type local:TilingRoot}">
|
|
||||||
<local:TilingRoot />
|
|
||||||
</DataTemplate>
|
|
||||||
|
|
||||||
</ResourceDictionary>
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
<ResourceDictionary
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:local="clr-namespace:Qrakhen.TilingFrames">
|
|
||||||
<Style TargetType="{x:Type local:CustomControl1}">
|
|
||||||
<Setter Property="Template">
|
|
||||||
<Setter.Value>
|
|
||||||
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
|
|
||||||
<Border Background="{TemplateBinding Background}"
|
|
||||||
BorderBrush="{TemplateBinding BorderBrush}"
|
|
||||||
BorderThickness="{TemplateBinding BorderThickness}">
|
|
||||||
</Border>
|
|
||||||
</ControlTemplate>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
</Style>
|
|
||||||
</ResourceDictionary>
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
|
||||||
<ResourceDictionary.MergedDictionaries>
|
|
||||||
<ResourceDictionary Source="./Controls/DropArea.xaml" />
|
|
||||||
<ResourceDictionary Source="./Controls/TilingRoot.xaml" />
|
|
||||||
<ResourceDictionary Source="./Controls/Panel.xaml" />
|
|
||||||
<ResourceDictionary Source="./Controls/Host.xaml" />
|
|
||||||
<ResourceDictionary Source="./Controls/HostFrame.xaml" />
|
|
||||||
</ResourceDictionary.MergedDictionaries>
|
|
||||||
</ResourceDictionary>
|
|
||||||
Loading…
Reference in New Issue