Skip to content

Commit

Permalink
refactor(ui/window): refactor appkit internals (#42)
Browse files Browse the repository at this point in the history
Signed-off-by: Tony Gorez <[email protected]>
  • Loading branch information
tony-go authored Nov 20, 2024
1 parent a8a5c87 commit 6385b8a
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 115 deletions.
131 changes: 67 additions & 64 deletions src/ui/window/appkit/window_appkit.mm
Original file line number Diff line number Diff line change
Expand Up @@ -2,78 +2,81 @@

#import <AppKit/AppKit.h> // NSWindowController

@interface WindowController : NSWindowController
- (id)initWithNewWindow;
@end

@implementation WindowController
- (id)initWithNewWindow {
// The style of the window, which determines different aspects of the
// window's appearance and behavior. For more information, see
// https://developer.apple.com/documentation/appkit/nswindowstylemask?language=objc
NSWindowStyleMask windowStyleMask =
NSWindowStyleMaskTitled | NSWindowStyleMaskResizable |
NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable;

NSWindow *window = [[NSWindow alloc]
initWithContentRect:NSMakeRect(0, 0, 0, 0)
styleMask:windowStyleMask
// You should use this mode.
// It supports hardware acceleration, Quartz drawing,
// and takes advantage of the GPU when possible.
backing:NSBackingStoreBuffered
// When defer is set to YES, the window server postpones the
// creation of the window device (the low-level resources
// for rendering) until the window is moved onscreen. This
// can optimize performance by delaying the allocation of
// graphics resources until they are needed.
defer:YES];

// By default, we don't want the title to be visible
[window setTitleVisibility:NSWindowTitleHidden];

// By default, we don't want the title bar to be transparent
// as we hide the title. It lets the window's content extend to the
// top of the window.
[window setTitlebarAppearsTransparent:YES];

[window.contentView setAutoresizesSubviews:YES];

return [super initWithWindow:window];
}
@end

namespace sourcemeta::native {
Window::Window() {
internal_ =
static_cast<void *>([[WindowController alloc] initWithNewWindow].window);
}

Window::~Window() {
NSWindow *window = static_cast<NSWindow *>(internal_);
[[window windowController] close];
class Window::Internal {
public:
Internal() {
// The style of the window, which determines different aspects of the
// window's appearance and behavior. For more information, see
// https://developer.apple.com/documentation/appkit/nswindowstylemask?language=objc
NSWindowStyleMask windowStyleMask =
NSWindowStyleMaskTitled | NSWindowStyleMaskResizable |
NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable;

NSWindow *window = [[NSWindow alloc]
initWithContentRect:NSMakeRect(0, 0, 0, 0)
styleMask:windowStyleMask
// You should use this mode.
// It supports hardware acceleration, Quartz drawing,
// and takes advantage of the GPU when possible.
backing:NSBackingStoreBuffered
// When defer is set to YES, the window server postpones
// the creation of the window device (the low-level
// resources for rendering) until the window is moved
// onscreen. This can optimize performance by delaying the
// allocation of graphics resources until they are needed.
defer:YES];

// By default, we don't want the title to be visible
[window setTitleVisibility:NSWindowTitleHidden];

// By default, we don't want the title bar to be transparent
// as we hide the title. It lets the window's content extend to the
// top of the window.
[window setTitlebarAppearsTransparent:YES];

[window.contentView setAutoresizesSubviews:YES];

window_ = window;
}

~Internal() { [[window_ windowController] close]; }

if (internal_) {
CFBridgingRelease(internal_);
auto show() -> void {
NSWindowController *controller = [window_ windowController];
[controller showWindow:nil];
[window_ makeKeyAndOrderFront:controller];
}
}

auto Window::size(const unsigned int width, const unsigned int height) -> void {
NSWindow *window = static_cast<NSWindow *>(internal_);
NSRect frame = [window frame];
auto size(const unsigned int width, const unsigned int height) -> void {
NSRect frame = [window_ frame];

frame.size.width = width;
frame.size.height = height;
frame.size.width = width;
frame.size.height = height;

[window setFrame:frame display:YES animate:YES];
}
[window_ setFrame:frame display:YES animate:YES];
}

auto Window::show() -> void {
NSWindow *window = static_cast<NSWindow *>(internal_);
WindowController *controller = [window windowController];
[controller showWindow:nil];
[window makeKeyAndOrderFront:controller];
auto handle() -> void * { return window_; }

private:
NSWindow *window_;
};

Window::Window() { internal_ = new Window::Internal(); }

Window::~Window() { delete internal_; }

auto Window::size(const unsigned int width, const unsigned int height) -> void {
if (width == 0 || height == 0) {
return;
}

internal_->size(width, height);
}

auto Window::handle() -> void * { return internal_; }
auto Window::show() -> void { internal_->show(); }

auto Window::handle() -> void * { return internal_->handle(); }
} // namespace sourcemeta::native
4 changes: 2 additions & 2 deletions src/ui/window/include/sourcemeta/native/window.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ class Window {
template <typename T> auto add(T &child) -> void { add_(child); }

private:
using Internal = void *;
Internal internal_;
class Internal;
Internal *internal_;

// TODO(tonygo): Hide this implementation detail
template <typename T> void add_(T &child) {
Expand Down
122 changes: 73 additions & 49 deletions src/ui/window/win32/window_win32.cc
Original file line number Diff line number Diff line change
@@ -1,23 +1,35 @@
#include <Windows.h>
#include <sourcemeta/native/window.h>

#include <Windows.h>

#include <functional>
#include <iostream>
#include <vector>

namespace sourcemeta::native {

struct WindowInternal {
HWND hwnd;
std::vector<std::function<void(void)>> resize_callbacks;
class Window::Internal {
public:
Internal() {
WNDCLASS wc = {};
wc.lpfnWndProc = &Internal::WindowProc;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = this->class_name_;
RegisterClass(&wc);

// TODO: Make this configurable
this->title_ = "Native Window";
}

// The `CALLBACK` macro is a Windows-specific calling convention that
// guarantees that the stack is cleaned up properly.
static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam,
LPARAM lParam) {
if (uMsg == WM_NCCREATE) {
// Windows gives us back our internal pointer that we passed to
// CreateWindowEx
auto *cs = reinterpret_cast<CREATESTRUCT *>(lParam);
auto internal = static_cast<WindowInternal *>(cs->lpCreateParams);
auto internal = static_cast<Internal *>(cs->lpCreateParams);

// We store it with the window so we can get it back later
SetWindowLongPtr(hwnd, GWLP_USERDATA,
Expand All @@ -26,10 +38,10 @@ struct WindowInternal {

switch (uMsg) {
case WM_SIZE: {
auto internal = reinterpret_cast<WindowInternal *>(
GetWindowLongPtr(hwnd, GWLP_USERDATA));
auto internal =
reinterpret_cast<Internal *>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
if (internal) {
for (auto &callback : internal->resize_callbacks) {
for (auto &callback : internal->get_resize_callbacks()) {
callback();
}
}
Expand All @@ -44,62 +56,74 @@ struct WindowInternal {
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

auto size(const unsigned int width, const unsigned int height) -> void {
if (this->hwnd_) {
SetWindowPos(static_cast<HWND>(this->hwnd_), NULL, 0,
0, // Ignore position
width, height, SWP_NOMOVE | SWP_NOZORDER);
}
}

auto create_window() -> void {
// Create the window
this->hwnd_ =
CreateWindowEx(0, this->class_name_, this->title_, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, NULL, NULL, GetModuleHandle(NULL), this);
if (!this->hwnd_) {
std::cerr << "Failed to create window. Error: " << GetLastError()
<< std::endl;
return;
}
}

auto show() -> void {
if (!this->hwnd_) {
this->create_window();
}
if (this->hwnd_) {
ShowWindow(this->hwnd_, SW_SHOW);
}
}

auto handle() -> void * { return &this->hwnd_; }

auto add_resize_callback(std::function<void(void)> callback) -> void {
this->resize_callbacks_.push_back(callback);
}

auto get_resize_callbacks() -> std::vector<std::function<void(void)>> {
return this->resize_callbacks_;
}

private:
HWND hwnd_;
std::vector<std::function<void(void)>> resize_callbacks_;
const char *class_name_ = "NativeWin32WindowClass";
const char *title_;
};

Window::Window() : internal_(new WindowInternal{}) {
const char CLASS_NAME[] = "NativeWin32WindowClass";
WNDCLASS wc = {};
wc.lpfnWndProc = WindowInternal::WindowProc;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = CLASS_NAME;
RegisterClass(&wc);
}
Window::Window() { internal_ = new Internal(); }

Window::~Window() {
if (internal_) {
auto internal = static_cast<WindowInternal *>(internal_);
std::cout << "Window::~Window(): delete internal" << std::endl;
delete internal;
delete internal_;
}
}

auto Window::size(const unsigned int width, const unsigned int height) -> void {
auto internal = static_cast<WindowInternal *>(internal_);
if (internal->hwnd) {
SetWindowPos(static_cast<HWND>(internal->hwnd), NULL, 0,
0, // Ignore position
width, height, SWP_NOMOVE | SWP_NOZORDER);
}
internal_->size(width, height);
}

auto Window::show() -> void {
const char CLASS_NAME[] = "NativeWin32WindowClass";
auto internal = static_cast<WindowInternal *>(internal_);
// TODO(tony): add parameter for title
HWND hwnd =
CreateWindowEx(0, CLASS_NAME, "Native Window", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, // Parent window
NULL, // Menu
GetModuleHandle(NULL), internal);

if (!hwnd) {
std::cerr << "Failed to create window. Error: " << GetLastError()
<< std::endl;
return;
}

internal->hwnd = hwnd;
ShowWindow(hwnd, SW_SHOW);
internal_->create_window();
internal_->show();
}

auto Window::handle() -> void * {
auto internal = static_cast<WindowInternal *>(internal_);
return &internal->hwnd;
}
auto Window::handle() -> void * { return internal_->handle(); }

auto Window::on_resize(std::function<void(void)> callback) -> void {
auto internal = static_cast<WindowInternal *>(internal_);
internal->resize_callbacks.push_back(callback);
internal_->add_resize_callback(callback);
}
} // namespace sourcemeta::native

0 comments on commit 6385b8a

Please sign in to comment.