diff --git a/.gitignore b/.gitignore
index ea8c4bf..dadf223 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,5 @@
/target
+*.user
+bin/
+obj/
+.vs/
\ No newline at end of file
diff --git a/AppxManifest.xml b/AppxManifest.xml
new file mode 100644
index 0000000..1b11d22
--- /dev/null
+++ b/AppxManifest.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+ Minesweeper-rs
+ robmi
+ assets\StoreLogo.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
index 1102616..8d762c7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,11 +5,13 @@ authors = ["Robert Mikhayelyan "]
edition = "2018"
[dependencies]
-winit = "0.22.0"
-raw-window-handle = "0.3.3"
winrt = { git = "https://github.com/microsoft/winrt-rs" }
rand = "0.7.3"
bindings = { path = "bindings" }
+[target.'cfg(target_vendor = "pc")'.dependencies]
+winit = "0.22.2"
+raw-window-handle = "0.3.3"
+
[features]
show-mines = []
diff --git a/ClickOnce/Minesweeper.csproj b/ClickOnce/Minesweeper.csproj
new file mode 100644
index 0000000..8c67a2c
--- /dev/null
+++ b/ClickOnce/Minesweeper.csproj
@@ -0,0 +1,15 @@
+
+
+ WinExe
+ net5.0-windows
+ true
+ Release
+ ClickOnceProfile
+ Square44x44Logo.ico
+
+
+
+ Always
+
+
+
\ No newline at end of file
diff --git a/ClickOnce/Program.cs b/ClickOnce/Program.cs
new file mode 100644
index 0000000..8ba6fbc
--- /dev/null
+++ b/ClickOnce/Program.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Diagnostics;
+using System.Windows.Forms;
+
+namespace WindowsFormsApp1
+{
+ static class Program
+ {
+ static void Main()
+ {
+ try
+ {
+ Process.Start(new ProcessStartInfo { CreateNoWindow = true, FileName = "minesweeper-rs.exe" });
+ }
+ catch (Exception x)
+ {
+ MessageBox.Show(x.Message, "Error Starting Minesweeper", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+ }
+ }
+}
diff --git a/ClickOnce/Properties/PublishProfiles/ClickOnceProfile.pubxml b/ClickOnce/Properties/PublishProfiles/ClickOnceProfile.pubxml
new file mode 100644
index 0000000..a281a5a
--- /dev/null
+++ b/ClickOnce/Properties/PublishProfiles/ClickOnceProfile.pubxml
@@ -0,0 +1,34 @@
+
+
+ 0
+ 0.1.0.*
+ True
+ Release
+ True
+ True
+ true
+ Web
+ True
+ True
+ true
+ false
+ Any CPU
+ bin\publish\
+ bin\publish\
+ ClickOnce
+ False
+ False
+ False
+ sha256RSA
+ True
+ net5.0-windows
+ True
+ Foreground
+
+
+
+ true
+ .NET Desktop Runtime 5.0.1 (x64)
+
+
+
diff --git a/ClickOnce/Properties/PublishProfiles/ClickOnceProfile.pubxml.user.template b/ClickOnce/Properties/PublishProfiles/ClickOnceProfile.pubxml.user.template
new file mode 100644
index 0000000..74cb83e
--- /dev/null
+++ b/ClickOnce/Properties/PublishProfiles/ClickOnceProfile.pubxml.user.template
@@ -0,0 +1,6 @@
+
+
+ https://ctaggart.github.io/minesweeper-rs/
+ 1A92BF20220B301077205787F406B1BCEE6DA97E
+
+
diff --git a/ClickOnce/Square44x44Logo.ico b/ClickOnce/Square44x44Logo.ico
new file mode 100644
index 0000000..ffdfbf6
Binary files /dev/null and b/ClickOnce/Square44x44Logo.ico differ
diff --git a/ClickOnce/build.ps1 b/ClickOnce/build.ps1
new file mode 100644
index 0000000..49b2f5b
--- /dev/null
+++ b/ClickOnce/build.ps1
@@ -0,0 +1 @@
+msbuild $PSScriptRoot /t:Restore,Publish /v:m
diff --git a/README.md b/README.md
index bc9764a..fb77ee8 100644
--- a/README.md
+++ b/README.md
@@ -2,10 +2,25 @@
A port of [robmikh/Minesweeper](https://github.com/robmikh/Minesweeper) using [winrt-rs](https://github.com/microsoft/winrt-rs).
## Running
-Running this sample requires at least Windows build 1803 (v10.0.17134.0). To compile and run (after setting up), use:
+Running this sample requires at least Windows build 1803 (v10.0.17134.0). Instructions are a little different between desktop and UWP:
+
+### Desktop
+Running Minesweeper as a normal desktop application can be done as follows:
```
cargo run --release
```
+### UWP
+
+Running Minesweeper as a UWP application can be done by building for a `*-uwp-windows-msvc` target and then registering the app. More information can be found [here](UWP.md).
+
+```
+cargo +nightly build -Z build-std=std,panic_abort --target x86_64-uwp-windows-msvc
+(powershell.exe) Add-AppxPackage -Register AppxManifest.xml
+```
+*NOTE: AppManifest.xml currently assumes the `x86_64-uwp-windows-msvc` target but can be updated.*
+
+Then launch minesweeper-rs from the Start Menu.
+

diff --git a/UWP.md b/UWP.md
new file mode 100644
index 0000000..14a7a57
--- /dev/null
+++ b/UWP.md
@@ -0,0 +1,26 @@
+# Building for `*-uwp-windows-msvc` targets
+
+## Required tools
+First, you'll need to install the nightly toolchain:
+
+```
+rustup toolchain install nightly
+rustup component add rust-src
+```
+
+I'm using version `1.50.0-nightly (1c389ffef 2020-11-24)`. If you already have a nightly toolchain installed and you're seeing an error about `SetThreadStackGuarantee`, update your nightly toolchain.
+
+## Building Minesweeper
+From the appropriate VS command prompt (e.g. "x64 Native Tools Command Prompt for VS 2019" when building for x86_64), run cargo but target a uwp target:
+
+```
+cargo +nightly build -Z build-std=std,panic_abort --target x86_64-uwp-windows-msvc
+```
+
+After that, you should be able to register your application:
+
+```
+(powershell.exe) Add-AppxPackage -Register AppxManifest.xml
+```
+
+Special thanks to [bdbai](https://github.com/bdbai) for the [firstuwp-rs](https://github.com/bdbai/firstuwp-rs) project. Without that, I wouldn't have known about the [build-std](https://doc.rust-lang.org/cargo/reference/unstable.html#build-std) cargo feature.
\ No newline at end of file
diff --git a/assets/LockScreenLogo.png b/assets/LockScreenLogo.png
new file mode 100644
index 0000000..99e3933
Binary files /dev/null and b/assets/LockScreenLogo.png differ
diff --git a/assets/SplashScreen.png b/assets/SplashScreen.png
new file mode 100644
index 0000000..1605ae2
Binary files /dev/null and b/assets/SplashScreen.png differ
diff --git a/assets/Square150x150Logo.png b/assets/Square150x150Logo.png
new file mode 100644
index 0000000..66b5fd9
Binary files /dev/null and b/assets/Square150x150Logo.png differ
diff --git a/assets/Square44x44Logo.png b/assets/Square44x44Logo.png
new file mode 100644
index 0000000..4aa5c0e
Binary files /dev/null and b/assets/Square44x44Logo.png differ
diff --git a/assets/StoreLogo.png b/assets/StoreLogo.png
new file mode 100644
index 0000000..d8275a0
Binary files /dev/null and b/assets/StoreLogo.png differ
diff --git a/assets/Wide310x150Logo.png b/assets/Wide310x150Logo.png
new file mode 100644
index 0000000..16712d7
Binary files /dev/null and b/assets/Wide310x150Logo.png differ
diff --git a/bindings/build.rs b/bindings/build.rs
index 2735865..06a59e7 100644
--- a/bindings/build.rs
+++ b/bindings/build.rs
@@ -1,5 +1,11 @@
fn main() {
winrt::build!(
+ windows::application_model::core::{
+ CoreApplication,
+ CoreApplicationView,
+ IFrameworkViewSource,
+ IFrameworkView,
+ }
windows::foundation::numerics::{Vector2, Vector3}
windows::foundation::TimeSpan
windows::graphics::SizeInt32
@@ -18,5 +24,12 @@ fn main() {
}
windows::ui::composition::desktop::DesktopWindowTarget
windows::ui::Colors
+ windows::ui::core::{
+ CoreDispatcher,
+ CoreWindow,
+ CoreProcessEventsOption,
+ WindowSizeChangedEventArgs,
+ PointerEventArgs,
+ }
);
}
diff --git a/src/desktop/interop.rs b/src/desktop/interop.rs
new file mode 100644
index 0000000..746571c
--- /dev/null
+++ b/src/desktop/interop.rs
@@ -0,0 +1,95 @@
+use bindings::windows::{
+ system::DispatcherQueueController, ui::composition::desktop::DesktopWindowTarget,
+};
+
+// Note: This COM ABI code will be generated for you when this issue is completed:
+// https://github.com/microsoft/winrt-rs/issues/81
+
+#[repr(C)]
+pub struct ICompositorDesktopInterop_vtable(
+ usize,
+ usize,
+ usize,
+ extern "system" fn(
+ winrt::RawPtr,
+ winrt::RawPtr,
+ bool,
+ &mut Option,
+ ) -> winrt::ErrorCode,
+);
+
+unsafe impl winrt::Interface for ICompositorDesktopInterop {
+ type Vtable = ICompositorDesktopInterop_vtable;
+
+ const IID: winrt::Guid =
+ winrt::Guid::from_values(702976506, 17767, 19914, [179, 25, 208, 242, 7, 235, 104, 7]);
+}
+
+#[repr(transparent)]
+#[derive(Clone, PartialEq)]
+pub struct ICompositorDesktopInterop(winrt::IUnknown);
+
+impl ICompositorDesktopInterop {
+ pub fn create_desktop_window_target(
+ &self,
+ hwnd: winrt::RawPtr,
+ is_topmost: bool,
+ ) -> winrt::Result {
+ use winrt::{Abi, Interface};
+ let mut result = None;
+ unsafe { (self.vtable().3)(self.abi(), hwnd, is_topmost, &mut result).and_some(result) }
+ }
+}
+
+#[link(name = "coremessaging")]
+extern "stdcall" {
+ fn CreateDispatcherQueueController(
+ options: DispatcherQueueOptions,
+ dispatcherQueueController: &mut Option,
+ ) -> winrt::ErrorCode;
+}
+
+#[repr(C)]
+struct DispatcherQueueOptions {
+ size: u32,
+ thread_type: DispatcherQueueThreadType,
+ apartment_type: DispatcherQueueThreadApartmentType,
+}
+
+#[allow(dead_code)]
+#[repr(i32)]
+pub enum DispatcherQueueThreadType {
+ Dedicated = 1,
+ Current = 2,
+}
+
+#[allow(dead_code)]
+#[repr(i32)]
+pub enum DispatcherQueueThreadApartmentType {
+ None = 0,
+ ASTA = 1,
+ STA = 2,
+}
+
+pub fn create_dispatcher_queue_controller(
+ thread_type: DispatcherQueueThreadType,
+ apartment_type: DispatcherQueueThreadApartmentType,
+) -> winrt::Result {
+ let options = DispatcherQueueOptions {
+ size: std::mem::size_of::() as u32,
+ thread_type,
+ apartment_type,
+ };
+ unsafe {
+ let mut result = None;
+ CreateDispatcherQueueController(options, &mut result).and_some(result)
+ }
+}
+
+pub fn create_dispatcher_queue_controller_for_current_thread(
+) -> winrt::Result {
+ create_dispatcher_queue_controller(
+ DispatcherQueueThreadType::Current,
+ DispatcherQueueThreadApartmentType::None,
+ )
+}
diff --git a/src/desktop/mod.rs b/src/desktop/mod.rs
new file mode 100644
index 0000000..41837be
--- /dev/null
+++ b/src/desktop/mod.rs
@@ -0,0 +1,5 @@
+mod interop;
+mod run;
+mod window_target;
+
+pub use run::run;
diff --git a/src/desktop/run.rs b/src/desktop/run.rs
new file mode 100644
index 0000000..c7a08ee
--- /dev/null
+++ b/src/desktop/run.rs
@@ -0,0 +1,74 @@
+use crate::desktop::interop::create_dispatcher_queue_controller_for_current_thread;
+use crate::desktop::window_target::CompositionDesktopWindowTargetSource;
+use crate::interop::{ro_initialize, RoInitType};
+use crate::minesweeper::Minesweeper;
+use winit::{
+ event::{ElementState, Event, MouseButton, WindowEvent},
+ event_loop::{ControlFlow, EventLoop},
+ window::WindowBuilder,
+};
+
+use bindings::windows::{foundation::numerics::Vector2, ui::composition::Compositor};
+
+pub fn run() -> winrt::Result<()> {
+ ro_initialize(RoInitType::MultiThreaded)?;
+ let _controller = create_dispatcher_queue_controller_for_current_thread()?;
+
+ let event_loop = EventLoop::new();
+ let window = WindowBuilder::new().build(&event_loop).unwrap();
+ window.set_title("Minesweeper");
+
+ let compositor = Compositor::new()?;
+ let target = window.create_window_target(&compositor, false)?;
+
+ let root = compositor.create_container_visual()?;
+ root.set_relative_size_adjustment(Vector2 { x: 1.0, y: 1.0 })?;
+ target.set_root(&root)?;
+
+ let window_size = window.inner_size();
+ let window_size = Vector2 {
+ x: window_size.width as f32,
+ y: window_size.height as f32,
+ };
+ let mut game = Minesweeper::new(&root, &window_size)?;
+
+ event_loop.run(move |event, _, control_flow| {
+ *control_flow = ControlFlow::Wait;
+ match event {
+ Event::WindowEvent {
+ event: WindowEvent::CloseRequested,
+ window_id,
+ } if window_id == window.id() => *control_flow = ControlFlow::Exit,
+ Event::WindowEvent {
+ event: WindowEvent::Resized(size),
+ ..
+ } => {
+ let size = Vector2 {
+ x: size.width as f32,
+ y: size.height as f32,
+ };
+ game.on_parent_size_changed(&size).unwrap();
+ }
+ Event::WindowEvent {
+ event: WindowEvent::CursorMoved { position, .. },
+ ..
+ } => {
+ let point = Vector2 {
+ x: position.x as f32,
+ y: position.y as f32,
+ };
+ game.on_pointer_moved(&point).unwrap();
+ }
+ Event::WindowEvent {
+ event: WindowEvent::MouseInput { state, button, .. },
+ ..
+ } => {
+ if state == ElementState::Pressed {
+ game.on_pointer_pressed(button == MouseButton::Right, false)
+ .unwrap();
+ }
+ }
+ _ => (),
+ }
+ });
+}
diff --git a/src/window_target.rs b/src/desktop/window_target.rs
similarity index 94%
rename from src/window_target.rs
rename to src/desktop/window_target.rs
index dc82fb4..a4b3fd4 100644
--- a/src/window_target.rs
+++ b/src/desktop/window_target.rs
@@ -1,4 +1,4 @@
-use crate::interop::ICompositorDesktopInterop;
+use crate::desktop::interop::ICompositorDesktopInterop;
use bindings::windows::ui::composition::{desktop::DesktopWindowTarget, Compositor};
use raw_window_handle::HasRawWindowHandle;
use winrt::Interface;
diff --git a/src/interop.rs b/src/interop.rs
index d0c752e..61f7b39 100644
--- a/src/interop.rs
+++ b/src/interop.rs
@@ -1,46 +1,3 @@
-use bindings::windows::{
- system::DispatcherQueueController, ui::composition::desktop::DesktopWindowTarget,
-};
-
-// Note: This COM ABI code will be generated for you when this issue is completed:
-// https://github.com/microsoft/winrt-rs/issues/81
-
-#[repr(C)]
-pub struct ICompositorDesktopInterop_vtable(
- usize,
- usize,
- usize,
- extern "system" fn(
- winrt::RawPtr,
- winrt::RawPtr,
- bool,
- &mut Option,
- ) -> winrt::ErrorCode,
-);
-
-unsafe impl winrt::Interface for ICompositorDesktopInterop {
- type Vtable = ICompositorDesktopInterop_vtable;
-
- const IID: winrt::Guid =
- winrt::Guid::from_values(702976506, 17767, 19914, [179, 25, 208, 242, 7, 235, 104, 7]);
-}
-
-#[repr(transparent)]
-#[derive(Clone, PartialEq)]
-pub struct ICompositorDesktopInterop(winrt::IUnknown);
-
-impl ICompositorDesktopInterop {
- pub fn create_desktop_window_target(
- &self,
- hwnd: winrt::RawPtr,
- is_topmost: bool,
- ) -> winrt::Result {
- use winrt::{Abi, Interface};
- let mut result = None;
- unsafe { (self.vtable().3)(self.abi(), hwnd, is_topmost, &mut result).and_some(result) }
- }
-}
-
#[link(name = "windowsapp")]
extern "stdcall" {
fn RoInitialize(init_type: RoInitType) -> winrt::ErrorCode;
@@ -53,59 +10,7 @@ pub enum RoInitType {
SingleThreaded = 1,
}
+#[allow(dead_code)]
pub fn ro_initialize(init_type: RoInitType) -> winrt::Result<()> {
unsafe { RoInitialize(init_type).ok() }
}
-
-#[link(name = "coremessaging")]
-extern "stdcall" {
- fn CreateDispatcherQueueController(
- options: DispatcherQueueOptions,
- dispatcherQueueController: &mut Option,
- ) -> winrt::ErrorCode;
-}
-
-#[repr(C)]
-struct DispatcherQueueOptions {
- size: u32,
- thread_type: DispatcherQueueThreadType,
- apartment_type: DispatcherQueueThreadApartmentType,
-}
-
-#[allow(dead_code)]
-#[repr(i32)]
-pub enum DispatcherQueueThreadType {
- Dedicated = 1,
- Current = 2,
-}
-
-#[allow(dead_code)]
-#[repr(i32)]
-pub enum DispatcherQueueThreadApartmentType {
- None = 0,
- ASTA = 1,
- STA = 2,
-}
-
-pub fn create_dispatcher_queue_controller(
- thread_type: DispatcherQueueThreadType,
- apartment_type: DispatcherQueueThreadApartmentType,
-) -> winrt::Result {
- let options = DispatcherQueueOptions {
- size: std::mem::size_of::() as u32,
- thread_type,
- apartment_type,
- };
- unsafe {
- let mut result = None;
- CreateDispatcherQueueController(options, &mut result).and_some(result)
- }
-}
-
-pub fn create_dispatcher_queue_controller_for_current_thread(
-) -> winrt::Result {
- create_dispatcher_queue_controller(
- DispatcherQueueThreadType::Current,
- DispatcherQueueThreadApartmentType::None,
- )
-}
diff --git a/src/main.rs b/src/main.rs
index 42c6081..ce7e997 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,84 +1,21 @@
+#![windows_subsystem = "windows"]
+
mod comp_assets;
mod comp_ui;
mod interop;
mod minesweeper;
mod numerics;
mod visual_grid;
-mod window_target;
-
-use interop::{create_dispatcher_queue_controller_for_current_thread, ro_initialize, RoInitType};
-use minesweeper::Minesweeper;
-use window_target::CompositionDesktopWindowTargetSource;
-use winit::{
- event::{ElementState, Event, MouseButton, WindowEvent},
- event_loop::{ControlFlow, EventLoop},
- window::WindowBuilder,
-};
-
-use bindings::windows::{foundation::numerics::Vector2, ui::composition::Compositor};
-
-fn run() -> winrt::Result<()> {
- ro_initialize(RoInitType::MultiThreaded)?;
- let _controller = create_dispatcher_queue_controller_for_current_thread()?;
- let event_loop = EventLoop::new();
- let window = WindowBuilder::new().build(&event_loop).unwrap();
- window.set_title("Minesweeper");
+#[cfg(target_vendor = "pc")]
+mod desktop;
+#[cfg(target_vendor = "uwp")]
+mod uwp;
- let compositor = Compositor::new()?;
- let target = window.create_window_target(&compositor, false)?;
-
- let root = compositor.create_container_visual()?;
- root.set_relative_size_adjustment(Vector2 { x: 1.0, y: 1.0 })?;
- target.set_root(&root)?;
-
- let window_size = window.inner_size();
- let window_size = Vector2 {
- x: window_size.width as f32,
- y: window_size.height as f32,
- };
- let mut game = Minesweeper::new(&root, &window_size)?;
-
- event_loop.run(move |event, _, control_flow| {
- *control_flow = ControlFlow::Wait;
- match event {
- Event::WindowEvent {
- event: WindowEvent::CloseRequested,
- window_id,
- } if window_id == window.id() => *control_flow = ControlFlow::Exit,
- Event::WindowEvent {
- event: WindowEvent::Resized(size),
- ..
- } => {
- let size = Vector2 {
- x: size.width as f32,
- y: size.height as f32,
- };
- game.on_parent_size_changed(&size).unwrap();
- }
- Event::WindowEvent {
- event: WindowEvent::CursorMoved { position, .. },
- ..
- } => {
- let point = Vector2 {
- x: position.x as f32,
- y: position.y as f32,
- };
- game.on_pointer_moved(&point).unwrap();
- }
- Event::WindowEvent {
- event: WindowEvent::MouseInput { state, button, .. },
- ..
- } => {
- if state == ElementState::Pressed {
- game.on_pointer_pressed(button == MouseButton::Right, false)
- .unwrap();
- }
- }
- _ => (),
- }
- });
-}
+#[cfg(target_vendor = "pc")]
+use desktop::run;
+#[cfg(target_vendor = "uwp")]
+use uwp::run;
fn main() {
let result = run();
diff --git a/src/uwp/app.rs b/src/uwp/app.rs
new file mode 100644
index 0000000..22bc56d
--- /dev/null
+++ b/src/uwp/app.rs
@@ -0,0 +1,152 @@
+use crate::minesweeper::Minesweeper;
+use bindings::windows::{
+ application_model::core::{CoreApplicationView, IFrameworkView},
+ foundation::numerics::Vector2,
+ foundation::TypedEventHandler,
+ ui::composition::{CompositionTarget, Compositor},
+ ui::core::{CoreProcessEventsOption, CoreWindow, PointerEventArgs, WindowSizeChangedEventArgs},
+};
+use bindings::*;
+use std::sync::{Arc, Mutex};
+
+struct AppState {
+ _window: CoreWindow,
+ _compositor: Compositor,
+ _target: CompositionTarget,
+
+ game: Minesweeper,
+}
+
+#[winrt::implement(windows::application_model::core::IFrameworkViewSource)]
+pub struct MinesweeperAppSource {}
+
+impl MinesweeperAppSource {
+ fn create_view(&mut self) -> winrt::Result {
+ let app = MinesweeperApp {
+ state: Arc::new(Mutex::new(None)),
+ };
+ let view: IFrameworkView = app.into();
+ Ok(view)
+ }
+}
+
+// TOOD: A way to do this without the arc/mutex?
+#[winrt::implement(windows::application_model::core::IFrameworkView)]
+pub struct MinesweeperApp {
+ state: Arc>>,
+}
+
+impl MinesweeperApp {
+ fn initialize(&mut self, _window: &Option) -> winrt::Result<()> {
+ Ok(())
+ }
+
+ fn set_window(&mut self, _window: &Option) -> winrt::Result<()> {
+ Ok(())
+ }
+
+ fn load(&mut self, _entry_point: &winrt::HString) -> winrt::Result<()> {
+ Ok(())
+ }
+
+ fn run(&mut self) -> winrt::Result<()> {
+ let window = CoreWindow::get_for_current_thread()?;
+
+ // Init Composition
+ let compositor = Compositor::new()?;
+ let root = compositor.create_container_visual()?;
+ root.set_relative_size_adjustment(Vector2 { x: 1.0, y: 1.0 })?;
+ let target = compositor.create_target_for_current_view()?;
+ target.set_root(&root)?;
+
+ // Init minesweeper
+ let window_size = get_window_size(&window)?;
+ let game = Minesweeper::new(&root, &window_size)?;
+
+ // Initialize our internal state
+ let state = AppState {
+ _window: window.clone(),
+ _compositor: compositor,
+ _target: target,
+
+ game,
+ };
+ self.state.lock().unwrap().replace(state);
+
+ // Hook events
+ type SizeChangedHandler = TypedEventHandler;
+ type PointerMovedHandler = TypedEventHandler;
+ type PointerPressedHandler = TypedEventHandler;
+
+ let size_changed_handler = SizeChangedHandler::new({
+ let state = self.state.clone();
+ move |_sender, args| {
+ let args = args.as_ref().unwrap();
+ let size = args.size()?;
+ let size = Vector2 {
+ x: size.width as f32,
+ y: size.height as f32,
+ };
+ let mut state = state.lock().unwrap();
+ let state = state.as_mut().unwrap();
+ let game = &mut state.game;
+ game.on_parent_size_changed(&size)?;
+ Ok(())
+ }
+ });
+ let pointer_moved_handler = PointerMovedHandler::new({
+ let state = self.state.clone();
+ move |_sender, args| {
+ let args = args.as_ref().unwrap();
+ let point = args.current_point()?.position()?;
+ let point = Vector2 {
+ x: point.x as f32,
+ y: point.y as f32,
+ };
+ let mut state = state.lock().unwrap();
+ let state = state.as_mut().unwrap();
+ let game = &mut state.game;
+ game.on_pointer_moved(&point)?;
+ Ok(())
+ }
+ });
+ let pointer_pressed_handler = PointerPressedHandler::new({
+ let state = self.state.clone();
+ move |_sender, args| {
+ let args = args.as_ref().unwrap();
+ let properties = args.current_point()?.properties()?;
+ let is_right = properties.is_right_button_pressed()?;
+ let is_eraser = properties.is_eraser()?;
+ let mut state = state.lock().unwrap();
+ let state = state.as_mut().unwrap();
+ let game = &mut state.game;
+ game.on_pointer_pressed(is_right, is_eraser)?;
+ Ok(())
+ }
+ });
+
+ window.size_changed(size_changed_handler)?;
+ window.pointer_moved(pointer_moved_handler)?;
+ window.pointer_pressed(pointer_pressed_handler)?;
+
+ // Activate the window and start running the dispatcher
+ window.activate()?;
+
+ let dispatcher = window.dispatcher()?;
+ dispatcher.process_events(CoreProcessEventsOption::ProcessUntilQuit)?;
+
+ Ok(())
+ }
+
+ fn uninitialize(&mut self) -> winrt::Result<()> {
+ Ok(())
+ }
+}
+
+fn get_window_size(window: &CoreWindow) -> winrt::Result {
+ let bounds = window.bounds()?;
+ Ok(Vector2 {
+ x: bounds.width as f32,
+ y: bounds.height as f32,
+ })
+}
diff --git a/src/uwp/mod.rs b/src/uwp/mod.rs
new file mode 100644
index 0000000..648d42f
--- /dev/null
+++ b/src/uwp/mod.rs
@@ -0,0 +1,4 @@
+mod app;
+mod run;
+
+pub use run::run;
diff --git a/src/uwp/run.rs b/src/uwp/run.rs
new file mode 100644
index 0000000..ee75e4b
--- /dev/null
+++ b/src/uwp/run.rs
@@ -0,0 +1,9 @@
+use crate::uwp::app::MinesweeperAppSource;
+use bindings::windows::application_model::core::{CoreApplication, IFrameworkViewSource};
+
+pub fn run() -> winrt::Result<()> {
+ let app_source = MinesweeperAppSource {};
+ let view_source: IFrameworkViewSource = app_source.into();
+ CoreApplication::run(&view_source)?;
+ Ok(())
+}