use crate::utils::UpdatableSignal;
use cotis::cotis_app::CotisApp;
use cotis::layout::LayoutManagerCompatible;
use cotis::renders::RenderCompatibleWith;
use cotis::utils::ElementId;
use cotis_utils::element_state::ElementBoundingBox;
use cotis_utils::interactivity::mouse::{MouseButton, MouseProvider};
#[allow(clippy::too_many_arguments)]
pub fn cursor_in_rect_with_margins(
cursor_x: f32,
cursor_y: f32,
x: f32,
y: f32,
width: f32,
height: f32,
margin_left: f32,
margin_top: f32,
margin_right: f32,
margin_bottom: f32,
) -> bool {
let min_x = x - margin_left;
let max_x = x + width + margin_right;
let min_y = y - margin_top;
let max_y = y + height + margin_bottom;
cursor_x >= min_x && cursor_x < max_x && cursor_y >= min_y && cursor_y < max_y
}
pub trait ElementCursorState {
fn is_hovered_with_margin(&self, id: ElementId, margin: f32) -> bool {
self.is_hovered_with_margins(id, margin, margin, margin, margin)
}
fn is_hovered_with_axis_margin(&self, id: ElementId, horizontal: f32, vertical: f32) -> bool {
self.is_hovered_with_margins(id, horizontal, vertical, horizontal, vertical)
}
fn is_hovered_with_margins(
&self,
id: ElementId,
margin_left: f32,
margin_top: f32,
margin_right: f32,
margin_bottom: f32,
) -> bool;
fn is_hovered(&self, id: ElementId) -> bool {
self.is_hovered_with_margins(id, 0.0, 0.0, 0.0, 0.0)
}
}
pub trait FullElementCursorState: ElementCursorState {
fn mouse_button_down(&self, mouse_button: MouseButton) -> bool;
}
impl<
Renderer: RenderCompatibleWith<Manager> + MouseProvider,
Manager: LayoutManagerCompatible<Renderer> + ElementBoundingBox,
Pipe,
> ElementCursorState for CotisApp<Renderer, Manager, Pipe>
{
fn is_hovered_with_margins(
&self,
id: ElementId,
margin_left: f32,
margin_top: f32,
margin_right: f32,
margin_bottom: f32,
) -> bool {
let cursor_pos = self.render_borrow().get_mouse_position();
let bound_box = self.layout_manager_borrow().bounding_box(id);
if let Some(bound_box) = bound_box {
cursor_in_rect_with_margins(
cursor_pos.x,
cursor_pos.y,
bound_box.x,
bound_box.y,
bound_box.width,
bound_box.height,
margin_left,
margin_top,
margin_right,
margin_bottom,
)
} else {
false
}
}
}
impl<
Renderer: RenderCompatibleWith<Manager> + MouseProvider,
Manager: LayoutManagerCompatible<Renderer> + ElementBoundingBox,
Pipe,
> FullElementCursorState for CotisApp<Renderer, Manager, Pipe>
{
fn mouse_button_down(&self, mouse_button: MouseButton) -> bool {
MouseProvider::mouse_button_down(self.render_borrow(), mouse_button)
}
}
pub enum MouseClickingStrategy {
Pressed,
NotPressed,
FallingEdge,
RisingEdge,
DoubleEdge {
rising_edge: bool,
},
RisingHold {
rising_edge: bool,
time: f32,
goal: f32,
},
Hold {
time: f32,
goal: f32,
},
}
pub struct MouseButtonManager {
strategy: MouseClickingStrategy,
signal: UpdatableSignal,
cooldown: f32,
}
impl MouseButtonManager {
pub fn new(strategy: MouseClickingStrategy, cooldown: f32) -> MouseButtonManager {
Self {
strategy,
signal: UpdatableSignal::new(false),
cooldown,
}
}
pub fn update(&mut self, button_pressed: bool, delta_time: f32, hovered: bool) {
self.cooldown -= delta_time;
let gated_press = button_pressed && self.cooldown <= 0.0;
self.signal.update(gated_press);
if button_pressed {
match &mut self.strategy {
MouseClickingStrategy::Hold { time, .. } => {
if !hovered {
*time = 0.0;
} else {
*time += delta_time
}
}
MouseClickingStrategy::RisingHold {
rising_edge, time, ..
} => {
*rising_edge = self.cooldown <= 0.0
&& hovered
&& (*rising_edge || self.signal.rising_edge())
&& (button_pressed || self.signal.falling_edge());
if *rising_edge {
*time += delta_time;
} else {
*time -= delta_time;
}
}
MouseClickingStrategy::DoubleEdge { rising_edge } => {
*rising_edge = self.cooldown <= 0.0
&& hovered
&& (*rising_edge || self.signal.rising_edge())
&& (button_pressed || self.signal.falling_edge());
}
_ => {}
}
} else {
match &mut self.strategy {
MouseClickingStrategy::Hold { time, .. }
| MouseClickingStrategy::RisingHold { time, .. } => {
*time = 0.0;
}
_ => {}
}
}
}
pub fn reset_cooldown(&mut self) {
self.cooldown = 0.0;
}
pub fn change_cooldown(&mut self, cooldown: f32) {
self.cooldown = cooldown;
}
pub fn is_clicked(&self) -> bool {
if self.cooldown > 0.0 {
return false;
}
match &self.strategy {
MouseClickingStrategy::Pressed => self.signal.is_on(),
MouseClickingStrategy::NotPressed => !self.signal.is_on(),
MouseClickingStrategy::FallingEdge => self.signal.falling_edge(),
MouseClickingStrategy::RisingEdge => self.signal.rising_edge(),
MouseClickingStrategy::DoubleEdge { rising_edge } => {
self.signal.falling_edge() && *rising_edge
}
MouseClickingStrategy::RisingHold { time, goal, .. } => time >= goal,
MouseClickingStrategy::Hold { time, goal } => time >= goal,
}
}
}
pub struct MouseButtonElementManager {
key: MouseButton,
manager: MouseButtonManager,
element: ElementId,
}
impl MouseButtonElementManager {
pub fn new(
element_id: ElementId,
key: MouseButton,
strategy: MouseClickingStrategy,
cooldown: f32,
) -> MouseButtonElementManager {
Self {
key,
element: element_id,
manager: MouseButtonManager::new(strategy, cooldown),
}
}
pub fn update(&mut self, element_state_provider: &dyn FullElementCursorState, delta_time: f32) {
self.manager.update(
FullElementCursorState::mouse_button_down(element_state_provider, self.key),
delta_time,
element_state_provider.is_hovered(self.element),
);
}
pub fn reset_cooldown(&mut self) {
self.manager.reset_cooldown()
}
pub fn change_cooldown(&mut self, cooldown: f32) {
self.manager.change_cooldown(cooldown)
}
pub fn is_clicked(&self) -> bool {
self.manager.is_clicked()
}
}