C++ test with an example of using the Windows API directly to create a slint platform

This commit is contained in:
Olivier Goffart 2023-05-05 05:23:03 -07:00 committed by Olivier Goffart
parent 6803f05170
commit f9e5559eeb
8 changed files with 430 additions and 0 deletions

View file

@ -55,4 +55,5 @@ if(SLINT_FEATURE_EXPERIMENTAL)
if(Qt6_FOUND)
add_subdirectory(manual/platform_qt)
endif(Qt6_FOUND)
add_subdirectory(manual/platform_native)
endif(SLINT_FEATURE_EXPERIMENTAL)

View file

@ -0,0 +1,9 @@
# Copyright © SixtyFPS GmbH <info@slint-ui.com>
# SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
if (WIN32)
add_executable(platform_native WIN32 main.cpp appview.cpp)
target_link_libraries(platform_native PRIVATE Slint::Slint)
slint_target_sources(platform_native appwindow.slint)
endif(WIN32)

View file

@ -0,0 +1,7 @@
This shows how one can use the Slint C++ platform API to integrate into any Windows application
- main.cpp is basically a shell of an application written using the native WIN32 api.
- appview.h is an interface that is used by the application to show a Slint Window.
the implementation of this interface could even be in a plugin.
- appview.cpp is the implementation of this interface and instantiate the UI made with Slint
- windowadapter_win.h contains the glue code used to implement a Slint platform using native WIN32 API

View file

@ -0,0 +1,43 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
#ifndef UNICODE
# define UNICODE
#endif
#include "appwindow.h"
#include <slint_platform.h>
#if defined(_WIN32) || defined(_WIN64)
# include "windowadapter_win.h"
#endif
namespace slint_platform = slint::experimental::platform;
struct MyPlatform : public slint_platform::Platform
{
mutable std::unique_ptr<MyWindowAdapter> the_window;
std::unique_ptr<slint_platform::AbstractWindowAdapter> create_window_adapter() const override
{
return std::move(the_window);
}
};
AppView::AppView() { }
void AppView::setGeometry(int x, int y, int width, int height)
{
myWindow->setGeometry(x, y, width, height);
}
void AppView::attachToWindow(WINDOW_HANDLE winId)
{
auto p = std::make_unique<MyPlatform>();
p->the_window = std::make_unique<MyWindowAdapter>(winId);
myWindow = p->the_window.get();
slint_platform::Platform::register_platform(std::move(p));
// AppWindow is the auto-generated slint code
static auto app = AppWindow::create();
app->show();
}

View file

@ -0,0 +1,22 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
#pragma once
#if defined(_WIN32) || defined(_WIN64)
# include <windows.h>
typedef HWND WINDOW_HANDLE;
#endif
struct MyWindowAdapter;
class AppView
{
MyWindowAdapter *myWindow = nullptr;
public:
AppView();
void attachToWindow(WINDOW_HANDLE winId);
void setGeometry(int x, int y, int width, int height);
};

View file

@ -0,0 +1,20 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
import {Button, AboutSlint} from "std-widgets.slint";
export component AppWindow inherits Window {
preferred-width: 600px;
preferred-height: 300px;
property <int> count;
VerticalLayout {
AboutSlint { }
Button {
text: "Press me";
clicked => { count += 1; }
}
Text { text: count; }
Rectangle { }
}
}

View file

@ -0,0 +1,92 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
#include <windows.h>
#include "appview.h"
#include <memory>
#define THE_BUTTON_ID 101
static std::unique_ptr<AppView> app;
extern "C" static LRESULT WindowProc(HWND h, UINT msg, WPARAM wp, LPARAM lp)
{
switch (msg) {
/* Add a win32 push button and do something when it's clicked. */
case WM_CREATE: {
HWND hbutton = CreateWindow(
"BUTTON", "Hey There", /* class and title */
WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, /* style */
0, 0, 100, 30, /* position */
h, /* parent */
(HMENU)THE_BUTTON_ID, /* unique (within the application) integer identifier */
GetModuleHandle(0), 0 /* GetModuleHandle(0) gets the hinst */
);
app = std::make_unique<AppView>();
app->attachToWindow(h);
} break;
case WM_SIZE: {
UINT width = LOWORD(lp);
UINT height = HIWORD(lp);
if (app)
app->setGeometry(0, 40, width, height - 40);
} break;
case WM_COMMAND: {
switch (LOWORD(wp)) {
case THE_BUTTON_ID:
app = nullptr;
PostQuitMessage(0);
break;
default:;
}
} break;
case WM_CLOSE:
app = nullptr;
PostQuitMessage(0);
break;
default:
return DefWindowProc(h, msg, wp, lp);
}
return 0;
}
extern "C" int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hprev, LPSTR cmdline, int show)
{
if (!hprev) {
WNDCLASS c = { 0 };
c.lpfnWndProc = (WNDPROC)WindowProc;
c.hInstance = hinst;
c.hIcon = LoadIcon(0, IDI_APPLICATION);
c.hCursor = LoadCursor(0, IDC_ARROW);
c.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
c.lpszClassName = "MainWindow";
RegisterClass(&c);
}
HWND h = CreateWindow("MainWindow", /* window class name*/
"WindowTitle", /* title */
WS_OVERLAPPEDWINDOW, /* style */
CW_USEDEFAULT, CW_USEDEFAULT, /* position */
CW_USEDEFAULT, CW_USEDEFAULT, /* size */
0, /* parent */
0, /* menu */
hinst, 0 /* lparam */
);
ShowWindow(h, show);
while (1) { /* or while(running) */
MSG msg;
while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT)
return (int)msg.wParam;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return 0;
}

View file

