From 8ad2966933957ac5d6ff8dcd5e732736fd5e4dc6 Mon Sep 17 00:00:00 2001 From: crupest Date: Sun, 15 May 2022 14:08:06 +0800 Subject: ... --- src/platform/gui/osx/UiApplication.mm | 260 ++++++++++++++++++++++++++++++++++ 1 file changed, 260 insertions(+) create mode 100644 src/platform/gui/osx/UiApplication.mm (limited to 'src/platform/gui/osx/UiApplication.mm') diff --git a/src/platform/gui/osx/UiApplication.mm b/src/platform/gui/osx/UiApplication.mm new file mode 100644 index 00000000..ef62af58 --- /dev/null +++ b/src/platform/gui/osx/UiApplication.mm @@ -0,0 +1,260 @@ +#include "cru/platform/gui/osx/UiApplication.h" + +#include "ClipboardPrivate.h" +#include "cru/common/log/Logger.h" +#include "cru/common/platform/osx/Convert.h" +#include "cru/platform/graphics/quartz/Factory.h" +#include "cru/platform/gui/osx/Clipboard.h" +#include "cru/platform/gui/osx/Cursor.h" +#include "cru/platform/gui/osx/Menu.h" +#include "cru/platform/gui/osx/Window.h" +#include "cru/platform/graphics/Factory.h" +#include "cru/platform/gui/Base.h" +#include "cru/platform/gui/UiApplication.h" +#include "cru/platform/gui/Window.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +@interface CruAppDelegate : NSObject +- (id)init:(cru::platform::gui::osx::details::OsxUiApplicationPrivate*)p; +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)sender; +- (void)applicationWillTerminate:(NSNotification*)notification; +@end + +namespace cru::platform::gui::osx { + +using cru::platform::osx::Convert; + +namespace details { +class OsxUiApplicationPrivate { + friend OsxUiApplication; + + public: + explicit OsxUiApplicationPrivate(OsxUiApplication* osx_ui_application) + : osx_ui_application_(osx_ui_application) { + app_delegate_ = [[CruAppDelegate alloc] init:this]; + } + + CRU_DELETE_COPY(OsxUiApplicationPrivate) + CRU_DELETE_MOVE(OsxUiApplicationPrivate) + + ~OsxUiApplicationPrivate() = default; + + void CallQuitHandlers(); + + private: + OsxUiApplication* osx_ui_application_; + CruAppDelegate* app_delegate_; + std::vector> quit_handlers_; + bool quit_on_all_window_closed_ = true; + + long long current_timer_id_ = 1; + std::unordered_map> next_tick_; + std::unordered_map timers_; + + std::vector windows_; + + std::unique_ptr cursor_manager_; + + std::unique_ptr clipboard_; + + std::unique_ptr quartz_graphics_factory_; +}; + +void OsxUiApplicationPrivate::CallQuitHandlers() { + for (const auto& handler : quit_handlers_) { + handler(); + } +} +} + +OsxUiApplication::OsxUiApplication() + : OsxGuiResource(this), p_(new details::OsxUiApplicationPrivate(this)) { + [NSApplication sharedApplication]; + + [NSApp setDelegate:p_->app_delegate_]; + p_->quartz_graphics_factory_ = std::make_unique(); + p_->cursor_manager_ = std::make_unique(this); + p_->clipboard_ = std::make_unique( + this, std::make_unique([NSPasteboard generalPasteboard])); +} + +OsxUiApplication::~OsxUiApplication() {} + +int OsxUiApplication::Run() { + [NSApp run]; + return 0; +} + +void OsxUiApplication::RequestQuit(int quit_code) { + [NSApp terminate:[NSNumber numberWithInteger:quit_code]]; +} + +void OsxUiApplication::AddOnQuitHandler(std::function handler) { + p_->quit_handlers_.push_back(std::move(handler)); +} + +bool OsxUiApplication::IsQuitOnAllWindowClosed() { return p_->quit_on_all_window_closed_; } + +void OsxUiApplication::SetQuitOnAllWindowClosed(bool quit_on_all_window_closed) { + p_->quit_on_all_window_closed_ = quit_on_all_window_closed; +} + +long long OsxUiApplication::SetImmediate(std::function action) { + const long long id = p_->current_timer_id_++; + p_->next_tick_.emplace(id, std::move(action)); + + [[NSRunLoop mainRunLoop] performBlock:^{ + const auto i = p_->next_tick_.find(id); + if (i != p_->next_tick_.cend()) { + i->second(); + } + p_->next_tick_.erase(i); + }]; + + return id; +} + +long long OsxUiApplication::SetTimeout(std::chrono::milliseconds milliseconds, + std::function action) { + long long id = p_->current_timer_id_++; + p_->timers_.emplace(id, [NSTimer scheduledTimerWithTimeInterval:milliseconds.count() / 1000.0 + repeats:false + block:^(NSTimer* timer) { + action(); + p_->timers_.erase(id); + }]); + + return id; +} + +long long OsxUiApplication::SetInterval(std::chrono::milliseconds milliseconds, + std::function action) { + long long id = p_->current_timer_id_++; + p_->timers_.emplace(id, [NSTimer scheduledTimerWithTimeInterval:milliseconds.count() / 1000.0 + repeats:true + block:^(NSTimer* timer) { + action(); + }]); + + return id; +} + +void OsxUiApplication::CancelTimer(long long id) { + p_->next_tick_.erase(id); + auto i = p_->timers_.find(id); + if (i != p_->timers_.cend()) { + [i->second invalidate]; + p_->timers_.erase(i); + } +} + +std::vector OsxUiApplication::GetAllWindow() { + std::vector result; + std::transform(p_->windows_.cbegin(), p_->windows_.cend(), std::back_inserter(result), + [](OsxWindow* w) { return static_cast(w); }); + return result; +} + +INativeWindow* OsxUiApplication::CreateWindow() { + auto window = new OsxWindow(this); + p_->windows_.push_back(window); + return window; +} + +ICursorManager* OsxUiApplication::GetCursorManager() { return p_->cursor_manager_.get(); } + +IClipboard* OsxUiApplication::GetClipboard() { return p_->clipboard_.get(); } + +IMenu* OsxUiApplication::GetApplicationMenu() { return OsxMenu::CreateOrGetApplicationMenu(this); } + +graphics::IGraphicsFactory* OsxUiApplication::GetGraphicsFactory() { + return p_->quartz_graphics_factory_.get(); +} + +std::optional OsxUiApplication::ShowSaveDialog(SaveDialogOptions options) { + NSSavePanel* panel = [NSSavePanel savePanel]; + [panel setTitle:(NSString*)Convert(options.title)]; + [panel setPrompt:(NSString*)Convert(options.prompt)]; + [panel setMessage:(NSString*)Convert(options.message)]; + + NSMutableArray* allowed_content_types = [NSMutableArray array]; + + for (const auto& file_type : options.allowed_file_types) { + [allowed_content_types + addObject:[UTType typeWithFilenameExtension:(NSString*)Convert(file_type)]]; + } + + [panel setAllowedContentTypes:allowed_content_types]; + [panel setAllowsOtherFileTypes:options.allow_all_file_types]; + + auto model_result = [panel runModal]; + if (model_result == NSModalResponseOK) { + return Convert((CFStringRef)[[panel URL] path]); + } else { + return std::nullopt; + } +} + +std::optional> OsxUiApplication::ShowOpenDialog(OpenDialogOptions options) { + NSOpenPanel* panel = [NSOpenPanel openPanel]; + [panel setTitle:(NSString*)Convert(options.title)]; + [panel setPrompt:(NSString*)Convert(options.prompt)]; + [panel setMessage:(NSString*)Convert(options.message)]; + + NSMutableArray* allowed_content_types = [NSMutableArray array]; + + for (const auto& file_type : options.allowed_file_types) { + [allowed_content_types + addObject:[UTType typeWithFilenameExtension:(NSString*)Convert(file_type)]]; + } + + [panel setAllowedContentTypes:allowed_content_types]; + [panel setAllowsOtherFileTypes:options.allow_all_file_types]; + + [panel setCanChooseFiles:options.can_choose_files]; + [panel setCanChooseDirectories:options.can_choose_directories]; + [panel setAllowsMultipleSelection:options.allow_mulitple_selection]; + + auto model_result = [panel runModal]; + if (model_result == NSModalResponseOK) { + std::vector result; + for (NSURL* url in [panel URLs]) { + result.push_back(Convert((CFStringRef)[url path])); + } + return result; + } else { + return std::nullopt; + } +} + +void OsxUiApplication::UnregisterWindow(OsxWindow* window) { + p_->windows_.erase( + std::remove(p_->windows_.begin(), p_->windows_.end(), static_cast(window)), + p_->windows_.cend()); +} +} + +@implementation CruAppDelegate { + cru::platform::gui::osx::details::OsxUiApplicationPrivate* _p; +} + +- (id)init:(cru::platform::gui::osx::details::OsxUiApplicationPrivate*)p { + _p = p; + return self; +} +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)sender { + return NSApplicationTerminateReply::NSTerminateNow; +} +- (void)applicationWillTerminate:(NSNotification*)notification { + _p->CallQuitHandlers(); +} +@end -- cgit v1.2.3