summaryrefslogtreecommitdiffstats
path: root/gui/src
diff options
context:
space:
mode:
Diffstat (limited to 'gui/src')
-rw-r--r--gui/src/main.rs125
1 files changed, 108 insertions, 17 deletions
diff --git a/gui/src/main.rs b/gui/src/main.rs
index 82babb6..e0338fa 100644
--- a/gui/src/main.rs
+++ b/gui/src/main.rs
@@ -1,3 +1,5 @@
1#![cfg_attr(windows, windows_subsystem = "windows")]
2
1//! Desktop frontend for zagan-bet. 3//! Desktop frontend for zagan-bet.
2//! 4//!
3//! Phase 4: the egui shell over the shared `ClientCore`, with the transport on 5//! Phase 4: the egui shell over the shared `ClientCore`, with the transport on
@@ -15,13 +17,15 @@ fn main() -> eframe::Result {
15 } 17 }
16 }; 18 };
17 if args.help { 19 if args.help {
18 println!( 20 let usage = "usage: zagan-bet-gui [-d <dir>] [--unsafe]
19 "usage: zagan-bet-gui [-d <dir>] [--unsafe]
20 21
21 -d, --data-dir <dir> where to keep client.db (default: XDG data dir) 22 -d, --data-dir <dir> where to keep client.db (default: XDG data dir)
22 --unsafe skip certificate verification 23 --unsafe skip certificate verification
23 -h, --help show this message" 24 -h, --help show this message";
24 ); 25 #[cfg(windows)]
26 show_dialog(usage, false);
27 #[cfg(not(windows))]
28 println!("{usage}");
25 return Ok(()); 29 return Ok(());
26 } 30 }
27 31
@@ -31,23 +35,110 @@ fn main() -> eframe::Result {
31 std::process::exit(1); 35 std::process::exit(1);
32 } 36 }
33 37
34 let options = eframe::NativeOptions {
35 viewport: eframe::egui::ViewportBuilder::default()
36 .with_title("Zagan-Bet")
37 .with_app_id("zagan-bet")
38 .with_inner_size([1440.0, 900.0])
39 .with_min_inner_size([1100.0, 720.0]),
40 renderer: eframe::Renderer::Glow,
41 ..Default::default()
42 };
43
44 let startup = Startup { 38 let startup = Startup {
45 data_dir, 39 data_dir,
46 unsafe_mode: args.unsafe_mode, 40 unsafe_mode: args.unsafe_mode,
47 }; 41 };
48 eframe::run_native( 42 let result = eframe::run_native(
49 "Zagan-Bet", 43 "Zagan-Bet",
50 options, 44 native_options(),
51 Box::new(move |cc| Ok(Box::new(ZaganApp::new(cc, startup)))), 45 Box::new(move |cc| Ok(Box::new(ZaganApp::new(cc, startup)))),
52 ) 46 );
47 #[cfg(windows)]
48 if let Err(error) = &result {
49 // The binary is a GUI-subsystem app, so this is the only way a startup
50 // failure reaches the player.
51 show_dialog(&format!("Zagan-Bet could not start.\n\n{error}"), true);
52 }
53 result
54}
55
56/// A native Windows message box, for help and for startup failures (there is
57/// no console attached).
58#[cfg(windows)]
59fn show_dialog(message: &str, error: bool) {
60 use std::ffi::OsStr;
61 use std::os::windows::ffi::OsStrExt;
62
63 use windows_sys::Win32::UI::WindowsAndMessaging::{
64 MB_ICONERROR, MB_ICONINFORMATION, MessageBoxW,
65 };
66
67 let wide = |text: &str| -> Vec<u16> {
68 OsStr::new(text)
69 .encode_wide()
70 .chain(std::iter::once(0))
71 .collect()
72 };
73 let text = wide(message);
74 let title = wide("Zagan-Bet");
75 let icon = if error {
76 MB_ICONERROR
77 } else {
78 MB_ICONINFORMATION
79 };
80 // SAFETY: both strings are NUL-terminated and outlive the call.
81 unsafe {
82 MessageBoxW(std::ptr::null_mut(), text.as_ptr(), title.as_ptr(), icon);
83 }
84}
85
86fn viewport() -> eframe::egui::ViewportBuilder {
87 eframe::egui::ViewportBuilder::default()
88 .with_title("Zagan-Bet")
89 .with_app_id("zagan-bet")
90 .with_inner_size([1440.0, 900.0])
91 .with_min_inner_size([1100.0, 720.0])
92}
93
94/// Windows renders through Direct3D 12. egui's default adapter choice prefers
95/// hardware; this selector also accepts WARP, the software rasterizer, so a
96/// Remote Desktop or VM session with no OpenGL driver still runs the app.
97#[cfg(windows)]
98fn native_options() -> eframe::NativeOptions {
99 use std::sync::Arc;
100
101 use eframe::egui_wgpu::{WgpuConfiguration, WgpuSetup};
102 use eframe::wgpu;
103
104 let mut wgpu_options = WgpuConfiguration::default();
105 if let WgpuSetup::CreateNew(setup) = &mut wgpu_options.wgpu_setup {
106 setup.instance_descriptor.backends = wgpu::Backends::DX12;
107 setup.native_adapter_selector = Some(Arc::new(|adapters, surface| {
108 let usable = |adapter: &wgpu::Adapter| {
109 surface.is_none_or(|surface| adapter.is_surface_supported(surface))
110 };
111 for kind in [
112 wgpu::DeviceType::DiscreteGpu,
113 wgpu::DeviceType::IntegratedGpu,
114 wgpu::DeviceType::VirtualGpu,
115 wgpu::DeviceType::Cpu,
116 wgpu::DeviceType::Other,
117 ] {
118 if let Some(adapter) = adapters
119 .iter()
120 .find(|adapter| adapter.get_info().device_type == kind && usable(adapter))
121 {
122 return Ok(adapter.clone());
123 }
124 }
125 Err("no Direct3D 12 adapter (hardware or WARP) is available".to_string())
126 }));
127 }
128
129 eframe::NativeOptions {
130 viewport: viewport(),
131 renderer: eframe::Renderer::Wgpu,
132 wgpu_options,
133 ..Default::default()
134 }
135}
136
137#[cfg(not(windows))]
138fn native_options() -> eframe::NativeOptions {
139 eframe::NativeOptions {
140 viewport: viewport(),
141 renderer: eframe::Renderer::Glow,
142 ..Default::default()
143 }
53} 144}