grafix_toolbox/kit/opengl/context/
window.rs

1use super::{event::*, *};
2use crate::{math::*, sync::*, GL::FrameInfo};
3
4pub trait WindowSpec {
5	fn clipboard(&self) -> String;
6	fn set_clipboard(&mut self, str: &str);
7	fn set_vsync(&mut self, enabled: bool);
8	fn resize(&mut self, size: uVec2);
9
10	fn spawn_offhand_gl(&mut self, _: impl FnOnce() + SendStat) -> JoinHandle<()>;
11	fn poll_events(&mut self) -> Vec<Event>;
12	fn swap(&mut self);
13}
14
15pub type Window = GlfwWindow;
16
17pub struct GlfwWindow {
18	window: glfw::PWindow,
19	events: glfw::GlfwReceiver<(f64, glfw::WindowEvent)>,
20	resized_hint: bool,
21	info: FrameInfo,
22}
23impl GlfwWindow {
24	pub fn get(args: impl WINSize, title: &str) -> Res<Self> {
25		use glfw::{WindowHint::*, *};
26
27		let init_ctx: fn() -> Res<_> = || {
28			let mut ctx = Res(glfw::init(|e, d| ERROR!("{e}: {d}"))).explain_err(|| "GLFW initialization failed")?;
29
30			ctx.window_hint(ClientApi(ClientApiHint::OpenGl));
31			ctx.window_hint(ContextVersion(GL::unigl::GL_VERSION.0, GL::unigl::GL_VERSION.1));
32			ctx.window_hint(OpenGlForwardCompat(true));
33			ctx.window_hint(OpenGlDebugContext(GL::unigl::IS_DEBUG));
34			ctx.window_hint(OpenGlProfile(OpenGlProfileHint::Core));
35			ctx.window_hint(Samples(Some(4)));
36			//ctx.window_hint(DoubleBuffer(false));
37			//ctx.window_hint(Resizable(false));
38			Ok(ctx)
39		};
40
41		let (x, y, w, h) = args.get();
42		let (mut window, events) = Res(init_ctx()?.create_window(w, h, title, WindowMode::Windowed)).explain_err(|| "Cannot create GLFW window")?;
43
44		window.set_pos(x, y);
45		window.make_current();
46		window.set_size_polling(true);
47		window.set_cursor_pos_polling(true);
48		window.set_mouse_button_polling(true);
49		window.set_scroll_polling(true);
50		window.set_key_polling(true);
51		window.set_char_polling(true);
52		window.glfw.set_swap_interval(SwapInterval::Sync(1));
53
54		gl::load_with(|s| window.get_proc_address(s) as *const _);
55
56		let version = Res(unsafe { std::ffi::CStr::from_ptr(gl::GetString(gl::VERSION) as *const i8) }.to_str())?;
57		PRINT!("Initialized OpenGL, {version}");
58		GL::macro_uses::gl_was_initialized(true);
59		if GL::unigl::IS_DEBUG {
60			GL::EnableDebugContext(GL::DebugLevel::All);
61		}
62		crate::GL!(gl::Disable(gl::DITHER));
63
64		window.glfw.set_error_callback(|e, d| match e {
65			glfw::Error::FormatUnavailable => panic!(), // Emitted on bad clipboard
66			_ => ERROR!("{e}: {d}"),
67		});
68
69		let info = FrameInfo::new((w, h));
70		Ok(Self { window, events, resized_hint: true, info })
71	}
72	pub fn info(&self) -> &FrameInfo {
73		&self.info
74	}
75}
76impl WindowSpec for GlfwWindow {
77	fn clipboard(&self) -> String {
78		self.window.get_clipboard_string().unwrap_or_default()
79	}
80	fn set_clipboard(&mut self, s: &str) {
81		self.window.set_clipboard_string(s)
82	}
83	fn set_vsync(&mut self, e: bool) {
84		use glfw::SwapInterval::*;
85		self.window.glfw.set_swap_interval(if e { Sync(1) } else { None });
86	}
87	fn resize(&mut self, size: uVec2) {
88		let Self { window, resized_hint, info, .. } = self;
89		*info = FrameInfo::new(size);
90		let (w, h) = iVec2(size);
91		window.set_size(w, h);
92		*resized_hint = true;
93	}
94	fn spawn_offhand_gl(&mut self, f: impl FnOnce() + SendStat) -> JoinHandle<()> {
95		use glfw::{WindowHint::*, *};
96		let ctx_lock = Arc::new(Barrier::new(2));
97		let ctx = &mut *self.window as *mut Window as usize;
98		make_context_current(None);
99		let offhand = thread::Builder::new()
100			.name("gl_offhand".into())
101			.spawn({
102				let l = ctx_lock.clone();
103				move || {
104					let mut ctx = {
105						let ctx = unsafe { &mut *(ctx as *mut Window) };
106						ctx.make_current();
107						ctx.glfw.window_hint(Visible(false));
108						let Some((w, _)) = ctx.create_shared(1, 1, "offhand_dummy", WindowMode::Windowed) else {
109							l.wait();
110							return FAIL!("Cannot create offhand context");
111						};
112
113						w
114					};
115					ctx.make_current();
116					l.wait();
117					GL::macro_uses::gl_was_initialized(true);
118					f();
119				}
120			})
121			.explain_err(|| "Cannot spawn offhand")
122			.fail();
123
124		ctx_lock.wait();
125		self.window.make_current();
126		offhand
127	}
128	fn poll_events(&mut self) -> Vec<Event> {
129		let action = |a| match a {
130			glfw::Action::Press => Mod::PRESS,
131			glfw::Action::Repeat => Mod::REPEAT,
132			glfw::Action::Release => Mod::RELEASE,
133		};
134		let mods = |m: glfw::Modifiers| {
135			let check = |f, a| if m.contains(f) { a } else { Mod::empty() };
136			check(glfw::Modifiers::Shift, Mod::SHIFT) | check(glfw::Modifiers::Control, Mod::CTRL) | check(glfw::Modifiers::Alt, Mod::ALT) | check(glfw::Modifiers::Super, Mod::WIN)
137		};
138
139		let Self { window, events, resized_hint, info, .. } = self;
140		window.glfw.poll_events();
141		let collect_mods = || {
142			use {glfw::*, Key::*};
143			let mouse = |k| window.get_mouse_button(k) == Action::Press;
144			let active = |k| window.get_key(k) == Action::Press;
145			let shift = active(LeftShift) || active(RightShift);
146			let ctrl = active(LeftControl) || active(RightControl);
147			let alt = active(LeftAlt) || active(RightAlt);
148			let win = active(LeftSuper) || active(RightSuper);
149			let left = mouse(MouseButtonLeft);
150			let mid = mouse(MouseButtonMiddle);
151			let right = mouse(MouseButtonRight);
152			let add = |s, a| if s { a } else { Mod::empty() };
153			add(shift, Mod::SHIFT) | add(ctrl, Mod::CTRL) | add(alt, Mod::ALT) | add(win, Mod::WIN) | add(left, Mod::LEFT) | add(mid, Mod::MID) | add(right, Mod::RIGHT)
154		};
155		let mut events = glfw::flush_messages(events)
156			.filter_map(|(_, event)| match event {
157				glfw::WindowEvent::CursorPos(x, y) => {
158					let ((x, y), (w, h)) = ((2., 2.).mul((x, y)), Vec2(info.size));
159					let at = (x - w, h - y).div(w.min(h));
160					let state = collect_mods();
161					Some(Event::MouseMove { at, state })
162				}
163				glfw::WindowEvent::MouseButton(b, a, m) => {
164					let button = match b {
165						glfw::MouseButtonLeft => Click::Left,
166						glfw::MouseButtonRight => Click::Right,
167						glfw::MouseButtonMiddle => Click::Middle,
168						_ => {
169							INFO!("Excessive buttons on mouse");
170							Click::Middle
171						}
172					};
173
174					let state = action(a) | mods(m);
175					Some(Event::MouseButton { button, state })
176				}
177				glfw::WindowEvent::Key(key, _, a, m) => Some(Event::Keyboard { key, state: action(a) | mods(m) }),
178				glfw::WindowEvent::Scroll(x, y) => Some(Event::Scroll { at: Vec2((x, y)), state: collect_mods() }),
179				glfw::WindowEvent::Char(ch) => Some(Event::Char { ch }),
180
181				glfw::WindowEvent::Size(w, h) => {
182					*info = FrameInfo::new(uVec2((w, h)));
183					None
184				}
185				e => {
186					INFO!("Registered event not covered {e:?}");
187					None
188				}
189			})
190			.collect_vec();
191		if *resized_hint {
192			*resized_hint = false;
193			let ((x, y), (w, h)) = ((2., 2.).mul(window.get_cursor_pos()), Vec2(info.size));
194			let at = (x - w, h - y).div(w.min(h));
195			let state = collect_mods();
196			events.push(Event::MouseMove { at, state })
197		}
198		events
199	}
200	fn swap(&mut self) {
201		use glfw::*;
202		self.window.swap_buffers();
203	}
204}
205
206type WArgs = (i32, i32, u32, u32);
207pub trait WINSize {
208	fn get(self) -> WArgs;
209}
210impl<A, B, C, D> WINSize for (A, B, C, D)
211where
212	i32: Cast<A> + Cast<B>,
213	u32: Cast<C> + Cast<D>,
214{
215	fn get(self) -> WArgs {
216		<_>::to(self)
217	}
218}
219impl<A, B> WINSize for (A, B)
220where
221	u32: Cast<A> + Cast<B>,
222{
223	fn get(self) -> WArgs {
224		(0, 0, u32(self.0), u32(self.1))
225	}
226}