use core::marker::PhantomData;
use embedded_touch::Phase;
use crate::{
environment::LayoutEnvironment,
event::{Event, EventContext, EventResult},
focus::{FocusAction, Role},
layout::ResolvedLayout,
primitives::ProposedDimensions,
render::IntrinsicShape,
transition::Opacity,
view::{ViewLayout, ViewMarker},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
enum ButtonTouchState {
CaptivePressed(u8),
Captive(u8),
AtRest,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ButtonState {
touch: ButtonTouchState,
is_focused: bool,
}
impl ButtonState {
#[must_use]
pub fn is_focused(&self) -> bool {
self.is_focused
}
#[must_use]
pub fn is_pressed(&self) -> bool {
matches!(self.touch, ButtonTouchState::CaptivePressed(_))
}
}
#[derive(Debug, Clone)]
pub struct Button<ViewFn, Inner, Action> {
_inner_marker: PhantomData<Inner>,
view: ViewFn,
action: Action,
}
impl<ViewFn, Inner: ViewMarker, Action> Button<ViewFn, Inner, Action> {
#[allow(missing_docs)]
pub fn new(action: Action, view: ViewFn) -> Self
where
ViewFn: Fn(ButtonState) -> Inner,
{
Self {
view,
action,
_inner_marker: PhantomData,
}
}
}
impl<ViewFn, Inner: ViewMarker, Action> ViewMarker for Button<ViewFn, Inner, Action>
where
Inner::Renderables: IntrinsicShape,
{
type Renderables = Inner::Renderables;
type Transition = Opacity;
}
impl<Captures, Inner, ViewFn, Action> ViewLayout<Captures> for Button<ViewFn, Inner, Action>
where
Action: Fn(&mut Captures),
Captures: ?Sized,
Inner: ViewLayout<Captures>,
Inner::Renderables: IntrinsicShape,
ViewFn: Fn(ButtonState) -> Inner,
{
type State = (ButtonState, Inner::State);
type Sublayout = ResolvedLayout<Inner::Sublayout>;
type FocusTree = Inner::FocusTree;
fn transition(&self) -> Self::Transition {
Opacity
}
fn build_state(&self, captures: &mut Captures) -> Self::State {
let initial_state = ButtonState {
touch: ButtonTouchState::AtRest,
is_focused: false,
};
let inner_state = (self.view)(initial_state.clone()).build_state(captures);
(initial_state, inner_state)
}
fn layout(
&self,
offer: &ProposedDimensions,
env: &impl LayoutEnvironment,
captures: &mut Captures,
state: &mut Self::State,
) -> ResolvedLayout<Self::Sublayout> {
let inner_layout = (self.view)(state.0.clone()).layout(offer, env, captures, &mut state.1);
ResolvedLayout {
resolved_size: inner_layout.resolved_size,
sublayouts: inner_layout,
}
}
fn render_tree(
&self,
layout: &Self::Sublayout,
origin: crate::primitives::Point,
env: &impl LayoutEnvironment,
captures: &mut Captures,
state: &mut Self::State,
) -> Self::Renderables {
(self.view)(state.0.clone()).render_tree(
&layout.sublayouts,
origin,
env,
captures,
&mut state.1,
)
}
fn handle_event(
&self,
event: &Event,
context: &EventContext,
render_tree: &mut Self::Renderables,
captures: &mut Captures,
state: &mut Self::State,
_focus: &mut Self::FocusTree,
) -> EventResult {
match event {
Event::Touch(touch) => {
if let ButtonTouchState::Captive(touch_id)
| ButtonTouchState::CaptivePressed(touch_id) = state.0.touch
&& touch.id != touch_id
{
return EventResult::Deferred;
}
let point = touch.location.into();
match touch.phase {
Phase::Started => {
if render_tree.content_shape().contains(point) {
state.0.touch = ButtonTouchState::CaptivePressed(touch.id);
context.request_view_rebuild();
return EventResult::handled_unfocused();
}
}
Phase::Ended => {
if state.0.touch != ButtonTouchState::AtRest {
state.0.touch = ButtonTouchState::AtRest;
context.request_view_rebuild();
let content_shape = render_tree.content_shape();
if content_shape.contains(point) {
(self.action)(captures);
return EventResult::handled_focused(content_shape);
}
return EventResult::handled_unfocused();
}
}
Phase::Moved => {
match (render_tree.content_shape().contains(point), state.0.touch) {
(true, ButtonTouchState::Captive(touch_id)) => {
state.0.touch = ButtonTouchState::CaptivePressed(touch_id);
context.request_view_rebuild();
return EventResult::handled_unfocused();
}
(false, ButtonTouchState::CaptivePressed(touch_id)) => {
state.0.touch = ButtonTouchState::Captive(touch_id);
context.request_view_rebuild();
return EventResult::handled_unfocused();
}
(true, ButtonTouchState::CaptivePressed(_))
| (false, ButtonTouchState::Captive(_)) => {
return EventResult::handled_unfocused();
}
(_, ButtonTouchState::AtRest) => (),
}
}
Phase::Cancelled => {
let was_pressed =
matches!(state.0.touch, ButtonTouchState::CaptivePressed(_));
state.0.touch = ButtonTouchState::AtRest;
state.0.is_focused = false;
if was_pressed {
context.request_view_rebuild();
return EventResult::handled_unfocused();
}
return EventResult::Deferred;
}
Phase::Hovering(_) => {}
}
EventResult::Deferred
}
Event::Focus {
action: focus_event,
..
} => {
if !context.roles.contains(Role::Button) {
return EventResult::Deferred;
}
context.request_redraw();
match focus_event {
FocusAction::Teardown => {
state.0.is_focused = false;
context.request_view_rebuild();
EventResult::handled_unfocused()
}
FocusAction::Blur | FocusAction::Next | FocusAction::Previous => {
state.0.is_focused = false;
context.request_view_rebuild();
EventResult::Deferred
}
FocusAction::Focus(_) => {
if !state.0.is_focused {
context.request_view_rebuild();
}
state.0.is_focused = true;
EventResult::handled_focused(render_tree.content_shape())
}
FocusAction::Select => {
(self.action)(captures);
state.0.is_focused = true;
context.request_view_rebuild();
EventResult::handled_focused(render_tree.content_shape())
}
}
}
_ => EventResult::Deferred,
}
}
}