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.
|
|||
// 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.ComponentModel; |
|||
using System.Runtime.CompilerServices; |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace Avalonia.Diagnostics.ViewModels |
|||
{ |
|||
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) |
|||
{ |
|||
if (!EqualityComparer<T>.Default.Equals(field, value)) |
|||
{ |
|||
field = value; |
|||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); |
|||
RaisePropertyChanged(propertyName); |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
[NotifyPropertyChangedInvocator] |
|||
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" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
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> |
|||
<vm:BoolToBrushConverter x:Key="boolToBrush" /> |
|||
<conv:BoolToBrushConverter x:Key="boolToBrush" Brush="#d9ffdc"/> |
|||
</UserControl.Resources> |
|||
<Grid ColumnDefinitions="*,4,3*"> |
|||
<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" |
|||
xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels" |
|||
x:Class="Avalonia.Diagnostics.Views.TreePageView"> |
|||
<Grid ColumnDefinitions="*,Auto,3*"> |
|||
<TreeView Name="tree" Items="{Binding Nodes}" SelectedItem="{Binding SelectedNode, Mode=TwoWay}"> |
|||
<Grid ColumnDefinitions="*,4,3*"> |
|||
<TreeView Name="tree" |
|||
BorderThickness="0" |
|||
Items="{Binding Nodes}" |
|||
SelectedItem="{Binding SelectedNode, Mode=TwoWay}"> |
|||
<TreeView.DataTemplates> |
|||
<TreeDataTemplate DataType="vm:TreeNode" |
|||
ItemsSource="{Binding Children}"> |
|||
<StackPanel Orientation="Horizontal" Spacing="8"> |
|||
<TextBlock Text="{Binding Type}" /> |
|||
<TextBlock Text="{Binding Classes}" /> |
|||
<TextBlock Text="{Binding Type}"/> |
|||
<TextBlock Text="{Binding Classes}"/> |
|||
</StackPanel> |
|||
</TreeDataTemplate> |
|||
</TreeView.DataTemplates> |
|||
<TreeView.Styles> |
|||
<Style Selector="TreeViewItem"> |
|||
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" /> |
|||
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/> |
|||
</Style> |
|||
</TreeView.Styles> |
|||
</TreeView> |
|||
|
|||
<GridSplitter Grid.Column="1" /> |
|||
<ContentControl Content="{Binding Details}" Grid.Column="2" /> |
|||
<GridSplitter Background="{DynamicResource ThemeControlMidBrush}" Width="1" Grid.Column="1"/> |
|||
<ContentControl Content="{Binding Details}" Grid.Column="2"/> |
|||
</Grid> |
|||
</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