Skip to content

Commit

Permalink
feat: wrap win32 application properly into native::Application class (#…
Browse files Browse the repository at this point in the history
…16)

Signed-off-by: Tony Gorez <[email protected]>
  • Loading branch information
tony-go authored Oct 28, 2024
1 parent 1f6de20 commit 924d178
Show file tree
Hide file tree
Showing 14 changed files with 221 additions and 68 deletions.
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ if(APPLE)
list(APPEND NATIVE_LANGUAGES OBJC OBJCXX)
endif()


project(native
VERSION 0.0.1
LANGUAGES ${NATIVE_LANGUAGES}
DESCRIPTION "Build your macOS app with C++"
)

set_property(GLOBAL PROPERTY USE_FOLDERS ON)

# Load Native CMake module
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
include(cmake/native.cmake)
Expand Down
3 changes: 1 addition & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ install: build
$(CMAKE) --install ./build --prefix ./build/dist --config $(PRESET)

test: build
cmake --build build --target hello_world_run --config $(PRESET)
# cmake --build build --target cli_run --config $(PRESET)
$(CMAKE) --build build --target hello_world_run --config $(PRESET)

clean:
$(CMAKE) -E rm -R -f build
Expand Down
25 changes: 15 additions & 10 deletions cmake/example.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,21 @@ function(add_example)
add_dependencies(${EXAMPLE_NAME}_build ${EXAMPLE_NAME}_configure)

# Run target
if(EXAMPLE_TYPE STREQUAL "desktop")
if(APPLE)
if(EXAMPLE_TYPE STREQUAL "desktop")
add_custom_target(${EXAMPLE_NAME}_run
COMMAND "${EXAMPLE_BINARY_DIR}/${EXAMPLE_APP_NAME}.app/Contents/MacOS/${EXAMPLE_APP_NAME}"
COMMENT "Running ${EXAMPLE_NAME} example (bundle)")
else()
add_custom_target(${EXAMPLE_NAME}_run
COMMAND "${EXAMPLE_BINARY_DIR}/${EXAMPLE_APP_NAME}" --foo bar
COMMENT "Running ${EXAMPLE_NAME} example (executable)")
endif()
add_dependencies(${EXAMPLE_NAME}_run ${EXAMPLE_NAME}_build)
elseif(WIN32)
add_custom_target(${EXAMPLE_NAME}_run
COMMAND "${EXAMPLE_BINARY_DIR}/${EXAMPLE_APP_NAME}.app/Contents/MacOS/${EXAMPLE_APP_NAME}"
COMMENT "Running ${EXAMPLE_NAME} example (bundle)"
)
else()
add_custom_target(${EXAMPLE_NAME}_run
COMMAND "${EXAMPLE_BINARY_DIR}/${EXAMPLE_APP_NAME}" --foo bar
COMMENT "Running ${EXAMPLE_NAME} example (executable)"
)
COMMAND "${CMAKE_CURRENT_BINARY_DIR}/${EXAMPLE_NAME}/Debug/${EXAMPLE_APP_NAME}.exe"
COMMENT "Running ${EXAMPLE_NAME} example (Windows)")
add_dependencies(${EXAMPLE_NAME}_run ${EXAMPLE_NAME}_build)
endif()
add_dependencies(${EXAMPLE_NAME}_run ${EXAMPLE_NAME}_build)
endfunction()
19 changes: 19 additions & 0 deletions cmake/native.cmake
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
function(native_add_app)
if(APPLE)
_native_add_app_apple(${ARGN})
elseif(WIN32)
_native_add_app_win32(${ARGN})
else()
message(FATAL_ERROR "We only support Apple platforms")
endif()
Expand Down Expand Up @@ -139,3 +141,20 @@ function(_native_link_modules_apple)
message(WARNING "Unknown module: ${NATIVE_MODULE_MODULE}")
endif()
endfunction()


#
#
#
# Windows platform private functions
#
#
#

