From 792994be2899adec525c80c096cad8be25c2da83 Mon Sep 17 00:00:00 2001 From: Phillip Tennen Date: Tue, 20 Dec 2022 20:33:35 +0000 Subject: [PATCH] [awm2] Initial animations support --- rust_programs/awm2/src/animations.rs | 141 ++++++++++++++++++ rust_programs/awm2/src/desktop.rs | 51 ++++++- rust_programs/awm2/src/main.rs | 2 + rust_programs/awm2/src/main_axle.rs | 205 +++++++++++++++------------ rust_programs/awm2/src/main_std.rs | 5 +- 5 files changed, 308 insertions(+), 96 deletions(-) create mode 100644 rust_programs/awm2/src/animations.rs diff --git a/rust_programs/awm2/src/animations.rs b/rust_programs/awm2/src/animations.rs new file mode 100644 index 000000000..9b575ffe2 --- /dev/null +++ b/rust_programs/awm2/src/animations.rs @@ -0,0 +1,141 @@ +use crate::println; +use crate::utils::get_timestamp; +use crate::window::Window; +use agx_definitions::{Point, Rect, Size}; +use alloc::rc::Rc; +use alloc::vec; +use alloc::vec::Vec; +use num_traits::One; + +fn lerp(a: f64, b: f64, percent: f64) -> f64 { + a + (percent * (b - a)) +} + +fn interpolate_window_frame(from: Rect, to: Rect, percent: f64) -> Rect { + /* + // Don't let the window get too small + // TODO(PT): Pull this out into a MIN_WINDOW_SIZE? + let to = Size::new( + isize::max(to.size.width, 1), + isize::max(to.size.height, (Window::TITLE_BAR_HEIGHT as isize) + 1) + ); + */ + Rect::from_parts( + Point::new( + lerp(from.min_x() as f64, to.min_x() as f64, percent) as isize, + lerp(from.min_y() as f64, to.min_y() as f64, percent) as isize, + ), + Size::new( + lerp(from.width() as f64, to.width() as f64, percent) as isize, + lerp(from.height() as f64, to.height() as f64, percent) as isize, + ), + ) +} + +/* +} +*/ + +pub struct WindowOpenAnimationParams { + start_time: usize, + end_time: usize, + pub window: Rc, + pub duration_ms: usize, + pub frame_from: Rect, + pub frame_to: Rect, +} + +impl WindowOpenAnimationParams { + pub fn new( + desktop_size: Size, + window: &Rc, + duration_ms: usize, + frame_to: Rect, + ) -> Self { + let from_size = Size::new(desktop_size.width / 10, desktop_size.height / 10); + let frame_from = Rect::from_parts( + Point::new( + ((desktop_size.width as f64 / 2.0) - (from_size.width as f64 / 2.0)) as isize, + desktop_size.height - from_size.height, + ), + from_size, + ); + let start_time = get_timestamp() as usize; + Self { + start_time, + end_time: start_time + duration_ms, + window: Rc::clone(window), + duration_ms, + frame_from, + frame_to, + } + } +} + +pub struct AnimationDamage { + pub area_to_recompute_drawable_regions: Rect, + pub rects_needing_composite: Vec, +} + +impl AnimationDamage { + pub fn new( + area_to_recompute_drawable_regions: Rect, + rects_needing_composite: Vec, + ) -> Self { + Self { + area_to_recompute_drawable_regions, + rects_needing_composite, + } + } +} + +pub enum Animation { + WindowOpen(WindowOpenAnimationParams), + //WindowClose(Rc), +} + +impl Animation { + pub fn start(&self) { + match self { + Animation::WindowOpen(params) => { + *params.window.frame.borrow_mut() = params.frame_from; + } + } + } + + pub fn step(&self, now: u64) -> AnimationDamage { + match self { + Animation::WindowOpen(params) => { + let update_region = { + let mut window_frame = params.window.frame.borrow_mut(); + let old_frame = *window_frame; + let elapsed = now - (params.start_time as u64); + let percent = f64::min(1.0, elapsed as f64 / params.duration_ms as f64); + println!("Window open animation step {percent:.2}%"); + let new_frame = + interpolate_window_frame(params.frame_from, params.frame_to, percent); + *window_frame = new_frame; + old_frame.union(new_frame) + }; + params.window.redraw_title_bar(); + AnimationDamage::new(update_region, vec![update_region]) + + //_window_resize(window, window->frame.size, should_inform_window_of_new_size); + /* + Rect total_update_frame = rect_union(current_frame, new_frame); + // Only queueing redraws for the difference between the frames is more efficient, + // but can cause artifacts where on some frames a few window pixels aren't cleaned up. + compositor_queue_rect_to_redraw(total_update_frame); + windows_invalidate_drawable_regions_in_rect(total_update_frame); + AnimationDamage::new(update_region, vec![update_region]) + */ + } + } + } + + pub fn is_complete(&self, now: u64) -> bool { + match self { + Animation::WindowOpen(params) => now as usize >= params.end_time, + } + } +} diff --git a/rust_programs/awm2/src/desktop.rs b/rust_programs/awm2/src/desktop.rs index a4191bac3..a8cca6444 100644 --- a/rust_programs/awm2/src/desktop.rs +++ b/rust_programs/awm2/src/desktop.rs @@ -23,6 +23,7 @@ use core::cmp::{max, min}; use core::fmt::{Display, Formatter}; use mouse_driver_messages::MousePacket; +use crate::animations::{Animation, WindowOpenAnimationParams}; use file_manager_messages::str_from_u8_nul_utf8_unchecked; use kb_driver_messages::{KeyEventType, KeyboardPacket}; use lazy_static::lazy_static; @@ -396,6 +397,7 @@ pub struct Desktop { next_desktop_element_id: usize, windows_to_render_remote_layers_this_cycle: Vec>, frame_render_logs: Vec, + ongoing_animations: Vec, } impl Desktop { @@ -427,6 +429,7 @@ impl Desktop { next_desktop_element_id: 0, windows_to_render_remote_layers_this_cycle: vec![], frame_render_logs: vec![], + ongoing_animations: vec![], } } @@ -704,6 +707,7 @@ impl Desktop { Self::copy_rect(buffer, vmem, mouse_rect); + /* let end = get_timestamp(); logs.push(format!("Finished frame in {}ms", end - start)); if end - start >= 10 { @@ -711,6 +715,7 @@ impl Desktop { println!("\t{l}"); } } + */ } fn copy_rect(src: &mut dyn LikeLayerSlice, dst: &mut dyn LikeLayerSlice, rect: Rect) { @@ -842,7 +847,13 @@ impl Desktop { self.windows.insert(0, Rc::clone(&new_window)); self.compositor_state .track_element(Rc::clone(&new_window) as Rc); - self.recompute_drawable_regions_in_rect(window_frame); + //self.recompute_drawable_regions_in_rect(window_frame); + self.start_animation(Animation::WindowOpen(WindowOpenAnimationParams::new( + desktop_size, + &new_window, + 200, + window_frame, + ))); // TODO(PT): Testing /* @@ -854,6 +865,40 @@ impl Desktop { new_window } + fn start_animation(&mut self, animation: Animation) { + animation.start(); + self.ongoing_animations.push(animation); + } + + pub fn step_animations(&mut self) { + // Don't bother fetching a timestamp (which is a syscall) if not necessary + if self.ongoing_animations.len() == 0 { + return; + } + + let now = get_timestamp(); + let mut rects_to_recompute_drawable_regions = vec![]; + for animation in self.ongoing_animations.iter() { + let animation_damage = animation.step(now); + for damaged_rect in animation_damage.rects_needing_composite.iter() { + self.compositor_state.queue_full_redraw(*damaged_rect); + } + rects_to_recompute_drawable_regions + .push(animation_damage.area_to_recompute_drawable_regions); + /* + match animation { + Animation::WindowOpen(params) => params.window.redraw_title_bar(), + }; + */ + } + for r in rects_to_recompute_drawable_regions.drain(..) { + self.recompute_drawable_regions_in_rect(r); + } + // Drop animations that are now complete + self.ongoing_animations + .retain(|anim| !anim.is_complete(now)); + } + fn window_containing_point(&self, p: Point) -> Option> { // Iterate from the topmost window to further back ones, // so if windows are overlapping the topmost window will receive it @@ -1281,6 +1326,10 @@ impl Desktop { slice.fill(random_color()); } } + + pub fn has_ongoing_animations(&self) -> bool { + self.ongoing_animations.len() > 0 + } } #[cfg(test)] diff --git a/rust_programs/awm2/src/main.rs b/rust_programs/awm2/src/main.rs index 440ab098a..e422c937e 100644 --- a/rust_programs/awm2/src/main.rs +++ b/rust_programs/awm2/src/main.rs @@ -1,3 +1,4 @@ +#![feature(btree_drain_filter)] #![cfg_attr(target_os = "axle", no_std)] #![cfg_attr(target_os = "axle", feature(start))] #![cfg_attr(target_os = "axle", feature(format_args_nl))] @@ -5,6 +6,7 @@ // PT: For the test suite #![cfg_attr(not(target_os = "axle"), feature(iter_zip))] +mod animations; mod desktop; mod effects; mod utils; diff --git a/rust_programs/awm2/src/main_axle.rs b/rust_programs/awm2/src/main_axle.rs index 04a5bf90c..589f9184b 100644 --- a/rust_programs/awm2/src/main_axle.rs +++ b/rust_programs/awm2/src/main_axle.rs @@ -31,7 +31,7 @@ use mouse_driver_messages::{MousePacket, MOUSE_DRIVER_SERVICE_NAME}; use axle_rt::core_commands::{ AmcAwmMapFramebuffer, AmcAwmMapFramebufferResponse, AmcSharedMemoryCreateRequest, - AmcSharedMemoryCreateResponse, AMC_CORE_SERVICE_NAME, + AmcSharedMemoryCreateResponse, AmcSleepUntilDelayOrMessage, AMC_CORE_SERVICE_NAME, }; use libgui::window_events::AwmWindowEvent; use libgui::AwmWindow; @@ -70,6 +70,102 @@ unsafe fn body_as_type_unchecked(body &*(body.as_ptr() as *const T) } +fn process_amc_messages(desktop: &mut Desktop, can_block_for_next_message: bool) { + if !amc_has_message(None) && !can_block_for_next_message { + return; + } + + let msg_unparsed: AmcMessage<[u8]> = unsafe { amc_message_await_untyped(None).unwrap() }; + + // Parse the first bytes of the message as a u32 event field + let raw_body = msg_unparsed.body(); + let event = u32::from_ne_bytes( + // We must slice the array to the exact size of a u32 for the conversion to succeed + raw_body[..core::mem::size_of::()] + .try_into() + .expect("Failed to get 4-length array from message body"), + ); + + // Each inner call to body_as_type_unchecked is unsafe because we must be + // sure we're casting to the right type. + // Since we verify the type on the LHS, each usage is safe. + // + // Wrap the whole thing in an unsafe block to reduce + // boilerplate in each match arm. + // + // Make a copy of the message source to pass around so that callers don't need to worry + // about its validity + let msg_source = msg_unparsed.source().to_string(); + unsafe { + // Try to match special messages from known services first + let consumed = match msg_source.as_str() { + MOUSE_DRIVER_SERVICE_NAME => { + match event { + MousePacket::EXPECTED_EVENT => { + desktop.handle_mouse_packet(body_as_type_unchecked(raw_body)) + } + _ => { + println!("Ignoring unknown message from mouse driver") + } + }; + true + } + KB_DRIVER_SERVICE_NAME => { + // PT: We can't use body_as_type_unchecked because the message from the KB driver lacks + // an event field. Do a direct cast until the KB driver message is fixed. + desktop.handle_keyboard_event(unsafe { &*(raw_body.as_ptr() as *const _) }); + true + } + PREFERENCES_SERVICE_NAME => { + match event { + AwmDesktopTraitsRequest::EXPECTED_EVENT => { + amc_message_send( + PREFERENCES_SERVICE_NAME, + AwmDesktopTraitsResponse::new( + desktop.background_gradient_inner_color, + desktop.background_gradient_outer_color, + ), + ); + true + } + _ => { + //println!("Ignoring unknown message from preferences"); + false + } + } + } + }; + if !consumed { + // Unknown sender - probably a client wanting to interact with the window manager + match event { + // Mouse driver events + // libgui events + // Keyboard events + AwmCreateWindow::EXPECTED_EVENT => { + desktop.spawn_window(&msg_source, body_as_type_unchecked(raw_body), None); + } + AwmWindowRedrawReady::EXPECTED_EVENT => { + //println!("Window said it was ready to redraw!"); + desktop.handle_window_requested_redraw(msg_unparsed.source()); + } + AwmWindowPartialRedraw::EXPECTED_EVENT => { + desktop.handle_window_requested_partial_redraw( + msg_unparsed.source(), + body_as_type_unchecked(raw_body), + ); + } + AwmWindowUpdateTitle::EXPECTED_EVENT => { + desktop + .handle_window_updated_title(&msg_source, body_as_type_unchecked(raw_body)); + } + _ => { + println!("Awm ignoring message with unknown event type: {event}"); + } + } + } + } +} + pub fn main() { amc_register_service(AWM2_SERVICE_NAME); @@ -88,101 +184,22 @@ pub fn main() { continue; } */ + // Block for a message if we don't have any ongoing animations + let must_remain_responsive = desktop.has_ongoing_animations(); + desktop.step_animations(); + process_amc_messages(&mut desktop, !must_remain_responsive); - let msg_unparsed: AmcMessage<[u8]> = unsafe { amc_message_await_untyped(None).unwrap() }; - - // Parse the first bytes of the message as a u32 event field - let raw_body = msg_unparsed.body(); - let event = u32::from_ne_bytes( - // We must slice the array to the exact size of a u32 for the conversion to succeed - raw_body[..core::mem::size_of::()] - .try_into() - .expect("Failed to get 4-length array from message body"), - ); - - // Each inner call to body_as_type_unchecked is unsafe because we must be - // sure we're casting to the right type. - // Since we verify the type on the LHS, each usage is safe. - // - // Wrap the whole thing in an unsafe block to reduce - // boilerplate in each match arm. - // - // Make a copy of the message source to pass around so that callers don't need to worry - // about its validity - let msg_source = msg_unparsed.source().to_string(); - unsafe { - // Try to match special messages from known services first - let consumed = match msg_source.as_str() { - MOUSE_DRIVER_SERVICE_NAME => { - match event { - MousePacket::EXPECTED_EVENT => { - desktop.handle_mouse_packet(body_as_type_unchecked(raw_body)) - } - _ => { - println!("Ignoring unknown message from mouse driver") - } - }; - true - } - KB_DRIVER_SERVICE_NAME => { - // PT: We can't use body_as_type_unchecked because the message from the KB driver lacks - // an event field. Do a direct cast until the KB driver message is fixed. - desktop.handle_keyboard_event(unsafe { &*(raw_body.as_ptr() as *const _) }); - true - } - PREFERENCES_SERVICE_NAME => { - match event { - AwmDesktopTraitsRequest::EXPECTED_EVENT => { - amc_message_send( - PREFERENCES_SERVICE_NAME, - AwmDesktopTraitsResponse::new( - desktop.background_gradient_inner_color, - desktop.background_gradient_outer_color, - ), - ); - true - } - _ => { - //println!("Ignoring unknown message from preferences"); - false - } - } - } - }; - if !consumed { - // Unknown sender - probably a client wanting to interact with the window manager - match event { - // Mouse driver events - // libgui events - // Keyboard events - AwmCreateWindow::EXPECTED_EVENT => { - desktop.spawn_window(&msg_source, body_as_type_unchecked(raw_body), None); - } - AwmWindowRedrawReady::EXPECTED_EVENT => { - //println!("Window said it was ready to redraw!"); - desktop.handle_window_requested_redraw(msg_unparsed.source()); - } - AwmWindowPartialRedraw::EXPECTED_EVENT => { - desktop.handle_window_requested_partial_redraw( - msg_unparsed.source(), - body_as_type_unchecked(raw_body), - ); - } - AwmWindowUpdateTitle::EXPECTED_EVENT => { - desktop.handle_window_updated_title( - &msg_source, - body_as_type_unchecked(raw_body), - ); - } - _ => { - println!("Awm ignoring message with unknown event type: {event}"); - } - } - } - } - - // Now that we've processed a message, draw a frame to reflect the updated state + // Draw a frame to reflect the updated state // Perhaps there are messages for which we can elide a draw? desktop.draw_frame(); + + // Put ourselves to sleep before drawing the next animation frame + // TODO(PT): We need to count how long until the next frame needs to be rendered + /* + if desktop.has_ongoing_animations() { + //println!("Sleeping until it's time to draw the next animation frame..."); + amc_message_send(AMC_CORE_SERVICE_NAME, AmcSleepUntilDelayOrMessage::new(10)); + } + */ } } diff --git a/rust_programs/awm2/src/main_std.rs b/rust_programs/awm2/src/main_std.rs index a7d1f8754..cf8b54277 100644 --- a/rust_programs/awm2/src/main_std.rs +++ b/rust_programs/awm2/src/main_std.rs @@ -26,10 +26,12 @@ use winit::{ }; pub fn main() -> Result<(), Box> { + /* for i in 0..100 { replay_capture(); } return Ok(()); + */ let event_loop = EventLoop::new(); let desktop_size = Size::new(1920, 1080); @@ -62,7 +64,7 @@ pub fn main() -> Result<(), Box> { .unwrap() .as_millis() as u64; let mut rng = SmallRng::seed_from_u64(seed); - for i in 0..20 { + for i in 0..2 { let window_size = Size::new( rng.gen_range(200..desktop_size.width), rng.gen_range(200..desktop_size.height - 30), @@ -122,6 +124,7 @@ pub fn main() -> Result<(), Box> { match event { Event::MainEventsCleared => { //for _ in (0..1024 * 32) { + desktop.step_animations(); desktop.draw_frame(); //} let mut pixel_buffer = layer.pixel_buffer.borrow_mut();