1#![no_std]
2#![deny(
3 deprecated,
4 rust_2018_idioms,
5 clippy::shadow_unrelated,
6 unreachable_code,
7 unused_imports,
8 unused_variables,
9 unsafe_op_in_unsafe_fn,
10 clippy::unwrap_used,
11 clippy::expect_used,
12 clippy::shadow_reuse,
13 clippy::shadow_same,
14 clippy::dbg_macro,
15 clippy::print_stdout,
16 clippy::print_stderr,
17 clippy::panic,
18 clippy::indexing_slicing,
19 clippy::arithmetic_side_effects,
20 clippy::float_arithmetic,
21 clippy::unwrap_in_result,
22 clippy::exit,
23 clippy::wildcard_imports,
24 missing_docs,
25 clippy::all,
26 trivial_casts,
27 trivial_numeric_casts,
28 unused_extern_crates,
29 unused_import_braces,
30 unused_qualifications,
31 unused_results,
32)]
33#![allow(clippy::tabs_in_doc_comments)]
34#![doc = include_str!("../README.md")]
35
36#[cfg(target_os = "none")]
37compile_error!("no bare metal support");
38
39mod platform;
40mod events;
41
42pub use events::Event;
43use platform::Wrapper;
44use log::{warn, info};
45
46use dirty::{
48 WResponse,
49 void,
50 String,
51 Vec
52};
53
54pub use dirty::{SurfaceWrapper as Surface, Color};
55
56#[allow(dead_code)]
58pub struct App<H>
59where
60 H: EventHandler + Send + Sync,
61{
62 pub windows: Vec<Window>,
64 pub cursor: Cursor,
66 theme: ThemeDefault,
67 handler: H,
68 name: String,
69}
70
71pub trait EventHandler: Send + Sync
73{
74 fn handle_events(event: Event); }
77
78impl<H: EventHandler> App<H>
79{
80 #[must_use]
82 pub fn new(handler: H, name: &'static str) -> Self
83 {
84 let theme = ThemeDefault {
85 blur: false,
86 dark: false,
87 accent_color: Color::from(255, 255, 255, 255),
88 background_color: Color::from(255, 255, 255, 255),
89 has_title: true,
90 };
91
92 Self {
93 windows: Vec::new(),
94 cursor: Cursor::get_cursor(),
95 theme,
96 handler,
97 name: String::from(name),
98 }
99 }
100
101 pub fn get_global_theme(&self) -> ThemeDefault
103 { self.theme.clone() }
104
105 pub fn set_global_theme(&mut self, theme: ThemeDefault)
108 { self.theme = theme }
109
110 pub fn new_window(
112 &mut self,
113 title: &'static str,
114 size: (f64, f64),
115 ) -> Result<Window, WResponse>
116 {
117 let window = Window::new(self.name.clone(), title, self.theme.clone(), size)?;
118 self.windows.push(window.clone());
119 Ok(window)
120 }
121
122 pub fn init(&self)
124 {
125 dirty::Thread::default(event_thread).run();
130
131 #[cfg(target_os = "macos")]
132 if let Some(window) = self.windows.first() {
133 window.decoration.run();
134 };
135 }
136}
137
138#[unsafe(no_mangle)]
139extern "C" fn event_thread(p: *mut void) -> *mut void
140{
141 log::debug!("creating event thread!");
142 p
143}
144
145#[derive(Debug, Clone, PartialEq)]
147pub struct ThemeDefault {
148 pub blur: bool,
150 pub dark: bool,
152 pub accent_color: Color,
154 pub background_color: Color,
156 pub has_title: bool,
158}
159
160pub trait NativeDecoration
162{
163 fn run(&self);
165 fn new(title: String, width: f64, height: f64, theme: ThemeDefault) -> Result<Self, WResponse> where Self: Sized;
167 fn apply_blur(&mut self) -> Result<(), WResponse>;
169 fn exit(&self) -> Result<(), WResponse>;
171 fn create_app_menu(&self, app_name: String) -> Result<(), WResponse>;
173}
174
175#[derive(Clone, PartialEq, Debug)]
178pub enum DecorationMode {
179 ClientSide,
181 ServerSide,
183}
184
185#[derive(Debug, PartialEq, Clone)]
187pub struct Decoration {
188 frame: *const void,
189 backend: Wrapper,
190 mode: DecorationMode,
191}
192
193impl Decoration {}
195
196#[derive(Clone, PartialEq, Debug)]
198pub struct Window {
199 pub title: String,
201 pub surface: Option<Surface>,
203 decoration: Decoration,
205 resizable: bool,
206 position: (f32, f32),
207 active: bool,
208 theme: ThemeDefault,
209}
210
211#[forbid(unsafe_code)]
212impl Window
213{
214 #[must_use]
216 pub fn get_backend(&self) -> *mut void
217 { void::to_handle(self.decoration.backend.clone()) }
218
219 pub fn connect_surface(&mut self, surface: Surface) -> Result<(), WResponse>
221 {
222 if !self.has_surface() {
223 self.surface = Some(surface);
224 return Ok(());
225 }
226 warn!("this window is already connected to a surface!");
227 info!("to connect to another surface, please remove the current one");
228 Err(WResponse::ChannelInUse)
229 }
230
231 #[must_use]
233 pub fn has_surface(&self) -> bool
234 { self.surface.is_some() }
235
236 #[must_use]
238 pub fn is_active(&self) -> bool { self.active }
239
240 pub fn resizable(&mut self, arg: bool) { self.resizable = arg }
242}
243
244trait PrivateWindow {
245 fn new(app_name: String, title: &'static str, theme: ThemeDefault, size: (f64, f64)) ->
246 Result<Window, WResponse>;
247}
248
249impl PrivateWindow for Window {
250 fn new(
251 app_name: String,
252 title: &'static str,
253 theme: ThemeDefault,
254 size: (f64, f64)
255 ) -> Result<Self, WResponse>
256 {
257 #[allow(unused_mut)]
258 let mut decoration = match Decoration::new(
259 String::from(title),
260 size.0,
261 size.1,
262 theme.clone()
263 ) {
264 Ok(v) => v,
265 Err(_) => return Err(WResponse::UnexpectedError),
266 };
267
268 let _menu = decoration.create_app_menu(app_name);
269
270 if theme.blur
271 && let Err(response) = decoration.apply_blur()
272 { warn!("{response:?}") }
273
274 Ok(Window {
275 decoration,
276 surface: None,
277 active: false,
278 resizable: true,
279 position: (0.0, 0.0),
280 title: String::from(title),
281 theme,
282 })
283 }
284}
285
286#[derive(Debug, PartialEq, Clone)]
288pub enum CursorType {
289 Default,
291 Pointer,
293 TextBox,
295 Loading,
297 Forbidden,
299 }
302
303#[allow(dead_code)]
305#[derive(Debug, PartialEq, Clone)]
306pub struct Cursor {
307 position: (f64, f64),
308 mode: CursorType,
309 visible: bool,
310 disabled: bool,
311}
312
313#[allow(dead_code)]
315impl Cursor {
316 #[must_use]
318 pub fn get_cursor() -> Self
319 {
320 Cursor {
321 position: Self::get_position(),
322 mode: CursorType::Default,
323 visible: true,
324 disabled: false,
325 }
326 }
327
328 pub fn get_relative_position() {}
330
331 #[must_use]
333 pub fn get_position() -> (f64, f64)
334 {
335 #[cfg(target_os = "macos")]
336 let pos = objc2_app_kit::NSEvent::mouseLocation();
337
338 #[cfg(not(target_os = "macos"))]
339 let (x, y) = (0.0, 0.0);
340
341 #[cfg(target_os = "macos")]
342 let (x, y) = (pos.x, pos.y);
343
344 (x, y)
345 }
346
347 pub fn change_position(&mut self, _new_pos: (f64, f64)) {}
349
350 pub fn change_relative_position(&mut self, _new_pos: (f64, f64)) {}
352
353 pub fn hide(&mut self)
356 {
357 #[cfg(target_os = "macos")]
358 let _err: objc2_core_graphics::CGError =
359 objc2_core_graphics::CGDisplayHideCursor(0);
360
361 self.visible = false;
362 }
363
364 pub fn show(&mut self)
367 {
368 #[cfg(target_os = "macos")]
369 let _err: objc2_core_graphics::CGError =
370 objc2_core_graphics::CGDisplayShowCursor(0);
371
372 self.visible = true;
373 }
374
375 pub fn disable(&mut self)
377 {
378 info!("disabling cursor");
379 self.disabled = true;
380 self.hide();
381 }
382
383 pub fn set_type(&mut self, mode: CursorType)
386 { self.mode = mode; }
387
388 #[must_use]
390 pub fn is_visible(&self) -> bool
391 { self.visible }
392}