function(_native_add_app_win32)
cmake_parse_arguments(NATIVE "" "TARGET;PLATFORM" "SOURCES" ${ARGN})

add_executable(${NATIVE_TARGET} WIN32 ${NATIVE_SOURCES})

target_link_libraries(${NATIVE_TARGET} sourcemeta::native::application::win32)
endfunction()
5 changes: 5 additions & 0 deletions config.cmake.in
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
@PACKAGE_INIT@

if (APPLE)
list(APPEND COMPONENTS
"application_appkit"
"application_foundation"
"window_appkit"
"args_foundation")
elseif (WIN32)
list(APPEND COMPONENTS
"application_win32")
endif()

# Include the native.cmake file
include("${CMAKE_CURRENT_LIST_DIR}/native.cmake")
Expand Down
5 changes: 4 additions & 1 deletion example/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
add_example(NAME hello_world TYPE desktop)
add_example(NAME cli TYPE cli)

if(APPLE)
add_example(NAME cli TYPE cli)
endif()
18 changes: 10 additions & 8 deletions example/hello_world/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ native_add_app(
PLATFORM desktop
SOURCES hello_world.cc)

native_set_profile(
TARGET hello_world_app
NAME "example_hello_world"
IDENTIFIER "com.native.example_hello_world"
VERSION "1.0.0"
DESCRIPTION "My app ..."
CODESIGN_IDENTITY "W4MF6H9XZ6"
MODULES "ui/window")
if(APPLE)
native_set_profile(
TARGET hello_world_app
NAME "example_hello_world"
IDENTIFIER "com.native.example_hello_world"
VERSION "1.0.0"
DESCRIPTION "My app ..."
CODESIGN_IDENTITY "W4MF6H9XZ6"
MODULES "ui/window")
endif()
7 changes: 7 additions & 0 deletions example/hello_world/hello_world.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#include <sourcemeta/native/application.h>

#ifdef __APPLE__ // Temporary until window module is available for windows
#include <sourcemeta/native/window.h>
#endif

#include <exception>
#include <iostream>
Expand All @@ -11,16 +14,20 @@ class App : public sourcemeta::native::Application {
auto on_ready() -> void override {
std::cout << "Ready!" << std::endl;

#ifdef __APPLE__ // Temporary
window.size(800, 600);
window.show();
#endif

this->exit();
}

auto on_error(std::exception_ptr) noexcept -> void override {}

#ifdef __APPLE__ // Temporary
private:
sourcemeta::native::Window window;
#endif
};

NATIVE_RUN(App)
16 changes: 15 additions & 1 deletion src/application/include/sourcemeta/native/application.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
#ifndef SOURCEMETA_NATIVE_APPLICATION_H
#define SOURCEMETA_NATIVE_APPLICATION_H

#ifdef _WIN32
#include <Windows.h>
#endif

#include <cassert>
#include <exception>

Expand Down Expand Up @@ -62,13 +66,23 @@ class Application {
friend struct ApplicationInternals;
};

// TODO(tonygo): remove platform-specific code from here
#ifdef _WIN32
#define NATIVE_RUN(class) \
int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) \
{ \
static_assert(std::is_base_of_v<sourcemeta::native::Application, class>, \
"You must pass a subclass of Application"); \
return (class{}).run(); \
}
#else
#define NATIVE_RUN(class) \
int main(const int, char *[]) { \
static_assert(std::is_base_of_v<sourcemeta::native::Application, class>, \
"You must pass a subclass of Application"); \
return (class {}).run(); \
}

#endif
} // namespace sourcemeta::native

#endif // SOURCEMETA_NATIVE_APPLICATION_H
7 changes: 5 additions & 2 deletions src/application/win32/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ noa_library(
NAME application
VARIANT win32
FOLDER "Native/Application"
SOURCES app_win32.cc)
SOURCES app_win32.cc delegate_win32.h delegate_win32.cc)

noa_library_install(
NAMESPACE sourcemeta
PROJECT native
NAME application
VARIANT win32)

