Merge pull request #25656 from bruvzg/macos_multi_instances

[macOS] Add ability to open multiple editor instances and global/dock menu access
This commit is contained in:
Rémi Verschelde 2019-08-26 22:04:37 +02:00 committed by GitHub
commit 2f63811c9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 305 additions and 1 deletions

View File

@ -185,10 +185,31 @@ _ResourceSaver::_ResourceSaver() {
/////////////////OS
void _OS::global_menu_add_item(const String &p_menu, const String &p_label, const Variant &p_signal, const Variant &p_meta) {
OS::get_singleton()->global_menu_add_item(p_menu, p_label, p_signal, p_meta);
}
void _OS::global_menu_add_separator(const String &p_menu) {
OS::get_singleton()->global_menu_add_separator(p_menu);
}
void _OS::global_menu_remove_item(const String &p_menu, int p_idx) {
OS::get_singleton()->global_menu_remove_item(p_menu, p_idx);
}
void _OS::global_menu_clear(const String &p_menu) {
OS::get_singleton()->global_menu_clear(p_menu);
}
Point2 _OS::get_mouse_position() const {
return OS::get_singleton()->get_mouse_position();
}
void _OS::set_window_title(const String &p_title) {
OS::get_singleton()->set_window_title(p_title);
@ -202,6 +223,7 @@ int _OS::get_mouse_button_state() const {
String _OS::get_unique_id() const {
return OS::get_singleton()->get_unique_id();
}
bool _OS::has_touchscreen_ui_hint() const {
return OS::get_singleton()->has_touchscreen_ui_hint();
@ -211,6 +233,7 @@ void _OS::set_clipboard(const String &p_text) {
OS::get_singleton()->set_clipboard(p_text);
}
String _OS::get_clipboard() const {
return OS::get_singleton()->get_clipboard();
@ -257,12 +280,14 @@ void _OS::set_video_mode(const Size2 &p_size, bool p_fullscreen, bool p_resizeab
vm.resizable = p_resizeable;
OS::get_singleton()->set_video_mode(vm, p_screen);
}
Size2 _OS::get_video_mode(int p_screen) const {
OS::VideoMode vm;
vm = OS::get_singleton()->get_video_mode(p_screen);
return Size2(vm.width, vm.height);
}
bool _OS::is_video_mode_fullscreen(int p_screen) const {
OS::VideoMode vm;
@ -1125,6 +1150,11 @@ void _OS::_bind_methods() {
//ClassDB::bind_method(D_METHOD("is_video_mode_resizable","screen"),&_OS::is_video_mode_resizable,DEFVAL(0));
//ClassDB::bind_method(D_METHOD("get_fullscreen_mode_list","screen"),&_OS::get_fullscreen_mode_list,DEFVAL(0));
ClassDB::bind_method(D_METHOD("global_menu_add_item", "menu", "label", "id", "meta"), &_OS::global_menu_add_item);
ClassDB::bind_method(D_METHOD("global_menu_add_separator", "menu"), &_OS::global_menu_add_separator);
ClassDB::bind_method(D_METHOD("global_menu_remove_item", "menu", "idx"), &_OS::global_menu_remove_item);
ClassDB::bind_method(D_METHOD("global_menu_clear", "menu"), &_OS::global_menu_clear);
ClassDB::bind_method(D_METHOD("get_video_driver_count"), &_OS::get_video_driver_count);
ClassDB::bind_method(D_METHOD("get_video_driver_name", "driver"), &_OS::get_video_driver_name);
ClassDB::bind_method(D_METHOD("get_current_video_driver"), &_OS::get_current_video_driver);

View File

@ -143,6 +143,11 @@ public:
MONTH_DECEMBER
};
void global_menu_add_item(const String &p_menu, const String &p_label, const Variant &p_signal, const Variant &p_meta);
void global_menu_add_separator(const String &p_menu);
void global_menu_remove_item(const String &p_menu, int p_idx);
void global_menu_clear(const String &p_menu);
Point2 get_mouse_position() const;
void set_window_title(const String &p_title);
int get_mouse_button_state() const;

View File

@ -49,6 +49,8 @@ void MainLoop::_bind_methods() {
BIND_VMETHOD(MethodInfo("_drop_files", PropertyInfo(Variant::POOL_STRING_ARRAY, "files"), PropertyInfo(Variant::INT, "from_screen")));
BIND_VMETHOD(MethodInfo("_finalize"));
BIND_VMETHOD(MethodInfo("_global_menu_action", PropertyInfo(Variant::NIL, "id"), PropertyInfo(Variant::NIL, "meta")));
BIND_CONSTANT(NOTIFICATION_WM_MOUSE_ENTER);
BIND_CONSTANT(NOTIFICATION_WM_MOUSE_EXIT);
BIND_CONSTANT(NOTIFICATION_WM_FOCUS_IN);
@ -115,6 +117,12 @@ void MainLoop::drop_files(const Vector<String> &p_files, int p_from_screen) {
get_script_instance()->call("_drop_files", p_files, p_from_screen);
}
void MainLoop::global_menu_action(const Variant &p_id, const Variant &p_meta) {
if (get_script_instance())
get_script_instance()->call("_global_menu_action", p_id, p_meta);
}
void MainLoop::finish() {
if (get_script_instance()) {

View File

@ -71,6 +71,7 @@ public:
virtual void finish();
virtual void drop_files(const Vector<String> &p_files, int p_from_screen = 0);
virtual void global_menu_action(const Variant &p_id, const Variant &p_meta);
void set_init_script(const Ref<Script> &p_init_script);

View File

@ -143,6 +143,11 @@ public:
static OS *get_singleton();
virtual void global_menu_add_item(const String &p_menu, const String &p_label, const Variant &p_signal, const Variant &p_meta){};
virtual void global_menu_add_separator(const String &p_menu){};
virtual void global_menu_remove_item(const String &p_menu, int p_idx){};
virtual void global_menu_clear(const String &p_menu){};
void print_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, Logger::ErrorType p_type = Logger::ERR_ERROR);
void print(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3;
void printerr(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3;

View File

@ -61,6 +61,16 @@
Called before the program exits.
</description>
</method>
<method name="_global_menu_action" qualifiers="virtual">
<return type="void">
</return>
<argument index="0" name="id" type="Variant">
</argument>
<argument index="1" name="meta" type="Variant">
</argument>
<description>
</description>
</method>
<method name="_idle" qualifiers="virtual">
<return type="bool">
</return>

View File

@ -497,6 +497,50 @@
Returns unobscured area of the window where interactive controls should be rendered.
</description>
</method>
<method name="global_menu_add_item">
<return type="void">
</return>
<argument index="0" name="menu" type="String">
</argument>
<argument index="1" name="label" type="String">
</argument>
<argument index="2" name="id" type="Variant">
</argument>
<argument index="3" name="meta" type="Variant">
</argument>
<description>
Add a new item with text "label" to global menu. Use "_dock" menu to add item to the macOS dock icon menu.
</description>
</method>
<method name="global_menu_add_separator">
<return type="void">
</return>
<argument index="0" name="menu" type="String">
</argument>
<description>
Add a separator between items. Separators also occupy an index.
</description>
</method>
<method name="global_menu_clear">
<return type="void">
</return>
<argument index="0" name="menu" type="String">
</argument>
<description>
Clear the global menu, in effect removing all items.
</description>
</method>
<method name="global_menu_remove_item">
<return type="void">
</return>
<argument index="0" name="menu" type="String">
</argument>
<argument index="1" name="idx" type="int">
</argument>
<description>
Removes the item at index "idx" from the global menu. Note that the indexes of items after the removed item are going to be shifted by one.
</description>
</method>
<method name="has_environment" qualifiers="const">
<return type="bool">
</return>

View File

@ -324,6 +324,15 @@
Emitted when files are dragged from the OS file manager and dropped in the game window. The arguments are a list of file paths and the identifier of the screen where the drag originated.
</description>
</signal>
<signal name="global_menu_action">
<argument index="0" name="id" type="Nil">
</argument>
<argument index="1" name="meta" type="Nil">
</argument>
<description>
Emitted whenever global menu item is clicked.
</description>
</signal>
<signal name="idle_frame">
<description>
Emitted immediately before [method Node._process] is called on every node in the [SceneTree].

View File

@ -135,6 +135,8 @@ void EditorNode::_update_scene_tabs() {
bool show_rb = EditorSettings::get_singleton()->get("interface/scene_tabs/show_script_button");
OS::get_singleton()->global_menu_clear("_dock");
scene_tabs->clear_tabs();
Ref<Texture> script_icon = gui_base->get_icon("Script", "EditorIcons");
for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
@ -149,11 +151,16 @@ void EditorNode::_update_scene_tabs() {
bool unsaved = (i == current) ? saved_version != editor_data.get_undo_redo().get_version() : editor_data.get_scene_version(i) != 0;
scene_tabs->add_tab(editor_data.get_scene_title(i) + (unsaved ? "(*)" : ""), icon);
OS::get_singleton()->global_menu_add_item("_dock", editor_data.get_scene_title(i) + (unsaved ? "(*)" : ""), GLOBAL_SCENE, i);
if (show_rb && editor_data.get_scene_root_script(i).is_valid()) {
scene_tabs->set_tab_right_button(i, script_icon);
}
}
OS::get_singleton()->global_menu_add_separator("_dock");
OS::get_singleton()->global_menu_add_item("_dock", TTR("New Window"), GLOBAL_NEW_WINDOW, Variant());
scene_tabs->set_current_tab(editor_data.get_edited_scene());
if (scene_tabs->get_offset_buttons_visible()) {
@ -290,6 +297,7 @@ void EditorNode::_notification(int p_what) {
get_tree()->get_root()->set_as_audio_listener_2d(false);
get_tree()->set_auto_accept_quit(false);
get_tree()->connect("files_dropped", this, "_dropped_files");
get_tree()->connect("global_menu_action", this, "_global_menu_action");
/* DO NOT LOAD SCENES HERE, WAIT FOR FILE SCANNING AND REIMPORT TO COMPLETE */
} break;
@ -4941,6 +4949,23 @@ void EditorNode::remove_tool_menu_item(const String &p_name) {
}
}
void EditorNode::_global_menu_action(const Variant &p_id, const Variant &p_meta) {
int id = (int)p_id;
if (id == GLOBAL_NEW_WINDOW) {
if (OS::get_singleton()->get_main_loop()) {
List<String> args;
String exec = OS::get_singleton()->get_executable_path();
OS::ProcessID pid = 0;
OS::get_singleton()->execute(exec, args, false, &pid);
}
} else if (id == GLOBAL_SCENE) {
int idx = (int)p_meta;
scene_tabs->set_current_tab(idx);
}
}
void EditorNode::_dropped_files(const Vector<String> &p_files, int p_screen) {
String to_path = ProjectSettings::get_singleton()->globalize_path(get_filesystem_dock()->get_selected_path());
@ -5322,6 +5347,7 @@ void EditorNode::_bind_methods() {
ClassDB::bind_method("_clear_undo_history", &EditorNode::_clear_undo_history);
ClassDB::bind_method("_dropped_files", &EditorNode::_dropped_files);
ClassDB::bind_method(D_METHOD("_global_menu_action"), &EditorNode::_global_menu_action, DEFVAL(Variant()));
ClassDB::bind_method("_toggle_distraction_free_mode", &EditorNode::_toggle_distraction_free_mode);
ClassDB::bind_method("edit_item_resource", &EditorNode::edit_item_resource);

View File

@ -207,6 +207,9 @@ private:
SET_VIDEO_DRIVER_SAVE_AND_RESTART,
GLOBAL_NEW_WINDOW,
GLOBAL_SCENE,
IMPORT_PLUGIN_BASE = 100,
TOOL_MENU_BASE = 1000
@ -504,6 +507,7 @@ private:
void _add_to_recent_scenes(const String &p_scene);
void _update_recent_scenes();
void _open_recent_scene(int p_idx);
void _global_menu_action(const Variant &p_id, const Variant &p_meta);
void _dropped_files(const Vector<String> &p_files, int p_screen);
void _add_dropped_files_recursive(const Vector<String> &p_files, String to_path);
String _recent_scene;

View File

@ -946,6 +946,11 @@ public:
static const char *SIGNAL_SELECTION_CHANGED;
static const char *SIGNAL_PROJECT_ASK_OPEN;
enum MenuOptions {
GLOBAL_NEW_WINDOW,
GLOBAL_OPEN_PROJECT
};
// Can often be passed by copy
struct Item {
String project_key;
@ -1181,6 +1186,7 @@ void ProjectList::load_projects() {
_projects.clear();
_last_clicked = "";
_selected_project_keys.clear();
OS::get_singleton()->global_menu_clear("_dock");
// Load data
// TODO Would be nice to change how projects and favourites are stored... it complicates things a bit.
@ -1218,6 +1224,9 @@ void ProjectList::load_projects() {
create_project_item_control(i);
}
OS::get_singleton()->global_menu_add_separator("_dock");
OS::get_singleton()->global_menu_add_item("_dock", TTR("New Window"), GLOBAL_NEW_WINDOW, Variant());
sort_projects();
set_v_scroll(0);
@ -1305,6 +1314,7 @@ void ProjectList::create_project_item_control(int p_index) {
fpath->set_clip_text(true);
_scroll_children->add_child(hb);
OS::get_singleton()->global_menu_add_item("_dock", item.project_name + " ( " + item.path + " )", GLOBAL_OPEN_PROJECT, Variant(item.path.plus_file("project.godot")));
item.control = hb;
}
@ -1894,6 +1904,29 @@ void ProjectManager::_confirm_update_settings() {
_open_selected_projects();
}
void ProjectManager::_global_menu_action(const Variant &p_id, const Variant &p_meta) {
int id = (int)p_id;
if (id == ProjectList::GLOBAL_NEW_WINDOW) {
List<String> args;
String exec = OS::get_singleton()->get_executable_path();
OS::ProcessID pid = 0;
OS::get_singleton()->execute(exec, args, false, &pid);
} else if (id == ProjectList::GLOBAL_OPEN_PROJECT) {
String conf = (String)p_meta;
if (conf != String()) {
List<String> args;
args.push_back(conf);
String exec = OS::get_singleton()->get_executable_path();
OS::ProcessID pid = 0;
OS::get_singleton()->execute(exec, args, false, &pid);
}
}
}
void ProjectManager::_open_selected_projects() {
const Set<String> &selected_list = _project_list->get_selected_project_keys();
@ -2236,6 +2269,7 @@ void ProjectManager::_bind_methods() {
ClassDB::bind_method("_open_selected_projects_ask", &ProjectManager::_open_selected_projects_ask);
ClassDB::bind_method("_open_selected_projects", &ProjectManager::_open_selected_projects);
ClassDB::bind_method(D_METHOD("_global_menu_action"), &ProjectManager::_global_menu_action, DEFVAL(Variant()));
ClassDB::bind_method("_run_project", &ProjectManager::_run_project);
ClassDB::bind_method("_run_project_confirm", &ProjectManager::_run_project_confirm);
ClassDB::bind_method("_scan_projects", &ProjectManager::_scan_projects);
@ -2561,6 +2595,7 @@ ProjectManager::ProjectManager() {
}
SceneTree::get_singleton()->connect("files_dropped", this, "_files_dropped");
SceneTree::get_singleton()->connect("global_menu_action", this, "_global_menu_action");
run_error_diag = memnew(AcceptDialog);
gui_base->add_child(run_error_diag);

View File

@ -43,6 +43,7 @@ class ProjectList;
class ProjectListFilter;
class ProjectManager : public Control {
GDCLASS(ProjectManager, Control);
Button *erase_btn;
@ -96,6 +97,7 @@ class ProjectManager : public Control {
void _restart_confirm();
void _exit_dialog();
void _scan_begin(const String &p_base);
void _global_menu_action(const Variant &p_id, const Variant &p_meta);
void _confirm_update_settings();

View File

@ -157,6 +157,26 @@ public:
int video_driver_index;
virtual int get_current_video_driver() const;
struct GlobalMenuItem {
String label;
Variant signal;
Variant meta;
GlobalMenuItem() {
//NOP
}
GlobalMenuItem(const String &p_label, const Variant &p_signal, const Variant &p_meta) {
label = p_label;
signal = p_signal;
meta = p_meta;
}
};
Map<String, Vector<GlobalMenuItem> > global_menus;
void _update_global_menu();
protected:
virtual void initialize_core();
virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver);
@ -168,6 +188,11 @@ protected:
public:
static OS_OSX *singleton;
void global_menu_add_item(const String &p_menu, const String &p_label, const Variant &p_signal, const Variant &p_meta);
void global_menu_add_separator(const String &p_menu);
void global_menu_remove_item(const String &p_menu, int p_idx);
void global_menu_clear(const String &p_menu);
void wm_minimized(bool p_minimized);
virtual String get_name() const;

View File

@ -204,11 +204,53 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
}
}
- (void)globalMenuCallback:(id)sender {
if (![sender representedObject])
return;
OS_OSX::GlobalMenuItem *item = (OS_OSX::GlobalMenuItem *)[[sender representedObject] pointerValue];
if (!item)
return;
OS_OSX::singleton->main_loop->global_menu_action(item->signal, item->meta);
}
- (NSMenu *)applicationDockMenu:(NSApplication *)sender {
NSMenu *menu = [[[NSMenu alloc] initWithTitle:@""] autorelease];
Vector<OS_OSX::GlobalMenuItem> &E = OS_OSX::singleton->global_menus["_dock"];
for (int i = 0; i < E.size(); i++) {
if (E[i].label == String()) {
[menu addItem:[NSMenuItem separatorItem]];
} else {
NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:E[i].label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:@""];
[menu_item setRepresentedObject:[NSValue valueWithPointer:&(E[i])]];
}
}
return menu;
}
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
// Note: called before main loop init!
// Note: may be called called before main loop init!
char *utfs = strdup([filename UTF8String]);
OS_OSX::singleton->open_with_filename.parse_utf8(utfs);
free(utfs);
#ifdef TOOLS_ENABLED
// Open new instance
if (OS_OSX::singleton->get_main_loop()) {
List<String> args;
args.push_back(OS_OSX::singleton->open_with_filename);
String exec = OS::get_singleton()->get_executable_path();
OS::ProcessID pid = 0;
OS::get_singleton()->execute(exec, args, false, &pid);
}
#endif
return YES;
}
@ -1266,6 +1308,56 @@ inline void sendPanEvent(double dx, double dy, int modifierFlags) {
@end
void OS_OSX::_update_global_menu() {
NSMenu *main_menu = [NSApp mainMenu];
for (int i = 1; i < [main_menu numberOfItems]; i++) {
[main_menu removeItemAtIndex:i];
}
for (Map<String, Vector<GlobalMenuItem> >::Element *E = global_menus.front(); E; E = E->next()) {
if (E->key() != "_dock") {
NSMenu *menu = [[[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:E->key().utf8().get_data()]] autorelease];
for (int i = 0; i < E->get().size(); i++) {
if (E->get()[i].label == String()) {
[menu addItem:[NSMenuItem separatorItem]];
} else {
NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:E->get()[i].label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:@""];
[menu_item setRepresentedObject:[NSValue valueWithPointer:&(E->get()[i])]];
}
}
NSMenuItem *menu_item = [main_menu addItemWithTitle:[NSString stringWithUTF8String:E->key().utf8().get_data()] action:nil keyEquivalent:@""];
[main_menu setSubmenu:menu forItem:menu_item];
}
}
}
void OS_OSX::global_menu_add_item(const String &p_menu, const String &p_label, const Variant &p_signal, const Variant &p_meta) {
global_menus[p_menu].push_back(GlobalMenuItem(p_label, p_signal, p_meta));
_update_global_menu();
}
void OS_OSX::global_menu_add_separator(const String &p_menu) {
global_menus[p_menu].push_back(GlobalMenuItem());
_update_global_menu();
}
void OS_OSX::global_menu_remove_item(const String &p_menu, int p_idx) {
ERR_FAIL_INDEX(p_idx, global_menus[p_menu].size());
global_menus[p_menu].remove(p_idx);
_update_global_menu();
}
void OS_OSX::global_menu_clear(const String &p_menu) {
global_menus[p_menu].clear();
_update_global_menu();
}
Point2 OS_OSX::get_ime_selection() const {
return im_selection;

View File

@ -1673,6 +1673,12 @@ void SceneTree::drop_files(const Vector<String> &p_files, int p_from_screen) {
MainLoop::drop_files(p_files, p_from_screen);
}
void SceneTree::global_menu_action(const Variant &p_id, const Variant &p_meta) {
emit_signal("global_menu_action", p_id, p_meta);
MainLoop::global_menu_action(p_id, p_meta);
}
Ref<SceneTreeTimer> SceneTree::create_timer(float p_delay_sec, bool p_process_pause) {
Ref<SceneTreeTimer> stt;
@ -1894,6 +1900,7 @@ void SceneTree::_bind_methods() {
ADD_SIGNAL(MethodInfo("physics_frame"));
ADD_SIGNAL(MethodInfo("files_dropped", PropertyInfo(Variant::POOL_STRING_ARRAY, "files"), PropertyInfo(Variant::INT, "screen")));
ADD_SIGNAL(MethodInfo("global_menu_action", PropertyInfo(Variant::NIL, "id"), PropertyInfo(Variant::NIL, "meta")));
ADD_SIGNAL(MethodInfo("network_peer_connected", PropertyInfo(Variant::INT, "id")));
ADD_SIGNAL(MethodInfo("network_peer_disconnected", PropertyInfo(Variant::INT, "id")));
ADD_SIGNAL(MethodInfo("connected_to_server"));

View File

@ -407,6 +407,7 @@ public:
static SceneTree *get_singleton() { return singleton; }
void drop_files(const Vector<String> &p_files, int p_from_screen = 0);
void global_menu_action(const Variant &p_id, const Variant &p_meta);
//network API