From 7a816b946d0ff47b0bd2a54716e916be400ea1a5 Mon Sep 17 00:00:00 2001 From: jcm Date: Sun, 3 Dec 2023 14:53:33 -0600 Subject: [PATCH] hiro: add cursor auto-hide --- desktop-ui/presentation/presentation.cpp | 15 ++++++++++ desktop-ui/presentation/presentation.hpp | 1 + desktop-ui/settings/options.cpp | 14 +++++++++ desktop-ui/settings/settings.cpp | 1 + desktop-ui/settings/settings.hpp | 7 ++++- hiro/cocoa/window.cpp | 38 ++++++++++++++++++++++++ hiro/cocoa/window.hpp | 11 ++++++- hiro/core/shared.hpp | 2 ++ hiro/core/window.cpp | 10 +++++++ hiro/core/window.hpp | 3 ++ 10 files changed, 100 insertions(+), 2 deletions(-) diff --git a/desktop-ui/presentation/presentation.cpp b/desktop-ui/presentation/presentation.cpp index c260ad4b76..c21939532d 100644 --- a/desktop-ui/presentation/presentation.cpp +++ b/desktop-ui/presentation/presentation.cpp @@ -15,6 +15,11 @@ Presentation::Presentation() { settingsMenu.setText("Settings"); videoSizeMenu.setText("Size").setIcon(Icon::Emblem::Image); + + bool hideCursorImplemented = false; +#if defined(PLATFORM_MACOS) + hideCursorImplemented = true; +#endif //generate size menu u32 multipliers = 5; @@ -95,6 +100,14 @@ Presentation::Presentation() { muteAudioSetting.setText("Mute Audio").setChecked(settings.audio.mute).onToggle([&] { settings.audio.mute = muteAudioSetting.checked(); }); + autoHideCursorSetting.setText("Auto-Hide Cursor").setChecked(settings.general.autoHideCursor).onToggle([&] { + settings.general.autoHideCursor = autoHideCursorSetting.checked(); + settingsWindow.optionSettings.autoHideCursorOption.setChecked(settings.general.autoHideCursor); + presentation.setHidesCursor(settings.general.autoHideCursor); + }); + if(!hideCursorImplemented) { + autoHideCursorSetting.setVisible(false); + } showStatusBarSetting.setText("Show Status Bar").setChecked(settings.general.showStatusBar).onToggle([&] { settings.general.showStatusBar = showStatusBarSetting.checked(); if(!showStatusBarSetting.checked()) { @@ -227,6 +240,8 @@ Presentation::Presentation() { } }); + setHidesCursor(settings.general.autoHideCursor); + iconLayout.setCollapsible(); iconSpacer.setCollapsible().setColor({0, 0, 0}).setDroppable().onDrop([&](auto filenames) { diff --git a/desktop-ui/presentation/presentation.hpp b/desktop-ui/presentation/presentation.hpp index 50ff8566c5..e34fbfa18a 100644 --- a/desktop-ui/presentation/presentation.hpp +++ b/desktop-ui/presentation/presentation.hpp @@ -39,6 +39,7 @@ struct Presentation : Window { Group preferRegionGroup{&preferNTSCU, &preferNTSCJ, &preferPAL}; MenuSeparator groupSettingsSeparatpr{&settingsMenu}; MenuCheckItem muteAudioSetting{&settingsMenu}; + MenuCheckItem autoHideCursorSetting{&settingsMenu}; MenuCheckItem showStatusBarSetting{&settingsMenu}; MenuSeparator audioSettingsSeparator{&settingsMenu}; MenuItem videoSettingsAction{&settingsMenu}; diff --git a/desktop-ui/settings/options.cpp b/desktop-ui/settings/options.cpp index 9b50933bf9..2bf9825eda 100644 --- a/desktop-ui/settings/options.cpp +++ b/desktop-ui/settings/options.cpp @@ -1,6 +1,10 @@ auto OptionSettings::construct() -> void { setCollapsible(); setVisible(false); + bool hideCursorImplemented = false; +#if defined(PLATFORM_MACOS) + hideCursorImplemented = true; +#endif commonSettingsLabel.setText("Emulator Options").setFont(Font().setBold()); @@ -37,5 +41,15 @@ auto OptionSettings::construct() -> void { }); nintendo64ExpansionPakLayout.setAlignment(1).setPadding(12_sx, 0); nintendo64ExpansionPakHint.setText("Enable/Disable the 4MB Expansion Pak").setFont(Font().setSize(7.0)).setForegroundColor(SystemColor::Sublabel); + + miscellaneousSettingsLabel.setVisible(hideCursorImplemented).setText("Miscellaneous").setFont(Font().setBold()); + + autoHideCursorOption.setText("Auto-Hide Cursor").setVisible(hideCursorImplemented).setChecked(settings.general.autoHideCursor).onToggle([&] { + settings.general.autoHideCursor = autoHideCursorOption.checked(); + presentation.setHidesCursor(settings.general.autoHideCursor); + presentation.autoHideCursorSetting.setChecked(settings.general.autoHideCursor); + }); + autoHideCursorLayout.setAlignment(1).setPadding(12_sx, 0); + autoHideCursorHint.setVisible(hideCursorImplemented).setText("Hide the mouse cursor when idle over the game window").setFont(Font().setSize(7.0)).setForegroundColor(SystemColor::Sublabel); } diff --git a/desktop-ui/settings/settings.cpp b/desktop-ui/settings/settings.cpp index c3f340bdda..18dfb9c23b 100644 --- a/desktop-ui/settings/settings.cpp +++ b/desktop-ui/settings/settings.cpp @@ -100,6 +100,7 @@ auto Settings::process(bool load) -> void { bind(boolean, "General/RunAhead", general.runAhead); bind(boolean, "General/AutoSaveMemory", general.autoSaveMemory); bind(boolean, "General/HomebrewMode", general.homebrewMode); + bind(boolean, "General/AutoHideCursor", general.autoHideCursor); bind(natural, "Rewind/Length", rewind.length); bind(natural, "Rewind/Frequency", rewind.frequency); diff --git a/desktop-ui/settings/settings.hpp b/desktop-ui/settings/settings.hpp index 7a538389ba..a937a4bec1 100644 --- a/desktop-ui/settings/settings.hpp +++ b/desktop-ui/settings/settings.hpp @@ -66,6 +66,7 @@ struct Settings : Markup::Node { bool runAhead = false; bool autoSaveMemory = true; bool homebrewMode = false; + bool autoHideCursor = true; } general; struct Rewind { @@ -251,6 +252,10 @@ struct OptionSettings : VerticalLayout { HorizontalLayout nintendo64ExpansionPakLayout{this, Size{~0, 0}, 5}; CheckLabel nintendo64ExpansionPakOption{&nintendo64ExpansionPakLayout, Size{0, 0}, 5}; Label nintendo64ExpansionPakHint{&nintendo64ExpansionPakLayout, Size{0, 0}}; + Label miscellaneousSettingsLabel{this, Size{~0, 0}, 5}; + HorizontalLayout autoHideCursorLayout{this, Size{~0, 0}, 5}; + CheckLabel autoHideCursorOption{&autoHideCursorLayout, Size{0, 0}, 5}; + Label autoHideCursorHint{&autoHideCursorLayout, Size{0,0}}; }; struct FirmwareSettings : VerticalLayout { @@ -432,4 +437,4 @@ extern OptionSettings& optionSettings; extern FirmwareSettings& firmwareSettings; extern PathSettings& pathSettings; extern DriverSettings& driverSettings; -extern DebugSettings& debugSettings; \ No newline at end of file +extern DebugSettings& debugSettings; diff --git a/hiro/cocoa/window.cpp b/hiro/cocoa/window.cpp index 8f6d05948d..893069a70c 100644 --- a/hiro/cocoa/window.cpp +++ b/hiro/cocoa/window.cpp @@ -6,7 +6,10 @@ window = &windowReference; NSUInteger style = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask; + hidesCursor = false; + cursorIsInWindow = false; if(window->state.resizable) style |= NSResizableWindowMask; + hideCursorTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(hideCursor:) userInfo:nil repeats:YES]; if(self = [super initWithContentRect:NSMakeRect(0, 0, 640, 480) styleMask:style backing:NSBackingStoreBuffered defer:YES]) { [self setDelegate:self]; @@ -137,6 +140,13 @@ return YES; } +-(void) hideCursor:(NSTimer*)timer { + CFTimeInterval secondsSinceCursorMoved = CGEventSourceSecondsSinceLastEventType(kCGEventSourceStateCombinedSessionState, kCGEventMouseMoved); + if(secondsSinceCursorMoved >= 2.0 && hidesCursor && cursorIsInWindow) { + [NSCursor setHiddenUntilMouseMoves:YES]; + } +} + -(NSMenu*) menuBar { return menuBar; } @@ -205,6 +215,18 @@ window->state.fullScreen = true; } +-(void)mouseEntered:(NSEvent *)theEvent { + cursorIsInWindow = true; +} + +-(void)mouseExited:(NSEvent *)theEvent { + cursorIsInWindow = false; +} + +-(void)setHidesCursor:(bool)hides { + hidesCursor = hides; +} + -(void)windowDidExitFullScreen:(NSNotification *)notification { window->state.fullScreen = false; } @@ -368,6 +390,10 @@ auto pWindow::setResizable(bool resizable) -> void { [cocoaWindow setStyleMask:style]; } +auto pWindow::setHidesCursor(bool hidesCursor) -> void { + [cocoaWindow setHidesCursor:hidesCursor]; +} + auto pWindow::setTitle(const string& text) -> void { [cocoaWindow setTitle:[NSString stringWithUTF8String:text]]; } @@ -405,6 +431,18 @@ auto pWindow::sizeEvent() -> void { } statusBarReposition(); + + NSView* theView = [cocoaWindow contentView]; + NSArray* trackingAreas = theView.trackingAreas; + for (NSTrackingArea* oldArea in trackingAreas) { + [theView removeTrackingArea:oldArea]; + } + NSTrackingArea *area = [[NSTrackingArea alloc] initWithRect: theView.bounds + options:(NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways) + owner:cocoaWindow + userInfo:nil]; + [theView addTrackingArea:area]; + if(!locked()) self().doSize(); } diff --git a/hiro/cocoa/window.hpp b/hiro/cocoa/window.hpp index 0b55f31682..ffd5aa0ee3 100644 --- a/hiro/cocoa/window.hpp +++ b/hiro/cocoa/window.hpp @@ -3,10 +3,14 @@ @interface CocoaWindow : NSWindow { @public hiro::mWindow* window; + bool hidesCursor; + bool cursorIsInWindow; + NSTimer* hideCursorTimer; NSMenu* menuBar; NSMenu* rootMenu; NSMenuItem* disableGatekeeper; NSTextField* statusBar; + NSTrackingArea* trackingArea; } -(id) initWith:(hiro::mWindow&)window; -(BOOL) canBecomeKeyWindow; @@ -14,6 +18,9 @@ -(void) windowDidBecomeMain:(NSNotification*)notification; -(void) windowDidMove:(NSNotification*)notification; -(void) windowDidResize:(NSNotification*)notification; +-(void) mouseEntered:(NSEvent*)theEvent; +-(void) mouseExited:(NSEvent*)theEvent; +-(void) setHidesCursor:(bool)hidesCursor; -(BOOL) windowShouldClose:(id)sender; -(NSDragOperation) draggingEntered:(id)sender; -(BOOL) performDragOperation:(id)sender; @@ -23,6 +30,7 @@ -(void) menuDisableGatekeeper; -(void) menuQuit; -(NSTextField*) statusBar; +-(void) hideCursor:(NSTimer*)timer; @end namespace hiro { @@ -55,7 +63,8 @@ struct pWindow : pObject { auto setTitle(const string& text) -> void; auto setAssociatedFile(const string& filename) -> void; auto setVisible(bool visible) -> void; - + auto setHidesCursor(bool hidesCursor) -> void; + auto moveEvent() -> void; auto sizeEvent() -> void; auto statusBarHeight() -> u32; diff --git a/hiro/core/shared.hpp b/hiro/core/shared.hpp index ff9f52e25b..0280df4e36 100644 --- a/hiro/core/shared.hpp +++ b/hiro/core/shared.hpp @@ -934,6 +934,7 @@ struct Window : sWindow { auto remove(sStatusBar statusBar) { return self().remove(statusBar), *this; } auto reset() { return self().reset(), *this; } auto resizable() const { return self().resizable(); } + auto hidesCursor() const { return self().hidesCursor(); } auto setAlignment(Alignment alignment = Alignment::Center) { return self().setAlignment(alignment), *this; } auto setAlignment(sWindow relativeTo, Alignment alignment = Alignment::Center) { return self().setAlignment(relativeTo, alignment), *this; } auto setBackgroundColor(Color color = {}) { return self().setBackgroundColor(color), *this; } @@ -953,6 +954,7 @@ struct Window : sWindow { auto setPosition(Position position) { return self().setPosition(position), *this; } auto setPosition(sWindow relativeTo, Position position) { return self().setPosition(relativeTo, position), *this; } auto setResizable(bool resizable = true) { return self().setResizable(resizable), *this; } + auto setHidesCursor(bool hidesCursor = true) { return self().setHidesCursor(hidesCursor), *this; } auto setSize(Size size) { return self().setSize(size), *this; } auto setTitle(const string& title = "") { return self().setTitle(title), *this; } auto setAssociatedFile(const string& filename = "") { return self().setAssociatedFile(filename), *this; } diff --git a/hiro/core/window.cpp b/hiro/core/window.cpp index 23ff03ba65..a1d1b736ee 100644 --- a/hiro/core/window.cpp +++ b/hiro/core/window.cpp @@ -187,6 +187,10 @@ auto mWindow::resizable() const -> bool { return state.resizable; } +auto mWindow::hidesCursor() const -> bool { + return state.hidesCursor; +} + auto mWindow::setAlignment(Alignment alignment) -> type& { auto workspace = Monitor::workspace(); auto geometry = frameGeometry(); @@ -349,6 +353,12 @@ auto mWindow::setResizable(bool resizable) -> type& { return *this; } +auto mWindow::setHidesCursor(bool hidesCursor) -> type& { + state.hidesCursor = hidesCursor; + signal(setHidesCursor, hidesCursor); + return *this; +} + auto mWindow::setSize(Size size) -> type& { return setGeometry({ state.geometry.x(), state.geometry.y(), diff --git a/hiro/core/window.hpp b/hiro/core/window.hpp index 52df3916c7..1086055e1d 100644 --- a/hiro/core/window.hpp +++ b/hiro/core/window.hpp @@ -39,6 +39,7 @@ struct mWindow : mObject { auto remove(sStatusBar statusBar) -> type&; auto reset() -> type& override; auto resizable() const -> bool; + auto hidesCursor() const -> bool; auto setAlignment(Alignment = Alignment::Center) -> type&; auto setAlignment(sWindow relativeTo, Alignment = Alignment::Center) -> type&; auto setBackgroundColor(Color color = {}) -> type&; @@ -58,6 +59,7 @@ struct mWindow : mObject { auto setPosition(Position) -> type&; auto setPosition(sWindow relativeTo, Position) -> type&; auto setResizable(bool resizable = true) -> type&; + auto setHidesCursor(bool hidesCursor = true) -> type&; auto setSize(Size size) -> type&; auto setTitle(const string& title = "") -> type&; auto setAssociatedFile(const string& filename = "") -> type&; @@ -89,6 +91,7 @@ struct mWindow : mObject { sSizable sizable; sStatusBar statusBar; string title; + bool hidesCursor; } state; auto destruct() -> void override;