diff --git a/Cocoa/ContinueTemplate.png b/Cocoa/ContinueTemplate.png new file mode 100644 index 0000000..eb72962 Binary files /dev/null and b/Cocoa/ContinueTemplate.png differ diff --git a/Cocoa/ContinueTemplate@2x.png b/Cocoa/ContinueTemplate@2x.png new file mode 100644 index 0000000..586ab3d Binary files /dev/null and b/Cocoa/ContinueTemplate@2x.png differ diff --git a/Cocoa/Document.h b/Cocoa/Document.h index 19fe0fe..322fc3d 100644 --- a/Cocoa/Document.h +++ b/Cocoa/Document.h @@ -4,6 +4,8 @@ #import "GBSplitView.h" #import "GBVisualizerView.h" #import "GBOSDView.h" +#import "GBOptionalVisualEffectView.h" +#import "GBDebuggerButton.h" @class GBCheatWindowController; @class GBPaletteView; @@ -58,6 +60,13 @@ @property uint8_t oamHeight; @property (strong) IBOutlet NSView *audioRecordingAccessoryView; @property (strong) IBOutlet NSPopUpButton *audioFormatButton; +@property (strong) IBOutlet GBOptionalVisualEffectView *debuggerSidebarEffectView; + +@property (strong) IBOutlet GBDebuggerButton *debuggerContinueButton; +@property (strong) IBOutlet GBDebuggerButton *debuggerNextButton; +@property (strong) IBOutlet GBDebuggerButton *debuggerStepButton; +@property (strong) IBOutlet GBDebuggerButton *debuggerFinishButton; + + (NSImage *) imageFromData:(NSData *)data width:(NSUInteger) width height:(NSUInteger) height scale:(double) scale; -(uint8_t) readMemory:(uint16_t) addr; diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 5dfe345..3f99b38 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -74,7 +74,7 @@ enum model { bool fullScreen; bool in_sync_input; - bool _debuggerCommandWhilePaused; + NSString *_debuggerCommandWhilePaused; HFController *hex_controller; NSString *lastConsoleInput; @@ -549,6 +549,10 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) - (void) start { + dispatch_async(dispatch_get_main_queue(), ^{ + [self updateDebuggerButtons]; + [slave updateDebuggerButtons]; + }); self.gbsPlayPauseButton.state = true; self.view.mouseHidingEnabled = (self.mainWindow.styleMask & NSWindowStyleMaskFullScreen) != 0; if (master) { @@ -562,6 +566,10 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) - (void) stop { + dispatch_async(dispatch_get_main_queue(), ^{ + [self updateDebuggerButtons]; + [slave updateDebuggerButtons]; + }); self.gbsPlayPauseButton.state = false; if (master) { if (!master->running) return; @@ -1151,16 +1159,21 @@ static bool is_path_writeable(const char *path) [[NSUserDefaults standardUserDefaults] setBool:!_audioClient.isPlaying forKey:@"Mute"]; } +- (bool) isPaused +{ + if (self.partner) { + return !self.partner->running || GB_debugger_is_stopped(&gb) || GB_debugger_is_stopped(&self.partner->gb); + } + return (!running) || GB_debugger_is_stopped(&gb); +} + - (BOOL)validateUserInterfaceItem:(id)anItem { if ([anItem action] == @selector(mute:)) { [(NSMenuItem *)anItem setState:!_audioClient.isPlaying]; } else if ([anItem action] == @selector(togglePause:)) { - if (master) { - [(NSMenuItem *)anItem setState:(!master->running) || (GB_debugger_is_stopped(&gb)) || (GB_debugger_is_stopped(&gb))]; - } - [(NSMenuItem *)anItem setState:(!running) || (GB_debugger_is_stopped(&gb))]; + [(NSMenuItem *)anItem setState:self.isPaused]; return !GB_debugger_is_stopped(&gb); } else if ([anItem action] == @selector(reset:) && anItem.tag != MODEL_NONE) { @@ -1322,15 +1335,27 @@ static bool is_path_writeable(const char *path) [self.consoleWindow orderBack:nil]; } -- (IBAction)consoleInput:(NSTextField *)sender +- (void)queueDebuggerCommand:(NSString *)command { if (!master && !running && !GB_debugger_is_stopped(&gb)) { - _debuggerCommandWhilePaused = true; + _debuggerCommandWhilePaused = command; GB_debugger_break(&gb); [self start]; return; } - + + if (!in_sync_input) { + [self log:">"]; + } + [self log:[command UTF8String]]; + [self log:"\n"]; + [has_debugger_input lock]; + [debugger_input_queue addObject:command]; + [has_debugger_input unlockWithCondition:1]; +} + +- (IBAction)consoleInput:(NSTextField *)sender +{ NSString *line = [sender stringValue]; if ([line isEqualToString:@""] && lastConsoleInput) { line = lastConsoleInput; @@ -1341,15 +1366,8 @@ static bool is_path_writeable(const char *path) else { line = @""; } - - if (!in_sync_input) { - [self log:">"]; - } - [self log:[line UTF8String]]; - [self log:"\n"]; - [has_debugger_input lock]; - [debugger_input_queue addObject:line]; - [has_debugger_input unlockWithCondition:1]; + + [self queueDebuggerCommand: line]; [sender setStringValue:@""]; } @@ -1397,7 +1415,7 @@ static bool is_path_writeable(const char *path) [console_output_lock unlock]; } -- (char *) getDebuggerInput +- (char *)getDebuggerInput { bool isPlaying = _audioClient.isPlaying; if (isPlaying) { @@ -1408,11 +1426,16 @@ static bool is_path_writeable(const char *path) [audioLock unlock]; in_sync_input = true; [self updateSideView]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self updateDebuggerButtons]; + }); + [self.partner updateDebuggerButtons]; [self log:">"]; if (_debuggerCommandWhilePaused) { - _debuggerCommandWhilePaused = false; + NSString *command = _debuggerCommandWhilePaused; + _debuggerCommandWhilePaused = nil; dispatch_async(dispatch_get_main_queue(), ^{ - [self consoleInput:self.consoleInput]; + [self queueDebuggerCommand:command]; }); } [has_debugger_input lockWhenCondition:1]; @@ -1426,6 +1449,8 @@ static bool is_path_writeable(const char *path) shouldClearSideView = false; [self.debuggerSideView setString:@""]; } + [self updateDebuggerButtons]; + [self.partner updateDebuggerButtons]; }); if (isPlaying) { [_audioClient start]; @@ -2231,7 +2256,7 @@ static bool is_path_writeable(const char *path) /* NSSplitView renders its separator without the proper vibrancy, so we made it transparent and move an NSBox-based separator that renders properly so it acts like the split view's separator. */ NSRect rect = self.debuggerVerticalLine.frame; - rect.origin.x = [[[splitview arrangedSubviews] firstObject] frame].size.width - 1; + rect.origin.x = [[[splitview arrangedSubviews] firstObject] frame].size.width - 2; self.debuggerVerticalLine.frame = rect; } @@ -2573,4 +2598,49 @@ static bool is_path_writeable(const char *path) }]; } +- (void)updateDebuggerButtons +{ + bool updateContinue = false; + if (@available(macOS 10.10, *)) { + if ([self.consoleInput.placeholderAttributedString.string isEqualToString:self.debuggerContinueButton.alternateTitle]) { + [self.debuggerContinueButton mouseExited:nil]; + updateContinue = true; + } + } + if (self.isPaused) { + self.debuggerContinueButton.toolTip = self.debuggerContinueButton.title = @"Continue"; + self.debuggerContinueButton.alternateTitle = @"continue"; + self.debuggerContinueButton.imagePosition = NSImageOnly; + if (@available(macOS 10.14, *)) { + self.debuggerContinueButton.contentTintColor = nil; + } + self.debuggerContinueButton.image = [NSImage imageNamed:@"ContinueTemplate"]; + + self.debuggerNextButton.enabled = true; + self.debuggerStepButton.enabled = true; + self.debuggerFinishButton.enabled = true; + } + else { + self.debuggerContinueButton.toolTip = self.debuggerContinueButton.title = @"Interrupt"; + self.debuggerContinueButton.alternateTitle = @"interrupt"; + self.debuggerContinueButton.imagePosition = NSImageOnly; + if (@available(macOS 10.14, *)) { + self.debuggerContinueButton.contentTintColor = [NSColor controlAccentColor]; + } + self.debuggerContinueButton.image = [NSImage imageNamed:@"InterruptTemplate"]; + + self.debuggerNextButton.enabled = false; + self.debuggerStepButton.enabled = false; + self.debuggerFinishButton.enabled = false; + } + if (updateContinue) { + [self.debuggerContinueButton mouseEntered:nil]; + } +} + +- (IBAction)debuggerButtonPressed:(NSButton *)sender +{ + [self queueDebuggerCommand:sender.alternateTitle]; +} + @end diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib index 24e2daa..b66644d 100644 --- a/Cocoa/Document.xib +++ b/Cocoa/Document.xib @@ -3,6 +3,7 @@ + @@ -14,9 +15,14 @@ + + + + + @@ -87,50 +93,31 @@ - - - - - - - - - NSAllRomanInputSourcesLocaleIdentifier - - - - - - - - - - - + - + - - + + - + - - + + - - + + - - + + @@ -138,29 +125,112 @@ - - + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + + + + + - - + + - + - + @@ -186,22 +256,22 @@ - + - + - - + + - + - + @@ -216,7 +286,7 @@ - + @@ -778,7 +848,7 @@ - + @@ -877,7 +947,12 @@ + + + + + diff --git a/Cocoa/FinishTemplate.png b/Cocoa/FinishTemplate.png new file mode 100644 index 0000000..5aa831d Binary files /dev/null and b/Cocoa/FinishTemplate.png differ diff --git a/Cocoa/FinishTemplate@2x.png b/Cocoa/FinishTemplate@2x.png new file mode 100644 index 0000000..06bbcd4 Binary files /dev/null and b/Cocoa/FinishTemplate@2x.png differ diff --git a/Cocoa/GBDebuggerButton.h b/Cocoa/GBDebuggerButton.h new file mode 100644 index 0000000..5c3a12f --- /dev/null +++ b/Cocoa/GBDebuggerButton.h @@ -0,0 +1,7 @@ +#import + +@class GBDocument; +@interface GBDebuggerButton : NSButton +@property (weak) IBOutlet NSTextField *textField; +@end + diff --git a/Cocoa/GBDebuggerButton.m b/Cocoa/GBDebuggerButton.m new file mode 100644 index 0000000..15e6eb0 --- /dev/null +++ b/Cocoa/GBDebuggerButton.m @@ -0,0 +1,48 @@ +#import "GBDebuggerButton.h" + +@implementation GBDebuggerButton +{ + NSTrackingArea *_trackingArea; +} +- (instancetype)initWithCoder:(NSCoder *)coder +{ + self = [super initWithCoder:coder]; + self.toolTip = self.title; + return self; +} + +- (void)mouseEntered:(NSEvent *)event +{ + if (@available(macOS 10.10, *)) { + NSDictionary *attributes = @{ + NSForegroundColorAttributeName: [NSColor colorWithWhite:1.0 alpha:0.5], + NSFontAttributeName: self.textField.font + }; + self.textField.placeholderAttributedString = + [[NSAttributedString alloc] initWithString:self.alternateTitle attributes:attributes]; + } +} + +- (void)mouseExited:(NSEvent *)event +{ + if (@available(macOS 10.10, *)) { + if ([self.textField.placeholderAttributedString.string isEqualToString:self.alternateTitle]) { + self.textField.placeholderAttributedString = nil; + } + } +} + +-(void)updateTrackingAreas +{ + [super updateTrackingAreas]; + if (_trackingArea) { + [self removeTrackingArea:_trackingArea]; + } + + _trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds] + options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways + owner:self + userInfo:nil]; + [self addTrackingArea:_trackingArea]; +} +@end diff --git a/Cocoa/GBTerminalTextFieldCell.m b/Cocoa/GBTerminalTextFieldCell.m index e1ba957..a6f76a4 100644 --- a/Cocoa/GBTerminalTextFieldCell.m +++ b/Cocoa/GBTerminalTextFieldCell.m @@ -1,7 +1,11 @@ #import #import "GBTerminalTextFieldCell.h" +#import "NSTextFieldCell+Inset.h" @interface GBTerminalTextView : NSTextView +{ + @public __weak NSTextField *_field; +} @property GB_gameboy_t *gb; @end @@ -10,7 +14,7 @@ GBTerminalTextView *field_editor; } -- (NSTextView *)fieldEditorForView:(NSView *)controlView +- (NSTextView *)fieldEditorForView:(NSTextField *)controlView { if (field_editor) { field_editor.gb = self.gb; @@ -19,6 +23,10 @@ field_editor = [[GBTerminalTextView alloc] init]; [field_editor setFieldEditor:true]; field_editor.gb = self.gb; + field_editor->_field = (NSTextField *)controlView; + ((NSTextFieldCell *)controlView.cell).textInset = + field_editor.textContainerInset = + NSMakeSize(0, 2); return field_editor; } @@ -185,14 +193,21 @@ return [super resignFirstResponder]; } --(void)drawRect:(NSRect)dirtyRect +- (NSColor *)backgroundColor +{ + return nil; +} + +- (void)drawRect:(NSRect)dirtyRect { - [super drawRect:dirtyRect]; if (reverse_search_mode && [super string].length == 0) { NSMutableDictionary *attributes = [self.typingAttributes mutableCopy]; NSColor *color = [attributes[NSForegroundColorAttributeName] colorWithAlphaComponent:0.5]; [attributes setObject:color forKey:NSForegroundColorAttributeName]; - [[[NSAttributedString alloc] initWithString:@"Reverse search..." attributes:attributes] drawAtPoint:NSMakePoint(2, 0)]; + [[[NSAttributedString alloc] initWithString:@"Reverse search..." attributes:attributes] drawAtPoint:NSMakePoint(2, 2)]; + } + else { + [super drawRect:dirtyRect]; } } diff --git a/Cocoa/HelpTemplate.png b/Cocoa/HelpTemplate.png new file mode 100644 index 0000000..6b12375 Binary files /dev/null and b/Cocoa/HelpTemplate.png differ diff --git a/Cocoa/HelpTemplate@2x.png b/Cocoa/HelpTemplate@2x.png new file mode 100644 index 0000000..d7f8237 Binary files /dev/null and b/Cocoa/HelpTemplate@2x.png differ diff --git a/Cocoa/InterruptTemplate.png b/Cocoa/InterruptTemplate.png new file mode 100644 index 0000000..3530727 Binary files /dev/null and b/Cocoa/InterruptTemplate.png differ diff --git a/Cocoa/InterruptTemplate@2x.png b/Cocoa/InterruptTemplate@2x.png new file mode 100644 index 0000000..eb26243 Binary files /dev/null and b/Cocoa/InterruptTemplate@2x.png differ diff --git a/Cocoa/NSTextFieldCell+Inset.h b/Cocoa/NSTextFieldCell+Inset.h new file mode 100644 index 0000000..3b3cac8 --- /dev/null +++ b/Cocoa/NSTextFieldCell+Inset.h @@ -0,0 +1,6 @@ +#import +#import + +@interface NSTextFieldCell (Inset) +@property NSSize textInset; +@end diff --git a/Cocoa/NSTextFieldCell+Inset.m b/Cocoa/NSTextFieldCell+Inset.m new file mode 100644 index 0000000..acc6b3b --- /dev/null +++ b/Cocoa/NSTextFieldCell+Inset.m @@ -0,0 +1,39 @@ +#import "NSTextFieldCell+Inset.h" +#import +#import + +@interface NSTextFieldCell () +- (CGRect)_textLayerDrawingRectForCellFrame:(CGRect)rect; +@property NSSize textInset; +@end + +@implementation NSTextFieldCell (Inset) + +- (void)setTextInset:(NSSize)textInset +{ + objc_setAssociatedObject(self, @selector(textInset), @(textInset), OBJC_ASSOCIATION_RETAIN); +} + +- (NSSize)textInset +{ + return [objc_getAssociatedObject(self, _cmd) sizeValue]; +} + +- (CGRect)_textLayerDrawingRectForCellFrameHook:(CGRect)rect +{ + CGRect ret = [self _textLayerDrawingRectForCellFrameHook:rect]; + NSSize inset = self.textInset; + ret.origin.x += inset.width; + ret.origin.y += inset.height; + ret.size.width -= inset.width; + ret.size.height -= inset.height; + return ret; +} + ++ (void)load +{ + method_exchangeImplementations(class_getInstanceMethod(self, @selector(_textLayerDrawingRectForCellFrame:)), + class_getInstanceMethod(self, @selector(_textLayerDrawingRectForCellFrameHook:))); +} + +@end diff --git a/Cocoa/NextTemplate.png b/Cocoa/NextTemplate.png new file mode 100644 index 0000000..071e750 Binary files /dev/null and b/Cocoa/NextTemplate.png differ diff --git a/Cocoa/NextTemplate@2x.png b/Cocoa/NextTemplate@2x.png new file mode 100644 index 0000000..616fb2e Binary files /dev/null and b/Cocoa/NextTemplate@2x.png differ diff --git a/Cocoa/StepTemplate.png b/Cocoa/StepTemplate.png new file mode 100644 index 0000000..1532bf6 Binary files /dev/null and b/Cocoa/StepTemplate.png differ diff --git a/Cocoa/StepTemplate@2x.png b/Cocoa/StepTemplate@2x.png new file mode 100644 index 0000000..8d4b1af Binary files /dev/null and b/Cocoa/StepTemplate@2x.png differ diff --git a/Core/debugger.c b/Core/debugger.c index b687226..2edcb59 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -710,7 +710,7 @@ static const char *lstrip(const char *str) #define STOPPED_ONLY \ if (!gb->debug_stopped) { \ -GB_log(gb, "Program is running. \n"); \ +GB_log(gb, "Program is running, use 'interrupt' to stop execution.\n"); \ return false; \ } @@ -749,6 +749,24 @@ static bool cont(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug return false; } +static bool interrupt(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + if (gb->debug_stopped) { + GB_log(gb, "Program already stopped.\n"); + return true; + } + + gb->debug_stopped = true; + return true; +} + static bool next(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { NO_MODIFIERS @@ -1966,6 +1984,7 @@ static bool help(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug /* Commands without implementations are aliases of the previous non-alias commands */ static const debugger_command_t commands[] = { {"continue", 1, cont, "Continue running until next stop"}, + {"interrupt", 1, interrupt, "Interrupt the program execution"}, {"next", 1, next, "Run the next instruction, skipping over function calls"}, {"step", 1, step, "Run the next instruction, stepping into function calls"}, {"finish", 1, finish, "Run until the current function returns"},