ltk/event_loop/
tooltip.rsuse super::app_data::AppData;
use super::surface::SurfaceFocus;
use crate::app::App;
use crate::types::Rect;
pub const TOOLTIP_DELAY: std::time::Duration = std::time::Duration::from_millis( 600 );
#[derive( Clone )]
pub struct TooltipPending
{
pub focus: SurfaceFocus,
pub flat_idx: usize,
pub deadline: std::time::Instant,
pub text: String,
pub anchor: Rect,
}
#[derive( Clone )]
pub struct TooltipVisible
{
pub focus: SurfaceFocus,
pub anchor: Rect,
pub text: String,
}
impl<A: App> AppData<A>
{
pub( crate ) fn arm_tooltip( &mut self, focus: SurfaceFocus, flat_idx: usize )
{
let Some( ss ) = self.try_surface( focus ) else { return };
let Some( w ) = crate::tree::find_widget( &ss.widget_rects, flat_idx ) else { return };
let Some( text ) = w.tooltip.clone() else
{
self.tooltip_pending = None;
return;
};
let anchor = w.rect;
if let Some( ref p ) = self.tooltip_pending
{
if p.focus == focus && p.flat_idx == flat_idx { return; }
}
self.tooltip_pending = Some( TooltipPending
{
focus,
flat_idx,
deadline: std::time::Instant::now() + TOOLTIP_DELAY,
text,
anchor,
} );
if self.tooltip_visible.is_some()
{
self.tooltip_visible = None;
self.overlays_dirty = true;
}
}
pub( crate ) fn cancel_tooltip( &mut self )
{
let was_visible = self.tooltip_visible.take().is_some();
self.tooltip_pending = None;
if was_visible { self.overlays_dirty = true; }
}
pub( crate ) fn next_tooltip_wakeup( &self ) -> Option<std::time::Duration>
{
let p = self.tooltip_pending.as_ref()?;
Some( p.deadline.saturating_duration_since( std::time::Instant::now() ) )
}
pub( crate ) fn check_tooltip_deadline( &mut self )
{
let Some( p ) = self.tooltip_pending.as_ref() else { return };
if std::time::Instant::now() < p.deadline { return; }
let p = self.tooltip_pending.take().unwrap();
self.tooltip_visible = Some( TooltipVisible
{
focus: p.focus,
anchor: p.anchor,
text: p.text,
} );
self.overlays_dirty = true;
self.main.request_redraw();
}
pub( crate ) fn tooltip_overlay( &self ) -> Option<crate::app::OverlaySpec<A::Message>>
{
let v = self.tooltip_visible.as_ref()?;
let ( ox, oy ) = self.surface_offset_for( v.focus );
let scale = self.try_surface( v.focus )?.scale_factor as f32;
let anchor_x = ox + v.anchor.x / scale;
let anchor_y = oy + v.anchor.y / scale;
let anchor_w = v.anchor.width / scale;
let anchor_h = v.anchor.height / scale;
let palette = crate::theme::palette();
let bg_col = crate::types::Color::rgba( palette.text_primary.r, palette.text_primary.g, palette.text_primary.b, 0.95 );
let pill: crate::widget::Element<A::Message> = crate::widget::container(
crate::widget::text( v.text.clone() )
.size( 13.0 )
.color( palette.bg )
)
.background( bg_col )
.padding_h( 12.0 )
.padding_v( 6.0 )
.radius( 8.0 )
.into();
let estimated_w = ( v.text.chars().count() as f32 * 7.5 + 24.0 ).clamp( 40.0, 320.0 );
let estimated_h = 28.0_f32;
let mut x = anchor_x + ( anchor_w - estimated_w ) / 2.0;
let mut y = anchor_y - estimated_h - 6.0;
let sw = self.main.width as f32;
let sh = self.main.height as f32;
x = x.clamp( 4.0, ( sw - estimated_w - 4.0 ).max( 4.0 ) );
if y < 4.0 { y = anchor_y + anchor_h + 6.0; }
if y + estimated_h > sh - 4.0 { y = ( sh - estimated_h - 4.0 ).max( 4.0 ); }
let view: crate::widget::Element<A::Message> = crate::layout::stack::stack()
.push_translated( pill, crate::layout::stack::HAlign::Start, crate::layout::stack::VAlign::Top, x, y )
.into();
use std::hash::{ Hash, Hasher };
let mut hasher = std::collections::hash_map::DefaultHasher::new();
"ltk-tooltip".hash( &mut hasher );
let id = crate::app::OverlayId( hasher.finish() as u32 );
Some( crate::app::OverlaySpec
{
id,
layer: crate::app::Layer::Overlay,
anchor: crate::app::Anchor::ALL,
size: ( 0, 0 ),
exclusive_zone: -1,
keyboard_exclusive: false,
input_region: Some( Vec::new() ),
view,
on_dismiss: None,
anchor_widget_id: None,
} )
}
}