ltk/event_loop/
tooltip.rsuse super::app_data::AppData;
use super::surface::SurfaceFocus;
use crate::app::App;
use crate::types::{ Length, Rect };
use crate::platform::Platform;
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: crate::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, P: Platform> AppData<A, P>
{
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.frame.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: crate::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( crate::time::Instant::now() ) )
}
pub( crate ) fn check_tooltip_deadline( &mut self )
{
let Some( p ) = self.tooltip_pending.as_ref() else { return };
if crate::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_popup( &self ) -> Option<( Rect, ( i32, i32 ) )>
{
let v = self.tooltip_visible.as_ref()?;
if v.focus != SurfaceFocus::Main || !self.main_is_toplevel() { return None; }
let ( w, h ) = tooltip_size( &v.text );
Some( ( v.anchor, ( w.ceil() as i32, h.ceil() as i32 ) ) )
}
pub( crate ) fn tooltip_overlay( &self ) -> Option<crate::app::OverlaySpec<A::Message>>
{
let v = self.tooltip_visible.as_ref()?;
let id = tooltip_id();
let sf = self.overlays.get( &id )
.map( |ss| ss.scale_factor )
.unwrap_or( self.main.scale_factor );
let ( ox, oy ) = self.surface_offset_for( v.focus );
let src_scale = self.try_surface( v.focus )?.scale_factor;
let anchor_x = ( ox + v.anchor.x / src_scale ) * sf;
let anchor_y = ( oy + v.anchor.y / src_scale ) * sf;
let anchor_w = v.anchor.width / src_scale * sf;
let anchor_h = v.anchor.height / src_scale * sf;
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 * sf )
.padding_v( 6.0 * sf )
.radius( 8.0 * sf )
.into();
let view: crate::widget::Element<A::Message> = if self.tooltip_popup().is_some()
{
crate::layout::stack::stack()
.push_aligned( pill, crate::layout::stack::HAlign::Center, crate::layout::stack::VAlign::Center )
.into()
} else {
self.tooltip_on_screen( pill, ( anchor_x, anchor_y, anchor_w, anchor_h ), &v.text, sf )
};
Some( crate::app::OverlaySpec
{
id,
layer: crate::app::Layer::Overlay,
anchor: crate::app::Anchor::ALL,
size: ( Length::px( 0.0 ), Length::px( 0.0 ) ),
exclusive_zone: -1,
keyboard_exclusive: false,
input_region: Some( Vec::new() ),
view,
on_dismiss: None,
anchor_widget_id: None,
} )
}
fn tooltip_on_screen( &self, pill: crate::widget::Element<A::Message>, ( anchor_x, anchor_y, anchor_w, anchor_h ): ( f32, f32, f32, f32 ), text: &str, sf: f32 ) -> crate::widget::Element<A::Message>
{
let ( estimated_w, estimated_h ) = tooltip_size( text );
let ( estimated_w, estimated_h ) = ( estimated_w * sf, estimated_h * sf );
let gap = 6.0 * sf;
let margin = 4.0 * sf;
let mut x = anchor_x + ( anchor_w - estimated_w ) / 2.0;
let mut y = anchor_y - estimated_h - gap;
let sw = self.main.width as f32 * sf;
let sh = self.main.height as f32 * sf;
x = x.clamp( margin, ( sw - estimated_w - margin ).max( margin ) );
if y < margin { y = anchor_y + anchor_h + gap; }
if y + estimated_h > sh - margin { y = ( sh - estimated_h - margin ).max( margin ); }
crate::layout::stack::stack()
.push_translated( pill, crate::layout::stack::HAlign::Start, crate::layout::stack::VAlign::Top, x, y )
.into()
}
}
pub( crate ) fn tooltip_id() -> crate::app::OverlayId
{
use std::hash::{ Hash, Hasher };
let mut hasher = std::collections::hash_map::DefaultHasher::new();
"ltk-tooltip".hash( &mut hasher );
crate::app::OverlayId( hasher.finish() as u32 )
}
fn tooltip_size( text: &str ) -> ( f32, f32 )
{
let ts = crate::types::text_scale();
let w = ( text.chars().count() as f32 * 7.5 * ts + 24.0 ).clamp( 40.0, 320.0 );
( w, 16.0 * ts + 12.0 )
}