use crate::{
ActiveMasksSecondary, BasicLayoutsSecondary, ComponentMask, Context, Display, EntitiesSlot,
EntityId, MichiuSoA, ParentsSecondary, Pipeline, SystemStore, TextEditStore,
VisualPropertiesSecondary, handle_on_blur, handle_on_focus,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
pub enum FocusTrigger {
Mouse,
Keyboard,
#[default]
Both,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ActiveFocusTrigger {
Mouse,
Keyboard,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
pub enum Focusable {
#[default]
None, SelfStyle(FocusTrigger), Inherit(FocusTrigger), }
pub(crate) struct FocusStore {}
impl FocusStore {
#[inline]
#[track_caller]
pub(crate) fn should_prevent_focus_steal(cx: &Context, target_id: EntityId) -> bool {
let mut curr = Some(target_id);
while let Some(curr_id) = curr {
let mask = cx.topology.topo_active_masks.at(curr_id);
if mask.has(ComponentMask::STYLE_PREVENT_FOCUS_STEAL)
&& curr_id == target_id
&& cx
.renders
.rnd_visual
.find(curr_id)
.and_then(|v| v.prevent_focus_steal)
.unwrap_or(false)
{
return true;
}
if mask.has(ComponentMask::STYLE_PREVENT_FOCUS_STEAL_WITHIN)
&& cx
.renders
.rnd_visual
.find(curr_id)
.and_then(|v| v.prevent_focus_steal_within)
.unwrap_or(false)
{
return true;
}
curr = *cx.topology.topo_parents.at(curr_id);
}
false
}
pub(crate) fn restrict_focusable_element(
id: EntityId,
topo_active_masks: &ActiveMasksSecondary,
rnd_visual: &VisualPropertiesSecondary,
) -> bool {
let focusable = rnd_visual.find(id).and_then(|v| v.focusable).or_else(|| {
let mask = topo_active_masks.at(id);
if mask.has_input_content() || mask.has_external_visual_content() {
Some(Focusable::Inherit(FocusTrigger::Both)) } else {
None
}
});
focusable.is_some_and(|f| match f {
Focusable::SelfStyle(trigger) | Focusable::Inherit(trigger) => {
trigger == FocusTrigger::Mouse || trigger == FocusTrigger::Both
}
Focusable::None => false,
})
}
#[inline]
pub(crate) fn set_focused_by_trigger(
cx: &mut Context,
id: EntityId,
focused: bool,
trigger: ActiveFocusTrigger,
) {
Pipeline::update_state(cx, id, ComponentMask::STATE_FOCUSED, focused);
let show_visible = focused && (trigger == ActiveFocusTrigger::Keyboard);
Pipeline::update_state(cx, id, ComponentMask::STATE_FOCUSED_VISIBLE, show_visible);
}
#[track_caller]
pub(crate) fn auto_focus_switch_by_trigger(
cx: &mut Context,
id: EntityId,
trigger: ActiveFocusTrigger,
) {
if cx.events.evt_interaction_states.focused == Some(id) {
FocusStore::set_focused_by_trigger(cx, id, true, trigger);
return;
}
if let Some(old_focus_id) = cx.events.evt_interaction_states.focused {
FocusStore::set_focused_by_trigger(cx, old_focus_id, false, trigger);
TextEditStore::clear_selection_highlight_rect(
old_focus_id,
&mut cx.topology.topo_active_masks,
&mut cx.contents.cont_input_contents,
&mut cx.contents.cont_text_spans,
&mut cx.states.edit.edit_selections,
&mut cx.states.edit.edit_selected_rects,
);
SystemStore::force_complete_ime_composition();
handle_on_blur(cx, old_focus_id);
}
FocusStore::set_focused_by_trigger(cx, id, true, trigger);
let is_input = cx.topology.topo_active_masks.at(id).has_input_content();
if is_input {
let contents = cx.contents.cont_input_contents.at(id);
SystemStore::unassociate_ime(contents, &mut cx.window.win_default_himc);
} else {
SystemStore::reset_ime_default_state(cx.window.win_default_himc.as_ref());
}
cx.events.evt_interaction_states.focused = Some(id);
handle_on_focus(cx, id);
}
#[inline]
pub(crate) fn handle_remove_focus(cx: &mut Context) {
let Some(old_focus_id) = cx.events.evt_interaction_states.focused else {
return;
};
cx.events.evt_interaction_states.focused = None;
FocusStore::set_focused_by_trigger(cx, old_focus_id, false, ActiveFocusTrigger::Mouse);
TextEditStore::clear_selection_highlight_rect(
old_focus_id,
&mut cx.topology.topo_active_masks,
&mut cx.contents.cont_input_contents,
&mut cx.contents.cont_text_spans,
&mut cx.states.edit.edit_selections,
&mut cx.states.edit.edit_selected_rects,
);
SystemStore::force_complete_ime_composition();
SystemStore::reset_ime_default_state(cx.window.win_default_himc.as_ref());
handle_on_blur(cx, old_focus_id);
}
#[track_caller]
pub(crate) fn is_keyboard_focusable(
id: EntityId,
topo_entities: &EntitiesSlot,
topo_active_masks: &ActiveMasksSecondary,
topo_parents: &ParentsSecondary,
lay_basic: &BasicLayoutsSecondary,
rnd_visual: &VisualPropertiesSecondary,
) -> bool {
if !topo_entities.contains_key(id) {
return false;
}
let mask = topo_active_masks.at(id);
if mask.has(ComponentMask::STATE_DISABLED) {
return false;
}
let focusable = rnd_visual.find(id).and_then(|v| v.focusable);
let is_target = match focusable {
Some(Focusable::SelfStyle(trigger) | Focusable::Inherit(trigger)) => {
matches!(trigger, FocusTrigger::Keyboard | FocusTrigger::Both)
}
Some(Focusable::None) => false,
None => mask.has_input_content() || mask.has_external_visual_content(),
};
if !is_target {
return false;
}
let mut curr = Some(id);
while let Some(curr_id) = curr {
if let Some(layout) = lay_basic.find(curr_id)
&& layout.display == Display::None
{
return false;
}
curr = *topo_parents.at(curr_id);
}
true
}
}