diff --git a/src/xenia/app/emulator_window.cc b/src/xenia/app/emulator_window.cc index 1b78f712a..544fabdec 100644 --- a/src/xenia/app/emulator_window.cc +++ b/src/xenia/app/emulator_window.cc @@ -61,6 +61,8 @@ DECLARE_bool(readback_resolve); DECLARE_bool(readback_memexport); +DECLARE_int32(keyboard_mode); + DEFINE_bool(fullscreen, false, "Whether to launch the emulator in fullscreen.", "Display"); @@ -577,6 +579,7 @@ bool EmulatorWindow::Initialize() { // FIXME: This code is really messy. auto main_menu = MenuItem::Create(MenuItem::Type::kNormal); auto file_menu = MenuItem::Create(MenuItem::Type::kPopup, "&File"); + auto testing_menu = MenuItem::Create(MenuItem::Type::kPopup, "&Testing"); auto recent_menu = MenuItem::Create(MenuItem::Type::kPopup, "&Open Recent"); auto zar_menu = MenuItem::Create(MenuItem::Type::kPopup, "&Zar Package"); FillRecentlyLaunchedTitlesMenu(recent_menu.get()); @@ -613,6 +616,17 @@ bool EmulatorWindow::Initialize() { } main_menu->AddChild(std::move(file_menu)); + testing_menu->AddChild( + std::move(MenuItem::Create(MenuItem::Type::kChecked, "&SubMenu 1"))); + testing_menu->AddChild( + std::move(MenuItem::Create(MenuItem::Type::kChecked, "&SubMenu 2"))); + testing_menu->AddChild( + std::move(MenuItem::Create(MenuItem::Type::kChecked, "&SubMenu 3"))); + testing_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator)); + testing_menu->AddChild( + std::move(MenuItem::Create(MenuItem::Type::kChecked, "&SubMenu 4"))); + main_menu->AddChild(std::move(testing_menu)); + // Profile Menu auto profile_menu = MenuItem::Create(MenuItem::Type::kPopup, "&Profile"); { @@ -728,6 +742,14 @@ bool EmulatorWindow::Initialize() { } main_menu->AddChild(std::move(help_menu)); + // if (cvars::keyboard_mode == 2) { + auto toggle_menu = + MenuItem::Create(MenuItem::Type::kString, "&Disable Toolbar", + std::bind(&EmulatorWindow::ToggleToolBar, this)); + + main_menu->AddChild(std::move(toggle_menu)); + //} + window_->SetMainMenu(std::move(main_menu)); window_->SetMainMenuEnabled(false); @@ -831,11 +853,30 @@ void EmulatorWindow::ApplyDisplayConfigForCvars() { } } +void EmulatorWindow::ToggleToolBar() { + const uint32_t toolbar_pos = 8; + + auto toolbar_item = window_->GetMainMenu()->GetItem(toolbar_pos); + + window_->SetMainMenuEnabled(!window_->GetMainMenuEnabled()); + toolbar_item->SetEnabled(true); + + if (window_->GetMainMenuEnabled()) { + toolbar_item->ModifyString("Disable Toolbar"); + } else { + toolbar_item->ModifyString("Enable Toolbar"); + } +} + void EmulatorWindow::OnKeyDown(ui::KeyEvent& e) { if (!emulator_initialized_) { return; } + if (!window_->GetMainMenuEnabled()) { + return; + } + switch (e.virtual_key()) { case ui::VirtualKey::kO: { if (!e.is_ctrl_pressed()) { diff --git a/src/xenia/app/emulator_window.h b/src/xenia/app/emulator_window.h index 7e58a3372..49fa5f3e0 100644 --- a/src/xenia/app/emulator_window.h +++ b/src/xenia/app/emulator_window.h @@ -202,6 +202,8 @@ class EmulatorWindow { GetGuestOutputPaintConfigForCvars(); void ApplyDisplayConfigForCvars(); + void ToggleToolBar(); + void OnKeyDown(ui::KeyEvent& e); void OnMouseDown(const ui::MouseEvent& e); void ToggleFullscreenOnDoubleClick(); diff --git a/src/xenia/ui/menu_item.cc b/src/xenia/ui/menu_item.cc index 225ca6444..ecf1295e2 100644 --- a/src/xenia/ui/menu_item.cc +++ b/src/xenia/ui/menu_item.cc @@ -29,6 +29,8 @@ MenuItem::MenuItem(Type type, const std::string& text, const std::string& hotkey, std::function callback) : type_(type), parent_item_(nullptr), + previous_item_(nullptr), + next_item_(nullptr), text_(text), hotkey_(hotkey), callback_(std::move(callback)) {} @@ -46,6 +48,14 @@ void MenuItem::AddChild(std::unique_ptr child_item) { void MenuItem::AddChild(MenuItemPtr child_item) { auto child_item_ptr = child_item.get(); + child_item_ptr->parent_item_ = this; + + // Doubly Linked List + if (children_.size()) { + child_item_ptr->previous_item_ = children_.back().get(); + child_item_ptr->previous_item_->next_item_ = child_item_ptr; + } + children_.emplace_back(std::move(child_item)); OnChildAdded(child_item_ptr); } @@ -60,9 +70,20 @@ void MenuItem::RemoveChild(MenuItem* child_item) { } } -MenuItem* MenuItem::child(size_t index) { return children_[index].get(); } +MenuItem* MenuItem::GetItem(uint32_t index) { return children_[index].get(); } + +void MenuItem::SetPreviousItem(MenuItem* previous_item) { + previous_item_ = previous_item; +} + +void MenuItem::SetNextItem(MenuItem* next_item) { next_item_ = next_item; } void MenuItem::OnSelected() { + if (type() == Type::kChecked) { + ResetChecked(); + SetChecked(true); + } + if (callback_) { callback_(); // Note that this MenuItem might have been destroyed by the callback. diff --git a/src/xenia/ui/menu_item.h b/src/xenia/ui/menu_item.h index 01bcbe5c5..fc618e4ba 100644 --- a/src/xenia/ui/menu_item.h +++ b/src/xenia/ui/menu_item.h @@ -27,10 +27,11 @@ class MenuItem { typedef std::unique_ptr MenuItemPtr; enum class Type { - kPopup, // Popup menu (submenu) - kSeparator, - kNormal, // Root menu - kString, // Menu is just a string + kPopup, // Popup menu (submenu) + kSeparator, // Seperator between elements + kNormal, // Root menu + kString, // Menu is just a string + kChecked // Menu is child of submenu with checkmarks }; static std::unique_ptr Create(Type type); @@ -43,8 +44,11 @@ class MenuItem { virtual ~MenuItem(); - MenuItem* parent_item() const { return parent_item_; } - Type type() { return type_; } + MenuItem* GetParentItem() const { return parent_item_; } + MenuItem* GetPreviousItem() const { return previous_item_; } + MenuItem* GetNextItem() const { return next_item_; } + + Type type() const { return type_; } const std::string& text() { return text_; } const std::string& hotkey() { return hotkey_; } @@ -57,9 +61,17 @@ class MenuItem { void AddChild(std::unique_ptr child_item); void AddChild(MenuItemPtr child_item); void RemoveChild(MenuItem* child_item); - MenuItem* child(size_t index); + void SetPreviousItem(MenuItem* previous_item); + void SetNextItem(MenuItem* next_item); + MenuItem* GetItem(uint32_t index); + virtual void SetEnabledCascade(bool enabled) {} virtual void SetEnabled(bool enabled) {} + virtual void SetEnabled(uint32_t position, bool enabled) {} + virtual void SetChecked(bool checked) {} + virtual void SetChecked(uint32_t identifier, bool checked) {} + virtual void ResetChecked() {}; + virtual void ModifyString(std::string modify_str) {} protected: MenuItem(Type type, const std::string& text, const std::string& hotkey, @@ -74,6 +86,8 @@ class MenuItem { Type type_; MenuItem* parent_item_; + MenuItem* previous_item_; + MenuItem* next_item_; std::vector children_; std::string text_; std::string hotkey_; diff --git a/src/xenia/ui/window.cc b/src/xenia/ui/window.cc index ed00afc6b..8c00cde01 100644 --- a/src/xenia/ui/window.cc +++ b/src/xenia/ui/window.cc @@ -317,7 +317,8 @@ void Window::SetMainMenuEnabled(bool enabled) { // pressing) that may execute callbacks potentially destroying the Window via // the outer architecture. WindowDestructionReceiver destruction_receiver(this); - main_menu_->SetEnabled(enabled); + main_menu_->SetEnabledCascade(enabled); + main_menu_enabled_ = enabled; if (destruction_receiver.IsWindowDestroyed()) { return; } diff --git a/src/xenia/ui/window.h b/src/xenia/ui/window.h index aaf7983a8..3e2124af0 100644 --- a/src/xenia/ui/window.h +++ b/src/xenia/ui/window.h @@ -349,6 +349,10 @@ class Window { } } + MenuItem* GetMainMenu() const { return main_menu_.get(); } + + bool GetMainMenuEnabled() const { return main_menu_enabled_; } + protected: // The receiver, which must never be instantiated in the Window object itself // (rather, usually it should be created as a local variable, because only @@ -509,7 +513,6 @@ class Window { // default one. Returns whether the icon has been updated successfully. virtual void LoadAndApplyIcon(const void* buffer, size_t size, bool can_apply_state_in_current_phase) {} - MenuItem* GetMainMenu() const { return main_menu_.get(); } // May be called to add, replace or remove the main menu. virtual void ApplyNewMainMenu(MenuItem* old_main_menu) {} // If there's main menu, and state can be applied, will be called to make the @@ -711,6 +714,8 @@ class Window { bool has_focus_ = false; + bool main_menu_enabled_ = false; + Presenter* presenter_ = nullptr; std::unique_ptr presenter_surface_; // Whether currently in InPaint to prevent recursive painting in case it's diff --git a/src/xenia/ui/window_win.cc b/src/xenia/ui/window_win.cc index 1de3e0448..8b41897e2 100644 --- a/src/xenia/ui/window_win.cc +++ b/src/xenia/ui/window_win.cc @@ -128,13 +128,14 @@ bool Win32Window::OpenImpl() { // Create the window. Though WM_NCCREATE will assign to `hwnd_` too, still do // the assignment here to handle the case of a failure after WM_NCCREATE, for // instance. - hwnd_ = CreateWindowExW( - window_ex_style, L"XeniaWindowClass", - reinterpret_cast(xe::to_utf16(GetTitle()).c_str()), window_style, - CW_USEDEFAULT, CW_USEDEFAULT, - window_size_rect.right - window_size_rect.left, - window_size_rect.bottom - window_size_rect.top, nullptr, nullptr, - hinstance, this); + const auto window_title = xe::to_utf16(GetTitle()); + + hwnd_ = CreateWindowExW(window_ex_style, L"XeniaWindowClass", + reinterpret_cast(window_title.c_str()), + window_style, CW_USEDEFAULT, CW_USEDEFAULT, + window_size_rect.right - window_size_rect.left, + window_size_rect.bottom - window_size_rect.top, + nullptr, nullptr, hinstance, this); if (!hwnd_) { XELOGE("CreateWindowExW failed"); return false; @@ -1202,16 +1203,16 @@ LRESULT Win32Window::WndProc(HWND hWnd, UINT message, WPARAM wParam, TABLET_ENABLE_MULTITOUCHDATA; case WM_MENUCOMMAND: { - MENUINFO menu_info = {0}; + MENUINFO menu_info = {}; menu_info.cbSize = sizeof(menu_info); menu_info.fMask = MIM_MENUDATA; GetMenuInfo(HMENU(lParam), &menu_info); auto parent_item = reinterpret_cast(menu_info.dwMenuData); - auto child_item = - reinterpret_cast(parent_item->child(wParam)); - assert_not_null(child_item); + auto next_item = reinterpret_cast( + parent_item->GetItem(static_cast(wParam))); + assert_not_null(next_item); WindowDestructionReceiver destruction_receiver(this); - child_item->OnSelected(); + next_item->OnSelected(); if (destruction_receiver.IsWindowDestroyed()) { break; } @@ -1286,19 +1287,25 @@ Win32MenuItem::Win32MenuItem(Type type, const std::string& text, switch (type) { case MenuItem::Type::kNormal: handle_ = CreateMenu(); + identifier_ = -1; break; case MenuItem::Type::kPopup: handle_ = CreatePopupMenu(); + identifier_ = -1; + break; + case MenuItem::Type::kSeparator: + identifier_ = -1; break; default: // May just be a placeholder. break; } + if (handle_) { - MENUINFO menu_info = {0}; + MENUINFO menu_info = {}; menu_info.cbSize = sizeof(menu_info); menu_info.fMask = MIM_MENUDATA | MIM_STYLE; - menu_info.dwMenuData = ULONG_PTR(this); + menu_info.dwMenuData = reinterpret_cast(this); menu_info.dwStyle = MNS_NOTIFYBYPOS; SetMenuInfo(handle_, &menu_info); } @@ -1310,16 +1317,87 @@ Win32MenuItem::~Win32MenuItem() { } } +void Win32MenuItem::SetEnabledCascade(bool enabled) { + for (const auto& item : children_) { + item->SetEnabled(enabled); + } +} + void Win32MenuItem::SetEnabled(bool enabled) { + SetEnabled(position(), enabled); +} + +void Win32MenuItem::SetEnabled(uint32_t position, bool enabled) { UINT enable_flags = MF_BYPOSITION | (enabled ? MF_ENABLED : MF_GRAYED); - UINT i = 0; - for (auto iter = children_.begin(); iter != children_.end(); ++iter, ++i) { - EnableMenuItem(handle_, i, enable_flags); + + const auto parent_item = static_cast(GetParentItem()); + + if (parent_item) { + EnableMenuItem(parent_item->handle(), position, enable_flags); + } else if (handle_) { + EnableMenuItem(handle_, position, enable_flags); + } +} + +void Win32MenuItem::SetChecked(bool checked) { + SetChecked(identifier(), checked); +} + +void Win32MenuItem::SetChecked(uint32_t identifier, bool checked) { + MENUITEMINFOW MII = {}; + MII.cbSize = sizeof(MENUITEMINFO); + MII.fMask = MIIM_STATE; + MII.fState = checked ? MFS_CHECKED : MFS_UNCHECKED; + + // assert_true(handle_ != 0); + + if (type() == Type::kChecked) { + const auto parent_item = static_cast(GetParentItem()); + + if (parent_item) { + SetMenuItemInfoW(parent_item->handle(), identifier, false, &MII); + } + } +} + +void Win32MenuItem::ResetChecked() { + if (type() == Type::kChecked) { + const auto parent = static_cast(GetParentItem()); + + if (parent) { + for (const auto& item : parent->children_) { + item->SetChecked(false); + } + } + } +} + +void Win32MenuItem::ModifyString(std::string modify_str) { + MENUITEMINFOW MII = {}; + MII.cbSize = sizeof(MENUITEMINFO); + MII.fMask = MIIM_STRING; + + const auto updated_string = xe::to_utf16(modify_str); + const auto updated_text = reinterpret_cast(updated_string.c_str()); + MII.dwTypeData = const_cast(updated_text); + + // assert_true(handle_ != 0); + + if (type() == Type::kString) { + const auto parent_item = static_cast(GetParentItem()); + + if (parent_item) { + SetMenuItemInfoW(parent_item->handle(), position(), true, &MII); + } } } void Win32MenuItem::OnChildAdded(MenuItem* generic_child_item) { auto child_item = static_cast(generic_child_item); + auto parent_item = static_cast(child_item->GetParentItem()); + + child_item->position_ = + static_cast(parent_item->children_.size()) - 1; switch (child_item->type()) { case MenuItem::Type::kNormal: @@ -1331,20 +1409,55 @@ void Win32MenuItem::OnChildAdded(MenuItem* generic_child_item) { reinterpret_cast(xe::to_utf16(child_item->text()).c_str())); break; case MenuItem::Type::kSeparator: - AppendMenuW(handle_, MF_SEPARATOR, UINT_PTR(child_item->handle_), 0); + AppendMenuW(handle_, MF_SEPARATOR, 0, nullptr); break; + case MenuItem::Type::kChecked: case MenuItem::Type::kString: - auto full_name = child_item->text(); + child_item->identifier_ = parent_item->next_identifier(); + + std::string full_name = child_item->text(); + if (!child_item->hotkey().empty()) { - full_name += "\t" + child_item->hotkey(); + full_name = fmt::format("{}\t{}", full_name, child_item->hotkey()); } - AppendMenuW(handle_, MF_STRING, UINT_PTR(child_item->handle_), + + AppendMenuW(handle_, MF_STRING, child_item->identifier(), reinterpret_cast(xe::to_utf16(full_name).c_str())); break; } } -void Win32MenuItem::OnChildRemoved(MenuItem* generic_child_item) {} +void Win32MenuItem::OnChildRemoved(MenuItem* generic_child_item) { + auto child_item = static_cast(generic_child_item); + auto previous_item = + static_cast(child_item->GetPreviousItem()); + auto next_item = static_cast(child_item->GetNextItem()); + auto parent_item = static_cast(child_item->GetParentItem()); + + UINT flags = MF_BYPOSITION; + const uint32_t position = child_item->position(); + + bool deleted_item = false; + + if (parent_item) { + deleted_item = DeleteMenu(parent_item->handle(), position, flags); + } else if (handle_) { + deleted_item = DeleteMenu(handle_, position, flags); + } + + // TODO: Reindex linked list positions + + // Update Linked List + if (deleted_item) { + if (previous_item) { + previous_item->SetNextItem(child_item->GetNextItem()); + } + + if (next_item) { + next_item->SetPreviousItem(child_item->GetPreviousItem()); + } + } +} } // namespace ui } // namespace xe diff --git a/src/xenia/ui/window_win.h b/src/xenia/ui/window_win.h index dd5d8b978..8a4503739 100644 --- a/src/xenia/ui/window_win.h +++ b/src/xenia/ui/window_win.h @@ -159,8 +159,30 @@ class Win32MenuItem : public MenuItem { ~Win32MenuItem() override; HMENU handle() const { return handle_; } + uint32_t identifier() const { return identifier_; } + uint32_t position() const { return position_; } + uint32_t next_identifier() const { + if (children_.size() > 1) { + auto item = static_cast( + children_.back().get()->GetPreviousItem()); + while (item != nullptr && item->identifier() == -1) { + item = static_cast(item->GetPreviousItem()); + } + + return item == nullptr ? 0 : item->identifier() + 1; + } else { + return 0; + } + } + + void SetEnabledCascade(bool enabled) override; void SetEnabled(bool enabled) override; + void SetEnabled(uint32_t position, bool enabled) override; + void SetChecked(bool checked) override; + void SetChecked(uint32_t identifier, bool checked) override; + void ResetChecked() override; + void ModifyString(std::string modify_str) override; using MenuItem::OnSelected; @@ -170,6 +192,8 @@ class Win32MenuItem : public MenuItem { private: HMENU handle_ = nullptr; + uint32_t identifier_ = 0; + uint32_t position_ = 0; }; } // namespace ui