Skip to main content

app/
app.rs

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
46//pub use nb;
47use dirty::{
48	WResponse,
49	void,
50	String,
51	Vec
52};
53
54pub use dirty::{SurfaceWrapper as Surface, Color};
55
56/// The default structure to handle and manage apps
57#[allow(dead_code)]
58pub struct App<H>
59where
60	H: EventHandler + Send + Sync,
61{
62	/// List of the program windows
63	pub windows: Vec<Window>,
64	/// Cursor information
65	pub cursor: Cursor,
66	theme: ThemeDefault,
67	handler: H,
68	name: String,
69}
70
71/// This is the bridge between system events and the lib events
72pub trait EventHandler: Send + Sync
73{
74	/// handle_events is the only function for the trait and it results a non blocking Event object
75	fn handle_events(event: Event); //-> nb::Result<(), nb::Error<()>>;
76}
77
78impl<H: EventHandler> App<H>
79{
80	/// Create a new `App`
81	#[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	/// Returns the global theme defined as Self::theme_get_default()
102	pub fn get_global_theme(&self) -> ThemeDefault
103		{ self.theme.clone() }
104
105	/// Modify the current window theme
106	/// If alread set as the value provided, it does nothing
107	pub fn set_global_theme(&mut self, theme: ThemeDefault)
108		{ self.theme = theme }
109
110	/// Creates a new Window element and pushes to the App
111	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	/// init event handler
123	pub fn init(&self)
124	{
125		/*let _event = thread::spawn(move || {
126			nb::block!(H::handle_events(Event::Generic)).unwrap();
127		});*/
128
129		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/// Theme struct
146#[derive(Debug, Clone, PartialEq)]
147pub struct ThemeDefault {
148	/// set the alpha value of the window
149	pub blur: bool,
150	/// default color scheme dark/light
151	pub dark: bool,
152	/// the default accent color of higlight text, buttons, etc
153	pub accent_color: Color,
154	/// the background of the window (not of the renderer)
155	pub background_color: Color,
156	/// Titlebar must be rendered or not
157	pub has_title: bool,
158}
159
160/// NativeDecoration provides the necessary abstraction used inside the `platform` modules
161pub trait NativeDecoration
162{
163	/// executes the application window
164	fn run(&self);
165	/// creates a new decoration on the system
166	fn new(title: String, width: f64, height: f64, theme: ThemeDefault) -> Result<Self, WResponse> where Self: Sized;
167	/// Apply blur to window
168	fn apply_blur(&mut self) -> Result<(), WResponse>;
169	/// exit handler
170	fn exit(&self) -> Result<(), WResponse>;
171	/// App Menu Controls
172	fn create_app_menu(&self, app_name: String) -> Result<(), WResponse>;
173}
174
175/// Detect if the current system prefers CSDs or SSDs
176/// By default, prefer server side decorations
177#[derive(Clone, PartialEq, Debug)]
178pub enum DecorationMode {
179	/// Render the window decorations on the compositor
180	ClientSide,
181	/// Render the window decorations in the window surface
182	ServerSide,
183}
184
185/// Default struct for window Decorations
186#[derive(Debug, PartialEq, Clone)]
187pub struct Decoration {
188	frame: *const void,
189	backend: Wrapper,
190	mode: DecorationMode,
191}
192
193/// OS specific. Check platform apple, nt, linux, etc
194impl Decoration {}
195
196/// Window interface
197#[derive(Clone, PartialEq, Debug)]
198pub struct Window {
199	/// Window title
200	pub title: String,
201	/// The graphical backend (on our case, vulkan)
202	pub surface: Option<Surface>,
203	/// The native window frame
204	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	/// Get system specific window backend (for renderer)
215	#[must_use]
216	pub fn get_backend(&self) -> *mut void
217		{ void::to_handle(self.decoration.backend.clone()) }
218
219	/// Connects a specified vulkan surface with the current window
220	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	/// Returns if window does have a surface or not
232	#[must_use]
233	pub fn has_surface(&self) -> bool
234		{ self.surface.is_some() }
235
236	/// Detects if the window is focused
237	#[must_use]
238	pub fn is_active(&self) -> bool { self.active }
239
240	/// Changes the `window.resizable` argument to a specific bool val
241	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/// List of possible types for the cursor
287#[derive(Debug, PartialEq, Clone)]
288pub enum CursorType {
289	/// The generic arrow cursor
290	Default,
291	/// The pointer cursor
292	Pointer,
293	/// Text selection cursor
294	TextBox,
295	/// Loading cursor
296	Loading,
297	/// Forbidden cursor
298	Forbidden,
299	/*/// Custom cursor sprite
300	 Custom(Box<Path>)*/
301}
302
303/// Default cursor struct
304#[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// transform this into a trait?
314#[allow(dead_code)]
315impl Cursor {
316	/// Get the cursor object
317	#[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	/// Returns the current position of the cursor relative to the window
329	pub fn get_relative_position() {}
330
331	/// Returns the current position of the cursor
332	#[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	/// Modify the cursor position
348	pub fn change_position(&mut self, _new_pos: (f64, f64)) {}
349
350	/// Modify the cursor position relative to the window
351	pub fn change_relative_position(&mut self, _new_pos: (f64, f64)) {}
352
353	/// Hides the Cursor
354	/// If the cursor is already hidden, it does nothing
355	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	/// Shows the cursor
365	/// If the cursor is already visible, it does nothing
366	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	/// Locks the cursor in the current place and hides it
376	pub fn disable(&mut self)
377	{
378		info!("disabling cursor");
379		self.disabled = true;
380		self.hide();
381	}
382
383	/// Set the cursor type
384	/// For example: `CursorType::Pointer` for click actions or `CursorType::Custom(Path)` for custom textures
385	pub fn set_type(&mut self, mode: CursorType)
386		{ self.mode = mode; }
387
388	/// Detects if the cursor is visible or not
389	#[must_use]
390	pub fn is_visible(&self) -> bool
391		{ self.visible }
392}