use std::{cell::RefCell, char, collections::HashMap, rc::Rc, sync::mpsc, time::Duration};
use super::{KeyState, Shell, Window};
use crate::{
event::{ButtonState, Key, KeyEvent},
render::RenderContext2D,
utils::Rectangle,
window_adapter::WindowAdapter,
WindowRequest, WindowSettings,
};
pub struct WindowBuilder<'a, A: 'static>
where
A: WindowAdapter,
{
shell: &'a mut Shell<A>,
adapter: A,
title: String,
resizeable: bool,
always_on_top: bool,
borderless: bool,
fonts: HashMap<String, &'static [u8]>,
bounds: Rectangle,
request_receiver: Option<mpsc::Receiver<WindowRequest>>,
}
impl<'a, A> WindowBuilder<'a, A>
where
A: WindowAdapter,
{
pub fn new(shell: &'a mut Shell<A>, adapter: A) -> Self {
WindowBuilder {
shell,
adapter,
title: String::default(),
resizeable: false,
always_on_top: false,
borderless: false,
fonts: HashMap::new(),
bounds: Rectangle::new((0.0, 0.0), (100.0, 75.0)),
request_receiver: None,
}
}
pub fn from_settings(settings: WindowSettings, shell: &'a mut Shell<A>, adapter: A) -> Self {
WindowBuilder {
shell,
adapter,
title: settings.title,
resizeable: settings.resizeable,
always_on_top: settings.always_on_top,
borderless: settings.borderless,
fonts: settings.fonts,
bounds: Rectangle::new(settings.position, (settings.size.0, settings.size.1)),
request_receiver: None,
}
}
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = title.into();
self
}
pub fn borderless(mut self, borderless: bool) -> Self {
self.borderless = borderless;
self
}
pub fn resizeable(mut self, resizeable: bool) -> Self {
self.resizeable = resizeable;
self
}
pub fn always_on_top(mut self, always_on_top: bool) -> Self {
self.always_on_top = always_on_top;
self
}
pub fn bounds(mut self, bounds: impl Into<Rectangle>) -> Self {
self.bounds = bounds.into();
self
}
pub fn font(mut self, family: impl Into<String>, font_file: &'static [u8]) -> Self {
self.fonts.insert(family.into(), font_file);
self
}
pub fn request_receiver(mut self, request_receiver: mpsc::Receiver<WindowRequest>) -> Self {
self.request_receiver = Some(request_receiver);
self
}
pub fn build(self) {
let window_options = minifb::WindowOptions {
resize: self.resizeable,
topmost: self.always_on_top,
borderless: self.borderless,
title: !self.borderless,
scale_mode: minifb::ScaleMode::UpperLeft,
..Default::default()
};
let mut window = minifb::Window::new(
self.title.as_str(),
self.bounds.width() as usize,
self.bounds.height() as usize,
window_options,
)
.unwrap_or_else(|e| {
panic!("{}", e);
});
window.limit_update_rate(Some(Duration::from_micros(16600)));
let key_events = Rc::new(RefCell::new(vec![]));
window.set_input_callback(Box::new(KeyInputCallBack {
key_events: key_events.clone(),
}));
window.set_position(self.bounds.x() as isize, self.bounds.y() as isize);
let mut render_context = RenderContext2D::new(self.bounds.width(), self.bounds.height());
for (family, font) in self.fonts {
render_context.register_font(&family, font);
}
self.shell.window_shells.push(Window::new(
window,
self.adapter,
render_context,
self.request_receiver,
vec![
KeyState::new(minifb::Key::Backspace, Key::Backspace),
KeyState::new(minifb::Key::Left, Key::Left),
KeyState::new(minifb::Key::Right, Key::Right),
KeyState::new(minifb::Key::Up, Key::Up),
KeyState::new(minifb::Key::Down, Key::Down),
KeyState::new(minifb::Key::Delete, Key::Delete),
KeyState::new(minifb::Key::Enter, Key::Enter),
KeyState::new(minifb::Key::LeftCtrl, Key::Control),
KeyState::new(minifb::Key::RightCtrl, Key::Control),
KeyState::new(minifb::Key::LeftShift, Key::ShiftL),
KeyState::new(minifb::Key::RightShift, Key::ShiftR),
KeyState::new(minifb::Key::LeftAlt, Key::Alt),
KeyState::new(minifb::Key::RightAlt, Key::Alt),
KeyState::new(minifb::Key::Escape, Key::Escape),
KeyState::new(minifb::Key::Home, Key::Home),
KeyState::new(minifb::Key::NumPad0, Key::Numpad0),
KeyState::new(minifb::Key::NumPad1, Key::Numpad1),
KeyState::new(minifb::Key::NumPad2, Key::Numpad2),
KeyState::new(minifb::Key::NumPad3, Key::Numpad3),
KeyState::new(minifb::Key::NumPad4, Key::Numpad4),
KeyState::new(minifb::Key::NumPad5, Key::Numpad5),
KeyState::new(minifb::Key::NumPad6, Key::Numpad6),
KeyState::new(minifb::Key::NumPad7, Key::Numpad7),
KeyState::new(minifb::Key::NumPad8, Key::Numpad8),
KeyState::new(minifb::Key::NumPad9, Key::Numpad9),
KeyState::new(minifb::Key::NumPadSlash, Key::NumpadDivide),
KeyState::new(minifb::Key::NumPadAsterisk, Key::NumpadMultiply),
KeyState::new(minifb::Key::NumPadMinus, Key::NumpadSubtract),
KeyState::new(minifb::Key::NumPadPlus, Key::NumpadAdd),
KeyState::new(minifb::Key::NumPadEnter, Key::NumpadEnter),
KeyState::new(minifb::Key::NumPadDot, Key::NumpadDot),
KeyState::new(minifb::Key::A, Key::A(false)),
KeyState::new(minifb::Key::C, Key::C(false)),
KeyState::new(minifb::Key::V, Key::V(false)),
KeyState::new(minifb::Key::X, Key::X(false)),
],
key_events,
));
}
}
struct KeyInputCallBack {
key_events: Rc<RefCell<Vec<KeyEvent>>>,
}
impl KeyInputCallBack {
fn uni_char_to_key_event(&mut self, uni_char: u32) {
let mut text = String::new();
let key = if let Some(character) = char::from_u32(uni_char) {
text = character.to_string();
Key::from(character)
} else {
Key::Unknown
};
if key == Key::Up
|| key == Key::Down
|| key == Key::Left
|| key == Key::Right
|| key == Key::Backspace
|| key == Key::Control
|| key == Key::Home
|| key == Key::Escape
|| key == Key::Delete
{
return;
}
self.key_events.borrow_mut().push(KeyEvent {
key,
state: ButtonState::Down,
text,
});
}
}
impl minifb::InputCallback for KeyInputCallBack {
fn add_char(&mut self, uni_char: u32) {
self.uni_char_to_key_event(uni_char);
}
}