use std::collections::HashMap;
use std::error::Error;
use std::result::Result;
use std::sync::Arc;
use std::time::Instant;
use bevy_ecs::prelude::*;
use crossbeam_channel::Receiver;
use crossbeam_channel::unbounded;
use winit::application::ApplicationHandler;
use winit::error::EventLoopError;
use winit::event::MouseScrollDelta;
use winit::event::StartCause;
use winit::event::WindowEvent;
use winit::event_loop::ControlFlow;
use winit::event_loop::EventLoop;
use winit::keyboard::ModifiersState;
use winit::platform::macos::WindowAttributesExtMacOS as _;
use winit::window::Window;
use crate::keys::Key;
use crate::layout::RootNode;
use crate::layout::WindowSize;
use crate::layout::layout_setup;
use crate::message::Click;
use crate::message::DoubleClick;
use crate::message::EntityMessage;
use crate::message::KeyPress;
use crate::message::Scroll;
use crate::message::message_setup;
use crate::point::Point;
use crate::renderer::AppResize;
use crate::renderer::AppScaleFactor;
use crate::renderer::RenderState;
use crate::renderer::ScaleFactor;
use crate::renderer::renderer_setup;
use crate::renderer::schedule::AppResume;
use crate::renderer::schedule::AppSuspend;
use crate::renderer::schedule::Flush;
use crate::renderer::schedule::Measure;
use crate::renderer::schedule::Render;
use crate::style::Rectangle;
use crate::subscription::Subscription;
use crate::widget::Widget;
use crate::widget::WidgetNode;
use crate::widget::container::container_setup;
use crate::widget::text::text_setup;
const SCROLL_HEIGHT: f32 = 20.0;
#[derive(Debug, Clone, Copy)]
pub struct MousePosition(pub Point);
#[derive(PartialEq, Eq, Debug)]
enum RedrawReason {
Scroll,
}
#[derive(Debug)]
pub enum RuntimeError {
EventLoopError(EventLoopError),
}
impl Error for RuntimeError {}
impl std::fmt::Display for RuntimeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RuntimeError::EventLoopError(err) => write!(f, "Event loop error: {}", err),
}
}
}
pub struct Runtime<'runner, R: Runner<'runner>> {
_marker: std::marker::PhantomData<&'runner ()>,
mouse_last_position: Option<MousePosition>,
first_click_at: Option<Instant>,
key_modifiers: ModifiersState,
redraw_reason: Option<RedrawReason>,
runner: R,
world: World,
subscriptions: Vec<Receiver<R::M>>,
}
impl<'runner, R: Runner<'runner>> Runtime<'runner, R> {
pub fn new(runner: R) -> Result<Self, RuntimeError> {
let mut world = World::default();
text_setup(&mut world);
renderer_setup(&mut world);
container_setup(&mut world);
layout_setup(&mut world);
message_setup::<R::M>(&mut world);
Ok(Self {
_marker: std::marker::PhantomData,
mouse_last_position: None,
first_click_at: None,
key_modifiers: ModifiersState::default(),
redraw_reason: None,
runner,
world,
subscriptions: vec![],
})
}
pub fn subscription(&mut self) -> Subscription<R::M> {
let (tx, rx) = unbounded();
let subscription = Subscription::new(tx);
self.subscriptions.push(rx);
subscription
}
pub fn run(mut self) -> Result<(), RuntimeError> {
let event_loop = EventLoop::<R::M>::with_user_event()
.build()
.map_err(RuntimeError::EventLoopError)?;
event_loop.set_control_flow(ControlFlow::Wait);
for subscription in self.subscriptions.drain(..) {
let proxy = event_loop.create_proxy();
std::thread::spawn(move || {
while let Ok(message) = subscription.recv() {
if let Err(_err) = proxy.send_event(message) {
tracing::error!("Failed to send event from subscription");
}
}
});
}
event_loop
.run_app(&mut self)
.map_err(RuntimeError::EventLoopError)?;
Ok(())
}
}
impl<'widget, 'runner, R: Runner<'runner>> ApplicationHandler<R::M> for Runtime<'runner, R> {
fn new_events(
&mut self,
event_loop: &winit::event_loop::ActiveEventLoop,
cause: winit::event::StartCause,
) {
let _ = (event_loop, cause);
if let StartCause::Init = cause {
let window_attributes = Window::default_attributes()
.with_title("Musica")
.with_theme(None) .with_title_hidden(true)
.with_titlebar_transparent(true)
.with_fullsize_content_view(true);
let window = event_loop.create_window(window_attributes).unwrap();
self.world.insert_resource(RenderState::Suspended {
window: Arc::new(window),
});
}
}
fn resumed(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) {
self.world.run_schedule(AppResume);
self.world.flush();
}
fn window_event(
&mut self,
event_loop: &winit::event_loop::ActiveEventLoop,
window_id: winit::window::WindowId,
event: WindowEvent,
) {
let app_state = self.world.resource::<RenderState>();
let RenderState::Running {
ref surface,
ref valid_surface,
ref window,
} = *app_state
else {
unreachable!("got window event while suspended");
};
if window.id() != window_id {
return;
}
match event {
WindowEvent::ActivationTokenDone {
serial: _,
token: _,
} => todo!(),
WindowEvent::Resized(size) => {
tracing::info!("Resized: {:?}", size);
self.world.trigger(AppResize(size));
self.world.flush();
}
WindowEvent::Moved(position) => {
tracing::info!("Moved: {:?}", position);
}
WindowEvent::CloseRequested => {
event_loop.exit();
}
WindowEvent::Destroyed => {
tracing::info!("Destroyed");
}
WindowEvent::DroppedFile(_) => todo!(),
WindowEvent::HoveredFile(_) => todo!(),
WindowEvent::HoveredFileCancelled => todo!(),
WindowEvent::Focused(focused) => {
tracing::info!("Focused: {:?}", focused);
}
WindowEvent::KeyboardInput {
device_id: _,
event,
is_synthetic: _,
} => {
tracing::info!("KeyboardInput: {:?}", event);
match event.state {
winit::event::ElementState::Pressed => {
}
winit::event::ElementState::Released => {
match event.physical_key {
winit::keyboard::PhysicalKey::Code(key_code) => {
let (tx, rx) = unbounded();
let observer_entity = self
.world
.add_observer(move |message: On<EntityMessage<R::M>>| {
match tx.send((*message).0.clone()) {
Ok(_) => (),
Err(err) => {
tracing::error!(
"Failed to send message from EntityMessage observer: {:?}",
err
);
}
}
})
.id();
self.world.trigger(KeyPress(
Into::<Key>::into(key_code).with_modifiers(self.key_modifiers),
));
self.world.flush();
self.world.despawn(observer_entity);
let mut key_press_happened = false;
for message in rx.try_iter() {
self.runner.update(message);
key_press_happened = true;
}
if key_press_happened {
let app_state = self.world.resource::<RenderState>();
let RenderState::Running { ref window, .. } = *app_state else {
unreachable!("got window event while suspended");
};
window.request_redraw();
}
}
winit::keyboard::PhysicalKey::Unidentified(_native_key_code) => {
}
}
}
}
}
WindowEvent::ModifiersChanged(modifiers) => {
tracing::info!("ModifiersChanged: {:?}", modifiers);
self.key_modifiers = modifiers.state();
}
WindowEvent::Ime(_) => todo!(),
WindowEvent::CursorMoved {
device_id: _,
position,
} => {
let scale_factor = self.world.resource::<ScaleFactor>().0;
self.mouse_last_position = Some(MousePosition(Point {
x: position.x as f32 / scale_factor,
y: position.y as f32 / scale_factor,
}));
}
WindowEvent::CursorEntered { device_id: _ } => {
}
WindowEvent::CursorLeft { device_id: _ } => {
}
WindowEvent::MouseWheel {
device_id: _,
delta,
phase,
} => {
tracing::debug!("MouseWheel: {phase:?} / {delta:?}");
let (tx, rx) = unbounded();
let observer_entity = self
.world
.add_observer(move |message: On<EntityMessage<R::M>>| {
match tx.send((*message).0.clone()) {
Ok(_) => (),
Err(err) => {
tracing::error!(
"Failed to send message from EntityMessage observer: {:?}",
err
);
}
}
})
.id();
match phase {
winit::event::TouchPhase::Started => {
tracing::trace!("MouseWheel started, waiting for move or end");
}
winit::event::TouchPhase::Moved => {
let mouse_position = self
.mouse_last_position
.as_ref()
.expect("Mouse moved has set the mouse position");
let scale_factor = self.world.resource::<ScaleFactor>().0;
self.world.trigger(Scroll(
*mouse_position,
match delta {
MouseScrollDelta::LineDelta(x, y) => Point {
x: -x * SCROLL_HEIGHT,
y: -y * SCROLL_HEIGHT,
},
MouseScrollDelta::PixelDelta(physical_position) => Point {
x: -(physical_position.x as f32 / scale_factor),
y: -(physical_position.y as f32 / scale_factor),
},
},
));
}
winit::event::TouchPhase::Ended => {
tracing::trace!("MouseWheel ended");
}
winit::event::TouchPhase::Cancelled => {
tracing::trace!("MouseWheel cancelled");
}
}
self.world.flush();
self.world.despawn(observer_entity);
let mut scroll_happened = false;
for message in rx.try_iter() {
self.runner.update(message);
scroll_happened = true;
}
if scroll_happened {
let app_state = self.world.resource::<RenderState>();
let RenderState::Running { ref window, .. } = *app_state else {
unreachable!("got window event while suspended");
};
self.redraw_reason = Some(RedrawReason::Scroll);
window.request_redraw();
}
}
WindowEvent::MouseInput {
device_id: _,
state,
button,
} => {
tracing::info!("MouseInput: {button:?} / {state:?}");
let (tx, rx) = unbounded();
let observer_entity = self
.world
.add_observer(move |message: On<EntityMessage<R::M>>| {
match tx.send((*message).0.clone()) {
Ok(_) => (),
Err(err) => {
tracing::error!(
"Failed to send message from EntityMessage observer: {:?}",
err
);
}
}
})
.id();
match state {
winit::event::ElementState::Pressed => {
}
winit::event::ElementState::Released => {
let mouse_position = self
.mouse_last_position
.as_ref()
.expect("Mouse moved has set the mouse position");
self.world.trigger(Click(*mouse_position));
if let Some(first_click_at) = self.first_click_at {
if first_click_at.elapsed().as_millis() < 300 {
self.world.trigger(DoubleClick(*mouse_position));
}
}
let now = Instant::now();
self.first_click_at = Some(now);
}
}
self.world.flush();
self.world.despawn(observer_entity);
let mut click_happened = false;
for message in rx.try_iter() {
self.runner.update(message);
click_happened = true;
}
if click_happened {
let app_state = self.world.resource::<RenderState>();
let RenderState::Running { ref window, .. } = *app_state else {
unreachable!("got window event while suspended");
};
window.request_redraw();
}
}
WindowEvent::PinchGesture {
device_id: _,
delta,
phase,
} => {
tracing::info!("PinchGesture: {phase:?} / {delta:?}");
}
WindowEvent::DoubleTapGesture { device_id: _ } => {
tracing::info!("DoubleTapGesture");
}
WindowEvent::RotationGesture {
device_id: _,
delta: _,
phase: _,
} => (),
WindowEvent::TouchpadPressure {
device_id: _,
pressure: _,
stage: _,
} => {
}
WindowEvent::AxisMotion {
device_id: _,
axis: _,
value: _,
} => todo!(),
WindowEvent::Touch(_) => todo!(),
WindowEvent::ScaleFactorChanged {
scale_factor,
inner_size_writer: _,
} => {
tracing::info!("ScaleFactorChanged: {:?}", scale_factor);
let scale_factor = ScaleFactor(scale_factor as f32);
self.world.trigger(AppScaleFactor(scale_factor));
self.world.flush();
}
WindowEvent::ThemeChanged(_) => todo!(),
WindowEvent::Occluded(occluded) => {
tracing::info!("Occluded: {:?}", occluded);
}
WindowEvent::RedrawRequested => {
if !*valid_surface {
tracing::warn!("RedrawRequested but surface is not valid, skipping redraw");
return;
}
window.pre_present_notify();
let width = surface.config.width;
let height = surface.config.height;
tracing::debug!("RedrawRequested: {:?}", (width, height));
let window_size = WindowSize(Rectangle::from_size(width as f32, height as f32));
self.world.insert_resource(window_size);
let t0 = Instant::now();
fn sync<'widget, 'runner, M, V>(
world: &mut World,
view: &'runner V,
parent_entity: Option<Entity>,
children: &Vec<Entity>,
widgets: &HashMap<Entity, Vec<Entity>>,
) where
M: Clone + Send + Sync + 'static,
'widget: 'runner,
V: Widget<'widget, M> + ?Sized,
{
let current_entity = match parent_entity {
Some(entity) => entity,
None => world.spawn_empty().id(),
};
let mut entity_commands = world.entity_mut(current_entity);
view.spawn(&mut entity_commands);
let mut index = 0;
view.get_children(&mut |child| {
let child_entity = if let Some(&existing) = children.get(index) {
existing
} else {
let new_child_entity = world.spawn_empty().id();
world.entity_mut(current_entity).add_child(new_child_entity);
new_child_entity
};
let grandchildren = widgets.get(&child_entity).cloned().unwrap_or_default();
sync(world, child, Some(child_entity), &grandchildren, widgets);
index += 1;
});
if index < children.len() {
for &extra_entity in &children[index..] {
world.despawn(extra_entity);
}
}
}
let roots = self
.world
.query_filtered::<(Entity, &Children), (With<WidgetNode>, With<RootNode>)>()
.iter(&mut self.world)
.map(|(entity, children)| (entity, children.iter().collect::<Vec<Entity>>()))
.collect::<Vec<_>>();
let widgets = self
.world
.query_filtered::<(Entity, &Children), (With<WidgetNode>, Without<RootNode>)>()
.iter(&mut self.world)
.map(|(entity, children)| (entity, children.iter().collect::<Vec<Entity>>()))
.collect::<HashMap<_, _>>();
if roots.is_empty() {
sync(
&mut self.world,
&self.runner.view(),
None,
&vec![],
&HashMap::new(),
);
} else {
for (root_entity, root_children) in roots {
sync(
&mut self.world,
&self.runner.view(),
Some(root_entity),
&root_children,
&widgets,
);
}
}
let t1 = Instant::now();
self.world.run_schedule(Measure);
let t2 = Instant::now();
self.world.run_schedule(Render);
let t3 = Instant::now();
self.world.run_schedule(Flush);
let t4 = Instant::now();
tracing::debug!(
"Spawn: {:?}, Measure: {:?}, Render: {:?}, Flush: {:?}, Total: {:?}",
t1 - t0,
t2 - t1,
t3 - t2,
t4 - t3,
t4 - t0,
);
self.world.flush();
self.redraw_reason = None;
}
WindowEvent::PanGesture {
device_id: _,
delta: _,
phase: _,
} => todo!(),
}
}
fn user_event(&mut self, event_loop: &winit::event_loop::ActiveEventLoop, event: R::M) {
let _ = event_loop;
tracing::debug!("Received user event from subscription");
let app_state = self.world.resource::<RenderState>();
let RenderState::Running { ref window, .. } = *app_state else {
unreachable!("got window event while suspended");
};
self.runner.update(event);
window.request_redraw();
}
fn device_event(
&mut self,
event_loop: &winit::event_loop::ActiveEventLoop,
device_id: winit::event::DeviceId,
event: winit::event::DeviceEvent,
) {
let _ = (event_loop, device_id, event);
}
fn about_to_wait(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
let _ = event_loop;
}
fn suspended(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) {
self.world.run_schedule(AppSuspend);
self.world.flush();
}
fn exiting(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
let _ = event_loop;
}
fn memory_warning(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
let _ = event_loop;
}
}
pub trait Runner<'a> {
type M: Clone + Send + Sync + 'static;
fn update(&mut self, message: Self::M) {
let _ = message;
}
fn view<'s>(&'s self) -> impl Widget<'s, Self::M> + 's;
}
pub fn run<'runner, R: Runner<'runner> + 'runner>(runner: R) -> Result<(), RuntimeError> {
let runtime = Runtime::new(runner)?;
runtime.run()
}
pub mod prelude {
pub use super::Runner;
pub use super::Runtime;
pub use super::run;
}