Skip to content

Commit

Permalink
Add menus (#80)
Browse files Browse the repository at this point in the history
Add menus

This commit adds menus to maxGUI
  • Loading branch information
ProgramMax authored Oct 29, 2024
1 parent c0bb810 commit abe0cc4
Show file tree
Hide file tree
Showing 12 changed files with 324 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .github/workflows/build-and-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions Code/Examples/3-ControlGalleryExample/EntryPoint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ class CustomButtonBehavior {

};

class ExitMenuBehavior {
public:

static void OnPressed() noexcept {
maxGUI::PostExitMessage(0);
}

};

class ControlGalleryForm {
public:

Expand Down Expand Up @@ -47,6 +56,9 @@ class ControlGalleryForm {
form->AddControl<maxGUI::RadioButton<>>(max::Containers::MakeRectangle(25, 750, 300, 25), "Option 2");

form->AddControl<maxGUI::TextBox<>>(max::Containers::MakeRectangle(25, 800, 300, 25), "Textbox");

auto file_menu = form->AppendMenu<maxGUI::ParentMenu>("&File");
file_menu->AppendMenu<maxGUI::PressableMenu<ExitMenuBehavior>>("E&xit");
}

void OnClosed(maxGUI::FormConcept* /*form*/) noexcept {
Expand Down
1 change: 1 addition & 0 deletions Code/maxGUI/FormConcept.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<HMENU>(INVALID_HANDLE_VALUE))
{}
#elif defined(MAX_PLATFORM_LINUX)
FormConcept::FormConcept(int width, int height, std::string title, FormStyles styles) noexcept
Expand Down
58 changes: 58 additions & 0 deletions Code/maxGUI/FormConcept.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@

#include <max/Compiling/Configuration.hpp>
#include <maxGUI/Control.hpp>
#include <maxGUI/Menu.hpp>

#if defined(MAX_PLATFORM_WINDOWS)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif

#include <Windows.h>

#include <maxGUI/Win32String.hpp>
#elif defined(MAX_PLATFORM_LINUX)
#include <string>

Expand All @@ -24,6 +28,21 @@
#include <maxGUI/FormStyles.hpp>
#endif

namespace {

// TODO: Use max's Exists here
template< typename T >
struct HasOnPressed {
typedef char yes[1];
typedef char no[2];

template <typename U> static yes& test(typename std::enable_if<std::is_function_v<decltype(U::OnPressed)>, bool>::type = 0);
template <typename U> static no& test(...);
static bool const value = sizeof(test<typename std::remove_cv<T>::type>(0)) == sizeof(yes&);
};

} // anonymous namespace

namespace maxGUI
{

Expand All @@ -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>(params)...);
auto menu_ptr = std::make_unique<T>(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<typename T, typename... Params>
T* AddControl(Params&&... params) noexcept {
#if defined(MAX_PLATFORM_WINDOWS)
Expand All @@ -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<std::unique_ptr<Control>> controls_;
#if defined(MAX_PLATFORM_WINDOWS)
std::vector<std::unique_ptr<Menu>> menus_;
#endif

};

Expand Down
19 changes: 18 additions & 1 deletion Code/maxGUI/FormContainer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<HMENU>(lparam), static_cast<UINT>(wparam), TRUE, &menu_item_info);
if (result == 0) {
// error
}

typedef void (*OnPressedType)();
OnPressedType on_pressed = reinterpret_cast<OnPressedType>(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 {
Expand Down
39 changes: 39 additions & 0 deletions Code/maxGUI/Menu.cpp
Original file line number Diff line number Diff line change
@@ -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 <maxGUI/Menu.hpp>

#include <utility>

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<UINT_PTR>(menu_handle), win32_text.text_);
if (result == 0) {
// error
}

return menu_handle;
}
#endif

}
90 changes: 90 additions & 0 deletions Code/maxGUI/Menu.hpp
Original file line number Diff line number Diff line change
@@ -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 <max/Compiling/Configuration.hpp>
#include <max/Containers/Rectangle.hpp>

#if defined(MAX_PLATFORM_WINDOWS)

#include <memory>
#include <string>
#include <vector>

#if defined(MAX_PLATFORM_WINDOWS)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif

#include <Windows.h>
#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<typename T, typename... Params>
T* AppendMenu(Params&&... params) noexcept;

std::vector<std::unique_ptr<Menu>> 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 <maxGUI/Menu.inl>

#endif // #ifndef MAXGUI_MENU_HPP
63 changes: 63 additions & 0 deletions Code/maxGUI/Menu.inl
Original file line number Diff line number Diff line change
@@ -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 <utility>

#if defined(MAX_PLATFORM_WINDOWS)

#include <maxGUI/Win32String.hpp>

namespace maxGUI
{

template<typename T, typename... Params>
T* ParentMenu::AppendMenu(Params&&... params) noexcept {
#if defined(MAX_PLATFORM_WINDOWS)
HMENU menu_handle = T::Create(menu_handle_, next_submenu_id_, std::forward<Params>(params)...);
auto menu_ptr = std::make_unique<T>(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<UINT_PTR>(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<ULONG_PTR>(&Behavior::OnPressed);

result = SetMenuItemInfo(parent_menu, id, TRUE, &menu_item_info);
if (result == 0) {
// error
}

return submenu_handle;
}

} // namespace maxGUI

#endif
1 change: 1 addition & 0 deletions Code/maxGUI/maxGUI.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <maxGUI/Frame.hpp>
#include <maxGUI/Label.hpp>
#include <maxGUI/ListBox.hpp>
#include <maxGUI/Menu.hpp>
#include <maxGUI/MultilineTextBox.hpp>
#include <maxGUI/ProgressBar.hpp>
#include <maxGUI/RadioButton.hpp>
Expand Down
Loading

0 comments on commit abe0cc4

Please sign in to comment.