Animated transforms on hover
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
use glam::FloatExt;
|
||||
use glam::{FloatExt, Vec2};
|
||||
|
||||
use crate::{
|
||||
event::WidgetCallback,
|
||||
layout::{WidgetID, WidgetMap},
|
||||
widget::WidgetObj,
|
||||
layout::{WidgetID, WidgetMap, WidgetNodeMap},
|
||||
widget::{WidgetData, WidgetObj},
|
||||
};
|
||||
|
||||
pub enum AnimationEasing {
|
||||
@@ -36,8 +36,10 @@ impl AnimationEasing {
|
||||
|
||||
pub struct CallbackData<'a> {
|
||||
pub obj: &'a mut dyn WidgetObj,
|
||||
pub data: &'a mut WidgetData,
|
||||
pub widgets: &'a WidgetMap,
|
||||
pub widget_id: WidgetID,
|
||||
pub widget_size: Vec2,
|
||||
pub pos: f32, // 0.0 (start of animation) - 1.0 (end of animation)
|
||||
pub needs_redraw: bool,
|
||||
pub dirty_nodes: &'a mut Vec<taffy::NodeId>,
|
||||
@@ -110,29 +112,40 @@ impl Animation {
|
||||
|
||||
fn call(
|
||||
&self,
|
||||
widgets: &WidgetMap,
|
||||
widget_map: &WidgetMap,
|
||||
widget_node_map: &WidgetNodeMap,
|
||||
tree: &taffy::tree::TaffyTree<WidgetID>,
|
||||
dirty_nodes: &mut Vec<taffy::NodeId>,
|
||||
pos: f32,
|
||||
) -> CallResult {
|
||||
let mut res = CallResult::default();
|
||||
|
||||
if let Some(widget) = widgets.get(self.target_widget).cloned() {
|
||||
let mut widget = widget.lock().unwrap();
|
||||
let Some(widget) = widget_map.get(self.target_widget).cloned() else {
|
||||
return res; // failed
|
||||
};
|
||||
|
||||
let data = &mut CallbackData {
|
||||
widget_id: self.target_widget,
|
||||
dirty_nodes,
|
||||
widgets,
|
||||
obj: widget.obj.as_mut(),
|
||||
pos,
|
||||
needs_redraw: false,
|
||||
};
|
||||
let widget_node = widget_node_map.get(self.target_widget);
|
||||
let layout = tree.layout(widget_node).unwrap(); // should always succeed
|
||||
|
||||
(self.callback)(data);
|
||||
let mut widget = widget.lock().unwrap();
|
||||
|
||||
if data.needs_redraw {
|
||||
res.needs_redraw = true;
|
||||
}
|
||||
let (data, obj) = widget.get_data_obj_mut();
|
||||
|
||||
let data = &mut CallbackData {
|
||||
widget_id: self.target_widget,
|
||||
dirty_nodes,
|
||||
widgets: widget_map,
|
||||
widget_size: Vec2::new(layout.size.width, layout.size.height),
|
||||
obj,
|
||||
data,
|
||||
pos,
|
||||
needs_redraw: false,
|
||||
};
|
||||
|
||||
(self.callback)(data);
|
||||
|
||||
if data.needs_redraw {
|
||||
res.needs_redraw = true;
|
||||
}
|
||||
|
||||
res
|
||||
@@ -147,7 +160,9 @@ pub struct Animations {
|
||||
impl Animations {
|
||||
pub fn tick(
|
||||
&mut self,
|
||||
widgets: &WidgetMap,
|
||||
widget_map: &WidgetMap,
|
||||
widget_node_map: &WidgetNodeMap,
|
||||
tree: &taffy::tree::TaffyTree<WidgetID>,
|
||||
dirty_nodes: &mut Vec<taffy::NodeId>,
|
||||
needs_redraw: &mut bool,
|
||||
) {
|
||||
@@ -163,7 +178,7 @@ impl Animations {
|
||||
anim.pos_prev = anim.pos;
|
||||
anim.pos = pos;
|
||||
|
||||
let res = anim.call(widgets, dirty_nodes, 1.0);
|
||||
let res = anim.call(widget_map, widget_node_map, tree, dirty_nodes, 1.0);
|
||||
|
||||
if anim.last_tick || res.needs_redraw {
|
||||
*needs_redraw = true;
|
||||
@@ -179,14 +194,16 @@ impl Animations {
|
||||
|
||||
pub fn process(
|
||||
&mut self,
|
||||
widgets: &WidgetMap,
|
||||
widget_map: &WidgetMap,
|
||||
widget_node_map: &WidgetNodeMap,
|
||||
tree: &taffy::tree::TaffyTree<WidgetID>,
|
||||
dirty_nodes: &mut Vec<taffy::NodeId>,
|
||||
alpha: f32,
|
||||
needs_redraw: &mut bool,
|
||||
) {
|
||||
for anim in &mut self.running_animations {
|
||||
let pos = anim.pos_prev.lerp(anim.pos, alpha);
|
||||
let res = anim.call(widgets, dirty_nodes, pos);
|
||||
let res = anim.call(widget_map, widget_node_map, tree, dirty_nodes, pos);
|
||||
|
||||
if res.needs_redraw {
|
||||
*needs_redraw = true;
|
||||
@@ -213,4 +230,4 @@ impl Animations {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ pub fn construct(
|
||||
},
|
||||
)?;
|
||||
|
||||
let mut widget = layout.widget_states.get(rect_id).unwrap().lock().unwrap();
|
||||
let mut widget = layout.widget_map.get(rect_id).unwrap().lock().unwrap();
|
||||
|
||||
let button = Arc::new(Button {
|
||||
body: rect_id,
|
||||
|
||||
@@ -156,7 +156,7 @@ fn draw_children(
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(widget) = layout.widget_states.get(widget_id) else {
|
||||
let Some(widget) = layout.widget_map.get(widget_id) else {
|
||||
debug_assert!(false);
|
||||
continue;
|
||||
};
|
||||
@@ -172,7 +172,7 @@ pub fn draw(layout: &Layout) -> anyhow::Result<Vec<RenderPrimitive>> {
|
||||
let mut transform_stack = TransformStack::new();
|
||||
let model = glam::Mat4::IDENTITY;
|
||||
|
||||
let Some(root_widget) = layout.widget_states.get(layout.root_widget) else {
|
||||
let Some(root_widget) = layout.widget_map.get(layout.root_widget) else {
|
||||
panic!();
|
||||
};
|
||||
|
||||
|
||||
@@ -18,6 +18,18 @@ use taffy::{TaffyTree, TraversePartialTree};
|
||||
pub type WidgetID = slotmap::DefaultKey;
|
||||
pub type BoxWidget = Arc<Mutex<WidgetState>>;
|
||||
pub type WidgetMap = HopSlotMap<slotmap::DefaultKey, BoxWidget>;
|
||||
#[derive(Default)]
|
||||
pub struct WidgetNodeMap(pub HashMap<WidgetID, taffy::NodeId>);
|
||||
|
||||
impl WidgetNodeMap {
|
||||
pub fn get(&self, widget_id: WidgetID) -> taffy::NodeId {
|
||||
let Some(node) = self.0.get(&widget_id).cloned() else {
|
||||
// this shouldn't happen!
|
||||
panic!("node_map is corrupted");
|
||||
};
|
||||
node
|
||||
}
|
||||
}
|
||||
|
||||
struct PushEventState<'a> {
|
||||
pub animations: &'a mut Vec<animation::Animation>,
|
||||
@@ -31,8 +43,8 @@ pub struct Layout {
|
||||
|
||||
pub assets: Box<dyn AssetProvider>,
|
||||
|
||||
pub widget_states: WidgetMap,
|
||||
pub widget_node_map: HashMap<WidgetID, taffy::NodeId>,
|
||||
pub widget_map: WidgetMap,
|
||||
pub widget_node_map: WidgetNodeMap,
|
||||
|
||||
pub root_widget: WidgetID,
|
||||
pub root_node: taffy::NodeId,
|
||||
@@ -48,47 +60,40 @@ pub struct Layout {
|
||||
|
||||
fn add_child_internal(
|
||||
tree: &mut taffy::TaffyTree<WidgetID>,
|
||||
widget_node_map: &mut HashMap<WidgetID, taffy::NodeId>,
|
||||
vec: &mut WidgetMap,
|
||||
widget_map: &mut WidgetMap,
|
||||
widget_node_map: &mut WidgetNodeMap,
|
||||
parent_node: Option<taffy::NodeId>,
|
||||
widget: WidgetState,
|
||||
style: taffy::Style,
|
||||
) -> anyhow::Result<(WidgetID, taffy::NodeId)> {
|
||||
#[allow(clippy::arc_with_non_send_sync)]
|
||||
let child_id = vec.insert(Arc::new(Mutex::new(widget)));
|
||||
let child_id = widget_map.insert(Arc::new(Mutex::new(widget)));
|
||||
let child_node = tree.new_leaf_with_context(style, child_id)?;
|
||||
|
||||
if let Some(parent_node) = parent_node {
|
||||
tree.add_child(parent_node, child_node)?;
|
||||
}
|
||||
|
||||
widget_node_map.insert(child_id, child_node);
|
||||
widget_node_map.0.insert(child_id, child_node);
|
||||
|
||||
Ok((child_id, child_node))
|
||||
}
|
||||
|
||||
impl Layout {
|
||||
pub fn get_node(&self, widget_id: WidgetID) -> anyhow::Result<taffy::NodeId> {
|
||||
let Some(node) = self.widget_node_map.get(&widget_id).cloned() else {
|
||||
anyhow::bail!("invalid parent widget");
|
||||
};
|
||||
Ok(node)
|
||||
}
|
||||
|
||||
pub fn add_child(
|
||||
&mut self,
|
||||
parent_widget_id: WidgetID,
|
||||
widget: WidgetState,
|
||||
style: taffy::Style,
|
||||
) -> anyhow::Result<(WidgetID, taffy::NodeId)> {
|
||||
let parent_node = self.get_node(parent_widget_id)?;
|
||||
let parent_node = self.widget_node_map.get(parent_widget_id);
|
||||
|
||||
self.needs_redraw = true;
|
||||
|
||||
add_child_internal(
|
||||
&mut self.tree,
|
||||
&mut self.widget_map,
|
||||
&mut self.widget_node_map,
|
||||
&mut self.widget_states,
|
||||
Some(parent_node),
|
||||
widget,
|
||||
style,
|
||||
@@ -123,7 +128,7 @@ impl Layout {
|
||||
|
||||
let style = self.tree.style(node_id)?;
|
||||
|
||||
let Some(widget) = self.widget_states.get(widget_id) else {
|
||||
let Some(widget) = self.widget_map.get(widget_id) else {
|
||||
debug_assert!(false);
|
||||
anyhow::bail!("invalid widget");
|
||||
};
|
||||
@@ -146,7 +151,7 @@ impl Layout {
|
||||
event,
|
||||
&mut EventParams {
|
||||
transform_stack: state.transform_stack,
|
||||
widgets: &self.widget_states,
|
||||
widgets: &self.widget_map,
|
||||
tree: &self.tree,
|
||||
animations: state.animations,
|
||||
needs_redraw: &mut state.needs_redraw,
|
||||
@@ -235,13 +240,13 @@ impl Layout {
|
||||
|
||||
pub fn new(assets: Box<dyn AssetProvider>) -> anyhow::Result<Self> {
|
||||
let mut tree = TaffyTree::new();
|
||||
let mut widget_node_map = HashMap::new();
|
||||
let mut widget_states = HopSlotMap::new();
|
||||
let mut widget_node_map = WidgetNodeMap::default();
|
||||
let mut widget_map = HopSlotMap::new();
|
||||
|
||||
let (root_widget, root_node) = add_child_internal(
|
||||
&mut tree,
|
||||
&mut widget_map,
|
||||
&mut widget_node_map,
|
||||
&mut widget_states,
|
||||
None, // no parent
|
||||
Div::create()?,
|
||||
taffy::Style {
|
||||
@@ -257,7 +262,7 @@ impl Layout {
|
||||
root_node,
|
||||
root_widget,
|
||||
widget_node_map,
|
||||
widget_states,
|
||||
widget_map,
|
||||
needs_redraw: true,
|
||||
haptics_triggered: false,
|
||||
animations: Animations::default(),
|
||||
@@ -269,7 +274,9 @@ impl Layout {
|
||||
let mut dirty_nodes = Vec::new();
|
||||
|
||||
self.animations.process(
|
||||
&self.widget_states,
|
||||
&self.widget_map,
|
||||
&self.widget_node_map,
|
||||
&self.tree,
|
||||
&mut dirty_nodes,
|
||||
timestep_alpha,
|
||||
&mut self.needs_redraw,
|
||||
@@ -301,7 +308,7 @@ impl Layout {
|
||||
match node_context {
|
||||
None => taffy::Size::ZERO,
|
||||
Some(h) => {
|
||||
if let Some(w) = self.widget_states.get(*h) {
|
||||
if let Some(w) = self.widget_map.get(*h) {
|
||||
w.lock()
|
||||
.unwrap()
|
||||
.obj
|
||||
@@ -330,7 +337,9 @@ impl Layout {
|
||||
let mut dirty_nodes = Vec::new();
|
||||
|
||||
self.animations.tick(
|
||||
&self.widget_states,
|
||||
&self.widget_map,
|
||||
&self.widget_node_map,
|
||||
&self.tree,
|
||||
&mut dirty_nodes,
|
||||
&mut self.needs_redraw,
|
||||
);
|
||||
@@ -344,7 +353,7 @@ impl Layout {
|
||||
|
||||
// helper function
|
||||
pub fn add_event_listener(&self, widget_id: WidgetID, listener: EventListener) {
|
||||
let Some(widget) = self.widget_states.get(widget_id) else {
|
||||
let Some(widget) = self.widget_map.get(widget_id) else {
|
||||
debug_assert!(false);
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use glam::{Mat4, Vec2, Vec3};
|
||||
use vulkano::buffer::BufferContents;
|
||||
|
||||
// binary compatible mat4 which could be transparently used by vulkano BufferContents
|
||||
@@ -6,13 +7,20 @@ use vulkano::buffer::BufferContents;
|
||||
pub struct WMat4(pub [f32; 16]);
|
||||
|
||||
impl WMat4 {
|
||||
pub fn from_glam(mat: &glam::Mat4) -> WMat4 {
|
||||
pub fn from_glam(mat: &Mat4) -> WMat4 {
|
||||
WMat4(*mat.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for WMat4 {
|
||||
fn default() -> Self {
|
||||
Self(*glam::Mat4::IDENTITY.as_ref())
|
||||
Self(*Mat4::IDENTITY.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
// works just like CSS transform-origin 50% 50%
|
||||
pub fn centered_matrix(box_size: Vec2, input: &Mat4) -> Mat4 {
|
||||
Mat4::from_translation(Vec3::new(box_size.x / 2.0, box_size.y / 2.0, 0.0))
|
||||
* *input
|
||||
* Mat4::from_translation(Vec3::new(-box_size.x / 2.0, -box_size.y / 2.0, 0.0))
|
||||
}
|
||||
|
||||
@@ -74,6 +74,12 @@ pub struct WidgetState {
|
||||
}
|
||||
|
||||
impl WidgetState {
|
||||
pub fn get_data_obj_mut(&mut self) -> (&mut WidgetData, &mut dyn WidgetObj) {
|
||||
let data = &mut self.data;
|
||||
let obj = self.obj.as_mut();
|
||||
(data, obj)
|
||||
}
|
||||
|
||||
fn new(obj: Box<dyn WidgetObj>) -> anyhow::Result<WidgetState> {
|
||||
Ok(Self {
|
||||
data: WidgetData {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||
|
||||
use glam::{Affine2, vec2, vec3a};
|
||||
use glam::{Affine2, Mat4, Vec2, Vec3, vec2, vec3a};
|
||||
use wgui::{
|
||||
animation::{Animation, AnimationEasing},
|
||||
drawing::Color,
|
||||
event::{self, EventListener},
|
||||
renderer_vk::util,
|
||||
taffy::{self, prelude::length},
|
||||
widget::{
|
||||
div::Div,
|
||||
@@ -161,7 +162,7 @@ where
|
||||
let key_state = {
|
||||
let widget = panel
|
||||
.layout
|
||||
.widget_states
|
||||
.widget_map
|
||||
.get(*widget_id)
|
||||
.unwrap() // want panic
|
||||
.lock()
|
||||
@@ -277,6 +278,28 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
const BUTTON_HOVER_SCALE: f32 = 0.1;
|
||||
|
||||
fn get_anim_transform(pos: f32, widget_size: Vec2) -> Mat4 {
|
||||
util::centered_matrix(
|
||||
widget_size,
|
||||
&Mat4::from_scale(Vec3::splat(BUTTON_HOVER_SCALE.mul_add(pos, 1.0))),
|
||||
)
|
||||
}
|
||||
|
||||
fn set_anim_color(key_state: &KeyState, rect: &mut Rectangle, pos: f32) {
|
||||
let br1 = pos * 0.25;
|
||||
let br2 = pos * 0.15;
|
||||
|
||||
rect.params.color.r = key_state.color.r + br1;
|
||||
rect.params.color.g = key_state.color.g + br1;
|
||||
rect.params.color.b = key_state.color.b + br1;
|
||||
|
||||
rect.params.color2.r = key_state.color2.r + br2;
|
||||
rect.params.color2.g = key_state.color2.g + br2;
|
||||
rect.params.color2.b = key_state.color2.b + br2;
|
||||
}
|
||||
|
||||
fn on_enter_anim(
|
||||
key_state: Rc<KeyState>,
|
||||
_keyboard_state: Rc<RefCell<KeyboardState>>,
|
||||
@@ -284,14 +307,12 @@ fn on_enter_anim(
|
||||
) {
|
||||
data.animations.push(Animation::new(
|
||||
data.widget_id,
|
||||
5,
|
||||
AnimationEasing::OutQuad,
|
||||
10,
|
||||
AnimationEasing::OutBack,
|
||||
Box::new(move |data| {
|
||||
let rect = data.obj.get_as_mut::<Rectangle>();
|
||||
let brightness = data.pos * 0.5;
|
||||
rect.params.color.r = key_state.color.r + brightness;
|
||||
rect.params.color.g = key_state.color.g + brightness;
|
||||
rect.params.color.b = key_state.color.b + brightness;
|
||||
set_anim_color(&key_state, rect, data.pos);
|
||||
data.data.transform = get_anim_transform(data.pos, data.widget_size);
|
||||
data.needs_redraw = true;
|
||||
}),
|
||||
));
|
||||
@@ -304,14 +325,12 @@ fn on_leave_anim(
|
||||
) {
|
||||
data.animations.push(Animation::new(
|
||||
data.widget_id,
|
||||
5,
|
||||
15,
|
||||
AnimationEasing::OutQuad,
|
||||
Box::new(move |data| {
|
||||
let rect = data.obj.get_as_mut::<Rectangle>();
|
||||
let brightness = (1.0 - data.pos) * 0.5;
|
||||
rect.params.color.r = key_state.color.r + brightness;
|
||||
rect.params.color.g = key_state.color.g + brightness;
|
||||
rect.params.color.b = key_state.color.b + brightness;
|
||||
set_anim_color(&key_state, rect, 1.0 - data.pos);
|
||||
data.data.transform = get_anim_transform(1.0 - data.pos, data.widget_size);
|
||||
data.needs_redraw = true;
|
||||
}),
|
||||
));
|
||||
|
||||
Reference in New Issue
Block a user