use crate::{
Context, Node,
ui::{Event, EventResult, View},
};
pub trait FocusableExt: Sized {
fn focusable(self) -> Focusable<Self> {
Focusable { inner: self }
}
}
impl<V> FocusableExt for V {}
pub struct Focusable<V> {
pub(crate) inner: V,
}
impl<State, V: View<State>> View<State> for Focusable<V> {
type Element = V::Element;
type Message = V::Message;
fn build(&self, ctx: &mut Context) -> Self::Element {
let element = self.inner.build(ctx);
let node = self.inner.get_node(&element);
ctx.register_focusable(node);
element
}
fn rebuild(&self, prev: &Self, ctx: &mut Context, element: &mut Self::Element) {
self.inner.rebuild(&prev.inner, ctx, element);
}
fn rebuild_with_parent(
&self,
prev: &Self,
ctx: &mut Context,
element: &mut Self::Element,
parent: Node,
next_sibling: Option<Node>,
) {
self.inner
.rebuild_with_parent(&prev.inner, ctx, element, parent, next_sibling);
}
fn teardown(&self, ctx: &mut Context, element: &mut Self::Element) {
let node = self.inner.get_node(element);
ctx.unregister_focusable(node);
self.inner.teardown(ctx, element);
}
fn get_node(&self, element: &Self::Element) -> Node {
self.inner.get_node(element)
}
fn handle_event(
&self,
element: &mut Self::Element,
state: &State,
event: Event,
ctx: &mut Context,
) -> (EventResult, Option<Self::Message>) {
let node = self.inner.get_node(element);
let (inner_res, inner_msg) = self.inner.handle_event(element, state, event.clone(), ctx);
if inner_msg.is_some() {
return (inner_res, inner_msg);
}
let mut handled = EventResult::Ignored;
if let Event::MouseInput {
button: winit::event::MouseButton::Left,
pressed: true,
hit_nodes,
..
} = &event
{
if hit_nodes.contains(&node) {
ctx.request_focus(node);
handled = EventResult::Handled;
}
}
(handled.or(inner_res), inner_msg)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ui::widgets::text;
#[test]
fn test_focusable_view_lifecycle_and_click_focus() {
let mut ctx = Context::new();
let view = text::<_, ()>("Focus Me").focusable();
let mut el = View::<()>::build(&view, &mut ctx);
let node = View::<()>::get_node(&view, &el);
assert!(ctx.active_focusable_nodes().contains(&node));
assert_eq!(ctx.focused_node(), None);
ctx.focus_next();
assert_eq!(ctx.focused_node(), Some(node));
ctx.clear_focus();
assert_eq!(ctx.focused_node(), None);
let (res, _) = View::<()>::handle_event(
&view,
&mut el,
&(),
Event::MouseInput {
button: winit::event::MouseButton::Left,
pressed: true,
x: 0.0,
y: 0.0,
hit_nodes: vec![node],
},
&mut ctx,
);
assert_eq!(res, EventResult::Handled);
assert_eq!(ctx.focused_node(), Some(node));
View::<()>::teardown(&view, &mut ctx, &mut el);
assert!(!ctx.active_focusable_nodes().contains(&node));
}
#[test]
fn test_check_click_focus_blur() {
let mut ctx = Context::new();
let parent = ctx.create_node();
let child = ctx.create_node();
let outside = ctx.create_node();
parent.append(&mut ctx, child);
assert_eq!(ctx.check_click_focus_blur(&[outside]), None);
assert_eq!(ctx.focused_node(), None);
ctx.request_focus(parent);
assert_eq!(ctx.check_click_focus_blur(&[parent]), None);
assert_eq!(ctx.focused_node(), Some(parent));
assert_eq!(ctx.check_click_focus_blur(&[child]), None);
assert_eq!(ctx.focused_node(), Some(parent));
assert_eq!(ctx.check_click_focus_blur(&[outside]), Some(parent));
assert_eq!(ctx.focused_node(), None);
ctx.request_focus(parent);
assert_eq!(ctx.check_click_focus_blur(&[]), Some(parent));
assert_eq!(ctx.focused_node(), None);
}
#[test]
fn test_focus_lost_event_and_on_blur_handler() {
use crate::ui::event::ViewEventExt;
let mut ctx = Context::new();
let view = text::<_, &'static str>("Blur Me")
.focusable()
.on_blur(|_| Some("blurred"));
let mut el = View::<()>::build(&view, &mut ctx);
let node = View::<()>::get_node(&view, &el);
let other_node = ctx.create_node();
let (res_other, msg_other) = View::<()>::handle_event(
&view,
&mut el,
&(),
Event::FocusLost { node: other_node },
&mut ctx,
);
assert_eq!(res_other, EventResult::Ignored);
assert_eq!(msg_other, None);
let (res_self, msg_self) =
View::<()>::handle_event(&view, &mut el, &(), Event::FocusLost { node }, &mut ctx);
assert_eq!(res_self, EventResult::Handled);
assert_eq!(msg_self, Some("blurred"));
}
#[test]
fn test_focus_transition_without_disruption() {
use crate::ui::event::ViewEventExt;
let mut ctx = Context::new();
let view_a = text::<_, &'static str>("View A")
.focusable()
.on_blur(|_| Some("a_lost_focus"));
let view_b = text::<_, &'static str>("View B").focusable();
let mut el_a = View::<()>::build(&view_a, &mut ctx);
let node_a = View::<()>::get_node(&view_a, &el_a);
let mut el_b = View::<()>::build(&view_b, &mut ctx);
let node_b = View::<()>::get_node(&view_b, &el_b);
ctx.request_focus(node_a);
assert_eq!(ctx.focused_node(), Some(node_a));
let blurred = ctx.check_click_focus_blur(&[node_b]);
assert_eq!(blurred, Some(node_a));
assert_eq!(ctx.focused_node(), None);
let (blur_res, blur_msg) = View::<()>::handle_event(
&view_a,
&mut el_a,
&(),
Event::FocusLost { node: node_a },
&mut ctx,
);
assert_eq!(blur_res, EventResult::Handled);
assert_eq!(blur_msg, Some("a_lost_focus"));
let (click_res, _) = View::<()>::handle_event(
&view_b,
&mut el_b,
&(),
Event::MouseInput {
button: winit::event::MouseButton::Left,
pressed: true,
x: 0.0,
y: 0.0,
hit_nodes: vec![node_b],
},
&mut ctx,
);
assert_eq!(click_res, EventResult::Handled);
assert_eq!(ctx.focused_node(), Some(node_b));
}
#[test]
fn test_input_text_focus_lost_clears_selection() {
use crate::ui::widgets::input_text;
let mut ctx = Context::new();
let view = input_text();
let text_val = "Hello World".to_string();
let mut el = View::<String>::build(&view, &mut ctx);
let node = View::<String>::get_node(&view, &el);
ctx.request_focus(node);
let (res, _) = View::<String>::handle_event(
&view,
&mut el,
&text_val,
Event::MouseInput {
button: winit::event::MouseButton::Left,
pressed: true,
x: 10.0,
y: 10.0,
hit_nodes: vec![node],
},
&mut ctx,
);
assert_eq!(res, EventResult::Handled);
let blurred = ctx.check_click_focus_blur(&[]);
assert_eq!(blurred, Some(node));
assert_eq!(ctx.focused_node(), None);
let (res_lost, _) = View::<String>::handle_event(
&view,
&mut el,
&text_val,
Event::FocusLost { node },
&mut ctx,
);
assert_eq!(res_lost, EventResult::Handled);
let render_info = node
.get_text_userdata::<crate::text::TextRenderInfo>(&ctx)
.unwrap();
assert_eq!(render_info.selection, None);
assert_eq!(render_info.cursor, None);
}
}