Tooltip fade-in animations, implement tooltip for CheckBox and Slider, use FnOnce for layout tasks

This commit is contained in:
Aleksander
2026-01-13 17:51:35 +01:00
parent d02efc2657
commit c5356724fd
11 changed files with 212 additions and 62 deletions

View File

@@ -1,11 +1,14 @@
use crate::{
animation::{Animation, AnimationEasing},
assets::AssetPath,
components::{self, Component, ComponentBase, ComponentTrait, RefreshData, tooltip::ComponentTooltip},
components::{
self, Component, ComponentBase, ComponentTrait, RefreshData,
tooltip::{ComponentTooltip, TooltipTrait},
},
drawing::{self, Boundary, Color},
event::{CallbackDataCommon, EventListenerCollection, EventListenerID, EventListenerKind},
i18n::Translation,
layout::{LayoutTask, WidgetID, WidgetPair},
layout::{WidgetID, WidgetPair},
renderer_vk::{
text::{FontWeight, TextStyle, custom_glyph::CustomGlyphData},
util::centered_matrix,
@@ -90,6 +93,12 @@ struct State {
last_pressed: Instant,
}
impl TooltipTrait for State {
fn get(&mut self) -> &mut Option<Rc<ComponentTooltip>> {
&mut self.active_tooltip
}
}
struct Data {
id_label: WidgetID, // Label
id_rect: WidgetID, // Rectangle
@@ -268,7 +277,7 @@ fn register_event_mouse_enter(
data: Rc<Data>,
state: Rc<RefCell<State>>,
listeners: &mut EventListenerCollection,
info: Option<components::tooltip::TooltipInfo>,
tooltip_info: Option<components::tooltip::TooltipInfo>,
anim_mult: f32,
) -> EventListenerID {
listeners.register(
@@ -281,17 +290,7 @@ fn register_event_mouse_enter(
.alterables
.animate(anim_hover_create(state.clone(), event_data.widget_id, true, anim_mult));
if let Some(info) = info.clone() {
common.alterables.tasks.push(LayoutTask::ModifyLayoutState({
let widget_to_watch = data.id_rect;
let state = state.clone();
Box::new(move |m| {
state.borrow_mut().active_tooltip =
Some(components::tooltip::show(m.layout, widget_to_watch, info.clone())?);
Ok(())
})
}));
}
ComponentTooltip::register_hover_in(common, &tooltip_info, data.id_rect, state.clone());
state.borrow_mut().hovered = true;
Ok(EventResult::Pass)

View File

@@ -9,7 +9,11 @@ use taffy::{
use crate::{
animation::{Animation, AnimationEasing},
components::{Component, ComponentBase, ComponentTrait, RefreshData, radio_group::ComponentRadioGroup},
components::{
Component, ComponentBase, ComponentTrait, RefreshData,
radio_group::ComponentRadioGroup,
tooltip::{self, ComponentTooltip, TooltipTrait},
},
drawing::Color,
event::{CallbackDataCommon, EventListenerCollection, EventListenerID, EventListenerKind},
i18n::Translation,
@@ -31,6 +35,7 @@ pub struct Params {
pub checked: bool,
pub radio_group: Option<Rc<ComponentRadioGroup>>,
pub value: Option<Rc<str>>,
pub tooltip: Option<tooltip::TooltipInfo>,
}
impl Default for Params {
@@ -42,6 +47,7 @@ impl Default for Params {
checked: false,
radio_group: None,
value: None,
tooltip: None,
}
}
}
@@ -59,6 +65,13 @@ struct State {
down: bool,
on_toggle: Option<CheckboxToggleCallback>,
self_ref: Weak<ComponentCheckbox>,
active_tooltip: Option<Rc<ComponentTooltip>>,
}
impl TooltipTrait for State {
fn get(&mut self) -> &mut Option<Rc<ComponentTooltip>> {
&mut self.active_tooltip
}
}
#[allow(clippy::struct_field_names)]
@@ -181,6 +194,7 @@ fn anim_hover_out(state: Rc<RefCell<State>>, widget_id: WidgetID, anim_mult: f32
fn register_event_mouse_enter(
state: Rc<RefCell<State>>,
listeners: &mut EventListenerCollection,
tooltip_info: Option<tooltip::TooltipInfo>,
anim_mult: f32,
) -> EventListenerID {
listeners.register(
@@ -190,6 +204,9 @@ fn register_event_mouse_enter(
common
.alterables
.animate(anim_hover_in(state.clone(), event_data.widget_id, anim_mult));
ComponentTooltip::register_hover_in(common, &tooltip_info, event_data.widget_id, state.clone());
state.borrow_mut().hovered = true;
Ok(EventResult::Pass)
}),
@@ -208,7 +225,13 @@ fn register_event_mouse_leave(
common
.alterables
.animate(anim_hover_out(state.clone(), event_data.widget_id, anim_mult));
state.borrow_mut().hovered = false;
{
let mut state = state.borrow_mut();
state.hovered = false;
state.active_tooltip = None;
}
Ok(EventResult::Pass)
}),
)
@@ -402,6 +425,7 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
hovered: false,
on_toggle: None,
self_ref: Weak::new(),
active_tooltip: None,
}));
let base = ComponentBase {
@@ -410,7 +434,7 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
let mut widget = ess.layout.state.widgets.get(id_container).unwrap().state();
let anim_mult = ess.layout.state.globals.defaults().animation_mult;
vec![
register_event_mouse_enter(state.clone(), &mut widget.event_listeners, anim_mult),
register_event_mouse_enter(state.clone(), &mut widget.event_listeners, params.tooltip, anim_mult),
register_event_mouse_leave(state.clone(), &mut widget.event_listeners, anim_mult),
register_event_mouse_press(state.clone(), &mut widget.event_listeners),
register_event_mouse_release(data.clone(), state.clone(), &mut widget.event_listeners),

View File

@@ -5,7 +5,10 @@ use taffy::prelude::{length, percent};
use crate::{
animation::{Animation, AnimationEasing},
components::{Component, ComponentBase, ComponentTrait, RefreshData},
components::{
Component, ComponentBase, ComponentTrait, RefreshData,
tooltip::{self, ComponentTooltip, TooltipTrait},
},
drawing::{self},
event::{
self, CallbackDataCommon, CallbackMetadata, EventAlterables, EventListenerCollection, EventListenerKind,
@@ -70,6 +73,7 @@ pub struct Params {
pub style: taffy::Style,
pub values: ValuesMinMax,
pub show_value: bool,
pub tooltip: Option<tooltip::TooltipInfo>,
}
struct State {
@@ -77,6 +81,13 @@ struct State {
hovered: bool,
values: ValuesMinMax,
on_value_changed: Option<SliderValueChangedCallback>,
active_tooltip: Option<Rc<ComponentTooltip>>,
}
impl TooltipTrait for State {
fn get(&mut self) -> &mut Option<Rc<ComponentTooltip>> {
&mut self.active_tooltip
}
}
struct Data {
@@ -304,14 +315,18 @@ fn register_event_mouse_enter(
data: Rc<Data>,
state: Rc<RefCell<State>>,
listeners: &mut EventListenerCollection,
tooltip_info: Option<tooltip::TooltipInfo>,
anim_mult: f32,
) -> event::EventListenerID {
listeners.register(
EventListenerKind::MouseEnter,
Box::new(move |common, _data, (), ()| {
Box::new(move |common, event_data, (), ()| {
common.alterables.trigger_haptics();
state.borrow_mut().hovered = true;
on_enter_anim(common, data.slider_handle_rect_id, anim_mult);
ComponentTooltip::register_hover_in(common, &tooltip_info, event_data.widget_id, state.clone());
Ok(EventResult::Pass)
}),
)
@@ -327,8 +342,14 @@ fn register_event_mouse_leave(
EventListenerKind::MouseLeave,
Box::new(move |common, _data, (), ()| {
common.alterables.trigger_haptics();
state.borrow_mut().hovered = false;
{
let mut state = state.borrow_mut();
state.hovered = false;
state.active_tooltip = None;
}
on_leave_anim(common, data.slider_handle_rect_id, anim_mult);
Ok(EventResult::Pass)
}),
)
@@ -474,6 +495,7 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
hovered: false,
values: params.values,
on_value_changed: None,
active_tooltip: None,
};
let globals = ess.layout.state.globals.clone();
@@ -515,7 +537,13 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
let mut widget = ess.layout.state.widgets.get(body_id).unwrap().state();
let anim_mult = ess.layout.state.globals.defaults().animation_mult;
vec![
register_event_mouse_enter(data.clone(), state.clone(), &mut widget.event_listeners, anim_mult),
register_event_mouse_enter(
data.clone(),
state.clone(),
&mut widget.event_listeners,
params.tooltip,
anim_mult,
),
register_event_mouse_leave(data.clone(), state.clone(), &mut widget.event_listeners, anim_mult),
register_event_mouse_motion(data.clone(), state.clone(), &mut widget.event_listeners),
register_event_mouse_press(data.clone(), state.clone(), &mut widget.event_listeners),

View File

@@ -1,10 +1,12 @@
use glam::{Mat4, Vec3};
use glam::{Mat4, Vec2, Vec3};
use std::{cell::RefCell, rc::Rc};
use taffy::prelude::length;
use crate::{
animation::{Animation, AnimationEasing},
components::{self, Component, ComponentBase, ComponentTrait, RefreshData},
drawing::Color,
event::CallbackDataCommon,
i18n::Translation,
layout::{self, LayoutTask, LayoutTasks, WidgetID, WidgetPair},
renderer_vk::text::{FontWeight, TextStyle},
@@ -17,6 +19,30 @@ use crate::{
},
};
pub trait TooltipTrait {
fn get(&mut self) -> &mut Option<Rc<ComponentTooltip>>;
}
impl ComponentTooltip {
pub fn register_hover_in(
common: &mut CallbackDataCommon,
tooltip_info: &Option<TooltipInfo>,
widget_to_watch: WidgetID,
state: Rc<RefCell<dyn TooltipTrait>>,
) {
let Some(info) = tooltip_info.clone() else {
return;
};
common.alterables.tasks.push(LayoutTask::ModifyLayoutState({
Box::new(move |m| {
let mut state = state.borrow_mut();
*state.get() = Some(components::tooltip::show(m.layout, widget_to_watch, info.clone())?);
Ok(())
})
}));
}
}
#[derive(Clone, Default)]
pub enum TooltipSide {
Left,
@@ -78,8 +104,6 @@ impl ComponentTrait for ComponentTooltip {
}
}
impl ComponentTooltip {}
impl Drop for ComponentTooltip {
fn drop(&mut self) {
self.tasks.push(LayoutTask::RemoveWidget(self.data.id_root));
@@ -180,7 +204,7 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
},
)?;
let (_label, _) = ess.layout.add_child(
let (label, _) = ess.layout.add_child(
rect.id,
WidgetLabel::create(
&mut globals.get(),
@@ -208,6 +232,35 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
tasks: ess.layout.tasks.clone(),
});
let direction = match params.info.side {
TooltipSide::Left => Vec2::new(-1.0, 0.0),
TooltipSide::Right => Vec2::new(1.0, 0.0),
TooltipSide::Top => Vec2::new(0.0, -1.0),
TooltipSide::Bottom => Vec2::new(0.0, 1.0),
};
let anim_mult = ess.layout.state.globals.defaults().animation_mult;
ess.layout.animations.add(Animation::new(
rect.id,
(10.0 * anim_mult) as u32,
AnimationEasing::OutQuad,
Box::new(move |common, data| {
let rect = data.obj.get_as_mut::<WidgetRectangle>().unwrap(); /* safe */
let alpha = data.pos;
rect.params.color.a = alpha;
rect.params.border_color.a = alpha;
let dir_mult = (1.0 - data.pos) * 5.0;
data.data.transform = Mat4::from_translation(Vec3::new(direction.x * dir_mult, direction.y * dir_mult, 0.0));
if let Some(mut label) = common.state.widgets.get_as::<WidgetLabel>(label.id) {
label.set_color(common, Color::new(1.0, 1.0, 1.0, alpha), true);
}
common.alterables.mark_redraw();
}),
));
ess.layout.defer_component_refresh(Component(tooltip.clone()));
Ok((div, tooltip))
}