use std::{
cell::{Cell, RefCell},
rc::Rc,
};
use cranpose_core::{CompositionLocal, compositionLocalOf};
use cranpose_ui_graphics::Rect;
use crate::modifier::Modifier;
pub const BRING_INTO_VIEW_MARGIN: f32 = 12.0;
pub fn scroll_delta_to_reveal(caret: Rect, viewport: Rect, ime_bottom: f32) -> f32 {
let ime_bottom = ime_bottom.max(0.0);
let visible_top = viewport.y;
let visible_bottom = viewport.y + viewport.height - ime_bottom;
if visible_bottom <= visible_top {
return 0.0;
}
let caret_top = caret.y;
let caret_bottom = caret.y + caret.height.max(0.0);
if caret_bottom + BRING_INTO_VIEW_MARGIN > visible_bottom {
let needed = caret_bottom + BRING_INTO_VIEW_MARGIN - visible_bottom;
let max_delta = (caret_top - visible_top).max(0.0);
return needed.min(max_delta);
}
if caret_top - BRING_INTO_VIEW_MARGIN < visible_top {
return caret_top - BRING_INTO_VIEW_MARGIN - visible_top;
}
0.0
}
#[derive(Clone)]
pub struct BringIntoViewResponder {
inner: Rc<dyn Fn(Rect, f32)>,
}
impl BringIntoViewResponder {
pub fn new(responder: impl Fn(Rect, f32) + 'static) -> Self {
Self {
inner: Rc::new(responder),
}
}
pub fn bring_into_view(&self, caret_window_rect: Rect, ime_bottom: f32) {
(self.inner)(caret_window_rect, ime_bottom);
}
}
impl PartialEq for BringIntoViewResponder {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.inner, &other.inner)
}
}
impl std::fmt::Debug for BringIntoViewResponder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BringIntoViewResponder").finish()
}
}
pub fn local_bring_into_view_responder() -> CompositionLocal<Option<BringIntoViewResponder>> {
thread_local! {
static LOCAL: RefCell<Option<CompositionLocal<Option<BringIntoViewResponder>>>> =
const { RefCell::new(None) };
}
LOCAL.with(|cell| {
cell.borrow_mut()
.get_or_insert_with(|| compositionLocalOf(|| None))
.clone()
})
}
impl Modifier {
pub fn report_window_rect(self, sink: Rc<Cell<Rect>>) -> Self {
self.then(Modifier::with_element(
crate::modifier_nodes::WindowRectReporterElement::new(sink),
))
}
pub fn report_window_rect_state(self, sink: cranpose_core::MutableState<Rect>) -> Self {
self.then(Modifier::with_element(
crate::modifier_nodes::WindowRectReporterElement::from_state(sink),
))
}
pub fn report_size(self, sink: Rc<Cell<cranpose_ui_graphics::Size>>) -> Self {
self.then(Modifier::with_element(
crate::modifier_nodes::SizeReporterElement::new(sink),
))
}
pub fn report_size_state(
self,
sink: cranpose_core::MutableState<cranpose_ui_graphics::Size>,
) -> Self {
self.then(Modifier::with_element(
crate::modifier_nodes::SizeReporterElement::from_state(sink),
))
}
}
#[cfg(test)]
#[path = "tests/bring_into_view_tests.rs"]
mod tests;