target_link_libraries(sourcemeta_native_application_win32 PRIVATE user32 gdi32)
target_link_libraries(sourcemeta_native_application_win32 PRIVATE
user32
gdi32
)
114 changes: 71 additions & 43 deletions src/application/win32/app_win32.cc
Original file line number Diff line number Diff line change
@@ -1,67 +1,95 @@
#include <windows.h>
#include <Windows.h>

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
#include <sourcemeta/native/application.h>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow) {
const char CLASS_NAME[] = "NativeApplication";
#include "delegate_win32.h"

#include <cassert>
#include <exception>
#include <iostream>

namespace {
sourcemeta::native::Application *instance_{nullptr};
}

namespace sourcemeta::native {

Application::Application() {
assert(!instance_);
instance_ = this;
}

Application::~Application() {
if (internal_) {
// DestroyWindow(static_cast<HWND>(internal_));
}
instance_ = nullptr;
}

Application &Application::instance() {
assert(instance_);
return *instance_;
}

auto Application::run() noexcept -> int {
assert(!running_);
running_ = true;

on_start();

const char CLASS_NAME[] = "NativeWin32ApplicationClass";

WNDCLASS wc = {};
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpfnWndProc = AppDelegate::WindowProc;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = CLASS_NAME;

RegisterClass(&wc);
if (!RegisterClass(&wc)) {
on_error(std::make_exception_ptr(
std::runtime_error("Failed to register window class")));
return EXIT_FAILURE;
}

// Create the window
HWND hwnd =
CreateWindowEx(0, // Optional window styles
CLASS_NAME, // Window class
"Your App Title", // Window text
WS_OVERLAPPEDWINDOW, // Window style

// Size and position
CreateWindowEx(0, CLASS_NAME, "Win32 Application", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,

NULL, // Parent window
NULL, // Menu
hInstance, // Instance handle
NULL // Additional application data
);
NULL, NULL, GetModuleHandle(NULL), NULL);

if (hwnd == NULL) {
return 0;
on_error(
std::make_exception_ptr(std::runtime_error("Failed to create window")));
return EXIT_FAILURE;
}

ShowWindow(hwnd, nCmdShow);
internal_ = hwnd;
ShowWindow(hwnd, SW_SHOW);

// Run the message loop
MSG msg = {};
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}

return 0;
return static_cast<int>(msg.wParam);
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam,
LPARAM lParam) {
switch (uMsg) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;

case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);

// All painting occurs here, between BeginPaint and EndPaint.
FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));

EndPaint(hwnd, &ps);
}
return 0;
auto Application::on_error(std::exception_ptr error) -> void {
try {
if (error)
std::rethrow_exception(error);
} catch (const std::exception &error) {
std::cerr << "Error: " << error.what() << std::endl;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
#ifndef NDEBUG
std::abort();
#else
throw error;
#endif
}

auto Application::exit(const int code) const noexcept -> void {
assert(running_);
PostQuitMessage(code);
}

} // namespace sourcemeta::native
41 changes: 41 additions & 0 deletions src/application/win32/delegate_win32.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include <Windows.h>

#include <sourcemeta/native/application.h>

#include "delegate_win32.h"

namespace sourcemeta::native {

LRESULT CALLBACK AppDelegate::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam,
LPARAM lParam) {
switch (uMsg) {
case WM_CREATE:
// try {
ApplicationInternals::on_ready();
// } catch (...) {
// ApplicationInternals::on_error(std::current_exception());
// }
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
EndPaint(hwnd, &ps);
}
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

auto ApplicationInternals::on_ready() -> void {
sourcemeta::native::Application::instance().on_ready();
}

auto ApplicationInternals::on_error(std::exception_ptr error) noexcept -> void {
sourcemeta::native::Application::instance().on_error(error);
}

} // namespace sourcemeta::native
Loading

0 comments on commit 924d178

Please sign in to comment.