refactor: split every monolithic module into focused submodules
Each source file that had grown beyond a single concern is replaced by an identically-named directory containing focused submodules. `src/event_loop/mod.rs` (878 lines) becomes a directory with clipboard, context_menu, cursor_shape, drag, focus, handlers, invalidation, overlays_reconcile, repeat, run, surface, text_editing, and tooltip. Every widget, input handler, and theme component follows the same split. Public interfaces are unchanged — only the internal file layout moves. image bumped from 0.25.2 to 0.25.9.
This commit is contained in:
144
src/event_loop/tooltip.rs
Normal file
144
src/event_loop/tooltip.rs
Normal file
@@ -0,0 +1,144 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-only
|
||||
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
|
||||
|
||||
use 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 ss = self.surface( focus );
|
||||
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.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,
|
||||
} )
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user