From cdf478446890e0917e63788a0a8817dfa82485d7 Mon Sep 17 00:00:00 2001 From: byuu <2107894+byuu@users.noreply.github.com> Date: Sat, 27 Jul 2019 00:52:29 +0900 Subject: [PATCH] macOS Cocoa improvements. --- bsnes/emulator/emulator.hpp | 2 +- bsnes/target-bsnes/GNUmakefile | 3 + bsnes/target-bsnes/program/program.cpp | 1 + bsnes/target-bsnes/settings/emulator.cpp | 2 +- bsnes/target-bsnes/settings/hotkeys.cpp | 2 +- bsnes/target-bsnes/settings/input.cpp | 2 +- hiro/cocoa/utility.cpp | 9 +- hiro/cocoa/widget/canvas.cpp | 12 +- hiro/cocoa/widget/canvas.hpp | 2 + hiro/cocoa/widget/label.cpp | 153 ++++++++++++++++++----- hiro/cocoa/widget/label.hpp | 18 ++- hiro/cocoa/widget/table-view.cpp | 1 - hiro/cocoa/widget/viewport.cpp | 6 + hiro/cocoa/widget/viewport.hpp | 1 + hiro/extension/about-dialog.cpp | 7 +- 15 files changed, 177 insertions(+), 44 deletions(-) diff --git a/bsnes/emulator/emulator.hpp b/bsnes/emulator/emulator.hpp index eb4afc61..b07c899f 100644 --- a/bsnes/emulator/emulator.hpp +++ b/bsnes/emulator/emulator.hpp @@ -31,7 +31,7 @@ using namespace nall; namespace Emulator { static const string Name = "bsnes"; - static const string Version = "107.15"; + static const string Version = "107.16"; static const string Author = "byuu"; static const string License = "GPLv3"; static const string Website = "https://byuu.org"; diff --git a/bsnes/target-bsnes/GNUmakefile b/bsnes/target-bsnes/GNUmakefile index 89904a5c..483b1596 100644 --- a/bsnes/target-bsnes/GNUmakefile +++ b/bsnes/target-bsnes/GNUmakefile @@ -39,7 +39,10 @@ ifeq ($(shell id -un),root) else ifeq ($(platform),windows) else ifeq ($(platform),macos) mkdir -p ~/Library/Application\ Support/$(name)/ + mkdir -p ~/Library/Application\ Support/$(name)/Database/ cp -R out/$(name).app /Applications/$(name).app + cp Database/* ~/Library/Application\ Support/$(name)/Database/ + cp ../icarus/Database/* ~/Library/Application\ Support/$(name)/Database/ else ifneq ($(filter $(platform),linux bsd),) mkdir -p $(prefix)/bin/ mkdir -p $(prefix)/share/applications/ diff --git a/bsnes/target-bsnes/program/program.cpp b/bsnes/target-bsnes/program/program.cpp index 288b2da1..c7e3f126 100644 --- a/bsnes/target-bsnes/program/program.cpp +++ b/bsnes/target-bsnes/program/program.cpp @@ -22,6 +22,7 @@ auto Program::create() -> void { presentation.create(); presentation.setVisible(); + presentation.viewport.setFocused(); settingsWindow.create(); videoSettings.create(); diff --git a/bsnes/target-bsnes/settings/emulator.cpp b/bsnes/target-bsnes/settings/emulator.cpp index a8b9275f..bd435fb2 100644 --- a/bsnes/target-bsnes/settings/emulator.cpp +++ b/bsnes/target-bsnes/settings/emulator.cpp @@ -137,7 +137,7 @@ auto EmulatorSettings::create() -> void { settings.emulator.hack.fastSuperFX = superFXClock.position() * 10 + 100; superFXValue.setText({settings.emulator.hack.fastSuperFX, "%"}); }).doChange(); - hacksNote.setForegroundColor({224, 0, 0}).setText("Note: some hack setting changes do not take effect until after reloading games."); + hacksNote.setText("Note: some hack setting changes do not take effect until after reloading games."); } auto EmulatorSettings::updateConfiguration() -> void { diff --git a/bsnes/target-bsnes/settings/hotkeys.cpp b/bsnes/target-bsnes/settings/hotkeys.cpp index f1e93620..daabc4d7 100644 --- a/bsnes/target-bsnes/settings/hotkeys.cpp +++ b/bsnes/target-bsnes/settings/hotkeys.cpp @@ -33,7 +33,7 @@ auto HotkeySettings::reloadMappings() -> void { mappingList.append(TableViewColumn().setText("Mapping").setExpandable()); for(auto& hotkey : inputManager.hotkeys) { mappingList.append(TableViewItem() - .append(TableViewCell().setText(hotkey.name).setFont(Font().setBold()).setBackgroundColor({240, 240, 255})) + .append(TableViewCell().setText(hotkey.name).setFont(Font().setBold())) .append(TableViewCell()) ); } diff --git a/bsnes/target-bsnes/settings/input.cpp b/bsnes/target-bsnes/settings/input.cpp index e2da602d..2611c509 100644 --- a/bsnes/target-bsnes/settings/input.cpp +++ b/bsnes/target-bsnes/settings/input.cpp @@ -97,7 +97,7 @@ auto InputSettings::reloadMappings() -> void { mappingList.append(TableViewColumn().setText("Mapping").setExpandable()); for(auto& mapping : activeDevice().mappings) { mappingList.append(TableViewItem() - .append(TableViewCell().setText(mapping.name).setFont(Font().setBold()).setBackgroundColor({240, 240, 255})) + .append(TableViewCell().setText(mapping.name).setFont(Font().setBold())) .append(TableViewCell()) ); } diff --git a/hiro/cocoa/utility.cpp b/hiro/cocoa/utility.cpp index 1570176c..3bd0e4b1 100755 --- a/hiro/cocoa/utility.cpp +++ b/hiro/cocoa/utility.cpp @@ -2,6 +2,13 @@ auto NSMakeColor(const hiro::Color& color) -> NSColor* { return [NSColor colorWithRed:(color.red() / 255.0) green:(color.green() / 255.0) blue:(color.blue() / 255.0) alpha:(color.alpha() / 255.0)]; } +auto NSMakeCursor(const hiro::MouseCursor& mouseCursor) -> NSCursor* { + if(mouseCursor == MouseCursor::Hand) return [NSCursor pointingHandCursor]; + if(mouseCursor == MouseCursor::HorizontalResize) return [NSCursor resizeLeftRightCursor]; + if(mouseCursor == MouseCursor::VerticalResize) return [NSCursor resizeUpDownCursor]; + return nil; +} + auto NSMakeImage(image icon, uint scaleWidth = 0, uint scaleHeight = 0) -> NSImage* { if(!icon) return nil; @@ -14,7 +21,7 @@ auto NSMakeImage(image icon, uint scaleWidth = 0, uint scaleHeight = 0) -> NSIma initWithBitmapDataPlanes:nil pixelsWide:icon.width() pixelsHigh:icon.height() bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES - isPlanar:NO colorSpaceName:NSCalibratedRGBColorSpace + isPlanar:NO colorSpaceName:NSDeviceRGBColorSpace bitmapFormat:NSAlphaNonpremultipliedBitmapFormat bytesPerRow:(4 * icon.width()) bitsPerPixel:32 ] autorelease]; diff --git a/hiro/cocoa/widget/canvas.cpp b/hiro/cocoa/widget/canvas.cpp index 85db2da9..7d067173 100755 --- a/hiro/cocoa/widget/canvas.cpp +++ b/hiro/cocoa/widget/canvas.cpp @@ -15,6 +15,12 @@ return self; } +-(void) resetCursorRects { + if(auto mouseCursor = NSMakeCursor(label->mouseCursor())) { + [self addCursorRect:self.bounds cursor:mouseCursor]; + } +} + -(NSDragOperation) draggingEntered:(id)sender { return DropPathsOperation(sender); } @@ -42,6 +48,10 @@ } } +-(void) mouseEntered:(NSEvent*)event { + canvas->doMouseEnter(); +} + -(void) mouseExited:(NSEvent*)event { canvas->doMouseLeave(); } @@ -183,7 +193,7 @@ auto pCanvas::_rasterize() -> void { initWithBitmapDataPlanes:nil pixelsWide:width pixelsHigh:height bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES - isPlanar:NO colorSpaceName:NSCalibratedRGBColorSpace + isPlanar:NO colorSpaceName:NSDeviceRGBColorSpace bitmapFormat:NSAlphaNonpremultipliedBitmapFormat bytesPerRow:(width * 4) bitsPerPixel:32 ] autorelease]; diff --git a/hiro/cocoa/widget/canvas.hpp b/hiro/cocoa/widget/canvas.hpp index 942e0fb0..95bcf917 100755 --- a/hiro/cocoa/widget/canvas.hpp +++ b/hiro/cocoa/widget/canvas.hpp @@ -5,9 +5,11 @@ hiro::mCanvas* canvas; } -(id) initWith:(hiro::mCanvas&)canvas; +-(void) resetCursorRects; -(NSDragOperation) draggingEntered:(id)sender; -(BOOL) performDragOperation:(id)sender; -(void) mouseButton:(NSEvent*)event down:(BOOL)isDown; +-(void) mouseEntered:(NSEvent*)event; -(void) mouseExited:(NSEvent*)event; -(void) mouseMove:(NSEvent*)event; -(void) mouseDown:(NSEvent*)event; diff --git a/hiro/cocoa/widget/label.cpp b/hiro/cocoa/widget/label.cpp index bc1f3cbc..c055e2c0 100755 --- a/hiro/cocoa/widget/label.cpp +++ b/hiro/cocoa/widget/label.cpp @@ -1,22 +1,123 @@ #if defined(Hiro_Label) -//todo: -//* Label::onButtonPress() -//* Label::onButtonRelease() - -@implementation CocoaLabel : NSTextView +@implementation CocoaLabel : NSView -(id) initWith:(hiro::mLabel&)labelReference { if(self = [super initWithFrame:NSMakeRect(0, 0, 0, 0)]) { label = &labelReference; - - [self setDrawsBackground:NO]; - [self setEditable:NO]; - [self setRichText:NO]; } return self; } +-(void) resetCursorRects { + if(auto mouseCursor = NSMakeCursor(label->mouseCursor())) { + [self addCursorRect:self.bounds cursor:mouseCursor]; + } +} + +-(void) drawRect:(NSRect)dirtyRect { + auto geometry = label->geometry(); + NSRect rect = {{geometry.x(), geometry.y()}, {geometry.width(), geometry.height()}}; + + if(auto backgroundColor = label->backgroundColor()) { + NSColor* color = NSMakeColor(backgroundColor); + [color setFill]; + NSRectFill(rect); + } + + NSFont* font = hiro::pFont::create(label->font(true)); + NSColor* color = [NSColor textColor]; + if(auto foregroundColor = label->foregroundColor()) { + color = NSMakeColor(foregroundColor); + } + NSMutableParagraphStyle* paragraphStyle = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] autorelease]; + paragraphStyle.alignment = NSTextAlignmentLeft; + paragraphStyle.lineBreakMode = NSLineBreakByClipping; + NSDictionary* attributes = @{ + NSFontAttributeName:font, + NSForegroundColorAttributeName:color, + NSParagraphStyleAttributeName:paragraphStyle + }; + + auto size = hiro::pFont::size(label->font(true), label->text()); + auto alignment = label->alignment(); + if(!alignment) alignment = {0.0, 0.5}; + + rect.origin.x = max(0, (geometry.width() - size.width()) * alignment.horizontal()); + rect.origin.y = max(0, (geometry.height() - size.height()) * alignment.vertical()); + rect.size.width = min(geometry.width(), size.width()); + rect.size.height = min(geometry.height(), size.height()); + + NSString* string = [NSString stringWithUTF8String:label->text()]; + [string drawInRect:rect withAttributes:attributes]; +} + +-(void) mouseButton:(NSEvent*)event down:(BOOL)isDown { + if(isDown) { + switch([event buttonNumber]) { + case 0: return label->doMousePress(hiro::Mouse::Button::Left); + case 1: return label->doMousePress(hiro::Mouse::Button::Right); + case 2: return label->doMousePress(hiro::Mouse::Button::Middle); + } + } else { + switch([event buttonNumber]) { + case 0: return label->doMouseRelease(hiro::Mouse::Button::Left); + case 1: return label->doMouseRelease(hiro::Mouse::Button::Right); + case 2: return label->doMouseRelease(hiro::Mouse::Button::Middle); + } + } +} + +-(void) mouseEntered:(NSEvent*)event { + label->doMouseEnter(); +} + +-(void) mouseExited:(NSEvent*)event { + label->doMouseLeave(); +} + +-(void) mouseMove:(NSEvent*)event { + if([event window] == nil) return; + NSPoint location = [self convertPoint:[event locationInWindow] fromView:nil]; + label->doMouseMove({(int)location.x, (int)([self frame].size.height - 1 - location.y)}); +} + +-(void) mouseDown:(NSEvent*)event { + [self mouseButton:event down:YES]; +} + +-(void) mouseUp:(NSEvent*)event { + [self mouseButton:event down:NO]; +} + +-(void) mouseDragged:(NSEvent*)event { + [self mouseMove:event]; +} + +-(void) rightMouseDown:(NSEvent*)event { + [self mouseButton:event down:YES]; +} + +-(void) rightMouseUp:(NSEvent*)event { + [self mouseButton:event down:NO]; +} + +-(void) rightMouseDragged:(NSEvent*)event { + [self mouseMove:event]; +} + +-(void) otherMouseDown:(NSEvent*)event { + [self mouseButton:event down:YES]; +} + +-(void) otherMouseUp:(NSEvent*)event { + [self mouseButton:event down:NO]; +} + +-(void) otherMouseDragged:(NSEvent*)event { + [self mouseMove:event]; +} + @end namespace hiro { @@ -27,6 +128,8 @@ auto pLabel::construct() -> void { pWidget::construct(); setAlignment(state().alignment); + setBackgroundColor(state().backgroundColor()); + setForegroundColor(state().foregroundColor()); setText(state().text); } } @@ -44,43 +147,25 @@ auto pLabel::minimumSize() const -> Size { auto pLabel::setAlignment(Alignment alignment) -> void { @autoreleasepool { - NSMutableParagraphStyle* paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; - paragraphStyle.alignment = NSTextAlignmentCenter; - if(alignment.horizontal() < 0.333) paragraphStyle.alignment = NSTextAlignmentLeft; - if(alignment.horizontal() > 0.666) paragraphStyle.alignment = NSTextAlignmentRight; - [cocoaView setDefaultParagraphStyle:paragraphStyle]; + [cocoaView setNeedsDisplay:YES]; } } auto pLabel::setBackgroundColor(Color color) -> void { - //todo + @autoreleasepool { + [cocoaView setNeedsDisplay:YES]; + } } auto pLabel::setForegroundColor(Color color) -> void { - //todo -} - -auto pLabel::setGeometry(Geometry geometry) -> void { - //NSTextView does not support vertical text centering: - //simulate this by adjusting the geometry placement (reduce height, move view down) - uint height = pFont::size(self().font(true), state().text).height(); - auto offset = geometry; - - if(geometry.height() > height) { - uint diff = geometry.height() - height; - offset.setY(offset.y() + (diff >> 1)); - offset.setHeight(offset.height() - (diff >> 1)); + @autoreleasepool { + [cocoaView setNeedsDisplay:YES]; } - - pWidget::setGeometry({ - offset.x() - 6, offset.y(), - offset.width() + 12, offset.height() - }); } auto pLabel::setText(const string& text) -> void { @autoreleasepool { - [cocoaView setString:[NSString stringWithUTF8String:text]]; + [cocoaView setNeedsDisplay:YES]; } } diff --git a/hiro/cocoa/widget/label.hpp b/hiro/cocoa/widget/label.hpp index e5d849c8..17541b72 100755 --- a/hiro/cocoa/widget/label.hpp +++ b/hiro/cocoa/widget/label.hpp @@ -1,10 +1,25 @@ #if defined(Hiro_Label) -@interface CocoaLabel : NSTextView { +@interface CocoaLabel : NSView { @public hiro::mLabel* label; } -(id) initWith:(hiro::mLabel&)label; +-(void) resetCursorRects; +-(void) drawRect:(NSRect)dirtyRect; +-(void) mouseButton:(NSEvent*)event down:(BOOL)isDown; +-(void) mouseEntered:(NSEvent*)event; +-(void) mouseExited:(NSEvent*)event; +-(void) mouseMove:(NSEvent*)event; +-(void) mouseDown:(NSEvent*)event; +-(void) mouseUp:(NSEvent*)event; +-(void) mouseDragged:(NSEvent*)event; +-(void) rightMouseDown:(NSEvent*)event; +-(void) rightMouseUp:(NSEvent*)event; +-(void) rightMouseDragged:(NSEvent*)event; +-(void) otherMouseDown:(NSEvent*)event; +-(void) otherMouseUp:(NSEvent*)event; +-(void) otherMouseDragged:(NSEvent*)event; @end namespace hiro { @@ -16,7 +31,6 @@ struct pLabel : pWidget { auto setAlignment(Alignment alignment) -> void; auto setBackgroundColor(Color color) -> void; auto setForegroundColor(Color color) -> void; - auto setGeometry(Geometry geometry) -> void override; auto setText(const string& text) -> void; CocoaLabel* cocoaLabel = nullptr; diff --git a/hiro/cocoa/widget/table-view.cpp b/hiro/cocoa/widget/table-view.cpp index 6a390b38..a0dc4a76 100755 --- a/hiro/cocoa/widget/table-view.cpp +++ b/hiro/cocoa/widget/table-view.cpp @@ -365,7 +365,6 @@ auto pTableView::setForegroundColor(Color color) -> void { auto pTableView::setHeadered(bool headered) -> void { @autoreleasepool { - if(headered == state().headered) return; if(headered) { [[cocoaView content] setHeaderView:[[[NSTableHeaderView alloc] init] autorelease]]; } else { diff --git a/hiro/cocoa/widget/viewport.cpp b/hiro/cocoa/widget/viewport.cpp index d6169745..3edc1dda 100755 --- a/hiro/cocoa/widget/viewport.cpp +++ b/hiro/cocoa/widget/viewport.cpp @@ -9,6 +9,12 @@ return self; } +-(void) resetCursorRects { + if(auto mouseCursor = NSMakeCursor(label->mouseCursor())) { + [self addCursorRect:self.bounds cursor:mouseCursor]; + } +} + -(void) drawRect:(NSRect)rect { [[NSColor blackColor] setFill]; NSRectFillUsingOperation(rect, NSCompositeSourceOver); diff --git a/hiro/cocoa/widget/viewport.hpp b/hiro/cocoa/widget/viewport.hpp index f6301a8c..cc135f8c 100755 --- a/hiro/cocoa/widget/viewport.hpp +++ b/hiro/cocoa/widget/viewport.hpp @@ -5,6 +5,7 @@ hiro::mViewport* viewport; } -(id) initWith:(hiro::mViewport&)viewport; +-(void) resetCursorRects; -(void) drawRect:(NSRect)rect; -(BOOL) acceptsFirstResponder; -(NSDragOperation) draggingEntered:(id)sender; diff --git a/hiro/extension/about-dialog.cpp b/hiro/extension/about-dialog.cpp index c833e3e6..dd40a3a0 100644 --- a/hiro/extension/about-dialog.cpp +++ b/hiro/extension/about-dialog.cpp @@ -137,7 +137,11 @@ auto AboutDialog::show() -> void { websiteValue.setAlignment(0.0); websiteValue.setFont(Font().setBold()); websiteValue.setForegroundColor({0, 0, 240}); - websiteValue.setText(string{state.website}.trimLeft("http://", 1L).trimLeft("https://", 1L)); + auto website = state.website; + if(0); //remove protocol prefix from displayed label: + else if(website.beginsWith("http://" )) website.trimLeft("http://" , 1L); + else if(website.beginsWith("https://")) website.trimLeft("https://", 1L); + websiteValue.setText(website); //so that the hand cursor is only visible when overing over the link. websiteValue.setMouseCursor(MouseCursor::Hand); websiteValue.onMouseRelease([&](auto button) { @@ -153,6 +157,7 @@ auto AboutDialog::show() -> void { window.setDismissable(); window.setVisible(); window.setModal(); + window.setVisible(false); } #endif