rat-focus 2.2.1

focus handling for ratatui widgets
Documentation
use crate::{FocusBuilder, HasFocus, Navigation, ratatui};
use std::cell::{Cell, RefCell};
use std::fmt::{Debug, Display, Formatter};
use std::hash::{Hash, Hasher};
use std::ptr;
use std::rc::Rc;

/// Holds the flags for the focus.
///
/// Add this to the widget state and implement [HasFocus] to
/// manage your widgets focus state.
///
/// __Note__
///
/// This struct is intended to be cloned and uses a Rc internally
/// to share the state.
///
/// __Note__
///
/// Equality and Hash and the id() function use the memory address of the
/// FocusFlag behind the internal Rc<>.
///
/// __See__
/// [HasFocus], [on_gained!](crate::on_gained!) and
/// [on_lost!](crate::on_lost!).
///
#[derive(Clone, Default)]
pub struct FocusFlag(Rc<FocusFlagCore>);

/// Equality for FocusFlag means pointer equality of the underlying
/// Rc using Rc::ptr_eq.
impl PartialEq for FocusFlag {
    fn eq(&self, other: &Self) -> bool {
        Rc::ptr_eq(&self.0, &other.0)
    }
}

impl Eq for FocusFlag {}

impl Hash for FocusFlag {
    fn hash<H: Hasher>(&self, state: &mut H) {
        ptr::hash(Rc::as_ptr(&self.0), state);
    }
}

impl Display for FocusFlag {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let name = self.0.name.borrow();
        if let Some(name) = &*name {
            write!(f, "|{}|", name)
        } else {
            write!(f, "")
        }
    }
}

impl HasFocus for FocusFlag {
    fn build(&self, builder: &mut FocusBuilder) {
        builder.leaf_widget(self);
    }

    fn focus(&self) -> FocusFlag {
        self.clone()
    }

    fn area(&self) -> ratatui::layout::Rect {
        ratatui::layout::Rect::default()
    }

    fn area_z(&self) -> u16 {
        0
    }

    fn navigable(&self) -> Navigation {
        Navigation::Regular
    }
}

struct FocusFlagCore {
    /// Field name for debugging purposes.
    name: RefCell<Option<Box<str>>>,
    /// Does this widget have the focus.
    /// Or, if the flag is used for a container, does any of
    /// widget inside the container have the focus.
    ///
    /// This flag is set by [Focus::handle].
    focus: Cell<bool>,
    /// This widget just gained the focus. This flag is set by [Focus::handle]
    /// if there is a focus transfer, and will be reset by the next
    /// call to [Focus::handle].
    ///
    /// See [on_gained!](crate::on_gained!)
    gained: Cell<bool>,
    /// Callback for set of gained.
    ///
    /// A widget can set this callback and will be notified
    /// by Focus whenever it gains the focus.
    ///
    /// It's a bit crude, as you have set up any widget-state
    /// you want to change as shared state with the callback-closure.
    /// But it's still preferable to relying on the fact that
    /// the `handle` event for a widget will be called while
    /// the gained flag is still set.
    on_gained: RefCell<Option<Box<dyn Fn()>>>,
    /// This widget just lost the focus. This flag is set by [Focus::handle]
    /// if there is a focus transfer, and will be reset by the next
    /// call to [Focus::handle].
    ///
    /// See [on_lost!](crate::on_lost!)
    lost: Cell<bool>,
    /// Callback for set of lost.
    ///
    /// A widget can set this callback and will be notified
    /// by Focus whenever it looses the focus.
    ///
    /// It's a bit crude, as you have set up any widget-state
    /// you want to change as shared state with the callback-closure.
    /// But it's still preferable to relying on the fact that
    /// the `handle` event for a widget will be called while
    /// the lost flag is still set.
    on_lost: RefCell<Option<Box<dyn Fn()>>>,
    /// Hit-tests all widgets for the given position.
    /// Obscured is set to false if the widget-area is hit
    /// and there are no containers/widgets with a *higher*
    /// z-index.
    ///
    /// This flag is set by [Focus::handle] for every mouse-event.
    ///
    /// The purpose is to disable mouse-event handling where a
    /// popup is covering some widget.
    obscured: Cell<bool>,
}

impl Debug for FocusFlag {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("FocusFlag")
            .field("name", &self.0.name)
            .field("focus", &self.0.focus.get())
            .field("widget_id", &self.widget_id())
            .field("gained", &self.0.gained.get())
            .field("on_gained", &self.0.on_gained.borrow().is_some())
            .field("lost", &self.0.lost.get())
            .field("on_lost", &self.0.on_lost.borrow().is_some())
            .finish()
    }
}

impl FocusFlag {
    /// Create a default flag.
    pub fn new() -> Self {
        Self::default()
    }

    /// Create a deep copy of the FocusFlag.
    ///
    /// Caution
    ///
    /// It will lose the on_gained() and on_lost() callbacks.
    /// Those can not be replicated/cloned as they
    /// might hold some Rc's to somewhere.
    /// You need to set them anew.
    pub fn new_instance(&self) -> Self {
        Self(Rc::new(self.0.new_instance()))
    }

