diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b8a17d..8ab37f0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,6 +38,8 @@ set(SrcMacOSX src/macosx/OSXWindow.m src/macosx/OSXWindowFrameView.h src/macosx/OSXWindowFrameView.m + src/macosx/OSXViewController.h + src/macosx/OSXViewController.m src/macosx/WindowData_OSX.h ) diff --git a/src/macosx/MacMiniFB.m b/src/macosx/MacMiniFB.m index b884f08..17679ad 100644 --- a/src/macosx/MacMiniFB.m +++ b/src/macosx/MacMiniFB.m @@ -1,5 +1,6 @@ #include "OSXWindow.h" #include "OSXWindowFrameView.h" +#include "OSXViewController.h" #include "WindowData_OSX.h" #include #include @@ -15,102 +16,6 @@ void init_keycodes(); -//------------------------------------- -#if defined(USE_METAL_API) - -id g_metal_device = nil; -id g_library = nil; - -//------------------------------------- -#define kShader(inc, src) @inc#src - -NSString *g_shader_src = kShader( - "#include \n", - using namespace metal; - - //--------------------- - struct VertexOutput { - float4 pos [[position]]; - float2 texcoord; - }; - - struct Vertex { - float4 position [[position]]; - }; - - //--------------------- - vertex VertexOutput - vertFunc(unsigned int vID[[vertex_id]], const device Vertex *pos [[ buffer(0) ]]) { - VertexOutput out; - - out.pos = pos[vID].position; - - out.texcoord.x = (float) (vID / 2); - out.texcoord.y = 1.0 - (float) (vID % 2); - - return out; - } - - //--------------------- - fragment float4 - fragFunc(VertexOutput input [[stage_in]], texture2d colorTexture [[ texture(0) ]]) { - constexpr sampler textureSampler(mag_filter::nearest, min_filter::nearest); - - // Sample the texture to obtain a color - const half4 colorSample = colorTexture.sample(textureSampler, input.texcoord); - - // We return the color of the texture - return float4(colorSample); - }; -); - -#endif - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#if defined(USE_METAL_API) -static bool -create_shaders(SWindowData_OSX *window_data_osx) { - NSError *error = 0x0; - - id g_library = [g_metal_device newLibraryWithSource:g_shader_src - options:[[MTLCompileOptions alloc] init] - error:&error - ]; - if (error || !g_library) { - NSLog(@"Unable to create shaders %@", error); - return false; - } - - id vertex_shader_func = [g_library newFunctionWithName:@"vertFunc"]; - id fragment_shader_func = [g_library newFunctionWithName:@"fragFunc"]; - - if (!vertex_shader_func) { - NSLog(@"Unable to get vertFunc!\n"); - return false; - } - - if (!fragment_shader_func) { - NSLog(@"Unable to get fragFunc!\n"); - return false; - } - - // Create a reusable pipeline state - MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; - pipelineStateDescriptor.label = @"MiniFB_pipeline"; - pipelineStateDescriptor.vertexFunction = vertex_shader_func; - pipelineStateDescriptor.fragmentFunction = fragment_shader_func; - pipelineStateDescriptor.colorAttachments[0].pixelFormat = 80; //bgra8Unorm; - - window_data_osx->metal.pipeline_state = [g_metal_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor error:&error]; - if (!window_data_osx->metal.pipeline_state) { - NSLog(@"Failed to created pipeline state, error %@", error); - } - - return true; -} -#endif - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// struct mfb_window * @@ -200,47 +105,10 @@ mfb_open_ex(const char *title, unsigned width, unsigned height, unsigned flags) } #if defined(USE_METAL_API) - g_metal_device = MTLCreateSystemDefaultDevice(); - if (!g_metal_device) { - NSLog(@"Metal is not supported on this device"); - return 0x0; - } - - if (!create_shaders((SWindowData_OSX *) window_data->specific)) { - return 0x0; - } - - static Vertex s_vertices[4] = { - {-1.0, -1.0, 0, 1}, - {-1.0, 1.0, 0, 1}, - { 1.0, -1.0, 0, 1}, - { 1.0, 1.0, 0, 1}, - }; - memcpy(window_data_osx->metal.vertices, s_vertices, sizeof(s_vertices)); - - // Setup command queue - window_data_osx->metal.command_queue = [g_metal_device newCommandQueue]; - - WindowViewController* viewController = [WindowViewController new]; - - // Indicate that each pixel has a blue, green, red, and alpha channel, where each channel is - // an 8-bit unsigned normalized value (i.e. 0 maps to 0.0 and 255 maps to 1.0) - MTLTextureDescriptor *td; - td = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm - width:width - height:height - mipmapped:false]; - - // Create the texture from the device by using the descriptor - for (size_t i = 0; i < MaxBuffersInFlight; ++i) { - viewController->texture_buffers[i] = [g_metal_device newTextureWithDescriptor:td]; - } - - // Used for syncing the CPU and GPU - viewController->semaphore = dispatch_semaphore_create(MaxBuffersInFlight); + OSXViewController *viewController = [[OSXViewController alloc] initWithWindowData:window_data]; MTKView* view = [[MTKView alloc] initWithFrame:rectangle]; - view.device = g_metal_device; + view.device = viewController->metal_device; view.delegate = viewController; view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; [window_data_osx->window.contentView addSubview:view]; diff --git a/src/macosx/OSXViewController.h b/src/macosx/OSXViewController.h new file mode 100644 index 0000000..a19734f --- /dev/null +++ b/src/macosx/OSXViewController.h @@ -0,0 +1,27 @@ +#pragma once + +#if defined(USE_METAL_API) +#include "WindowData_OSX.h" +#import + +// Number of textures in flight (tripple buffered) +enum { MaxBuffersInFlight = 3 }; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface OSXViewController : NSViewController +{ + @public SWindowData *window_data; + @public SWindowData_OSX *window_data_osx; + + @public id metal_device; + @public id metal_library; + @public id texture_buffers[MaxBuffersInFlight]; + @public int current_buffer; + @public dispatch_semaphore_t semaphore; // Used for syncing with CPU/GPU +} + +- (id) initWithWindowData:(SWindowData *) windowData; + +@end +#endif diff --git a/src/macosx/OSXViewController.m b/src/macosx/OSXViewController.m new file mode 100644 index 0000000..766d921 --- /dev/null +++ b/src/macosx/OSXViewController.m @@ -0,0 +1,212 @@ +#include "OSXViewController.h" + +#if defined(USE_METAL_API) +#import + +//------------------------------------- +#define kShader(inc, src) @inc#src + +NSString *g_shader_src = kShader( + "#include \n", + using namespace metal; + + //------------- + struct VertexOutput { + float4 pos [[position]]; + float2 texcoord; + }; + + //------------- + struct Vertex { + float4 position [[position]]; + }; + + //------------- + vertex VertexOutput + vertFunc(unsigned int vID[[vertex_id]], const device Vertex *pos [[ buffer(0) ]]) { + VertexOutput out; + + out.pos = pos[vID].position; + + out.texcoord.x = (float) (vID / 2); + out.texcoord.y = 1.0 - (float) (vID % 2); + + return out; + } + + //------------- + fragment float4 + fragFunc(VertexOutput input [[stage_in]], texture2d colorTexture [[ texture(0) ]]) { + constexpr sampler textureSampler(mag_filter::nearest, min_filter::nearest); + + // Sample the texture to obtain a color + const half4 colorSample = colorTexture.sample(textureSampler, input.texcoord); + + // We return the color of the texture + return float4(colorSample); + }; +); + +//------------------------------------- +@implementation OSXViewController + +//------------------------------------- +- (id) initWithWindowData:(SWindowData *) windowData { + self = [super init]; + if (self) { + window_data = windowData; + window_data_osx = (SWindowData_OSX *) windowData->specific; + + metal_device = MTLCreateSystemDefaultDevice(); + if (!metal_device) { + NSLog(@"Metal is not supported on this device"); + return 0x0; + } + + [self _create_shaders]; + [self _createAssets]; + + // Used for syncing the CPU and GPU + semaphore = dispatch_semaphore_create(MaxBuffersInFlight); + + // Setup command queue + window_data_osx->metal.command_queue = [metal_device newCommandQueue]; + } + return self; +} + +//------------------------------------- +- (bool) _create_shaders { + NSError *error = 0x0; + + metal_library = [metal_device newLibraryWithSource:g_shader_src + options:[[MTLCompileOptions alloc] init] + error:&error + ]; + if (error || !metal_library) { + NSLog(@"Unable to create shaders %@", error); + return false; + } + + id vertex_shader_func = [metal_library newFunctionWithName:@"vertFunc"]; + id fragment_shader_func = [metal_library newFunctionWithName:@"fragFunc"]; + + if (!vertex_shader_func) { + NSLog(@"Unable to get vertFunc!\n"); + return false; + } + + if (!fragment_shader_func) { + NSLog(@"Unable to get fragFunc!\n"); + return false; + } + + // Create a reusable pipeline state + MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; + pipelineStateDescriptor.label = @"MiniFB_pipeline"; + pipelineStateDescriptor.vertexFunction = vertex_shader_func; + pipelineStateDescriptor.fragmentFunction = fragment_shader_func; + pipelineStateDescriptor.colorAttachments[0].pixelFormat = 80; //bgra8Unorm; + + window_data_osx->metal.pipeline_state = [metal_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor error:&error]; + if (!window_data_osx->metal.pipeline_state) { + NSLog(@"Failed to created pipeline state, error %@", error); + } + + return true; +} + +//------------------------------------- +- (void) _createAssets { + static Vertex s_vertices[4] = { + {-1.0, -1.0, 0, 1}, + {-1.0, 1.0, 0, 1}, + { 1.0, -1.0, 0, 1}, + { 1.0, 1.0, 0, 1}, + }; + memcpy(window_data_osx->metal.vertices, s_vertices, sizeof(s_vertices)); + + // Indicate that each pixel has a blue, green, red, and alpha channel, where each channel is + // an 8-bit unsigned normalized value (i.e. 0 maps to 0.0 and 255 maps to 1.0) + MTLTextureDescriptor *td; + td = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm + width:window_data->buffer_width + height:window_data->buffer_height + mipmapped:false]; + + // Create the texture from the device by using the descriptor + for (size_t i = 0; i < MaxBuffersInFlight; ++i) { + texture_buffers[i] = [metal_device newTextureWithDescriptor:td]; + } +} + +//------------------------------------- +- (void) drawInMTKView:(nonnull MTKView *)view { + // Wait to ensure only MaxBuffersInFlight number of frames are getting proccessed + // by any stage in the Metal pipeline (App, Metal, Drivers, GPU, etc) + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); + + // Iterate through our Metal buffers, and cycle back to the first when we've written to MaxBuffersInFlight + current_buffer = (current_buffer + 1) % MaxBuffersInFlight; + + // Calculate the number of bytes per row of our image. + MTLRegion region = { { 0, 0, 0 }, { window_data->buffer_width, window_data->buffer_height, 1 } }; + + // Copy the bytes from our data object into the texture + [texture_buffers[current_buffer] replaceRegion:region mipmapLevel:0 withBytes:window_data->draw_buffer bytesPerRow:window_data->buffer_stride]; + + // Create a new command buffer for each render pass to the current drawable + id commandBuffer = [window_data_osx->metal.command_queue commandBuffer]; + commandBuffer.label = @"minifb_command_buffer"; + + // Add completion hander which signals _inFlightSemaphore when Metal and the GPU has fully + // finished processing the commands we're encoding this frame. This indicates when the + // dynamic buffers filled with our vertices, that we're writing to this frame, will no longer + // be needed by Metal and the GPU, meaning we can overwrite the buffer contents without + // corrupting the rendering. + __block dispatch_semaphore_t block_sema = semaphore; + [commandBuffer addCompletedHandler:^(id buffer) + { + (void)buffer; + dispatch_semaphore_signal(block_sema); + }]; + + MTLRenderPassDescriptor* renderPassDescriptor = view.currentRenderPassDescriptor; + if (renderPassDescriptor != nil) { + renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.0, 0.0, 1.0); + + // Create a render command encoder so we can render into something + id renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; + renderEncoder.label = @"minifb_command_encoder"; + + // Set render command encoder state + [renderEncoder setRenderPipelineState:window_data_osx->metal.pipeline_state]; + + [renderEncoder setVertexBytes:window_data_osx->metal.vertices length:sizeof(window_data_osx->metal.vertices) atIndex:0]; + + [renderEncoder setFragmentTexture:texture_buffers[current_buffer] atIndex:0]; + + // Draw the vertices of our quads + [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; + + // We're done encoding commands + [renderEncoder endEncoding]; + + // Schedule a present once the framebuffer is complete using the current drawable + [commandBuffer presentDrawable:view.currentDrawable]; + } + + // Finalize rendering here & push the command buffer to the GPU + [commandBuffer commit]; +} + +//------------------------------------- +- (void) mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size { + (void)view; + (void)size; + // resize +} + +@end + +#endif \ No newline at end of file diff --git a/src/macosx/OSXWindowFrameView.h b/src/macosx/OSXWindowFrameView.h index 8636e09..fe97db8 100644 --- a/src/macosx/OSXWindowFrameView.h +++ b/src/macosx/OSXWindowFrameView.h @@ -2,26 +2,6 @@ #include "WindowData.h" -#if defined(USE_METAL_API) -#import - -// Number of textures in flight (tripple buffered) -enum { MaxBuffersInFlight = 3 }; - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@interface WindowViewController : NSViewController -{ - @public id texture_buffers[MaxBuffersInFlight]; - @public int current_buffer; - @public dispatch_semaphore_t semaphore; // Used for syncing with CPU/GPU -} - -@end -#endif - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - @interface OSXWindowFrameView : NSView { @public SWindowData *window_data; diff --git a/src/macosx/OSXWindowFrameView.m b/src/macosx/OSXWindowFrameView.m index 8f1682f..e6c419e 100644 --- a/src/macosx/OSXWindowFrameView.m +++ b/src/macosx/OSXWindowFrameView.m @@ -1,98 +1,14 @@ #import "OSXWindowFrameView.h" #import "OSXWindow.h" -#include "WindowData_OSX.h" #include -#if defined(USE_METAL_API) -#import - -@implementation WindowViewController - -- (void) drawInMTKView:(nonnull MTKView *)view { - OSXWindow *window = (OSXWindow *) view.window; - SWindowData *window_data = window->window_data; - if(window_data == 0x0) { - return; - } - - // Wait to ensure only MaxBuffersInFlight number of frames are getting proccessed - // by any stage in the Metal pipeline (App, Metal, Drivers, GPU, etc) - dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); - - // Iterate through our Metal buffers, and cycle back to the first when we've written to MaxBuffersInFlight - current_buffer = (current_buffer + 1) % MaxBuffersInFlight; - - // Calculate the number of bytes per row of our image. - MTLRegion region = { { 0, 0, 0 }, { window_data->buffer_width, window_data->buffer_height, 1 } }; - - // Copy the bytes from our data object into the texture - [texture_buffers[current_buffer] replaceRegion:region mipmapLevel:0 withBytes:window_data->draw_buffer bytesPerRow:window_data->buffer_stride]; - - // Create a new command buffer for each render pass to the current drawable - SWindowData_OSX *window_data_osx = (SWindowData_OSX *) window->window_data->specific; - id commandBuffer = [window_data_osx->metal.command_queue commandBuffer]; - commandBuffer.label = @"minifb_command_buffer"; - - // Add completion hander which signals _inFlightSemaphore when Metal and the GPU has fully - // finished processing the commands we're encoding this frame. This indicates when the - // dynamic buffers filled with our vertices, that we're writing to this frame, will no longer - // be needed by Metal and the GPU, meaning we can overwrite the buffer contents without - // corrupting the rendering. - __block dispatch_semaphore_t block_sema = semaphore; - [commandBuffer addCompletedHandler:^(id buffer) - { - (void)buffer; - dispatch_semaphore_signal(block_sema); - }]; - - MTLRenderPassDescriptor* renderPassDescriptor = view.currentRenderPassDescriptor; - if (renderPassDescriptor != nil) { - renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.0, 0.0, 1.0); - - // Create a render command encoder so we can render into something - id renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; - renderEncoder.label = @"minifb_command_encoder"; - - // Set render command encoder state - OSXWindow *window = (OSXWindow *) view.window; - SWindowData_OSX *window_data_osx = (SWindowData_OSX *) window->window_data->specific; - [renderEncoder setRenderPipelineState:window_data_osx->metal.pipeline_state]; - - [renderEncoder setVertexBytes:window_data_osx->metal.vertices length:sizeof(window_data_osx->metal.vertices) atIndex:0]; - - [renderEncoder setFragmentTexture:texture_buffers[current_buffer] atIndex:0]; - - // Draw the vertices of our quads - [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; - - // We're done encoding commands - [renderEncoder endEncoding]; - - // Schedule a present once the framebuffer is complete using the current drawable - [commandBuffer presentDrawable:view.currentDrawable]; - } - - // Finalize rendering here & push the command buffer to the GPU - [commandBuffer commit]; -} - -- (void) mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size { - (void)view; - (void)size; - // resize -} - -@end -#endif - +//------------------------------------- @implementation OSXWindowFrameView -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - #if defined(USE_METAL_API) -- (void)updateTrackingAreas -{ +//------------------------------------- +- (void)updateTrackingAreas { if(tracking_area != nil) { [self removeTrackingArea:tracking_area]; [tracking_area release]; @@ -108,10 +24,8 @@ #else -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (NSRect)resizeRect -{ +//------------------------------------- +- (NSRect)resizeRect { const CGFloat resizeBoxSize = 16.0; const CGFloat contentViewPadding = 5.5; @@ -126,10 +40,8 @@ return resizeRect; } -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)drawRect:(NSRect)rect -{ +//------------------------------------- +- (void)drawRect:(NSRect)rect { (void)rect; if(window_data == 0x0) @@ -162,104 +74,87 @@ CGImageRelease(img); } + #endif -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (BOOL)acceptsFirstMouse:(NSEvent *)event -{ +//------------------------------------- +- (BOOL)acceptsFirstMouse:(NSEvent *)event { (void)event; return YES; } -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)mouseDown:(NSEvent*)event -{ +//------------------------------------- +- (void)mouseDown:(NSEvent*)event { (void)event; if(window_data != 0x0) { kCall(mouse_btn_func, MOUSE_BTN_1, window_data->mod_keys, true); } } -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)mouseUp:(NSEvent*)event -{ +//------------------------------------- +- (void)mouseUp:(NSEvent*)event { (void)event; if(window_data != 0x0) { kCall(mouse_btn_func, MOUSE_BTN_1, window_data->mod_keys, false); } } -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)rightMouseDown:(NSEvent*)event -{ +//------------------------------------- +- (void)rightMouseDown:(NSEvent*)event { (void)event; if(window_data != 0x0) { kCall(mouse_btn_func, MOUSE_BTN_2, window_data->mod_keys, true); } } -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)rightMouseUp:(NSEvent*)event -{ +//------------------------------------- +- (void)rightMouseUp:(NSEvent*)event { (void)event; if(window_data != 0x0) { kCall(mouse_btn_func, MOUSE_BTN_1, window_data->mod_keys, false); } } -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)otherMouseDown:(NSEvent *)event -{ +//------------------------------------- +- (void)otherMouseDown:(NSEvent *)event { (void)event; if(window_data != 0x0) { kCall(mouse_btn_func, [event buttonNumber], window_data->mod_keys, true); } } -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)otherMouseUp:(NSEvent *)event -{ +//------------------------------------- +- (void)otherMouseUp:(NSEvent *)event { (void)event; if(window_data != 0x0) { kCall(mouse_btn_func, [event buttonNumber], window_data->mod_keys, false); } } -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)scrollWheel:(NSEvent *)event -{ +//------------------------------------- +- (void)scrollWheel:(NSEvent *)event { if(window_data != 0x0) { kCall(mouse_wheel_func, window_data->mod_keys, [event deltaX], [event deltaY]); } } -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)mouseDragged:(NSEvent *)event -{ +//------------------------------------- +- (void)mouseDragged:(NSEvent *)event { [self mouseMoved:event]; } -- (void)rightMouseDragged:(NSEvent *)event -{ +//------------------------------------- +- (void)rightMouseDragged:(NSEvent *)event { [self mouseMoved:event]; } -- (void)otherMouseDragged:(NSEvent *)event -{ +//------------------------------------- +- (void)otherMouseDragged:(NSEvent *)event { [self mouseMoved:event]; } -- (void)mouseMoved:(NSEvent *)event -{ +//------------------------------------- +- (void)mouseMoved:(NSEvent *)event { if(window_data != 0x0) { NSPoint point = [event locationInWindow]; //NSPoint localPoint = [self convertPoint:point fromView:nil]; @@ -269,60 +164,44 @@ } } -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)mouseExited:(NSEvent *)event -{ +//------------------------------------- +- (void)mouseExited:(NSEvent *)event { (void)event; //printf("mouse exit\n"); } -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)mouseEntered:(NSEvent *)event -{ +//------------------------------------- +- (void)mouseEntered:(NSEvent *)event { (void)event; //printf("mouse enter\n"); } -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (BOOL)canBecomeKeyView -{ +//------------------------------------- +- (BOOL)canBecomeKeyView { return YES; } -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (NSView *)nextValidKeyView -{ +//------------------------------------- +- (NSView *)nextValidKeyView { return self; } -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (NSView *)previousValidKeyView -{ +//------------------------------------- +- (NSView *)previousValidKeyView { return self; } -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (BOOL)acceptsFirstResponder -{ +//------------------------------------- +- (BOOL)acceptsFirstResponder { return YES; } -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)viewDidMoveToWindow -{ +//------------------------------------- +- (void)viewDidMoveToWindow { } -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)dealloc -{ +//------------------------------------- +- (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; [super dealloc]; }