@ -0,0 +1,236 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
#pragma once
#include <optional>
#include <slint_platform.h>
#include "appview.h"
#include <cassert>
#include <windows.h>
namespace slint_platform = slint::experimental::platform;
struct Geometry
{
int x = 0;
int y = 0;
uint32_t width = 0;
uint32_t height = 0;
};
struct MyWindowAdapter : public slint_platform::WindowAdapter<slint_platform::SkiaRenderer>
{
HWND parentWindow;
HINSTANCE hInstance = GetModuleHandleW(nullptr);
mutable std::optional<HWND> hwnd;
Geometry geometry = { 0, 0, 600, 300 };
MyWindowAdapter(HWND winId) : parentWindow(winId) { }
slint::PhysicalSize windowSize() const
{
RECT r;
GetWindowRect(*hwnd, &r);
return slint::PhysicalSize({ uint32_t(r.right - r.left), uint32_t(r.bottom - r.top) });
}
void show() const override
{
if (hwnd)
return;
// Register the window class.
const wchar_t CLASS_NAME[] = L"Sample Window Class";
WNDCLASS wc = {};
wc.lpfnWndProc = MyWindowAdapter::windowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
RegisterClass(&wc);
// Create the window.
hwnd = CreateWindowEx(0, // Optional window styles.
CLASS_NAME, // Window class
L"Learn to Program Windows", // Window text
WS_CHILDWINDOW, // Window style
// Size and position
geometry.x, geometry.y, geometry.width, geometry.height,
parentWindow,
NULL, // Menu
hInstance, // Instance handle
NULL // Additional application data
);
SetWindowLongPtr(*hwnd, GWLP_USERDATA, (LONG_PTR)this);
ShowWindow(*hwnd, SW_SHOWNORMAL);
renderer().show(*hwnd, hInstance, slint::PhysicalSize({ geometry.width, geometry.height }));
}
void hide() const override
{
// TODO: destroy window
renderer().hide();
}
void request_redraw() const override
{
if (!hwnd)
return;
InvalidateRect(*hwnd, nullptr, false);
}
void render()
{
renderer().render(windowSize());
if (has_active_animations())
request_redraw();
}
void resize(uint32_t width, uint32_t height)
{
slint::PhysicalSize windowSize({ width, height });
renderer().resize(windowSize);
window().set_size(windowSize);
}
void setGeometry(int x, int y, int width, int height)
{
if (!hwnd) {
geometry = { x, y, uint32_t(width), uint32_t(height) };
return;
}
SetWindowPos(*hwnd, nullptr, x, y, width, height, 0);
}
std::optional<slint::cbindgen_private::MouseEvent> mouseEventForMessage(UINT uMsg,
LPARAM lParam)
{
float x = float(LOWORD(lParam));
float y = float(HIWORD(lParam));
auto makePressEvent = [=](UINT uMsg) {
slint::cbindgen_private::PointerEventButton button;
switch (uMsg) {
case WM_LBUTTONDOWN:
button = slint::cbindgen_private::PointerEventButton::Left;
break;
case WM_MBUTTONDOWN:
button = slint::cbindgen_private::PointerEventButton::Middle;
break;
case WM_RBUTTONDOWN:
button = slint::cbindgen_private::PointerEventButton::Right;
break;
default:
assert(!"not implemented");
}
return slint::cbindgen_private::MouseEvent {
.tag = slint::cbindgen_private::MouseEvent::Tag::Pressed,
.pressed =
slint::cbindgen_private::MouseEvent::Pressed_Body {
.position = { x, y },
.button = button,
}
};
};
auto makeReleaseEvent = [=](UINT uMsg) {
slint::cbindgen_private::PointerEventButton button;
switch (uMsg) {
case WM_LBUTTONUP:
button = slint::cbindgen_private::PointerEventButton::Left;
break;
case WM_MBUTTONUP:
button = slint::cbindgen_private::PointerEventButton::Middle;
break;
case WM_RBUTTONUP:
button = slint::cbindgen_private::PointerEventButton::Right;
break;
default:
assert(!"not implemented");
}
return slint::cbindgen_private::MouseEvent {
.tag = slint::cbindgen_private::MouseEvent::Tag::Released,
.released =
slint::cbindgen_private::MouseEvent::Released_Body {
.position = { x, y },
.button = button,
}
};
};
switch (uMsg) {
case WM_LBUTTONUP:
case WM_MBUTTONUP:
case WM_RBUTTONUP:
return makeReleaseEvent(uMsg);
case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
return makePressEvent(uMsg);
case WM_MOUSEMOVE:
return slint::cbindgen_private::MouseEvent {
.tag = slint::cbindgen_private::MouseEvent::Tag::Moved,
.moved =
slint::cbindgen_private::MouseEvent::Moved_Body {
.position = { x, y },
}
};
default:
break;
}
return std::nullopt;
}
void mouse_event(UINT uMsg, LPARAM lParam)
{
if (auto event = mouseEventForMessage(uMsg, lParam)) {
dispatch_pointer_event(*event);
}
}
static LRESULT CALLBACK windowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
MyWindowAdapter *self =
reinterpret_cast<MyWindowAdapter *>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
if (self == nullptr) {
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
switch (uMsg) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_PAINT: {
PAINTSTRUCT ps;
BeginPaint(hwnd, &ps);
slint_platform::update_timers_and_animations();
self->render();
EndPaint(hwnd, &ps);
return 0;
}
case WM_SIZE:
self->resize(LOWORD(lParam), HIWORD(lParam));
return 0;
case WM_LBUTTONUP:
case WM_LBUTTONDOWN:
case WM_MBUTTONUP:
case WM_MBUTTONDOWN:
case WM_RBUTTONUP:
case WM_RBUTTONDOWN:
case WM_MOUSEMOVE:
slint_platform::update_timers_and_animations();
self->mouse_event(uMsg, lParam);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
};