1use batbox_la::*;
2use futures::prelude::*;
3use serde::{Deserialize, Serialize};
4use std::cell::{Cell, RefCell};
5use std::collections::HashSet;
6use std::rc::Rc;
7use ugli::Ugli;
8
9mod backend;
10
11mod cursor;
12mod events;
13
14pub use cursor::*;
15pub use events::*;
16
17#[derive(Debug, Clone, Serialize, Deserialize, clap::Args, Default)]
18#[group(id = "window")]
19pub struct CliArgs {
20 #[clap(long, value_name = "BOOL")]
22 pub vsync: Option<bool>,
23 #[clap(long, value_name = "BOOL")]
25 pub antialias: Option<bool>,
26 #[clap(long = "window-width", value_name = "PIXELS")]
28 pub width: Option<usize>,
29 #[clap(long = "window-height", value_name = "PIXELS")]
31 pub height: Option<usize>,
32 #[clap(long, value_name = "BOOL")]
34 pub fullscreen: Option<bool>,
35}
36
37#[derive(Debug, Clone)]
38pub struct Options {
39 pub fullscreen: bool,
40 pub vsync: bool,
41 pub title: String,
42 pub antialias: bool,
43 pub transparency: bool,
44 pub mouse_passthrough: bool,
45 pub size: Option<vec2<usize>>,
46 pub auto_close: bool,
47 pub start_hidden: bool,
48}
49
50impl Options {
51 pub fn new(title: &str) -> Self {
52 Self {
53 title: title.to_owned(),
54 fullscreen: !cfg!(debug_assertions),
55 vsync: true,
56 antialias: true,
57 transparency: false,
58 mouse_passthrough: false,
59 size: None,
60 auto_close: true,
61 start_hidden: false,
62 }
63 }
64
65 pub fn with_cli(&mut self, args: &CliArgs) {
66 if let Some(vsync) = args.vsync {
67 self.vsync = vsync;
68 }
69 if let Some(antialias) = args.antialias {
70 self.antialias = antialias;
71 }
72 if let (Some(window_width), Some(window_height)) = (args.width, args.height) {
73 self.size = Some(vec2(window_width, window_height));
74 }
75 if let Some(fullscreen) = args.fullscreen {
76 self.fullscreen = fullscreen;
77 }
78 }
79}
80
81struct WindowImpl {
82 event_sender: async_broadcast::Sender<Event>,
83 event_receiver: RefCell<async_broadcast::Receiver<Event>>,
84 executor: async_executor::LocalExecutor<'static>,
85 backend: Rc<backend::Context>,
86 pressed_keys: Rc<RefCell<HashSet<Key>>>,
87 pressed_buttons: Rc<RefCell<HashSet<MouseButton>>>,
88 cursor_pos: Cell<Option<vec2<f64>>>,
89 cursor_type: Cell<CursorType>,
90 auto_close: Cell<bool>,
91}
92
93#[derive(Clone)]
94pub struct Window {
95 inner: Rc<WindowImpl>,
96}
97
98impl Window {
99 pub fn start_text_edit(&self, text: &str) {
100 self.inner.backend.start_text_edit(text);
101 }
102
103 pub fn stop_text_edit(&self) {
104 self.inner.backend.stop_text_edit();
105 }
106
107 pub fn is_editing_text(&self) -> bool {
108 self.inner.backend.is_editing_text()
109 }
110
111 pub fn real_size(&self) -> vec2<usize> {
112 self.inner.backend.real_size()
113 }
114 pub fn size(&self) -> vec2<usize> {
115 self.real_size().map(|x| x.max(1))
116 }
117
118 pub fn ugli(&self) -> &Ugli {
119 self.inner.backend.ugli()
120 }
121
122 pub fn is_key_pressed(&self, key: Key) -> bool {
123 self.inner.pressed_keys.borrow().contains(&key)
124 }
125
126 pub fn is_button_pressed(&self, button: MouseButton) -> bool {
127 self.inner.pressed_buttons.borrow().contains(&button)
128 }
129
130 pub fn pressed_keys(&self) -> HashSet<Key> {
131 self.inner.pressed_keys.borrow().clone()
132 }
133
134 pub fn pressed_buttons(&self) -> HashSet<MouseButton> {
135 self.inner.pressed_buttons.borrow().clone()
136 }
137
138 pub fn set_fullscreen(&self, fullscreen: bool) {
139 self.inner.backend.set_fullscreen(fullscreen);
140 }
141
142 pub fn is_fullscreen(&self) -> bool {
143 self.inner.backend.is_fullscreen()
144 }
145
146 pub fn toggle_fullscreen(&self) {
147 self.set_fullscreen(!self.is_fullscreen());
148 }
149
150 pub fn set_auto_close(&self, auto_close: bool) {
151 self.inner.auto_close.set(auto_close);
152 }
153
154 pub fn is_auto_close(&self) -> bool {
155 self.inner.auto_close.get()
156 }
157
158 #[cfg(not(target_arch = "wasm32"))]
159 pub fn set_icon(&self, path: impl AsRef<std::path::Path>) -> anyhow::Result<()> {
160 self.inner.backend.set_icon(path.as_ref())
161 }
162
163 pub fn spawn(
164 &self,
165 f: impl std::future::Future<Output = ()> + 'static,
166 ) -> async_executor::Task<()> {
167 self.inner.executor.spawn(f)
168 }
169
170 pub fn with_framebuffer<T>(&self, f: impl FnOnce(&mut ugli::Framebuffer) -> T) -> T {
171 self.inner.backend.with_framebuffer(f)
172 }
173
174 pub fn events(&self) -> impl futures::Stream<Item = Event> {
175 self.inner.event_receiver.borrow().clone()
176 }
177
178 pub fn show(&self) {
179 self.inner.backend.show();
180 }
181
182 pub async fn yield_now(&self) {
183 self.events()
184 .filter(|event| future::ready(matches!(event, Event::Draw)))
185 .next()
186 .await;
187 }
188}
189
190pub fn run<Fut>(options: &Options, f: impl 'static + FnOnce(Window) -> Fut)
191where
192 Fut: std::future::Future<Output = ()> + 'static,
193{
194 let options = options.clone();
195 backend::run(&options, move |backend| {
196 let (mut event_sender, event_receiver) = async_broadcast::broadcast(1);
198 event_sender.set_overflow(true);
199 let window = Window {
200 inner: Rc::new(WindowImpl {
201 event_sender,
202 event_receiver: RefCell::new(event_receiver),
204 executor: async_executor::LocalExecutor::new(),
205 backend,
206 pressed_keys: Rc::new(RefCell::new(HashSet::new())),
207 pressed_buttons: Rc::new(RefCell::new(HashSet::new())),
208 auto_close: Cell::new(options.auto_close),
209 cursor_pos: Cell::new(None),
210 cursor_type: Cell::new(CursorType::Default),
211 }),
212 };
213 if options.fullscreen {
214 window.set_fullscreen(true);
215 }
216 if !options.start_hidden {
217 window.show();
218 }
219
220 let f = f(window.clone());
221 let main_task = window.spawn(f);
222 while window.inner.executor.try_tick() {}
223 move |event| {
224 match event {
225 Event::KeyPress { key } => {
226 if !window.inner.pressed_keys.borrow_mut().insert(key) {
227 return std::ops::ControlFlow::Continue(());
228 }
229 }
230 Event::KeyRelease { key } => {
231 if !window.inner.pressed_keys.borrow_mut().remove(&key) {
232 return std::ops::ControlFlow::Continue(());
233 }
234 }
235 Event::MousePress { button } => {
236 window.inner.pressed_buttons.borrow_mut().insert(button);
237 }
238 Event::MouseRelease { button } => {
239 window.inner.pressed_buttons.borrow_mut().remove(&button);
240 }
241 Event::CursorMove { position } => {
242 window.inner.cursor_pos.set(Some(position));
243 if window.cursor_locked() {
244 return std::ops::ControlFlow::Continue(());
245 }
246 }
247 Event::RawMouseMove { .. } => {
248 if !window.cursor_locked() {
249 return std::ops::ControlFlow::Continue(());
250 }
251 }
252 Event::CloseRequested => {
253 if window.is_auto_close() {
254 return std::ops::ControlFlow::Break(());
255 }
256 }
257 _ => {}
258 }
259 if let Some(_removed) = window.inner.event_sender.try_broadcast(event).unwrap() {
260 }
262 window.inner.event_receiver.borrow_mut().try_recv().unwrap();
263 while window.inner.executor.try_tick() {
264 if main_task.is_finished() {
265 return std::ops::ControlFlow::Break(());
266 }
267 }
268 std::ops::ControlFlow::Continue(())
269 }
270 });
271}