    /// Return an identity value.
    ///
    /// This uses the memory address of the backing Rc so it will
    /// be unique during the runtime but will be different for each
    /// run.
    pub fn widget_id(&self) -> usize {
        Rc::as_ptr(&self.0) as usize
    }

    /// Create a named flag.
    ///
    /// The name is only used for debugging.
    #[deprecated(
        since = "1.4.0",
        note = "to dangerous, use FocusFlag::new().with_name(..) or FocusFlag::fake_clone(..) for a clone."
    )]
    pub fn named(name: impl AsRef<str>) -> Self {
        Self(Rc::new(FocusFlagCore::default().named(name.as_ref())))
    }

    /// Set a name for a FocusFlag.
    pub fn with_name(self, name: &str) -> Self {
        self.set_name(name);
        self
    }

    /// Has the focus.
    #[inline]
    pub fn get(&self) -> bool {
        self.0.focus.get()
    }

    /// Set the focus.
    #[inline]
    pub fn set(&self, focus: bool) {
        self.0.focus.set(focus);
    }

    /// Get the field-name.
    #[inline]
    pub fn name(&self) -> Box<str> {
        self.0.name.borrow().clone().unwrap_or_default()
    }

    /// Set the field-name.
    #[inline]
    pub fn set_name(&self, name: &str) {
        *self.0.name.borrow_mut() = Some(Box::from(name))
    }

    /// Just lost the focus.
    #[inline]
    pub fn lost(&self) -> bool {
        self.0.lost.get()
    }

    /// Set the lost-flag.
    ///
    /// This doesn't call the on_lost callback.
    #[inline]
    pub fn set_lost(&self, lost: bool) {
        self.0.lost.set(lost);
    }

    /// Set an on_lost callback. The intention is that widget-creators
    /// can use this to get guaranteed notifications on focus-changes.
    ///
    /// This is not an api for widget *users.
    #[inline]
    pub fn on_lost(&self, on_lost: impl Fn() + 'static) {
        *(self.0.on_lost.borrow_mut()) = Some(Box::new(on_lost));
    }

    /// Notify an on_lost() tragedy.
    #[inline]
    pub fn call_on_lost(&self) -> bool {
        let borrow = self.0.on_lost.borrow();
        if let Some(f) = borrow.as_ref() {
            f();
            true
        } else {
            false
        }
    }

    /// Just gained the focus.
    #[inline]
    pub fn gained(&self) -> bool {
        self.0.gained.get()
    }

    /// Set the gained-flag.
    ///
    /// This doesn't call the on_gained callback.
    #[inline]
    pub fn set_gained(&self, gained: bool) {
        self.0.gained.set(gained);
    }

    /// Set an on_gained callback. The intention is that widget-creators
    /// can use this to get guaranteed notifications on focus-changes.
    ///
    /// This is not an api for widget *users.
    #[inline]
    pub fn on_gained(&self, on_gained: impl Fn() + 'static) {
        *(self.0.on_gained.borrow_mut()) = Some(Box::new(on_gained));
    }

    /// Notify an on_gained() comedy.
    #[inline]
    pub fn call_on_gained(&self) -> bool {
        let borrow = self.0.on_gained.borrow();
        if let Some(f) = borrow.as_ref() {
            f();
            true
        } else {
            false
        }
    }

    /// Set the obscured flag.
    #[inline]
    pub fn set_obscured(&self, mf: bool) {
        self.0.obscured.set(mf);
    }

    /// Is the widget area hit by the current mouse-position
    /// and is there no other widget/container with a
    /// higher z-index. Then this widget is *not* obscured.
    ///
    /// This flag is set when handling a mouse-event.
    ///
    /// See [HasFocus::is_obscured()]
    #[inline]
    pub fn obscured(&self) -> bool {
        self.0.obscured.get()
    }

    /// Reset all flags to false.
    #[inline]
    pub fn clear(&self) {
        self.0.focus.set(false);
        self.0.lost.set(false);
        self.0.gained.set(false);
        self.0.obscured.set(false);
    }
}

impl Default for FocusFlagCore {
    fn default() -> Self {
        Self {
            name: RefCell::new(None),
            focus: Cell::new(false),
            gained: Cell::new(false),
            on_gained: RefCell::new(None),
            lost: Cell::new(false),
            on_lost: RefCell::new(None),
            obscured: Cell::new(false),
        }
    }
}

impl FocusFlagCore {
    #[inline(always)]
    pub(crate) fn named(self, name: &str) -> Self {
        *self.name.borrow_mut() = Some(Box::from(name));
        self
    }

    /// Create a deep clone.
    pub(crate) fn new_instance(&self) -> Self {
        Self {
            name: self.name.clone(),
            focus: Cell::new(self.focus.get()),
            gained: Cell::new(self.gained.get()),
            on_gained: RefCell::new(None),
            lost: Cell::new(self.lost.get()),
            on_lost: RefCell::new(None),
            obscured: Cell::new(self.obscured.get()),
        }
    }
}