committed by
GitHub
168 changed files with 3523 additions and 2013 deletions
@ -0,0 +1,12 @@ |
|||||
|
|
||||
|
@protocol IRenderTarget |
||||
|
-(void) setNewLayer: (CALayer*) layer; |
||||
|
-(HRESULT) setSwFrame: (AvnFramebuffer*) fb; |
||||
|
-(void) resize: (AvnPixelSize) size withScale: (float) scale; |
||||
|
-(AvnPixelSize) pixelSize; |
||||
|
-(IAvnGlSurfaceRenderTarget*) createSurfaceRenderTarget; |
||||
|
@end |
||||
|
|
||||
|
@interface IOSurfaceRenderTarget : NSObject<IRenderTarget> |
||||
|
-(IOSurfaceRenderTarget*) initWithOpenGlContext: (IAvnGlContext*) context; |
||||
|
@end |
||||
@ -0,0 +1,166 @@ |
|||||
|
#include "common.h" |
||||
|
#include <dlfcn.h> |
||||
|
|
||||
|
static CGLContextObj CreateCglContext(CGLContextObj share) |
||||
|
{ |
||||
|
int attributes[] = { |
||||
|
kCGLPFAAccelerated, |
||||
|
kCGLPFAOpenGLProfile, (CGLPixelFormatAttribute)kCGLOGLPVersion_3_2_Core, |
||||
|
kCGLPFADepthSize, 8, |
||||
|
kCGLPFAStencilSize, 8, |
||||
|
kCGLPFAColorSize, 32, |
||||
|
0 |
||||
|
}; |
||||
|
|
||||
|
CGLPixelFormatObj pix; |
||||
|
CGLError errorCode; |
||||
|
GLint num; // stores the number of possible pixel formats |
||||
|
errorCode = CGLChoosePixelFormat( (CGLPixelFormatAttribute*)attributes, &pix, &num ); |
||||
|
if(errorCode != 0) |
||||
|
return nil; |
||||
|
CGLContextObj ctx = nil; |
||||
|
errorCode = CGLCreateContext(pix, share, &ctx ); |
||||
|
CGLDestroyPixelFormat( pix ); |
||||
|
if(errorCode != 0) |
||||
|
return nil; |
||||
|
return ctx; |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
|
||||
|
class AvnGlContext : public virtual ComSingleObject<IAvnGlContext, &IID_IAvnGlContext> |
||||
|
{ |
||||
|
// Debug |
||||
|
int _usageCount = 0; |
||||
|
public: |
||||
|
CGLContextObj Context; |
||||
|
int SampleCount = 0, StencilBits = 0; |
||||
|
FORWARD_IUNKNOWN() |
||||
|
|
||||
|
class SavedGlContext : public virtual ComUnknownObject |
||||
|
{ |
||||
|
CGLContextObj _savedContext; |
||||
|
ComPtr<AvnGlContext> _parent; |
||||
|
public: |
||||
|
SavedGlContext(CGLContextObj saved, AvnGlContext* parent) |
||||
|
{ |
||||
|
_savedContext = saved; |
||||
|
_parent = parent; |
||||
|
_parent->_usageCount++; |
||||
|
} |
||||
|
|
||||
|
~SavedGlContext() |
||||
|
{ |
||||
|
if(_parent->Context == CGLGetCurrentContext()) |
||||
|
CGLSetCurrentContext(_savedContext); |
||||
|
_parent->_usageCount--; |
||||
|
CGLUnlockContext(_parent->Context); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
AvnGlContext(CGLContextObj context) |
||||
|
{ |
||||
|
Context = context; |
||||
|
CGLPixelFormatObj fmt = CGLGetPixelFormat(context); |
||||
|
CGLDescribePixelFormat(fmt, 0, kCGLPFASamples, &SampleCount); |
||||
|
CGLDescribePixelFormat(fmt, 0, kCGLPFAStencilSize, &StencilBits); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
virtual HRESULT LegacyMakeCurrent() override |
||||
|
{ |
||||
|
if(CGLSetCurrentContext(Context) != 0) |
||||
|
return E_FAIL; |
||||
|
return S_OK; |
||||
|
} |
||||
|
|
||||
|
virtual HRESULT MakeCurrent(IUnknown** ppv) override |
||||
|
{ |
||||
|
CGLContextObj saved = CGLGetCurrentContext(); |
||||
|
CGLLockContext(Context); |
||||
|
if(CGLSetCurrentContext(Context) != 0) |
||||
|
{ |
||||
|
CGLUnlockContext(Context); |
||||
|
return E_FAIL; |
||||
|
} |
||||
|
*ppv = new SavedGlContext(saved, this); |
||||
|
|
||||
|
return S_OK; |
||||
|
} |
||||
|
|
||||
|
virtual int GetSampleCount() override |
||||
|
{ |
||||
|
return SampleCount; |
||||
|
} |
||||
|
|
||||
|
virtual int GetStencilSize() override |
||||
|
{ |
||||
|
return StencilBits; |
||||
|
} |
||||
|
|
||||
|
virtual void* GetNativeHandle() override |
||||
|
{ |
||||
|
return Context; |
||||
|
} |
||||
|
|
||||
|
~AvnGlContext() |
||||
|
{ |
||||
|
CGLReleaseContext(Context); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
class AvnGlDisplay : public virtual ComSingleObject<IAvnGlDisplay, &IID_IAvnGlDisplay> |
||||
|
{ |
||||
|
void* _libgl; |
||||
|
|
||||
|
public: |
||||
|
FORWARD_IUNKNOWN() |
||||
|
|
||||
|
AvnGlDisplay() |
||||
|
{ |
||||
|
_libgl = dlopen("/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib", RTLD_LAZY); |
||||
|
} |
||||
|
|
||||
|
virtual void* GetProcAddress(char* proc) override |
||||
|
{ |
||||
|
return dlsym(_libgl, proc); |
||||
|
} |
||||
|
|
||||
|
virtual HRESULT CreateContext(IAvnGlContext* share, IAvnGlContext**ppv) override |
||||
|
{ |
||||
|
CGLContextObj shareContext = nil; |
||||
|
if(share != nil) |
||||
|
{ |
||||
|
AvnGlContext* shareCtx = dynamic_cast<AvnGlContext*>(share); |
||||
|
if(shareCtx != nil) |
||||
|
shareContext = shareCtx->Context; |
||||
|
} |
||||
|
CGLContextObj ctx = ::CreateCglContext(shareContext); |
||||
|
if(ctx == nil) |
||||
|
return E_FAIL; |
||||
|
*ppv = new AvnGlContext(ctx); |
||||
|
return S_OK; |
||||
|
} |
||||
|
|
||||
|
virtual HRESULT WrapContext(void* native, IAvnGlContext**ppv) override |
||||
|
{ |
||||
|
if(native == nil) |
||||
|
return E_INVALIDARG; |
||||
|
*ppv = new AvnGlContext((CGLContextObj) native); |
||||
|
return S_OK; |
||||
|
} |
||||
|
|
||||
|
virtual void LegacyClearCurrentContext() override |
||||
|
{ |
||||
|
CGLSetCurrentContext(nil); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
static IAvnGlDisplay* GlDisplay = new AvnGlDisplay(); |
||||
|
|
||||
|
|
||||
|
extern IAvnGlDisplay* GetGlDisplay() |
||||
|
{ |
||||
|
return GlDisplay; |
||||
|
}; |
||||
|
|
||||
@ -1,261 +0,0 @@ |
|||||
#include "common.h" |
|
||||
#include <OpenGL/gl.h> |
|
||||
#include <dlfcn.h> |
|
||||
#include "window.h" |
|
||||
|
|
||||
template <typename T, size_t N> char (&ArrayCounter(T (&a)[N]))[N]; |
|
||||
#define ARRAY_COUNT(a) (sizeof(ArrayCounter(a))) |
|
||||
|
|
||||
NSOpenGLPixelFormat* CreateFormat() |
|
||||
{ |
|
||||
NSOpenGLPixelFormatAttribute attribs[] = |
|
||||
{ |
|
||||
NSOpenGLPFADoubleBuffer, |
|
||||
NSOpenGLPFAColorSize, 32, |
|
||||
NSOpenGLPFAStencilSize, 8, |
|
||||
NSOpenGLPFADepthSize, 8, |
|
||||
0 |
|
||||
}; |
|
||||
return [[NSOpenGLPixelFormat alloc] initWithAttributes:attribs]; |
|
||||
} |
|
||||
|
|
||||
class AvnGlContext : public virtual ComSingleObject<IAvnGlContext, &IID_IAvnGlContext> |
|
||||
{ |
|
||||
public: |
|
||||
FORWARD_IUNKNOWN() |
|
||||
NSOpenGLContext* GlContext; |
|
||||
GLuint Framebuffer, RenderBuffer, StencilBuffer; |
|
||||
AvnGlContext(NSOpenGLContext* gl, bool offscreen) |
|
||||
{ |
|
||||
Framebuffer = 0; |
|
||||
RenderBuffer = 0; |
|
||||
StencilBuffer = 0; |
|
||||
GlContext = gl; |
|
||||
if(offscreen) |
|
||||
{ |
|
||||
[GlContext makeCurrentContext]; |
|
||||
|
|
||||
glGenFramebuffersEXT(1, &Framebuffer); |
|
||||
glBindFramebufferEXT(GL_FRAMEBUFFER, Framebuffer); |
|
||||
glGenRenderbuffersEXT(1, &RenderBuffer); |
|
||||
glGenRenderbuffersEXT(1, &StencilBuffer); |
|
||||
|
|
||||
glBindRenderbufferEXT(GL_RENDERBUFFER, StencilBuffer); |
|
||||
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, StencilBuffer); |
|
||||
glBindRenderbufferEXT(GL_RENDERBUFFER, RenderBuffer); |
|
||||
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, RenderBuffer); |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
|
|
||||
|
|
||||
virtual HRESULT MakeCurrent() override |
|
||||
{ |
|
||||
[GlContext makeCurrentContext];/* |
|
||||
glBindFramebufferEXT(GL_FRAMEBUFFER, Framebuffer); |
|
||||
glBindRenderbufferEXT(GL_RENDERBUFFER, RenderBuffer); |
|
||||
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, RenderBuffer); |
|
||||
glBindRenderbufferEXT(GL_RENDERBUFFER, StencilBuffer); |
|
||||
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, StencilBuffer);*/ |
|
||||
return S_OK; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
class AvnGlDisplay : public virtual ComSingleObject<IAvnGlDisplay, &IID_IAvnGlDisplay> |
|
||||
{ |
|
||||
int _sampleCount, _stencilSize; |
|
||||
void* _libgl; |
|
||||
|
|
||||
public: |
|
||||
FORWARD_IUNKNOWN() |
|
||||
|
|
||||
AvnGlDisplay(int sampleCount, int stencilSize) |
|
||||
{ |
|
||||
_sampleCount = sampleCount; |
|
||||
_stencilSize = stencilSize; |
|
||||
_libgl = dlopen("/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib", RTLD_LAZY); |
|
||||
} |
|
||||
|
|
||||
virtual HRESULT GetSampleCount(int* ret) override |
|
||||
{ |
|
||||
*ret = _sampleCount; |
|
||||
return S_OK; |
|
||||
} |
|
||||
virtual HRESULT GetStencilSize(int* ret) override |
|
||||
{ |
|
||||
*ret = _stencilSize; |
|
||||
return S_OK; |
|
||||
} |
|
||||
|
|
||||
virtual HRESULT ClearContext() override |
|
||||
{ |
|
||||
[NSOpenGLContext clearCurrentContext]; |
|
||||
return S_OK; |
|
||||
} |
|
||||
|
|
||||
virtual void* GetProcAddress(char* proc) override |
|
||||
{ |
|
||||
return dlsym(_libgl, proc); |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
|
|
||||
class GlFeature : public virtual ComSingleObject<IAvnGlFeature, &IID_IAvnGlFeature> |
|
||||
{ |
|
||||
IAvnGlDisplay* _display; |
|
||||
AvnGlContext *_immediate; |
|
||||
NSOpenGLContext* _shared; |
|
||||
public: |
|
||||
FORWARD_IUNKNOWN() |
|
||||
NSOpenGLPixelFormat* _format; |
|
||||
GlFeature(IAvnGlDisplay* display, AvnGlContext* immediate, NSOpenGLPixelFormat* format) |
|
||||
{ |
|
||||
_display = display; |
|
||||
_immediate = immediate; |
|
||||
_format = format; |
|
||||
_shared = [[NSOpenGLContext alloc] initWithFormat:_format shareContext:_immediate->GlContext]; |
|
||||
} |
|
||||
|
|
||||
NSOpenGLContext* CreateContext() |
|
||||
{ |
|
||||
return _shared; |
|
||||
//return [[NSOpenGLContext alloc] initWithFormat:_format shareContext:nil]; |
|
||||
} |
|
||||
|
|
||||
virtual HRESULT ObtainDisplay(IAvnGlDisplay**retOut) override |
|
||||
{ |
|
||||
*retOut = _display; |
|
||||
_display->AddRef(); |
|
||||
return S_OK; |
|
||||
} |
|
||||
|
|
||||
virtual HRESULT ObtainImmediateContext(IAvnGlContext**retOut) override |
|
||||
{ |
|
||||
*retOut = _immediate; |
|
||||
_immediate->AddRef(); |
|
||||
return S_OK; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
static GlFeature* Feature; |
|
||||
|
|
||||
GlFeature* CreateGlFeature() |
|
||||
{ |
|
||||
auto format = CreateFormat(); |
|
||||
if(format == nil) |
|
||||
{ |
|
||||
NSLog(@"Unable to choose pixel format"); |
|
||||
return NULL; |
|
||||
} |
|
||||
|
|
||||
auto immediateContext = [[NSOpenGLContext alloc] initWithFormat:format shareContext:nil]; |
|
||||
if(immediateContext == nil) |
|
||||
{ |
|
||||
NSLog(@"Unable to create NSOpenGLContext"); |
|
||||
return NULL; |
|
||||
} |
|
||||
|
|
||||
int stencilBits = 0, sampleCount = 0; |
|
||||
|
|
||||
auto fmt = CGLGetPixelFormat([immediateContext CGLContextObj]); |
|
||||
CGLDescribePixelFormat(fmt, 0, kCGLPFASamples, &sampleCount); |
|
||||
CGLDescribePixelFormat(fmt, 0, kCGLPFAStencilSize, &stencilBits); |
|
||||
|
|
||||
auto offscreen = new AvnGlContext(immediateContext, true); |
|
||||
auto display = new AvnGlDisplay(sampleCount, stencilBits); |
|
||||
|
|
||||
return new GlFeature(display, offscreen, format); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
static GlFeature* GetFeature() |
|
||||
{ |
|
||||
if(Feature == nil) |
|
||||
Feature = CreateGlFeature(); |
|
||||
return Feature; |
|
||||
} |
|
||||
|
|
||||
extern IAvnGlFeature* GetGlFeature() |
|
||||
{ |
|
||||
return GetFeature(); |
|
||||
} |
|
||||
|
|
||||
class AvnGlRenderingSession : public ComSingleObject<IAvnGlSurfaceRenderingSession, &IID_IAvnGlSurfaceRenderingSession> |
|
||||
{ |
|
||||
AvnView* _view; |
|
||||
AvnWindow* _window; |
|
||||
NSOpenGLContext* _context; |
|
||||
public: |
|
||||
FORWARD_IUNKNOWN() |
|
||||
AvnGlRenderingSession(AvnWindow*window, AvnView* view, NSOpenGLContext* context) |
|
||||
{ |
|
||||
_context = context; |
|
||||
_window = window; |
|
||||
_view = view; |
|
||||
} |
|
||||
|
|
||||
virtual HRESULT GetPixelSize(AvnPixelSize* ret) override |
|
||||
{ |
|
||||
*ret = [_view getPixelSize]; |
|
||||
return S_OK; |
|
||||
} |
|
||||
virtual HRESULT GetScaling(double* ret) override |
|
||||
{ |
|
||||
*ret = [_window getScaling]; |
|
||||
return S_OK; |
|
||||
} |
|
||||
|
|
||||
virtual ~AvnGlRenderingSession() |
|
||||
{ |
|
||||
[_context flushBuffer]; |
|
||||
[NSOpenGLContext clearCurrentContext]; |
|
||||
CGLUnlockContext([_context CGLContextObj]); |
|
||||
[_view unlockFocus]; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
class AvnGlRenderTarget : public ComSingleObject<IAvnGlSurfaceRenderTarget, &IID_IAvnGlSurfaceRenderTarget> |
|
||||
{ |
|
||||
NSView* _view; |
|
||||
NSWindow* _window; |
|
||||
NSOpenGLContext* _context; |
|
||||
public: |
|
||||
FORWARD_IUNKNOWN() |
|
||||
AvnGlRenderTarget(NSWindow* window, NSView*view) |
|
||||
{ |
|
||||
_window = window; |
|
||||
_view = view; |
|
||||
_context = GetFeature()->CreateContext(); |
|
||||
} |
|
||||
|
|
||||
virtual HRESULT BeginDrawing(IAvnGlSurfaceRenderingSession** ret) override |
|
||||
{ |
|
||||
auto f = GetFeature(); |
|
||||
if(f == NULL) |
|
||||
return E_FAIL; |
|
||||
|
|
||||
@try |
|
||||
{ |
|
||||
if(![_view lockFocusIfCanDraw]) |
|
||||
return E_ABORT; |
|
||||
} |
|
||||
@catch(NSException* exception) |
|
||||
{ |
|
||||
return E_ABORT; |
|
||||
} |
|
||||
|
|
||||
|
|
||||
auto gl = _context; |
|
||||
CGLLockContext([_context CGLContextObj]); |
|
||||
[gl setView: _view]; |
|
||||
[gl update]; |
|
||||
[gl makeCurrentContext]; |
|
||||
*ret = new AvnGlRenderingSession(_window, _view, gl); |
|
||||
return S_OK; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
extern IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(NSWindow* window, NSView* view) |
|
||||
{ |
|
||||
return new AvnGlRenderTarget(window, view); |
|
||||
} |
|
||||
@ -0,0 +1,284 @@ |
|||||
|
#include "common.h" |
||||
|
#include "rendertarget.h" |
||||
|
#import <IOSurface/IOSurface.h> |
||||
|
#import <IOSurface/IOSurfaceObjC.h> |
||||
|
|
||||
|
#include <OpenGL/CGLIOSurface.h> |
||||
|
#include <OpenGL/OpenGL.h> |
||||
|
#include <OpenGL/glext.h> |
||||
|
#include <OpenGL/gl3.h> |
||||
|
#include <OpenGL/gl3ext.h> |
||||
|
|
||||
|
@interface IOSurfaceHolder : NSObject |
||||
|
@end |
||||
|
|
||||
|
@implementation IOSurfaceHolder |
||||
|
{ |
||||
|
@public IOSurfaceRef surface; |
||||
|
@public AvnPixelSize size; |
||||
|
@public float scale; |
||||
|
ComPtr<IAvnGlContext> _context; |
||||
|
GLuint _framebuffer, _texture, _renderbuffer; |
||||
|
} |
||||
|
|
||||
|
- (IOSurfaceHolder*) initWithSize: (AvnPixelSize) size |
||||
|
withScale: (float)scale |
||||
|
withOpenGlContext: (IAvnGlContext*) context |
||||
|
{ |
||||
|
long bytesPerRow = IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, size.Width * 4); |
||||
|
long allocSize = IOSurfaceAlignProperty(kIOSurfaceAllocSize, size.Height * bytesPerRow); |
||||
|
NSDictionary* options = @{ |
||||
|
(id)kIOSurfaceWidth: @(size.Width), |
||||
|
(id)kIOSurfaceHeight: @(size.Height), |
||||
|
(id)kIOSurfacePixelFormat: @((uint)'BGRA'), |
||||
|
(id)kIOSurfaceBytesPerElement: @(4), |
||||
|
(id)kIOSurfaceBytesPerRow: @(bytesPerRow), |
||||
|
(id)kIOSurfaceAllocSize: @(allocSize), |
||||
|
|
||||
|
//(id)kIOSurfaceCacheMode: @(kIOMapWriteCombineCache), |
||||
|
(id)kIOSurfaceElementWidth: @(1), |
||||
|
(id)kIOSurfaceElementHeight: @(1) |
||||
|
}; |
||||
|
|
||||
|
surface = IOSurfaceCreate((CFDictionaryRef)options); |
||||
|
self->scale = scale; |
||||
|
self->size = size; |
||||
|
self->_context = context; |
||||
|
return self; |
||||
|
} |
||||
|
|
||||
|
-(HRESULT) prepareForGlRender |
||||
|
{ |
||||
|
if(_context == nil) |
||||
|
return E_FAIL; |
||||
|
if(CGLGetCurrentContext() != _context->GetNativeHandle()) |
||||
|
return E_FAIL; |
||||
|
if(_framebuffer == 0) |
||||
|
glGenFramebuffersEXT(1, &_framebuffer); |
||||
|
|
||||
|
|
||||
|
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, _framebuffer); |
||||
|
if(_texture == 0) |
||||
|
{ |
||||
|
glGenTextures(1, &_texture); |
||||
|
|
||||
|
glBindTexture(GL_TEXTURE_RECTANGLE_EXT, _texture); |
||||
|
CGLError res = CGLTexImageIOSurface2D((CGLContextObj)_context->GetNativeHandle(), |
||||
|
GL_TEXTURE_RECTANGLE_EXT, GL_RGBA8, |
||||
|
size.Width, size.Height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, surface, 0); |
||||
|
glBindTexture(GL_TEXTURE_RECTANGLE_EXT, 0); |
||||
|
|
||||
|
if(res != 0) |
||||
|
{ |
||||
|
glDeleteTextures(1, &_texture); |
||||
|
_texture = 0; |
||||
|
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); |
||||
|
return E_FAIL; |
||||
|
} |
||||
|
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_RECTANGLE_EXT, _texture, 0); |
||||
|
} |
||||
|
|
||||
|
if(_renderbuffer == 0) |
||||
|
{ |
||||
|
glGenRenderbuffers(1, &_renderbuffer); |
||||
|
glBindRenderbuffer(GL_RENDERBUFFER, _renderbuffer); |
||||
|
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, size.Width, size.Height); |
||||
|
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _renderbuffer); |
||||
|
} |
||||
|
|
||||
|
return S_OK; |
||||
|
} |
||||
|
|
||||
|
-(void) finishDraw |
||||
|
{ |
||||
|
ComPtr<IUnknown> release; |
||||
|
_context->MakeCurrent(release.getPPV()); |
||||
|
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); |
||||
|
glFlush(); |
||||
|
} |
||||
|
|
||||
|
-(void) dealloc |
||||
|
{ |
||||
|
|
||||
|
if(_framebuffer != 0) |
||||
|
{ |
||||
|
ComPtr<IUnknown> release; |
||||
|
_context->MakeCurrent(release.getPPV()); |
||||
|
glDeleteFramebuffers(1, &_framebuffer); |
||||
|
if(_texture != 0) |
||||
|
glDeleteTextures(1, &_texture); |
||||
|
if(_renderbuffer != 0) |
||||
|
glDeleteRenderbuffers(1, &_renderbuffer); |
||||
|
} |
||||
|
IOSurfaceDecrementUseCount(surface); |
||||
|
} |
||||
|
@end |
||||
|
|
||||
|
static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* target); |
||||
|
|
||||
|
@implementation IOSurfaceRenderTarget |
||||
|
{ |
||||
|
CALayer* _layer; |
||||
|
@public IOSurfaceHolder* surface; |
||||
|
@public NSObject* lock; |
||||
|
ComPtr<IAvnGlContext> _glContext; |
||||
|
} |
||||
|
|
||||
|
- (IOSurfaceRenderTarget*) initWithOpenGlContext: (IAvnGlContext*) context; |
||||
|
{ |
||||
|
self = [super init]; |
||||
|
_glContext = context; |
||||
|
lock = [NSObject new]; |
||||
|
surface = nil; |
||||
|
[self resize:{1,1} withScale: 1]; |
||||
|
|
||||
|
return self; |
||||
|
} |
||||
|
|
||||
|
- (AvnPixelSize) pixelSize { |
||||
|
return {1, 1}; |
||||
|
} |
||||
|
|
||||
|
- (CALayer *)layer { |
||||
|
return _layer; |
||||
|
} |
||||
|
|
||||
|
- (void)resize:(AvnPixelSize)size withScale: (float) scale;{ |
||||
|
@synchronized (lock) { |
||||
|
if(surface == nil |
||||
|
|| surface->size.Width != size.Width |
||||
|
|| surface->size.Height != size.Height |
||||
|
|| surface->scale != scale) |
||||
|
surface = [[IOSurfaceHolder alloc] initWithSize:size withScale:scale withOpenGlContext:_glContext.getRaw()]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
- (void)updateLayer { |
||||
|
if ([NSThread isMainThread]) |
||||
|
{ |
||||
|
@synchronized (lock) { |
||||
|
if(_layer == nil) |
||||
|
return; |
||||
|
[_layer setContents: nil]; |
||||
|
if(surface != nil) |
||||
|
{ |
||||
|
[_layer setContentsScale: surface->scale]; |
||||
|
[_layer setContents: (__bridge IOSurface*) surface->surface]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
dispatch_async(dispatch_get_main_queue(), ^{ |
||||
|
[self updateLayer]; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
- (void) setNewLayer:(CALayer *)layer { |
||||
|
_layer = layer; |
||||
|
[self updateLayer]; |
||||
|
} |
||||
|
|
||||
|
- (HRESULT)setSwFrame:(AvnFramebuffer *)fb { |
||||
|
@synchronized (lock) { |
||||
|
if(fb->PixelFormat == AvnPixelFormat::kAvnRgb565) |
||||
|
return E_INVALIDARG; |
||||
|
if(surface == nil) |
||||
|
return E_FAIL; |
||||
|
IOSurfaceRef surf = surface->surface; |
||||
|
if(IOSurfaceLock(surf, 0, nil)) |
||||
|
return E_FAIL; |
||||
|
size_t w = MIN(fb->Width, IOSurfaceGetWidth(surf)); |
||||
|
size_t h = MIN(fb->Height, IOSurfaceGetHeight(surf)); |
||||
|
size_t wbytes = w*4; |
||||
|
size_t sstride = IOSurfaceGetBytesPerRow(surf); |
||||
|
size_t fstride = fb->Stride; |
||||
|
char*pSurface = (char*)IOSurfaceGetBaseAddress(surf); |
||||
|
char*pFb = (char*)fb->Data; |
||||
|
for(size_t y = 0; y < h; y++) |
||||
|
{ |
||||
|
memcpy(pSurface + y*sstride, pFb + y*fstride, wbytes); |
||||
|
} |
||||
|
IOSurfaceUnlock(surf, 0, nil); |
||||
|
[self updateLayer]; |
||||
|
return S_OK; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
-(IAvnGlSurfaceRenderTarget*) createSurfaceRenderTarget |
||||
|
{ |
||||
|
return CreateGlRenderTarget(self); |
||||
|
} |
||||
|
|
||||
|
@end |
||||
|
|
||||
|
class AvnGlRenderingSession : public ComSingleObject<IAvnGlSurfaceRenderingSession, &IID_IAvnGlSurfaceRenderingSession> |
||||
|
{ |
||||
|
ComPtr<IUnknown> _releaseContext; |
||||
|
IOSurfaceRenderTarget* _target; |
||||
|
IOSurfaceHolder* _surface; |
||||
|
public: |
||||
|
FORWARD_IUNKNOWN() |
||||
|
AvnGlRenderingSession(IOSurfaceRenderTarget* target, ComPtr<IUnknown> releaseContext) |
||||
|
{ |
||||
|
_target = target; |
||||
|
// This happens in a synchronized block set up by AvnRenderTarget, so we take the current surface for this |
||||
|
// particular render session |
||||
|
_surface = _target->surface; |
||||
|
_releaseContext = releaseContext; |
||||
|
} |
||||
|
|
||||
|
virtual HRESULT GetPixelSize(AvnPixelSize* ret) override |
||||
|
{ |
||||
|
if(!_surface) |
||||
|
return E_FAIL; |
||||
|
*ret = _surface->size; |
||||
|
return S_OK; |
||||
|
} |
||||
|
|
||||
|
virtual HRESULT GetScaling(double* ret) override |
||||
|
{ |
||||
|
if(!_surface) |
||||
|
return E_FAIL; |
||||
|
*ret = _surface->scale; |
||||
|
return S_OK; |
||||
|
} |
||||
|
|
||||
|
virtual ~AvnGlRenderingSession() |
||||
|
{ |
||||
|
[_surface finishDraw]; |
||||
|
[_target updateLayer]; |
||||
|
_releaseContext = nil; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
class AvnGlRenderTarget : public ComSingleObject<IAvnGlSurfaceRenderTarget, &IID_IAvnGlSurfaceRenderTarget> |
||||
|
{ |
||||
|
IOSurfaceRenderTarget* _target; |
||||
|
public: |
||||
|
FORWARD_IUNKNOWN() |
||||
|
AvnGlRenderTarget(IOSurfaceRenderTarget* target) |
||||
|
{ |
||||
|
_target = target; |
||||
|
} |
||||
|
|
||||
|
virtual HRESULT BeginDrawing(IAvnGlSurfaceRenderingSession** ret) override |
||||
|
{ |
||||
|
ComPtr<IUnknown> releaseContext; |
||||
|
@synchronized (_target->lock) { |
||||
|
if(_target->surface == nil) |
||||
|
return E_FAIL; |
||||
|
_target->_glContext->MakeCurrent(releaseContext.getPPV()); |
||||
|
HRESULT res = [_target->surface prepareForGlRender]; |
||||
|
if(res) |
||||
|
return res; |
||||
|
*ret = new AvnGlRenderingSession(_target, releaseContext); |
||||
|
return S_OK; |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* target) |
||||
|
{ |
||||
|
return new AvnGlRenderTarget(target); |
||||
|
} |
||||
Binary file not shown.
@ -1,24 +0,0 @@ |
|||||
<UserControl xmlns="https://github.com/avaloniaui" |
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|
||||
x:Class="Avalonia.Diagnostics.DevTools"> |
|
||||
<Grid RowDefinitions="*,Auto" Margin="4"> |
|
||||
|
|
||||
<TabControl Grid.Row="0" Items="{Binding Tools}" SelectedItem="{Binding SelectedTool}"> |
|
||||
<TabControl.ItemTemplate> |
|
||||
<DataTemplate> |
|
||||
<TextBlock Text="{Binding Name}" /> |
|
||||
</DataTemplate> |
|
||||
</TabControl.ItemTemplate> |
|
||||
</TabControl> |
|
||||
|
|
||||
<StackPanel Grid.Row="1" Spacing="4" Orientation="Horizontal"> |
|
||||
<TextBlock>Hold Ctrl+Shift over a control to inspect.</TextBlock> |
|
||||
<Separator Width="8" /> |
|
||||
<TextBlock>Focused:</TextBlock> |
|
||||
<TextBlock Text="{Binding FocusedControl}" /> |
|
||||
<Separator Width="8" /> |
|
||||
<TextBlock>Pointer Over:</TextBlock> |
|
||||
<TextBlock Text="{Binding PointerOverElement}" /> |
|
||||
</StackPanel> |
|
||||
</Grid> |
|
||||
</UserControl> |
|
||||
@ -1,159 +0,0 @@ |
|||||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|
||||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq; |
|
||||
using System.Reactive.Linq; |
|
||||
using Avalonia.Controls; |
|
||||
using Avalonia.Controls.Primitives; |
|
||||
using Avalonia.Diagnostics.ViewModels; |
|
||||
using Avalonia.Input; |
|
||||
using Avalonia.Input.Raw; |
|
||||
using Avalonia.Interactivity; |
|
||||
using Avalonia.Markup.Xaml; |
|
||||
using Avalonia.Rendering; |
|
||||
using Avalonia.VisualTree; |
|
||||
|
|
||||
namespace Avalonia |
|
||||
{ |
|
||||
public static class DevToolsExtensions |
|
||||
{ |
|
||||
public static void AttachDevTools(this TopLevel control) |
|
||||
{ |
|
||||
Diagnostics.DevTools.Attach(control, new KeyGesture(Key.F12)); |
|
||||
} |
|
||||
|
|
||||
public static void AttachDevTools(this TopLevel control, KeyGesture gesture) |
|
||||
{ |
|
||||
Diagnostics.DevTools.Attach(control, gesture); |
|
||||
} |
|
||||
|
|
||||
public static void OpenDevTools(this TopLevel control) |
|
||||
{ |
|
||||
Diagnostics.DevTools.OpenDevTools(control); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
namespace Avalonia.Diagnostics |
|
||||
{ |
|
||||
public class DevTools : UserControl |
|
||||
{ |
|
||||
private static readonly Dictionary<TopLevel, Window> s_open = new Dictionary<TopLevel, Window>(); |
|
||||
private static readonly HashSet<IRenderRoot> s_visualTreeRoots = new HashSet<IRenderRoot>(); |
|
||||
private readonly IDisposable _keySubscription; |
|
||||
|
|
||||
public DevTools(IControl root) |
|
||||
{ |
|
||||
InitializeComponent(); |
|
||||
Root = root; |
|
||||
DataContext = new DevToolsViewModel(root); |
|
||||
|
|
||||
_keySubscription = InputManager.Instance.Process |
|
||||
.OfType<RawKeyEventArgs>() |
|
||||
.Subscribe(RawKeyDown); |
|
||||
} |
|
||||
|
|
||||
// HACK: needed for XAMLIL, will fix that later
|
|
||||
public DevTools() |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public IControl Root { get; } |
|
||||
|
|
||||
public static IDisposable Attach(TopLevel control, KeyGesture gesture) |
|
||||
{ |
|
||||
void PreviewKeyDown(object sender, KeyEventArgs e) |
|
||||
{ |
|
||||
if (gesture.Matches(e)) |
|
||||
{ |
|
||||
OpenDevTools(control); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return control.AddHandler( |
|
||||
KeyDownEvent, |
|
||||
PreviewKeyDown, |
|
||||
RoutingStrategies.Tunnel); |
|
||||
} |
|
||||
|
|
||||
internal static void OpenDevTools(TopLevel control) |
|
||||
{ |
|
||||
if (s_open.TryGetValue(control, out var devToolsWindow)) |
|
||||
{ |
|
||||
devToolsWindow.Activate(); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
var devTools = new DevTools(control); |
|
||||
|
|
||||
devToolsWindow = new Window |
|
||||
{ |
|
||||
Width = 1024, |
|
||||
Height = 512, |
|
||||
Content = devTools, |
|
||||
DataTemplates = { new ViewLocator<ViewModelBase>() }, |
|
||||
Title = "Avalonia DevTools" |
|
||||
}; |
|
||||
|
|
||||
devToolsWindow.Closed += devTools.DevToolsClosed; |
|
||||
s_open.Add(control, devToolsWindow); |
|
||||
MarkAsDevTool(devToolsWindow); |
|
||||
devToolsWindow.Show(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void DevToolsClosed(object sender, EventArgs e) |
|
||||
{ |
|
||||
var devToolsWindow = (Window)sender; |
|
||||
var devTools = (DevTools)devToolsWindow.Content; |
|
||||
s_open.Remove((TopLevel)devTools.Root); |
|
||||
RemoveDevTool(devToolsWindow); |
|
||||
_keySubscription.Dispose(); |
|
||||
devToolsWindow.Closed -= DevToolsClosed; |
|
||||
} |
|
||||
|
|
||||
private void InitializeComponent() |
|
||||
{ |
|
||||
AvaloniaXamlLoader.Load(this); |
|
||||
} |
|
||||
|
|
||||
private void RawKeyDown(RawKeyEventArgs e) |
|
||||
{ |
|
||||
const RawInputModifiers modifiers = RawInputModifiers.Control | RawInputModifiers.Shift; |
|
||||
|
|
||||
if (e.Modifiers == modifiers) |
|
||||
{ |
|
||||
var point = (Root.VisualRoot as IInputRoot)?.MouseDevice?.GetPosition(Root) ?? default(Point); |
|
||||
var control = Root.GetVisualsAt(point, x => (!(x is AdornerLayer) && x.IsVisible)) |
|
||||
.FirstOrDefault(); |
|
||||
|
|
||||
if (control != null) |
|
||||
{ |
|
||||
var vm = (DevToolsViewModel)DataContext; |
|
||||
vm.SelectControl((IControl)control); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Marks a visual as part of the DevTools, so it can be excluded from event tracking.
|
|
||||
/// </summary>
|
|
||||
/// <param name="visual">The visual whose root is to be marked.</param>
|
|
||||
public static void MarkAsDevTool(IVisual visual) |
|
||||
{ |
|
||||
s_visualTreeRoots.Add(visual.GetVisualRoot()); |
|
||||
} |
|
||||
|
|
||||
public static void RemoveDevTool(IVisual visual) |
|
||||
{ |
|
||||
s_visualTreeRoots.Remove(visual.GetVisualRoot()); |
|
||||
} |
|
||||
|
|
||||
public static bool BelongsToDevTool(IVisual visual) |
|
||||
{ |
|
||||
return s_visualTreeRoots.Contains(visual.GetVisualRoot()); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,31 @@ |
|||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Diagnostics; |
||||
|
using Avalonia.Input; |
||||
|
|
||||
|
namespace Avalonia |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Extension methods for attaching DevTools..
|
||||
|
/// </summary>
|
||||
|
public static class DevToolsExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Attaches DevTools to a window, to be opened with the F12 key.
|
||||
|
/// </summary>
|
||||
|
/// <param name="root">The window to attach DevTools to.</param>
|
||||
|
public static void AttachDevTools(this TopLevel root) |
||||
|
{ |
||||
|
DevTools.Attach(root, new KeyGesture(Key.F12)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Attaches DevTools to a window, to be opened with the specified key gesture.
|
||||
|
/// </summary>
|
||||
|
/// <param name="root">The window to attach DevTools to.</param>
|
||||
|
/// <param name="gesture">The key gesture to open DevTools.</param>
|
||||
|
public static void AttachDevTools(this TopLevel root, KeyGesture gesture) |
||||
|
{ |
||||
|
DevTools.Attach(root, gesture); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
using System; |
||||
|
using System.Globalization; |
||||
|
using Avalonia.Data.Converters; |
||||
|
using Avalonia.Media; |
||||
|
|
||||
|
namespace Avalonia.Diagnostics.Converters |
||||
|
{ |
||||
|
internal class BoolToBrushConverter : IValueConverter |
||||
|
{ |
||||
|
public IBrush Brush { get; set; } |
||||
|
|
||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) |
||||
|
{ |
||||
|
return (bool)value ? Brush : Brushes.Transparent; |
||||
|
} |
||||
|
|
||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,61 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Reactive.Disposables; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Diagnostics.Views; |
||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Interactivity; |
||||
|
|
||||
|
namespace Avalonia.Diagnostics |
||||
|
{ |
||||
|
public static class DevTools |
||||
|
{ |
||||
|
private static readonly Dictionary<TopLevel, Window> s_open = new Dictionary<TopLevel, Window>(); |
||||
|
|
||||
|
public static IDisposable Attach(TopLevel root, KeyGesture gesture) |
||||
|
{ |
||||
|
void PreviewKeyDown(object sender, KeyEventArgs e) |
||||
|
{ |
||||
|
if (gesture.Matches(e)) |
||||
|
{ |
||||
|
Open(root); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return root.AddHandler( |
||||
|
InputElement.KeyDownEvent, |
||||
|
PreviewKeyDown, |
||||
|
RoutingStrategies.Tunnel); |
||||
|
} |
||||
|
|
||||
|
public static IDisposable Open(TopLevel root) |
||||
|
{ |
||||
|
if (s_open.TryGetValue(root, out var window)) |
||||
|
{ |
||||
|
window.Activate(); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
window = new MainWindow |
||||
|
{ |
||||
|
Width = 1024, |
||||
|
Height = 512, |
||||
|
Root = root, |
||||
|
}; |
||||
|
|
||||
|
window.Closed += DevToolsClosed; |
||||
|
s_open.Add(root, window); |
||||
|
window.Show(); |
||||
|
} |
||||
|
|
||||
|
return Disposable.Create(() => window?.Close()); |
||||
|
} |
||||
|
|
||||
|
private static void DevToolsClosed(object sender, EventArgs e) |
||||
|
{ |
||||
|
var window = (MainWindow)sender; |
||||
|
s_open.Remove(window.Root); |
||||
|
window.Closed -= DevToolsClosed; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
#pragma warning disable IDE1006 // Naming Styles
|
||||
|
|
||||
|
using Avalonia.Diagnostics.ViewModels; |
||||
|
|
||||
|
namespace Avalonia.Diagnostics.Models |
||||
|
{ |
||||
|
public class ConsoleContext |
||||
|
{ |
||||
|
private readonly ConsoleViewModel _owner; |
||||
|
|
||||
|
internal ConsoleContext(ConsoleViewModel owner) => _owner = owner; |
||||
|
|
||||
|
public readonly string help = @"Welcome to Avalonia DevTools. Here you can execute arbitrary C# code using Roslyn scripting.
|
||||
|
|
||||
|
The following variables are available: |
||||
|
|
||||
|
e: The control currently selected in the logical or visual tree view |
||||
|
root: The root of the visual tree |
||||
|
|
||||
|
The following commands are available: |
||||
|
|
||||
|
clear(): Clear the output history |
||||
|
";
|
||||
|
|
||||
|
public dynamic e { get; internal set; } |
||||
|
public dynamic root { get; internal set; } |
||||
|
|
||||
|
internal static object NoOutput { get; } = new object(); |
||||
|
|
||||
|
public object clear() |
||||
|
{ |
||||
|
_owner.History.Clear(); |
||||
|
return NoOutput; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Media; |
||||
|
|
||||
|
namespace Avalonia.Diagnostics.Models |
||||
|
{ |
||||
|
internal class ConsoleHistoryItem |
||||
|
{ |
||||
|
public ConsoleHistoryItem(string input, object output) |
||||
|
{ |
||||
|
Input = input; |
||||
|
Output = output; |
||||
|
Foreground = output is Exception ? Brushes.Red : Brushes.Green; |
||||
|
} |
||||
|
|
||||
|
public string Input { get; } |
||||
|
public object Output { get; } |
||||
|
public IBrush Foreground { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,95 @@ |
|||||
|
using System.ComponentModel; |
||||
|
using Avalonia.Collections; |
||||
|
|
||||
|
namespace Avalonia.Diagnostics.ViewModels |
||||
|
{ |
||||
|
internal class AvaloniaPropertyViewModel : PropertyViewModel |
||||
|
{ |
||||
|
private readonly AvaloniaObject _target; |
||||
|
private string _type; |
||||
|
private object _value; |
||||
|
private string _priority; |
||||
|
private string _group; |
||||
|
|
||||
|
public AvaloniaPropertyViewModel(AvaloniaObject o, AvaloniaProperty property) |
||||
|
{ |
||||
|
_target = o; |
||||
|
Property = property; |
||||
|
|
||||
|
Name = property.IsAttached ? |
||||
|
$"[{property.OwnerType.Name}.{property.Name}]" : |
||||
|
property.Name; |
||||
|
|
||||
|
if (property.IsDirect) |
||||
|
{ |
||||
|
_group = "Properties"; |
||||
|
Priority = "Direct"; |
||||
|
} |
||||
|
|
||||
|
Update(); |
||||
|
} |
||||
|
|
||||
|
public AvaloniaProperty Property { get; } |
||||
|
public override object Key => Property; |
||||
|
public override string Name { get; } |
||||
|
public bool IsAttached => Property.IsAttached; |
||||
|
|
||||
|
public string Priority |
||||
|
{ |
||||
|
get => _priority; |
||||
|
private set => RaiseAndSetIfChanged(ref _priority, value); |
||||
|
} |
||||
|
|
||||
|
public override string Type => _type; |
||||
|
|
||||
|
public override string Value |
||||
|
{ |
||||
|
get => ConvertToString(_value); |
||||
|
set |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var convertedValue = ConvertFromString(value, Property.PropertyType); |
||||
|
_target.SetValue(Property, convertedValue); |
||||
|
} |
||||
|
catch { } |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public override string Group |
||||
|
{ |
||||
|
get => _group; |
||||
|
} |
||||
|
|
||||
|
public override void Update() |
||||
|
{ |
||||
|
if (Property.IsDirect) |
||||
|
{ |
||||
|
RaiseAndSetIfChanged(ref _value, _target.GetValue(Property), nameof(Value)); |
||||
|
RaiseAndSetIfChanged(ref _type, _value?.GetType().Name, nameof(Type)); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var val = _target.GetDiagnostic(Property); |
||||
|
|
||||
|
RaiseAndSetIfChanged(ref _value, val?.Value, nameof(Value)); |
||||
|
RaiseAndSetIfChanged(ref _type, _value?.GetType().Name, nameof(Type)); |
||||
|
|
||||
|
if (val != null) |
||||
|
{ |
||||
|
SetGroup(IsAttached ? "Attached Properties" : "Properties"); |
||||
|
Priority = val.Priority.ToString(); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
SetGroup(Priority = "Unset"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void SetGroup(string group) |
||||
|
{ |
||||
|
RaiseAndSetIfChanged(ref _group, group, nameof(Group)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,57 @@ |
|||||
|
using System.ComponentModel; |
||||
|
using System.Reflection; |
||||
|
|
||||
|
namespace Avalonia.Diagnostics.ViewModels |
||||
|
{ |
||||
|
internal class ClrPropertyViewModel : PropertyViewModel |
||||
|
{ |
||||
|
private readonly object _target; |
||||
|
private string _type; |
||||
|
private object _value; |
||||
|
|
||||
|
public ClrPropertyViewModel(object o, PropertyInfo property) |
||||
|
{ |
||||
|
_target = o; |
||||
|
Property = property; |
||||
|
|
||||
|
if (!property.DeclaringType.IsInterface) |
||||
|
{ |
||||
|
Name = property.Name; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Name = property.DeclaringType.Name + '.' + property.Name; |
||||
|
} |
||||
|
|
||||
|
Update(); |
||||
|
} |
||||
|
|
||||
|
public PropertyInfo Property { get; } |
||||
|
public override object Key => Name; |
||||
|
public override string Name { get; } |
||||
|
public override string Group => "CLR Properties"; |
||||
|
|
||||
|
public override string Type => _type; |
||||
|
|
||||
|
public override string Value |
||||
|
{ |
||||
|
get => ConvertToString(_value); |
||||
|
set |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var convertedValue = ConvertFromString(value, Property.PropertyType); |
||||
|
Property.SetValue(_target, convertedValue); |
||||
|
} |
||||
|
catch { } |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public override void Update() |
||||
|
{ |
||||
|
var val = Property.GetValue(_target); |
||||
|
RaiseAndSetIfChanged(ref _value, val, nameof(Value)); |
||||
|
RaiseAndSetIfChanged(ref _type, _value?.GetType().Name, nameof(Type)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,112 @@ |
|||||
|
using System; |
||||
|
using System.Reflection; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Collections; |
||||
|
using Avalonia.Diagnostics.Models; |
||||
|
using Microsoft.CodeAnalysis.CSharp.Scripting; |
||||
|
using Microsoft.CodeAnalysis.Scripting; |
||||
|
|
||||
|
namespace Avalonia.Diagnostics.ViewModels |
||||
|
{ |
||||
|
internal class ConsoleViewModel : ViewModelBase |
||||
|
{ |
||||
|
private readonly ConsoleContext _context; |
||||
|
private readonly Action<ConsoleContext> _updateContext; |
||||
|
private int _historyIndex = -1; |
||||
|
private string _input; |
||||
|
private bool _isVisible; |
||||
|
private ScriptState<object> _state; |
||||
|
|
||||
|
public ConsoleViewModel(Action<ConsoleContext> updateContext) |
||||
|
{ |
||||
|
_context = new ConsoleContext(this); |
||||
|
_updateContext = updateContext; |
||||
|
} |
||||
|
|
||||
|
public string Input |
||||
|
{ |
||||
|
get => _input; |
||||
|
set => RaiseAndSetIfChanged(ref _input, value); |
||||
|
} |
||||
|
|
||||
|
public bool IsVisible |
||||
|
{ |
||||
|
get => _isVisible; |
||||
|
set => RaiseAndSetIfChanged(ref _isVisible, value); |
||||
|
} |
||||
|
|
||||
|
public AvaloniaList<ConsoleHistoryItem> History { get; } = new AvaloniaList<ConsoleHistoryItem>(); |
||||
|
|
||||
|
public async Task Execute() |
||||
|
{ |
||||
|
if (string.IsNullOrWhiteSpace(Input)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
var options = ScriptOptions.Default |
||||
|
.AddReferences(Assembly.GetAssembly(typeof(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo))); |
||||
|
|
||||
|
_updateContext(_context); |
||||
|
|
||||
|
if (_state == null) |
||||
|
{ |
||||
|
_state = await CSharpScript.RunAsync(Input, options: options, globals: _context); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
_state = await _state.ContinueWithAsync(Input); |
||||
|
} |
||||
|
|
||||
|
if (_state.ReturnValue != ConsoleContext.NoOutput) |
||||
|
{ |
||||
|
History.Add(new ConsoleHistoryItem(Input, _state.ReturnValue ?? "(null)")); |
||||
|
} |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
History.Add(new ConsoleHistoryItem(Input, ex)); |
||||
|
} |
||||
|
|
||||
|
Input = string.Empty; |
||||
|
_historyIndex = -1; |
||||
|
} |
||||
|
|
||||
|
public void HistoryUp() |
||||
|
{ |
||||
|
if (History.Count > 0) |
||||
|
{ |
||||
|
if (_historyIndex == -1) |
||||
|
{ |
||||
|
_historyIndex = History.Count - 1; |
||||
|
} |
||||
|
else if (_historyIndex > 0) |
||||
|
{ |
||||
|
--_historyIndex; |
||||
|
} |
||||
|
|
||||
|
Input = History[_historyIndex].Input; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void HistoryDown() |
||||
|
{ |
||||
|
if (History.Count > 0 && _historyIndex >= 0) |
||||
|
{ |
||||
|
if (_historyIndex == History.Count - 1) |
||||
|
{ |
||||
|
_historyIndex = -1; |
||||
|
Input = string.Empty; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Input = History[++_historyIndex].Input; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void ToggleVisibility() => IsVisible = !IsVisible; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,179 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.ComponentModel; |
||||
|
using System.Linq; |
||||
|
using Avalonia.Collections; |
||||
|
using Avalonia.VisualTree; |
||||
|
|
||||
|
namespace Avalonia.Diagnostics.ViewModels |
||||
|
{ |
||||
|
internal class ControlDetailsViewModel : ViewModelBase, IDisposable |
||||
|
{ |
||||
|
private readonly IVisual _control; |
||||
|
private readonly IDictionary<object, List<PropertyViewModel>> _propertyIndex; |
||||
|
private AvaloniaPropertyViewModel _selectedProperty; |
||||
|
private string _propertyFilter; |
||||
|
|
||||
|
public ControlDetailsViewModel(IVisual control, string propertyFilter) |
||||
|
{ |
||||
|
_control = control; |
||||
|
|
||||
|
var properties = GetAvaloniaProperties(control) |
||||
|
.Concat(GetClrProperties(control)) |
||||
|
.OrderBy(x => x, PropertyComparer.Instance) |
||||
|
.ThenBy(x => x.Name) |
||||
|
.ToList(); |
||||
|
|
||||
|
_propertyIndex = properties.GroupBy(x => x.Key).ToDictionary(x => x.Key, x => x.ToList()); |
||||
|
_propertyFilter = propertyFilter; |
||||
|
|
||||
|
var view = new DataGridCollectionView(properties); |
||||
|
view.GroupDescriptions.Add(new DataGridPathGroupDescription(nameof(AvaloniaPropertyViewModel.Group))); |
||||
|
view.Filter = FilterProperty; |
||||
|
PropertiesView = view; |
||||
|
|
||||
|
if (control is INotifyPropertyChanged inpc) |
||||
|
{ |
||||
|
inpc.PropertyChanged += ControlPropertyChanged; |
||||
|
} |
||||
|
|
||||
|
if (control is AvaloniaObject ao) |
||||
|
{ |
||||
|
ao.PropertyChanged += ControlPropertyChanged; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public DataGridCollectionView PropertiesView { get; } |
||||
|
|
||||
|
public string PropertyFilter |
||||
|
{ |
||||
|
get => _propertyFilter; |
||||
|
set |
||||
|
{ |
||||
|
if (RaiseAndSetIfChanged(ref _propertyFilter, value)) |
||||
|
{ |
||||
|
PropertiesView.Refresh(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public AvaloniaPropertyViewModel SelectedProperty |
||||
|
{ |
||||
|
get => _selectedProperty; |
||||
|
set => RaiseAndSetIfChanged(ref _selectedProperty, value); |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
if (_control is INotifyPropertyChanged inpc) |
||||
|
{ |
||||
|
inpc.PropertyChanged -= ControlPropertyChanged; |
||||
|
} |
||||
|
|
||||
|
if (_control is AvaloniaObject ao) |
||||
|
{ |
||||
|
ao.PropertyChanged -= ControlPropertyChanged; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private IEnumerable<PropertyViewModel> GetAvaloniaProperties(object o) |
||||
|
{ |
||||
|
if (o is AvaloniaObject ao) |
||||
|
{ |
||||
|
return AvaloniaPropertyRegistry.Instance.GetRegistered(ao) |
||||
|
.Concat(AvaloniaPropertyRegistry.Instance.GetRegisteredAttached(ao.GetType())) |
||||
|
.Select(x => new AvaloniaPropertyViewModel(ao, x)); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
return Enumerable.Empty<AvaloniaPropertyViewModel>(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private IEnumerable<PropertyViewModel> GetClrProperties(object o) |
||||
|
{ |
||||
|
foreach (var p in GetClrProperties(o, o.GetType())) |
||||
|
{ |
||||
|
yield return p; |
||||
|
} |
||||
|
|
||||
|
foreach (var i in o.GetType().GetInterfaces()) |
||||
|
{ |
||||
|
foreach (var p in GetClrProperties(o, i)) |
||||
|
{ |
||||
|
yield return p; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private IEnumerable<PropertyViewModel> GetClrProperties(object o, Type t) |
||||
|
{ |
||||
|
return t.GetProperties() |
||||
|
.Where(x => x.GetIndexParameters().Length == 0) |
||||
|
.Select(x => new ClrPropertyViewModel(o, x)); |
||||
|
} |
||||
|
|
||||
|
private void ControlPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) |
||||
|
{ |
||||
|
if (_propertyIndex.TryGetValue(e.Property, out var properties)) |
||||
|
{ |
||||
|
foreach (var property in properties) |
||||
|
{ |
||||
|
property.Update(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void ControlPropertyChanged(object sender, PropertyChangedEventArgs e) |
||||
|
{ |
||||
|
if (_propertyIndex.TryGetValue(e.PropertyName, out var properties)) |
||||
|
{ |
||||
|
foreach (var property in properties) |
||||
|
{ |
||||
|
property.Update(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private bool FilterProperty(object arg) |
||||
|
{ |
||||
|
if (!string.IsNullOrWhiteSpace(PropertyFilter) && arg is PropertyViewModel property) |
||||
|
{ |
||||
|
return property.Name.IndexOf(PropertyFilter, StringComparison.OrdinalIgnoreCase) != -1; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
private class PropertyComparer : IComparer<PropertyViewModel> |
||||
|
{ |
||||
|
public static PropertyComparer Instance { get; } = new PropertyComparer(); |
||||
|
|
||||
|
public int Compare(PropertyViewModel x, PropertyViewModel y) |
||||
|
{ |
||||
|
var groupX = GroupIndex(x.Group); |
||||
|
var groupY = GroupIndex(y.Group); |
||||
|
|
||||
|
if (groupX != groupY) |
||||
|
{ |
||||
|
return groupX - groupY; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
return string.CompareOrdinal(x.Name, y.Name); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private int GroupIndex(string group) |
||||
|
{ |
||||
|
switch (group) |
||||
|
{ |
||||
|
case "Properties": return 0; |
||||
|
case "Attached Properties": return 1; |
||||
|
case "CLR Properties": return 2; |
||||
|
default: return 3; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,126 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Diagnostics.Models; |
||||
|
using Avalonia.Input; |
||||
|
|
||||
|
namespace Avalonia.Diagnostics.ViewModels |
||||
|
{ |
||||
|
internal class MainViewModel : ViewModelBase, IDisposable |
||||
|
{ |
||||
|
private readonly IControl _root; |
||||
|
private readonly TreePageViewModel _logicalTree; |
||||
|
private readonly TreePageViewModel _visualTree; |
||||
|
private readonly EventsPageViewModel _events; |
||||
|
private ViewModelBase _content; |
||||
|
private int _selectedTab; |
||||
|
private string _focusedControl; |
||||
|
private string _pointerOverElement; |
||||
|
|
||||
|
public MainViewModel(IControl root) |
||||
|
{ |
||||
|
_root = root; |
||||
|
_logicalTree = new TreePageViewModel(LogicalTreeNode.Create(root)); |
||||
|
_visualTree = new TreePageViewModel(VisualTreeNode.Create(root)); |
||||
|
_events = new EventsPageViewModel(root); |
||||
|
|
||||
|
UpdateFocusedControl(); |
||||
|
KeyboardDevice.Instance.PropertyChanged += (s, e) => |
||||
|
{ |
||||
|
if (e.PropertyName == nameof(KeyboardDevice.Instance.FocusedElement)) |
||||
|
{ |
||||
|
UpdateFocusedControl(); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
SelectedTab = 0; |
||||
|
root.GetObservable(TopLevel.PointerOverElementProperty) |
||||
|
.Subscribe(x => PointerOverElement = x?.GetType().Name); |
||||
|
Console = new ConsoleViewModel(UpdateConsoleContext); |
||||
|
} |
||||
|
|
||||
|
public ConsoleViewModel Console { get; } |
||||
|
|
||||
|
public ViewModelBase Content |
||||
|
{ |
||||
|
get { return _content; } |
||||
|
private set |
||||
|
{ |
||||
|
if (_content is TreePageViewModel oldTree && |
||||
|
value is TreePageViewModel newTree && |
||||
|
oldTree?.SelectedNode?.Visual is IControl control) |
||||
|
{ |
||||
|
newTree.SelectControl(control); |
||||
|
} |
||||
|
|
||||
|
RaiseAndSetIfChanged(ref _content, value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public int SelectedTab |
||||
|
{ |
||||
|
get { return _selectedTab; } |
||||
|
set |
||||
|
{ |
||||
|
_selectedTab = value; |
||||
|
|
||||
|
switch (value) |
||||
|
{ |
||||
|
case 0: |
||||
|
Content = _logicalTree; |
||||
|
break; |
||||
|
case 1: |
||||
|
Content = _visualTree; |
||||
|
break; |
||||
|
case 2: |
||||
|
Content = _events; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
RaisePropertyChanged(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public string FocusedControl |
||||
|
{ |
||||
|
get { return _focusedControl; } |
||||
|
private set { RaiseAndSetIfChanged(ref _focusedControl, value); } |
||||
|
} |
||||
|
|
||||
|
public string PointerOverElement |
||||
|
{ |
||||
|
get { return _pointerOverElement; } |
||||
|
private set { RaiseAndSetIfChanged(ref _pointerOverElement, value); } |
||||
|
} |
||||
|
|
||||
|
private void UpdateConsoleContext(ConsoleContext context) |
||||
|
{ |
||||
|
context.root = _root; |
||||
|
|
||||
|
if (Content is TreePageViewModel tree) |
||||
|
{ |
||||
|
context.e = tree.SelectedNode?.Visual; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void SelectControl(IControl control) |
||||
|
{ |
||||
|
var tree = Content as TreePageViewModel; |
||||
|
|
||||
|
if (tree != null) |
||||
|
{ |
||||
|
tree.SelectControl(control); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
_logicalTree.Dispose(); |
||||
|
_visualTree.Dispose(); |
||||
|
} |
||||
|
|
||||
|
private void UpdateFocusedControl() |
||||
|
{ |
||||
|
FocusedControl = KeyboardDevice.Instance.FocusedElement?.GetType().Name; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,60 @@ |
|||||
|
using System; |
||||
|
using System.ComponentModel; |
||||
|
using System.Globalization; |
||||
|
using System.Reflection; |
||||
|
|
||||
|
namespace Avalonia.Diagnostics.ViewModels |
||||
|
{ |
||||
|
internal abstract class PropertyViewModel : ViewModelBase |
||||
|
{ |
||||
|
private const BindingFlags PublicStatic = BindingFlags.Public | BindingFlags.Static; |
||||
|
private static readonly Type[] StringParameter = new[] { typeof(string) }; |
||||
|
private static readonly Type[] StringIFormatProviderParameters = new[] { typeof(string), typeof(IFormatProvider) }; |
||||
|
|
||||
|
public abstract object Key { get; } |
||||
|
public abstract string Name { get; } |
||||
|
public abstract string Group { get; } |
||||
|
public abstract string Type { get; } |
||||
|
public abstract string Value { get; set; } |
||||
|
public abstract void Update(); |
||||
|
|
||||
|
protected static string ConvertToString(object value) |
||||
|
{ |
||||
|
if (value is null) |
||||
|
{ |
||||
|
return "(null)"; |
||||
|
} |
||||
|
|
||||
|
var converter = TypeDescriptor.GetConverter(value); |
||||
|
return converter?.ConvertToString(value) ?? value.ToString(); |
||||
|
} |
||||
|
|
||||
|
protected static object ConvertFromString(string s, Type targetType) |
||||
|
{ |
||||
|
var converter = TypeDescriptor.GetConverter(targetType); |
||||
|
|
||||
|
if (converter != null && converter.CanConvertFrom(typeof(string))) |
||||
|
{ |
||||
|
return converter.ConvertFrom(null, CultureInfo.InvariantCulture, s); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var method = targetType.GetMethod("Parse", PublicStatic, null, StringIFormatProviderParameters, null); |
||||
|
|
||||
|
if (method != null) |
||||
|
{ |
||||
|
return method.Invoke(null, new object[] { s, CultureInfo.InvariantCulture }); |
||||
|
} |
||||
|
|
||||
|
method = targetType.GetMethod("Parse", PublicStatic, null, StringParameter, null); |
||||
|
|
||||
|
if (method != null) |
||||
|
{ |
||||
|
return method.Invoke(null, new object[] { s }); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
throw new InvalidCastException("Unable to convert value."); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,34 +1,42 @@ |
|||||
// Copyright (c) The Avalonia Project. All rights reserved.
|
using System; |
||||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|
||||
|
|
||||
using System.Collections.Generic; |
using System.Collections.Generic; |
||||
using System.ComponentModel; |
using System.ComponentModel; |
||||
using System.Runtime.CompilerServices; |
using System.Runtime.CompilerServices; |
||||
using JetBrains.Annotations; |
|
||||
|
|
||||
namespace Avalonia.Diagnostics.ViewModels |
namespace Avalonia.Diagnostics.ViewModels |
||||
{ |
{ |
||||
internal class ViewModelBase : INotifyPropertyChanged |
internal class ViewModelBase : INotifyPropertyChanged |
||||
{ |
{ |
||||
public event PropertyChangedEventHandler PropertyChanged; |
private PropertyChangedEventHandler _propertyChanged; |
||||
|
private List<string> events = new List<string>(); |
||||
|
|
||||
|
public event PropertyChangedEventHandler PropertyChanged |
||||
|
{ |
||||
|
add { _propertyChanged += value; events.Add("added"); } |
||||
|
remove { _propertyChanged -= value; events.Add("removed"); } |
||||
|
} |
||||
|
|
||||
|
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
[NotifyPropertyChangedInvocator] |
|
||||
protected bool RaiseAndSetIfChanged<T>(ref T field, T value, [CallerMemberName] string propertyName = null) |
protected bool RaiseAndSetIfChanged<T>(ref T field, T value, [CallerMemberName] string propertyName = null) |
||||
{ |
{ |
||||
if (!EqualityComparer<T>.Default.Equals(field, value)) |
if (!EqualityComparer<T>.Default.Equals(field, value)) |
||||
{ |
{ |
||||
field = value; |
field = value; |
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); |
RaisePropertyChanged(propertyName); |
||||
return true; |
return true; |
||||
} |
} |
||||
|
|
||||
return false; |
return false; |
||||
} |
} |
||||
|
|
||||
[NotifyPropertyChangedInvocator] |
|
||||
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null) |
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null) |
||||
{ |
{ |
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); |
var e = new PropertyChangedEventArgs(propertyName); |
||||
|
OnPropertyChanged(e); |
||||
|
_propertyChanged?.Invoke(this, e); |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
@ -0,0 +1,56 @@ |
|||||
|
<UserControl xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
x:Class="Avalonia.Diagnostics.Views.ConsoleView"> |
||||
|
<UserControl.Styles> |
||||
|
<Style Selector="TextBox.console"> |
||||
|
<Setter Property="FontFamily" Value="/Assets/Fonts/SourceSansPro-Regular.ttf"/> |
||||
|
<Setter Property="Template"> |
||||
|
<ControlTemplate> |
||||
|
<Border Name="border" |
||||
|
Background="{TemplateBinding Background}" |
||||
|
BorderBrush="{TemplateBinding BorderBrush}" |
||||
|
BorderThickness="{TemplateBinding BorderThickness}"> |
||||
|
<DockPanel Margin="{TemplateBinding Padding}"> |
||||
|
<TextBlock DockPanel.Dock="Left" Margin="0,0,4,0">></TextBlock> |
||||
|
<TextPresenter Name="PART_TextPresenter" |
||||
|
Text="{TemplateBinding Text, Mode=TwoWay}" |
||||
|
CaretIndex="{TemplateBinding CaretIndex}" |
||||
|
SelectionStart="{TemplateBinding SelectionStart}" |
||||
|
SelectionEnd="{TemplateBinding SelectionEnd}" |
||||
|
TextAlignment="{TemplateBinding TextAlignment}" |
||||
|
TextWrapping="{TemplateBinding TextWrapping}" |
||||
|
PasswordChar="{TemplateBinding PasswordChar}"/> |
||||
|
</DockPanel> |
||||
|
</Border> |
||||
|
</ControlTemplate> |
||||
|
</Setter> |
||||
|
</Style> |
||||
|
</UserControl.Styles> |
||||
|
|
||||
|
<DockPanel> |
||||
|
<TextBox Name="input" |
||||
|
Classes="console" |
||||
|
DockPanel.Dock="Bottom" |
||||
|
BorderThickness="0" |
||||
|
Text="{Binding Input}"/> |
||||
|
|
||||
|
<ListBox Name="historyList" |
||||
|
BorderBrush="{DynamicResource ThemeControlMidBrush}" |
||||
|
BorderThickness="0,0,0,1" |
||||
|
FontFamily="/Assets/Fonts/SourceSansPro-Regular.ttf" |
||||
|
Items="{Binding History}" |
||||
|
VirtualizationMode="None"> |
||||
|
<ListBox.ItemTemplate> |
||||
|
<DataTemplate> |
||||
|
<StackPanel Orientation="Vertical"> |
||||
|
<DockPanel> |
||||
|
<TextBlock DockPanel.Dock="Left" Margin="0,0,4,0">></TextBlock> |
||||
|
<TextBlock Text="{Binding Input}"/> |
||||
|
</DockPanel> |
||||
|
<TextBlock Foreground="{Binding Foreground}" Text="{Binding Output}"/> |
||||
|
</StackPanel> |
||||
|
</DataTemplate> |
||||
|
</ListBox.ItemTemplate> |
||||
|
</ListBox> |
||||
|
</DockPanel> |
||||
|
</UserControl> |
||||
@ -0,0 +1,64 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Specialized; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Diagnostics.ViewModels; |
||||
|
using Avalonia.Input; |
||||
|
using Avalonia.LogicalTree; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
using Avalonia.Threading; |
||||
|
|
||||
|
namespace Avalonia.Diagnostics.Views |
||||
|
{ |
||||
|
internal class ConsoleView : UserControl |
||||
|
{ |
||||
|
private readonly ListBox _historyList; |
||||
|
private readonly TextBox _input; |
||||
|
|
||||
|
public ConsoleView() |
||||
|
{ |
||||
|
this.InitializeComponent(); |
||||
|
_historyList = this.FindControl<ListBox>("historyList"); |
||||
|
((ILogical)_historyList).LogicalChildren.CollectionChanged += HistoryChanged; |
||||
|
_input = this.FindControl<TextBox>("input"); |
||||
|
_input.KeyDown += InputKeyDown; |
||||
|
} |
||||
|
|
||||
|
public void FocusInput() => _input.Focus(); |
||||
|
|
||||
|
private void InitializeComponent() |
||||
|
{ |
||||
|
AvaloniaXamlLoader.Load(this); |
||||
|
} |
||||
|
|
||||
|
private void HistoryChanged(object sender, NotifyCollectionChangedEventArgs e) |
||||
|
{ |
||||
|
if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems[0] is IControl control) |
||||
|
{ |
||||
|
DispatcherTimer.RunOnce(control.BringIntoView, TimeSpan.Zero); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void InputKeyDown(object sender, KeyEventArgs e) |
||||
|
{ |
||||
|
var vm = (ConsoleViewModel)DataContext; |
||||
|
|
||||
|
switch (e.Key) |
||||
|
{ |
||||
|
case Key.Enter: |
||||
|
vm.Execute(); |
||||
|
e.Handled = true; |
||||
|
break; |
||||
|
case Key.Up: |
||||
|
vm.HistoryUp(); |
||||
|
_input.CaretIndex = _input.Text.Length; |
||||
|
e.Handled = true; |
||||
|
break; |
||||
|
case Key.Down: |
||||
|
vm.HistoryDown(); |
||||
|
_input.CaretIndex = _input.Text.Length; |
||||
|
e.Handled = true; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
<UserControl xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
xmlns:conv="clr-namespace:Avalonia.Diagnostics.Converters" |
||||
|
x:Class="Avalonia.Diagnostics.Views.ControlDetailsView"> |
||||
|
<Grid ColumnDefinitions="*"> |
||||
|
<DockPanel Grid.Column="0"> |
||||
|
<TextBox DockPanel.Dock="Top" |
||||
|
BorderThickness="0" |
||||
|
Text="{Binding PropertyFilter}" |
||||
|
Watermark="Filter properties"/> |
||||
|
<DataGrid Items="{Binding PropertiesView}" |
||||
|
BorderThickness="0" |
||||
|
RowBackground="Transparent" |
||||
|
SelectedItem="{Binding SelectedProperty, Mode=TwoWay}" |
||||
|
CanUserResizeColumns="true"> |
||||
|
<DataGrid.Columns> |
||||
|
<DataGridTextColumn Header="Property" Binding="{Binding Name}" IsReadOnly="True"/> |
||||
|
<DataGridTextColumn Header="Value" Binding="{Binding Value}"/> |
||||
|
<DataGridTextColumn Header="Type" Binding="{Binding Type}"/> |
||||
|
<DataGridTextColumn Header="Priority" Binding="{Binding Priority}" IsReadOnly="True"/> |
||||
|
</DataGrid.Columns> |
||||
|
</DataGrid> |
||||
|
</DockPanel> |
||||
|
</Grid> |
||||
|
</UserControl> |
||||
@ -0,0 +1,18 @@ |
|||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
|
||||
|
namespace Avalonia.Diagnostics.Views |
||||
|
{ |
||||
|
internal class ControlDetailsView : UserControl |
||||
|
{ |
||||
|
public ControlDetailsView() |
||||
|
{ |
||||
|
InitializeComponent(); |
||||
|
} |
||||
|
|
||||
|
private void InitializeComponent() |
||||
|
{ |
||||
|
AvaloniaXamlLoader.Load(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,9 +1,10 @@ |
|||||
<UserControl xmlns="https://github.com/avaloniaui" |
<UserControl xmlns="https://github.com/avaloniaui" |
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels" |
xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels" |
||||
x:Class="Avalonia.Diagnostics.Views.EventsView"> |
xmlns:conv="clr-namespace:Avalonia.Diagnostics.Converters" |
||||
|
x:Class="Avalonia.Diagnostics.Views.EventsPageView"> |
||||
<UserControl.Resources> |
<UserControl.Resources> |
||||
<vm:BoolToBrushConverter x:Key="boolToBrush" /> |
<conv:BoolToBrushConverter x:Key="boolToBrush" Brush="#d9ffdc"/> |
||||
</UserControl.Resources> |
</UserControl.Resources> |
||||
<Grid ColumnDefinitions="*,4,3*"> |
<Grid ColumnDefinitions="*,4,3*"> |
||||
<TreeView Name="tree" Items="{Binding Nodes}" SelectedItem="{Binding SelectedNode, Mode=TwoWay}" |
<TreeView Name="tree" Items="{Binding Nodes}" SelectedItem="{Binding SelectedNode, Mode=TwoWay}" |
||||
@ -0,0 +1,55 @@ |
|||||
|
<UserControl xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
xmlns:views="clr-namespace:Avalonia.Diagnostics.Views" |
||||
|
x:Class="Avalonia.Diagnostics.Views.MainView"> |
||||
|
<Grid Name="rootGrid" RowDefinitions="Auto,Auto,*,Auto,0,Auto"> |
||||
|
<Menu> |
||||
|
<MenuItem Header="_File"> |
||||
|
<MenuItem Header="E_xit" Command="{Binding $parent[Window].Close}"/> |
||||
|
</MenuItem> |
||||
|
<MenuItem Header="_View"> |
||||
|
<MenuItem Header="_Console" Command="{Binding $parent[UserControl].ToggleConsole}"> |
||||
|
<MenuItem.Icon> |
||||
|
<CheckBox BorderThickness="0" |
||||
|
IsChecked="{Binding Console.IsVisible}" |
||||
|
IsEnabled="False"/> |
||||
|
</MenuItem.Icon> |
||||
|
</MenuItem> |
||||
|
</MenuItem> |
||||
|
</Menu> |
||||
|
|
||||
|
<TabStrip Grid.Row="1" SelectedIndex="{Binding SelectedTab, Mode=TwoWay}"> |
||||
|
<TabStripItem Content="Logical Tree"/> |
||||
|
<TabStripItem Content="Visual Tree"/> |
||||
|
<TabStripItem Content="Events"/> |
||||
|
</TabStrip> |
||||
|
|
||||
|
<ContentControl Grid.Row="2" |
||||
|
BorderBrush="{DynamicResource ThemeControlMidBrush}" |
||||
|
BorderThickness="0,1,0,0" |
||||
|
Content="{Binding Content}"/> |
||||
|
|
||||
|
<GridSplitter Name="consoleSplitter" Grid.Row="3" Height="1" |
||||
|
Background="{DynamicResource ThemeControlMidBrush}" |
||||
|
IsVisible="False"/> |
||||
|
|
||||
|
<views:ConsoleView Name="console" |
||||
|
Grid.Row="4" |
||||
|
DataContext="{Binding Console}" |
||||
|
IsVisible="{Binding IsVisible}"/> |
||||
|
|
||||
|
<Border Grid.Row="5" |
||||
|
BorderBrush="{DynamicResource ThemeControlMidBrush}" |
||||
|
BorderThickness="0,1,0,0"> |
||||
|
<StackPanel Spacing="4" Orientation="Horizontal"> |
||||
|
<TextBlock>Hold Ctrl+Shift over a control to inspect.</TextBlock> |
||||
|
<Separator Width="8"/> |
||||
|
<TextBlock>Focused:</TextBlock> |
||||
|
<TextBlock Text="{Binding FocusedControl}"/> |
||||
|
<Separator Width="8"/> |
||||
|
<TextBlock>Pointer Over:</TextBlock> |
||||
|
<TextBlock Text="{Binding PointerOverElement}"/> |
||||
|
</StackPanel> |
||||
|
</Border> |
||||
|
</Grid> |
||||
|
</UserControl> |
||||
@ -0,0 +1,66 @@ |
|||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Diagnostics.ViewModels; |
||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Interactivity; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
using Avalonia.Threading; |
||||
|
|
||||
|
namespace Avalonia.Diagnostics.Views |
||||
|
{ |
||||
|
internal class MainView : UserControl |
||||
|
{ |
||||
|
private readonly ConsoleView _console; |
||||
|
private readonly GridSplitter _consoleSplitter; |
||||
|
private readonly Grid _rootGrid; |
||||
|
private readonly int _consoleRow; |
||||
|
private double _consoleHeight = -1; |
||||
|
|
||||
|
public MainView() |
||||
|
{ |
||||
|
InitializeComponent(); |
||||
|
AddHandler(KeyDownEvent, PreviewKeyDown, RoutingStrategies.Tunnel); |
||||
|
_console = this.FindControl<ConsoleView>("console"); |
||||
|
_consoleSplitter = this.FindControl<GridSplitter>("consoleSplitter"); |
||||
|
_rootGrid = this.FindControl<Grid>("rootGrid"); |
||||
|
_consoleRow = Grid.GetRow(_console); |
||||
|
} |
||||
|
|
||||
|
public void ToggleConsole() |
||||
|
{ |
||||
|
var vm = (MainViewModel)DataContext; |
||||
|
|
||||
|
if (_consoleHeight == -1) |
||||
|
{ |
||||
|
_consoleHeight = Bounds.Height / 3; |
||||
|
} |
||||
|
|
||||
|
vm.Console.ToggleVisibility(); |
||||
|
_consoleSplitter.IsVisible = vm.Console.IsVisible; |
||||
|
|
||||
|
if (vm.Console.IsVisible) |
||||
|
{ |
||||
|
_rootGrid.RowDefinitions[_consoleRow].Height = new GridLength(_consoleHeight, GridUnitType.Pixel); |
||||
|
Dispatcher.UIThread.Post(() => _console.FocusInput(), DispatcherPriority.Background); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
_consoleHeight = _rootGrid.RowDefinitions[_consoleRow].Height.Value; |
||||
|
_rootGrid.RowDefinitions[_consoleRow].Height = new GridLength(0, GridUnitType.Pixel); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void InitializeComponent() |
||||
|
{ |
||||
|
AvaloniaXamlLoader.Load(this); |
||||
|
} |
||||
|
|
||||
|
private void PreviewKeyDown(object sender, KeyEventArgs e) |
||||
|
{ |
||||
|
if (e.Key == Key.Escape) |
||||
|
{ |
||||
|
ToggleConsole(); |
||||
|
e.Handled = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
<Window xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
xmlns:views="clr-namespace:Avalonia.Diagnostics.Views" |
||||
|
xmlns:diag="clr-namespace:Avalonia.Diagnostics" |
||||
|
Title="Avalonia DevTools" |
||||
|
x:Class="Avalonia.Diagnostics.Views.MainWindow"> |
||||
|
<Window.DataTemplates> |
||||
|
<diag:ViewLocator/> |
||||
|
</Window.DataTemplates> |
||||
|
|
||||
|
<Window.Styles> |
||||
|
<StyleInclude Source="resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default"/> |
||||
|
<StyleInclude Source="resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"/> |
||||
|
<StyleInclude Source="resm:Avalonia.Controls.DataGrid.Themes.Default.xaml?assembly=Avalonia.Controls.DataGrid"/> |
||||
|
</Window.Styles> |
||||
|
|
||||
|
<views:MainView/> |
||||
|
</Window> |
||||
@ -0,0 +1,74 @@ |
|||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Reactive.Linq; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Controls.Primitives; |
||||
|
using Avalonia.Diagnostics.ViewModels; |
||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Input.Raw; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
using Avalonia.Styling; |
||||
|
using Avalonia.VisualTree; |
||||
|
|
||||
|
namespace Avalonia.Diagnostics.Views |
||||
|
{ |
||||
|
internal class MainWindow : Window, IStyleHost |
||||
|
{ |
||||
|
private TopLevel _root; |
||||
|
private IDisposable _keySubscription; |
||||
|
|
||||
|
public MainWindow() |
||||
|
{ |
||||
|
InitializeComponent(); |
||||
|
|
||||
|
_keySubscription = InputManager.Instance.Process |
||||
|
.OfType<RawKeyEventArgs>() |
||||
|
.Subscribe(RawKeyDown); |
||||
|
} |
||||
|
|
||||
|
public TopLevel Root |
||||
|
{ |
||||
|
get => _root; |
||||
|
set |
||||
|
{ |
||||
|
if (_root != value) |
||||
|
{ |
||||
|
_root = value; |
||||
|
DataContext = new MainViewModel(value); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
IStyleHost IStyleHost.StylingParent => null; |
||||
|
|
||||
|
protected override void OnClosed(EventArgs e) |
||||
|
{ |
||||
|
base.OnClosed(e); |
||||
|
_keySubscription.Dispose(); |
||||
|
((MainViewModel)DataContext)?.Dispose(); |
||||
|
} |
||||
|
|
||||
|
private void InitializeComponent() |
||||
|
{ |
||||
|
AvaloniaXamlLoader.Load(this); |
||||
|
} |
||||
|
|
||||
|
private void RawKeyDown(RawKeyEventArgs e) |
||||
|
{ |
||||
|
const RawInputModifiers modifiers = RawInputModifiers.Control | RawInputModifiers.Shift; |
||||
|
|
||||
|
if (e.Modifiers == modifiers) |
||||
|
{ |
||||
|
var point = (Root as IInputRoot)?.MouseDevice?.GetPosition(Root) ?? default; |
||||
|
var control = Root.GetVisualsAt(point, x => (!(x is AdornerLayer) && x.IsVisible)) |
||||
|
.FirstOrDefault(); |
||||
|
|
||||
|
if (control != null) |
||||
|
{ |
||||
|
var vm = (MainViewModel)DataContext; |
||||
|
vm.SelectControl((IControl)control); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,26 +1,29 @@ |
|||||
<UserControl xmlns="https://github.com/avaloniaui" |
<UserControl xmlns="https://github.com/avaloniaui" |
||||
xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels" |
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels" |
||||
x:Class="Avalonia.Diagnostics.Views.TreePageView"> |
x:Class="Avalonia.Diagnostics.Views.TreePageView"> |
||||
<Grid ColumnDefinitions="*,Auto,3*"> |
<Grid ColumnDefinitions="*,4,3*"> |
||||
<TreeView Name="tree" Items="{Binding Nodes}" SelectedItem="{Binding SelectedNode, Mode=TwoWay}"> |
<TreeView Name="tree" |
||||
|
BorderThickness="0" |
||||
|
Items="{Binding Nodes}" |
||||
|
SelectedItem="{Binding SelectedNode, Mode=TwoWay}"> |
||||
<TreeView.DataTemplates> |
<TreeView.DataTemplates> |
||||
<TreeDataTemplate DataType="vm:TreeNode" |
<TreeDataTemplate DataType="vm:TreeNode" |
||||
ItemsSource="{Binding Children}"> |
ItemsSource="{Binding Children}"> |
||||
<StackPanel Orientation="Horizontal" Spacing="8"> |
<StackPanel Orientation="Horizontal" Spacing="8"> |
||||
<TextBlock Text="{Binding Type}" /> |
<TextBlock Text="{Binding Type}"/> |
||||
<TextBlock Text="{Binding Classes}" /> |
<TextBlock Text="{Binding Classes}"/> |
||||
</StackPanel> |
</StackPanel> |
||||
</TreeDataTemplate> |
</TreeDataTemplate> |
||||
</TreeView.DataTemplates> |
</TreeView.DataTemplates> |
||||
<TreeView.Styles> |
<TreeView.Styles> |
||||
<Style Selector="TreeViewItem"> |
<Style Selector="TreeViewItem"> |
||||
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" /> |
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/> |
||||
</Style> |
</Style> |
||||
</TreeView.Styles> |
</TreeView.Styles> |
||||
</TreeView> |
</TreeView> |
||||
|
|
||||
<GridSplitter Grid.Column="1" /> |
<GridSplitter Background="{DynamicResource ThemeControlMidBrush}" Width="1" Grid.Column="1"/> |
||||
<ContentControl Content="{Binding Details}" Grid.Column="2" /> |
<ContentControl Content="{Binding Details}" Grid.Column="2"/> |
||||
</Grid> |
</Grid> |
||||
</UserControl> |
</UserControl> |
||||
@ -1,25 +0,0 @@ |
|||||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|
||||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|
||||
|
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq; |
|
||||
using Avalonia.VisualTree; |
|
||||
|
|
||||
namespace Avalonia.Diagnostics.ViewModels |
|
||||
{ |
|
||||
internal class ControlDetailsViewModel : ViewModelBase |
|
||||
{ |
|
||||
public ControlDetailsViewModel(IVisual control) |
|
||||
{ |
|
||||
if (control is AvaloniaObject avaloniaObject) |
|
||||
{ |
|
||||
Properties = AvaloniaPropertyRegistry.Instance.GetRegistered(avaloniaObject) |
|
||||
.Select(x => new PropertyDetails(avaloniaObject, x)) |
|
||||
.OrderBy(x => x.IsAttached) |
|
||||
.ThenBy(x => x.Name); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public IEnumerable<PropertyDetails> Properties { get; } |
|
||||
} |
|
||||
} |
|
||||
@ -1,76 +0,0 @@ |
|||||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|
||||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.ObjectModel; |
|
||||
using System.Linq; |
|
||||
using Avalonia.Controls; |
|
||||
using Avalonia.Input; |
|
||||
|
|
||||
namespace Avalonia.Diagnostics.ViewModels |
|
||||
{ |
|
||||
internal class DevToolsViewModel : ViewModelBase |
|
||||
{ |
|
||||
private IDevToolViewModel _selectedTool; |
|
||||
private string _focusedControl; |
|
||||
private string _pointerOverElement; |
|
||||
|
|
||||
public DevToolsViewModel(IControl root) |
|
||||
{ |
|
||||
Tools = new ObservableCollection<IDevToolViewModel> |
|
||||
{ |
|
||||
new TreePageViewModel(LogicalTreeNode.Create(root), "Logical Tree"), |
|
||||
new TreePageViewModel(VisualTreeNode.Create(root), "Visual Tree"), |
|
||||
new EventsViewModel(root) |
|
||||
}; |
|
||||
|
|
||||
SelectedTool = Tools.First(); |
|
||||
|
|
||||
UpdateFocusedControl(); |
|
||||
|
|
||||
KeyboardDevice.Instance.PropertyChanged += (s, e) => |
|
||||
{ |
|
||||
if (e.PropertyName == nameof(KeyboardDevice.Instance.FocusedElement)) |
|
||||
{ |
|
||||
UpdateFocusedControl(); |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
root.GetObservable(TopLevel.PointerOverElementProperty) |
|
||||
.Subscribe(x => PointerOverElement = x?.GetType().Name); |
|
||||
} |
|
||||
|
|
||||
public IDevToolViewModel SelectedTool |
|
||||
{ |
|
||||
get => _selectedTool; |
|
||||
set => RaiseAndSetIfChanged(ref _selectedTool, value); |
|
||||
} |
|
||||
|
|
||||
public ObservableCollection<IDevToolViewModel> Tools { get; } |
|
||||
|
|
||||
public string FocusedControl |
|
||||
{ |
|
||||
get => _focusedControl; |
|
||||
private set => RaiseAndSetIfChanged(ref _focusedControl, value); |
|
||||
} |
|
||||
|
|
||||
public string PointerOverElement |
|
||||
{ |
|
||||
get => _pointerOverElement; |
|
||||
private set => RaiseAndSetIfChanged(ref _pointerOverElement, value); |
|
||||
} |
|
||||
|
|
||||
public void SelectControl(IControl control) |
|
||||
{ |
|
||||
if (SelectedTool is TreePageViewModel tree) |
|
||||
{ |
|
||||
tree.SelectControl(control); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void UpdateFocusedControl() |
|
||||
{ |
|
||||
FocusedControl = KeyboardDevice.Instance.FocusedElement?.GetType().Name; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,16 +0,0 @@ |
|||||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|
||||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|
||||
|
|
||||
namespace Avalonia.Diagnostics.ViewModels |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// View model interface for tool showing up in DevTools
|
|
||||
/// </summary>
|
|
||||
public interface IDevToolViewModel |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Name of a tool.
|
|
||||
/// </summary>
|
|
||||
string Name { get; } |
|
||||
} |
|
||||
} |
|
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue