use crate::texture::{ImageTexture, ToTexture};
use crate::*;
use glfw::Context as GlfwContext;
pub use glfw::{Action, Key, Modifiers, MouseButton, WindowEvent as Event, WindowId};
pub struct WindowSet<T: Type, C: Color> {
pub glfw: std::cell::RefCell<glfw::Glfw>,
pub windows: std::collections::BTreeMap<glfw::WindowId, Window<T, C>>,
}
unsafe impl<T: Type, C: Color> Send for WindowSet<T, C> {}
unsafe impl<T: Type, C: Color> Sync for WindowSet<T, C> {}
pub struct Window<T: Type, C: Color> {
id: WindowId,
glfw: glfw::Glfw,
inner: glfw::Window,
events: std::sync::mpsc::Receiver<(f64, Event)>,
image: Image<T, C>,
image_texture: ImageTexture<T, C>,
size: Size,
position: Point,
closed: bool,
data: Option<Box<dyn std::any::Any>>,
dirty: bool,
}
impl<T: Type, C: Color> WindowSet<T, C>
where
Image<T, C>: ToTexture<T, C>,
{
pub fn new() -> Result<Self, Error> {
let glfw = std::cell::RefCell::new(glfw::init::<()>(glfw::FAIL_ON_ERRORS)?);
Ok(WindowSet {
glfw,
windows: std::collections::BTreeMap::new(),
})
}
pub fn new_with_error_callback<E: 'static>(
f: glfw::Callback<fn(glfw::Error, String, &E), E>,
) -> Result<Self, Error> {
let glfw = std::cell::RefCell::new(glfw::init::<E>(Some(f))?);
Ok(WindowSet {
glfw,
windows: std::collections::BTreeMap::new(),
})
}
pub fn glfw_context(&self) -> std::cell::Ref<glfw::Glfw> {
self.glfw.borrow()
}
pub fn glfw_context_mut(&self) -> std::cell::RefMut<glfw::Glfw> {
self.glfw.borrow_mut()
}
pub fn add(&mut self, window: Window<T, C>) -> Result<WindowId, Error> {
let id = window.id;
self.windows.insert(id, window);
Ok(id)
}
pub fn create(&mut self, title: impl AsRef<str>, image: Image<T, C>) -> Result<WindowId, Error>
where
Image<T, C>: ToTexture<T, C>,
{
let window = Window::new(self, image, title)?;
self.add(window)
}
pub fn get(&self, window_id: &WindowId) -> Option<&Window<T, C>> {
self.windows.get(window_id)
}
pub fn get_mut(&mut self, window_id: &WindowId) -> Option<&mut Window<T, C>> {
self.windows.get_mut(window_id)
}
pub fn remove(&mut self, window_id: &WindowId) -> Option<Window<T, C>> {
self.windows.remove(window_id)
}
pub fn iter(&self) -> impl Iterator<Item = (&WindowId, &Window<T, C>)> {
self.windows.iter().filter(|(_, x)| !x.is_closed())
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&WindowId, &mut Window<T, C>)> {
self.windows.iter_mut().filter(|(_, x)| !x.is_closed())
}
pub fn iter_windows(&self) -> impl Iterator<Item = &Window<T, C>> {
self.windows.values().filter(|x| !x.is_closed())
}
pub fn iter_windows_mut(&mut self) -> impl Iterator<Item = &mut Window<T, C>> {
self.windows.values_mut().filter(|x| !x.is_closed())
}
pub fn into_images(self) -> impl Iterator<Item = Image<T, C>> {
self.windows.into_values().map(|x| x.into_image())
}
pub fn step<F: FnMut(&mut Window<T, C>, Option<Event>) -> Result<(), Error>>(
&mut self,
mut event_handler: F,
) -> Result<bool, Error> {
let mut count = 0;
for window in self.iter_windows_mut() {
count += 1;
window.handle_events(&mut event_handler)?;
}
Ok(count > 0)
}
pub fn wait_events(&self, timeout: f64) {
self.glfw_context_mut().wait_events_timeout(timeout);
}
pub fn run<F: FnMut(&mut Window<T, C>, Option<Event>) -> Result<(), Error>>(
&mut self,
mut event_handler: F,
) -> Result<(), Error> {
while self.step(&mut event_handler)? {
self.wait_events(0.1)
}
Ok(())
}
}
impl<T: Type, C: Color> Window<T, C>
where
Image<T, C>: ToTexture<T, C>,
{
pub fn new(
context: &WindowSet<T, C>,
image: Image<T, C>,
title: impl AsRef<str>,
) -> Result<Window<T, C>, Error> {
let (mut inner, events) = match context.glfw.borrow_mut().create_window(
image.width() as u32,
image.height() as u32,
title.as_ref(),
glfw::WindowMode::Windowed,
) {
Some(x) => x,
None => return Err(Error::Message("Unable to open window".into())),
};
inner.set_all_polling(true);
inner.make_current();
let ctx = unsafe {
glow::Context::from_loader_function(|ptr| {
context.glfw.borrow().get_proc_address_raw(ptr)
})
};
let image_texture = image.create_image_texture(&ctx)?;
let (width, height) = inner.get_size();
let id = inner.window_id();
let size = Size::new(width as usize, height as usize);
let mut window = Window {
id,
glfw: context.glfw.borrow().clone(),
inner,
events,
position: Point::default(),
size,
closed: false,
data: None,
image_texture,
image,
dirty: false,
};
window.draw()?;
Ok(window)
}
pub fn id(&self) -> WindowId {
self.id
}
pub fn mark_as_dirty(&mut self) {
self.dirty = true;
}
pub fn is_dirty(&self) -> bool {
self.dirty
}
pub fn events(&mut self) -> Result<Vec<Event>, Error> {
let mut events = vec![];
for (_, event) in glfw::flush_messages(&self.events) {
let event = match event {
Event::CursorPos(x, y) => {
let pt = self.fix_mouse_position((x as usize, y as usize));
self.position = pt;
Event::CursorPos(pt.x as f64, pt.y as f64)
}
Event::Size(w, h) => {
self.size = Size::new(w as usize, h as usize);
Event::Size(w, h)
}
Event::Close => {
self.close();
break;
}
event => event,
};
events.push(event);
}
Ok(events)
}
pub fn handle_events<F: FnMut(&mut Window<T, C>, Option<Event>) -> Result<(), Error>>(
&mut self,
mut event_handler: F,
) -> Result<(), Error> {
let events = self.events()?;
if events.is_empty() {
event_handler(self, None)?;
} else {
for event in events {
event_handler(self, Some(event))?;
}
}
if self.is_dirty() {
self.draw()?;
}
Ok(())
}
pub fn set_data<X: std::any::Any>(&mut self, data: X) {
self.data = Some(Box::new(data))
}
pub fn data<X: std::any::Any>(&self) -> Option<&X> {
self.data.as_deref().and_then(|x| x.downcast_ref())
}
pub fn data_mut<X: std::any::Any>(&mut self) -> Option<&mut X> {
self.data.as_deref_mut().and_then(|x| x.downcast_mut())
}
pub fn mouse_position(&self) -> Point {
self.position
}
pub fn fix_mouse_position(&self, pt: impl Into<Point>) -> Point {
let pt = pt.into();
let ratio = (self.size.width as f64 / self.image.meta.width() as f64)
.min(self.size.height as f64 / self.image.meta.height() as f64);
let display_width = (self.image.meta.width() as f64 * ratio) as usize;
let display_height = (self.image.meta.height() as f64 * ratio) as usize;
let x = self.size.width.saturating_sub(display_width) / 2;
let y = self.size.height.saturating_sub(display_height) / 2;
self.scale_mouse_position(pt, x, y, display_width, display_height, ratio)
}
fn scale_mouse_position(
&self,
pt: impl Into<Point>,
x: usize,
y: usize,
display_width: usize,
display_height: usize,
ratio: f64,
) -> Point {
let mut pt = pt.into();
pt.x = pt.x.saturating_sub(x);
pt.y = pt.y.saturating_sub(y);
if pt.x >= display_width {
pt.x = display_width.saturating_sub(1);
}
if pt.y >= display_height {
pt.y = display_height.saturating_sub(1);
}
Point::new(
(pt.x as f64 / ratio) as usize,
(pt.y as f64 / ratio) as usize,
)
}
pub fn into_image(self) -> Image<T, C> {
self.image
}
pub fn image(&self) -> &Image<T, C> {
&self.image
}
pub fn image_mut(&mut self) -> &mut Image<T, C> {
self.mark_as_dirty();
&mut self.image
}
pub fn is_closed(&self) -> bool {
self.closed || self.inner.should_close()
}
pub fn close(&mut self) {
self.inner.set_should_close(true);
self.inner.hide();
self.closed = true;
}
pub fn draw(&mut self) -> Result<(), Error> {
self.inner.make_current();
let meta = self.image.meta();
let size = self.size;
let ratio = (size.width as f64 / meta.width() as f64)
.min(size.height as f64 / meta.height() as f64);
let display_width = (meta.width() as f64 * ratio) as usize;
let display_height = (meta.height() as f64 * ratio) as usize;
let x = size.width.saturating_sub(display_width) / 2;
let y = size.height.saturating_sub(display_height) / 2;
let ctx = unsafe {
glow::Context::from_loader_function(|ptr| self.glfw.get_proc_address_raw(ptr))
};
self.image.draw_image_texture(
&ctx,
&self.image_texture,
(display_width, display_height).into(),
(x, y).into(),
)?;
self.inner.swap_buffers();
self.dirty = false;
Ok(())
}
}
pub fn show<T: Type, C: Color, F: FnMut(&mut Window<T, C>, Option<Event>) -> Result<(), Error>>(
title: impl AsRef<str>,
image: Image<T, C>,
mut f: F,
) -> Result<Image<T, C>, Error>
where
Image<T, C>: ToTexture<T, C>,
{
let mut windows = WindowSet::new()?;
let id = windows.create(title, image)?;
windows.run(|window, event| {
if let Some(Event::Key(k, _, action, _)) = event {
if k == Key::Escape && action == Action::Press {
window.close();
}
}
f(window, event)
})?;
if let Some(window) = windows.remove(&id) {
return Ok(window.into_image());
}
Err(Error::Message("Cannot find window".into()))
}
pub fn show_all<
T: Type,
C: Color,
F: FnMut(&mut Window<T, C>, Option<Event>) -> Result<(), Error>,
>(
images: impl IntoIterator<Item = (impl Into<String>, Image<T, C>)>,
mut f: F,
) -> Result<Vec<Image<T, C>>, Error>
where
Image<T, C>: ToTexture<T, C>,
{
let mut windows = WindowSet::new()?;
for (title, image) in images.into_iter() {
windows.create(title.into(), image)?;
}
windows.run(|window, event| {
if let Some(Event::Key(k, _, action, _)) = event {
if k == Key::Escape && action == Action::Press {
window.close();
}
}
f(window, event)
})?;
Ok(windows.into_images().collect())
}