From abe0cc4531139c78085099cb7e4f9621f1b741ad Mon Sep 17 00:00:00 2001 From: Chris Blume Date: Tue, 29 Oct 2024 16:37:55 -0700 Subject: [PATCH] Add menus (#80) Add menus This commit adds menus to maxGUI --- .github/workflows/build-and-test.yaml | 3 + .../3-ControlGalleryExample/EntryPoint.cpp | 12 +++ Code/maxGUI/FormConcept.cpp | 1 + Code/maxGUI/FormConcept.hpp | 58 ++++++++++++ Code/maxGUI/FormContainer.cpp | 19 +++- Code/maxGUI/Menu.cpp | 39 ++++++++ Code/maxGUI/Menu.hpp | 90 +++++++++++++++++++ Code/maxGUI/Menu.inl | 63 +++++++++++++ Code/maxGUI/maxGUI.hpp | 1 + Projects/VisualStudio/maxGUI/maxGUI.vcxproj | 18 ++++ .../maxGUI/maxGUI.vcxproj.filters | 15 ++++ Projects/VisualStudio/maxGUI/packages.config | 6 ++ 12 files changed, 324 insertions(+), 1 deletion(-) create mode 100644 Code/maxGUI/Menu.cpp create mode 100644 Code/maxGUI/Menu.hpp create mode 100644 Code/maxGUI/Menu.inl create mode 100644 Projects/VisualStudio/maxGUI/packages.config diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml index 8e4e1f7..26d0675 100644 --- a/.github/workflows/build-and-test.yaml +++ b/.github/workflows/build-and-test.yaml @@ -25,6 +25,9 @@ jobs: - name: Add MSBuild to PATH uses: microsoft/setup-msbuild@v1.0.2 + - name: Update NuGet packages + uses: nuget update + - name: Build working-directory: ${{env.GITHUB_WORKSPACE}} run: msbuild /m /p:Configuration=${{ matrix.configuration }} /p:Platform=${{ matrix.platform }} .\Projects\VisualStudio diff --git a/Code/Examples/3-ControlGalleryExample/EntryPoint.cpp b/Code/Examples/3-ControlGalleryExample/EntryPoint.cpp index 75b6f1e..08aa68c 100644 --- a/Code/Examples/3-ControlGalleryExample/EntryPoint.cpp +++ b/Code/Examples/3-ControlGalleryExample/EntryPoint.cpp @@ -18,6 +18,15 @@ class CustomButtonBehavior { }; +class ExitMenuBehavior { +public: + + static void OnPressed() noexcept { + maxGUI::PostExitMessage(0); + } + +}; + class ControlGalleryForm { public: @@ -47,6 +56,9 @@ class ControlGalleryForm { form->AddControl>(max::Containers::MakeRectangle(25, 750, 300, 25), "Option 2"); form->AddControl>(max::Containers::MakeRectangle(25, 800, 300, 25), "Textbox"); + + auto file_menu = form->AppendMenu("&File"); + file_menu->AppendMenu>("E&xit"); } void OnClosed(maxGUI::FormConcept* /*form*/) noexcept { diff --git a/Code/maxGUI/FormConcept.cpp b/Code/maxGUI/FormConcept.cpp index af0637a..a875921 100644 --- a/Code/maxGUI/FormConcept.cpp +++ b/Code/maxGUI/FormConcept.cpp @@ -11,6 +11,7 @@ namespace maxGUI { #if defined(MAX_PLATFORM_WINDOWS) FormConcept::FormConcept(HWND window_handle) noexcept : window_handle_(std::move(window_handle)) + , menu_bar_handle_(static_cast(INVALID_HANDLE_VALUE)) {} #elif defined(MAX_PLATFORM_LINUX) FormConcept::FormConcept(int width, int height, std::string title, FormStyles styles) noexcept diff --git a/Code/maxGUI/FormConcept.hpp b/Code/maxGUI/FormConcept.hpp index c822a87..420909d 100644 --- a/Code/maxGUI/FormConcept.hpp +++ b/Code/maxGUI/FormConcept.hpp @@ -10,12 +10,16 @@ #include #include +#include #if defined(MAX_PLATFORM_WINDOWS) #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif + #include + + #include #elif defined(MAX_PLATFORM_LINUX) #include @@ -24,6 +28,21 @@ #include #endif +namespace { + + // TODO: Use max's Exists here + template< typename T > + struct HasOnPressed { + typedef char yes[1]; + typedef char no[2]; + + template static yes& test(typename std::enable_if, bool>::type = 0); + template static no& test(...); + static bool const value = sizeof(test::type>(0)) == sizeof(yes&); + }; + +} // anonymous namespace + namespace maxGUI { @@ -44,6 +63,40 @@ namespace maxGUI virtual LRESULT OnWindowMessage(FormConcept* form, UINT message, WPARAM wparam, LPARAM lparam) noexcept = 0; #endif + template< typename T, typename... Params > + T* AppendMenu(Params&&... params) noexcept { +#if defined(MAX_PLATFORM_WINDOWS) + bool is_first_menu = menus_.size() == 0; + if (is_first_menu) { + menu_bar_handle_ = CreateMenu(); + + MENUINFO menu_info = { 0 }; + menu_info.cbSize = sizeof(menu_info); + menu_info.fMask = MIM_STYLE; + menu_info.dwStyle = MNS_NOTIFYBYPOS; + SetMenuInfo(menu_bar_handle_, &menu_info); + + ::SetMenu(window_handle_, menu_bar_handle_); + } + + HMENU menu_handle = T::Create(menu_bar_handle_, std::forward(params)...); + auto menu_ptr = std::make_unique(std::move(menu_handle)); + T* raw_menu_ptr = menu_ptr.get(); + menus_.push_back(std::move(menu_ptr)); + + + if (is_first_menu) { + // TODO: This is only required if the window has already been drawn. IE the menus were added after WM_CREATE + DrawMenuBar(window_handle_); + } + + return raw_menu_ptr; +#else + // TODO: Implement on other platforms + return nullptr; +#endif + } + template T* AddControl(Params&&... params) noexcept { #if defined(MAX_PLATFORM_WINDOWS) @@ -65,10 +118,15 @@ namespace maxGUI #if defined(MAX_PLATFORM_WINDOWS) HWND window_handle_; + HMENU menu_bar_handle_; #elif defined(MAX_PLATFORM_LINUX) QWidget window_; #endif + std::vector> controls_; +#if defined(MAX_PLATFORM_WINDOWS) + std::vector> menus_; +#endif }; diff --git a/Code/maxGUI/FormContainer.cpp b/Code/maxGUI/FormContainer.cpp index 0dd18b0..057016d 100644 --- a/Code/maxGUI/FormContainer.cpp +++ b/Code/maxGUI/FormContainer.cpp @@ -98,12 +98,29 @@ namespace maxGUI { case WM_NCDESTROY: RemoveProp(window_handle, maxgui_formconcept_property_name); return 0; + case WM_MENUCOMMAND: + { + MENUITEMINFO menu_item_info; + menu_item_info.cbSize = sizeof(MENUITEMINFO); + menu_item_info.fMask = MIIM_DATA | MIIM_ID; + BOOL result = GetMenuItemInfo(reinterpret_cast(lparam), static_cast(wparam), TRUE, &menu_item_info); + if (result == 0) { + // error + } + + typedef void (*OnPressedType)(); + OnPressedType on_pressed = reinterpret_cast(menu_item_info.dwItemData); + if (on_pressed != nullptr) { + on_pressed(); + } + return 0; + } case WM_COMMAND: { auto form = GetFormFromHWND(window_handle); if (HIWORD(wparam) == 0 && lparam == 0) // menu { - //auto menu_identifier = LOWORD(wparam); + //auto menu_id = LOWORD(wparam); } else if (HIWORD(wparam) == 1 && lparam == 0) { // accelerator //auto accelerator_identifier = LOWORD(wparam); } else { diff --git a/Code/maxGUI/Menu.cpp b/Code/maxGUI/Menu.cpp new file mode 100644 index 0000000..561c34e --- /dev/null +++ b/Code/maxGUI/Menu.cpp @@ -0,0 +1,39 @@ +// Copyright 2024, The maxGUI Contributors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +namespace maxGUI +{ + +#if defined(MAX_PLATFORM_WINDOWS) + Menu::Menu(HMENU menu_handle) noexcept + : menu_handle_(std::move(menu_handle)) + {} +#endif + +#if defined(MAX_PLATFORM_WINDOWS) + ParentMenu::ParentMenu(HMENU menu_handle) noexcept + : Menu(std::move(menu_handle)) + , next_submenu_id_(0) + {} +#endif + +#if defined(MAX_PLATFORM_WINDOWS) + HMENU ParentMenu::Create(HMENU parent_menu, std::string text) noexcept { + HMENU menu_handle = CreateMenu(); + + auto win32_text = Utf8ToWin32String(std::move(text)); + BOOL result = ::AppendMenu(parent_menu, MF_POPUP | MF_STRING, reinterpret_cast(menu_handle), win32_text.text_); + if (result == 0) { + // error + } + + return menu_handle; + } +#endif + +} \ No newline at end of file diff --git a/Code/maxGUI/Menu.hpp b/Code/maxGUI/Menu.hpp new file mode 100644 index 0000000..5c30e8d --- /dev/null +++ b/Code/maxGUI/Menu.hpp @@ -0,0 +1,90 @@ +// Copyright 2024, The maxGUI Contributors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MAXGUI_MENU_HPP +#define MAXGUI_MENU_HPP + + +#include +#include + +#if defined(MAX_PLATFORM_WINDOWS) + +#include +#include +#include + +#if defined(MAX_PLATFORM_WINDOWS) + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + + #include +#endif + + +namespace maxGUI +{ + + class Menu { + public: + +#if defined(MAX_PLATFORM_WINDOWS) + explicit Menu(HMENU menu_handle) noexcept; +#endif + + virtual ~Menu() noexcept = default; + + protected: + +#if defined(MAX_PLATFORM_WINDOWS) + HMENU menu_handle_; +#endif + + }; + + class ParentMenu : public Menu + { + public: + + explicit ParentMenu(HMENU menu_handle) noexcept; + + ~ParentMenu() noexcept override = default; + +#if defined(MAX_PLATFORM_WINDOWS) + static HMENU Create(HMENU parent_menu, std::string text) noexcept; +#endif + + template + T* AppendMenu(Params&&... params) noexcept; + + std::vector> submenus_; + + ULONG_PTR next_submenu_id_; + + }; + + class DefaultMenuBehavior { + }; + + template< class Behavior = DefaultMenuBehavior > + class PressableMenu : public Menu + { + public: + + explicit PressableMenu(HMENU menu_handle) noexcept; + + ~PressableMenu() noexcept override = default; + + static HMENU Create(HMENU parent_menu, ULONG_PTR id, std::string text) noexcept; + + }; + +} // namespace maxGUI + +#endif // #if defined(MAX_PLATFORM_WINDOWS) + +#include + +#endif // #ifndef MAXGUI_MENU_HPP \ No newline at end of file diff --git a/Code/maxGUI/Menu.inl b/Code/maxGUI/Menu.inl new file mode 100644 index 0000000..15f92ad --- /dev/null +++ b/Code/maxGUI/Menu.inl @@ -0,0 +1,63 @@ +// Copyright 2024, The maxGUI Contributors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#if defined(MAX_PLATFORM_WINDOWS) + +#include + +namespace maxGUI +{ + + template + T* ParentMenu::AppendMenu(Params&&... params) noexcept { +#if defined(MAX_PLATFORM_WINDOWS) + HMENU menu_handle = T::Create(menu_handle_, next_submenu_id_, std::forward(params)...); + auto menu_ptr = std::make_unique(menu_handle); + + next_submenu_id_++; + + T* raw_menu_ptr = menu_ptr.get(); + submenus_.push_back(std::move(menu_ptr)); +#endif + return raw_menu_ptr; + } + + template< class Behavior > + PressableMenu< Behavior >::PressableMenu(HMENU menu_handle) noexcept + : Menu(std::move(menu_handle)) + {} + + template< class Behavior > + HMENU PressableMenu< Behavior >::Create(HMENU parent_menu, ULONG_PTR id, std::string text) noexcept { + HMENU submenu_handle = CreatePopupMenu(); + + auto win32_text = Utf8ToWin32String(std::move(text)); + BOOL result = ::AppendMenu(parent_menu, MF_STRING, reinterpret_cast(submenu_handle), win32_text.text_); + if (result == 0) { + // error + } + + MENUITEMINFO menu_item_info = { 0 }; + menu_item_info.cbSize = sizeof(menu_item_info); + //menu_item_info.fMask = MIIM_DATA | MIIM_STRING | MIIM_ID;// | MIIM_TYPE; + //menu_item_info.fMask = MIIM_DATA | MIIM_TYPE | MIIM_ID; + menu_item_info.fMask = MIIM_DATA | MIIM_TYPE;// | MIIM_ID; + menu_item_info.fType = MFT_STRING; + menu_item_info.dwTypeData = win32_text.text_; + menu_item_info.cch = win32_text.char_count_; + menu_item_info.dwItemData = reinterpret_cast(&Behavior::OnPressed); + + result = SetMenuItemInfo(parent_menu, id, TRUE, &menu_item_info); + if (result == 0) { + // error + } + + return submenu_handle; + } + +} // namespace maxGUI + +#endif \ No newline at end of file diff --git a/Code/maxGUI/maxGUI.hpp b/Code/maxGUI/maxGUI.hpp index 40da99a..32f7076 100644 --- a/Code/maxGUI/maxGUI.hpp +++ b/Code/maxGUI/maxGUI.hpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include diff --git a/Projects/VisualStudio/maxGUI/maxGUI.vcxproj b/Projects/VisualStudio/maxGUI/maxGUI.vcxproj index 98f3972..e6743f2 100644 --- a/Projects/VisualStudio/maxGUI/maxGUI.vcxproj +++ b/Projects/VisualStudio/maxGUI/maxGUI.vcxproj @@ -1,5 +1,6 @@  + Debug @@ -30,6 +31,7 @@ + @@ -44,6 +46,7 @@ + @@ -64,6 +67,7 @@ + @@ -94,6 +98,7 @@ + @@ -121,6 +126,11 @@ + + + Designer + + {3A9E3E87-9F00-4240-8E5D-489DC37C73DA} Win32Proj @@ -288,5 +298,13 @@ + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/Projects/VisualStudio/maxGUI/maxGUI.vcxproj.filters b/Projects/VisualStudio/maxGUI/maxGUI.vcxproj.filters index 4c1ffcd..36018e1 100644 --- a/Projects/VisualStudio/maxGUI/maxGUI.vcxproj.filters +++ b/Projects/VisualStudio/maxGUI/maxGUI.vcxproj.filters @@ -119,6 +119,10 @@ Code + + Code + + @@ -192,6 +196,9 @@ Code + + Code + @@ -347,5 +354,13 @@ Code + + Code + + + + + Docs + \ No newline at end of file diff --git a/Projects/VisualStudio/maxGUI/packages.config b/Projects/VisualStudio/maxGUI/packages.config new file mode 100644 index 0000000..f8cce01 --- /dev/null +++ b/Projects/VisualStudio/maxGUI/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file