use std::cell::RefCell;
use crate::animation::Tween;
use crate::geometry::Rect;
use crate::widget::Widget;
pub(crate) const SAFETY_MARGIN: f64 = 8.0;
const LIFT_DURATION_SECS: f64 = 0.22;
thread_local! {
static LIFT: RefCell<Tween> = RefCell::new(Tween::new(0.0, LIFT_DURATION_SECS));
}
pub fn request_lift(target: f64) {
LIFT.with(|c| c.borrow_mut().set_target(target.max(0.0)));
crate::animation::request_draw();
}
pub fn current_lift() -> f64 {
LIFT.with(|c| c.borrow().value())
}
pub fn tick_lift() -> f64 {
LIFT.with(|c| c.borrow_mut().tick())
}
pub fn is_lift_animating() -> bool {
LIFT.with(|c| c.borrow().is_animating())
}
#[cfg(test)]
pub fn reset_lift_for_test() {
LIFT.with(|c| *c.borrow_mut() = Tween::new(0.0, LIFT_DURATION_SECS));
}
#[cfg(test)]
pub fn lift_target_for_test() -> f64 {
LIFT.with(|c| c.borrow().target())
}
#[inline]
pub fn lift_to_world(screen_pos: crate::geometry::Point) -> crate::geometry::Point {
let lift = current_lift();
if lift.abs() < 0.001 {
screen_pos
} else {
crate::geometry::Point::new(screen_pos.x, screen_pos.y - lift)
}
}
pub(crate) fn paint_lifted_tree(
root: &mut dyn Widget,
ctx: &mut dyn crate::draw_ctx::DrawCtx,
viewport: crate::geometry::Size,
lift: f64,
) {
use crate::widget::paint::{paint_global_overlays, paint_subtree};
let lifted = lift.abs() > 0.001;
if lifted {
ctx.save();
ctx.translate(0.0, lift);
}
paint_subtree(root, ctx);
crate::widgets::combo_box::paint_global_combo_popups(ctx);
crate::widgets::tooltip::paint_global_tooltips(ctx, viewport);
paint_global_overlays(root, ctx);
crate::widgets::combo_box::paint_global_combo_popups(ctx);
if lifted {
ctx.restore();
}
}
pub(crate) fn notify_focus_change(
new_path: Option<&[usize]>,
viewport_width: f64,
root: &mut dyn Widget,
) {
use crate::widgets::on_screen_keyboard::{set_text_input_focused, KeyboardInputMode};
let (accepts, existing_text, mode) = match new_path {
Some(p) => {
let w = mutable_widget_at_path(root, p);
(
w.accepts_text_input(),
w.text_input_value(),
w.text_input_mode(),
)
}
None => (false, None, KeyboardInputMode::Text),
};
set_text_input_focused(accepts, existing_text.as_deref(), mode);
if accepts {
ensure_focused_visible_above_keyboard(new_path, viewport_width, root);
} else {
request_lift(0.0);
}
}
pub(crate) fn ensure_focused_visible_above_keyboard(
focus: Option<&[usize]>,
viewport_width: f64,
root: &mut dyn Widget,
) {
let Some(path) = focus else {
return;
};
let Some(rect) = focused_widget_screen_bounds(&*root, path) else {
return;
};
let panel_h = crate::widgets::on_screen_keyboard::target_panel_height(viewport_width);
if panel_h <= 0.0 {
return;
}
let required = panel_h + SAFETY_MARGIN;
if rect.y >= required {
request_lift(0.0);
return;
}
let deficit = required - rect.y;
let absorbed_by_scroll = apply_lift_along_path(root, path, deficit);
let residual = (deficit - absorbed_by_scroll).max(0.0);
request_lift(residual);
}
pub(crate) fn focused_widget_screen_bounds(root: &dyn Widget, path: &[usize]) -> Option<Rect> {
let mut accum_x = root.bounds().x;
let mut accum_y = root.bounds().y;
let mut widget: &dyn Widget = root;
for &idx in path.iter() {
let children = widget.children();
if idx >= children.len() {
return None;
}
widget = children[idx].as_ref();
let b = widget.bounds();
accum_x += b.x;
accum_y += b.y;
}
let b = widget.bounds();
Some(Rect::new(accum_x, accum_y, b.width, b.height))
}
pub(crate) fn apply_lift_along_path(
root: &mut dyn Widget,
path: &[usize],
mut deficit: f64,
) -> f64 {
if deficit.abs() < 0.5 {
return 0.0;
}
let mut total_applied = 0.0;
let n = path.len();
if n == 0 {
return 0.0;
}
for ancestor_depth in (0..n).rev() {
let ancestor_path = &path[..ancestor_depth];
let ancestor = mutable_widget_at_path(root, ancestor_path);
let applied = ancestor.try_scroll_to_lift(deficit);
total_applied += applied;
deficit -= applied;
if deficit.abs() < 0.5 {
break;
}
}
total_applied
}
fn mutable_widget_at_path<'a>(root: &'a mut dyn Widget, path: &[usize]) -> &'a mut dyn Widget {
if path.is_empty() {
return root;
}
let idx = path[0];
let child = &mut root.children_mut()[idx];
mutable_widget_at_path(child.as_mut(), &path[1..])
}