use super::accessibility::{AccessibilityProvider, create_provider};
use super::config::ClickHelperConfig;
use super::overlay::ClickHelperOverlay;
use crate::error::Result;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ClickHelperState {
Inactive,
Scanning,
WaitingForInput,
Clicking,
Exiting,
}
#[derive(Debug, Clone)]
pub enum ClickHelperAction {
Continue,
Click((f32, f32)),
NoMatch,
Cancelled,
Error(String),
}
pub struct ClickHelperMode {
state: ClickHelperState,
config: ClickHelperConfig,
overlay: Option<ClickHelperOverlay>,
accessibility: Box<dyn AccessibilityProvider>,
}
impl ClickHelperMode {
pub fn new() -> Self {
Self {
state: ClickHelperState::Inactive,
config: ClickHelperConfig::load().unwrap_or_default(),
overlay: None,
accessibility: create_provider(),
}
}
pub fn with_config(config: ClickHelperConfig) -> Self {
Self {
state: ClickHelperState::Inactive,
config,
overlay: None,
accessibility: create_provider(),
}
}
pub fn state(&self) -> ClickHelperState {
self.state
}
pub fn is_active(&self) -> bool {
self.state != ClickHelperState::Inactive
}
pub fn config(&self) -> &ClickHelperConfig {
&self.config
}
pub fn set_config(&mut self, config: ClickHelperConfig) {
self.config = config;
}
pub fn is_accessibility_trusted(&self) -> bool {
self.accessibility.is_trusted()
}
pub fn request_accessibility_permission(&self) {
self.accessibility.request_permission();
}
pub fn activate(&mut self) -> Result<()> {
if self.state != ClickHelperState::Inactive {
return Ok(());
}
if !self.accessibility.is_trusted() {
self.accessibility.request_permission();
}
log::info!("Activating Click Helper mode");
self.state = ClickHelperState::Scanning;
match self.accessibility.get_clickable_elements() {
Ok(elements) => {
if elements.is_empty() {
log::warn!("No clickable elements found");
self.state = ClickHelperState::Inactive;
return Ok(());
}
log::info!("Found {} clickable elements", elements.len());
self.overlay = Some(ClickHelperOverlay::new(elements, &self.config));
self.state = ClickHelperState::WaitingForInput;
Ok(())
}
Err(e) => {
log::error!("Error getting clickable elements: {}", e);
self.state = ClickHelperState::Inactive;
Err(e)
}
}
}
pub fn deactivate(&mut self) {
log::info!("Deactivating Click Helper mode");
self.overlay = None;
self.state = ClickHelperState::Inactive;
}
pub fn handle_key(&mut self, c: char) -> ClickHelperAction {
if self.state != ClickHelperState::WaitingForInput {
return ClickHelperAction::Continue;
}
let Some(ref mut overlay) = self.overlay else {
return ClickHelperAction::Continue;
};
overlay.add_input(c);
let match_count = overlay.matching_count();
match match_count {
0 => {
log::debug!("No match for input, resetting");
overlay.reset_input();
ClickHelperAction::NoMatch
}
1 => {
if let Some(element) = overlay.get_unique_match() {
log::info!("Match found, clicking at {:?}", element.position);
let pos = element.position;
self.deactivate();
ClickHelperAction::Click(pos)
} else {
ClickHelperAction::Continue
}
}
_ => {
if let Some(element) = overlay.get_exact_match() {
log::info!("Exact match found, clicking at {:?}", element.position);
let pos = element.position;
self.deactivate();
ClickHelperAction::Click(pos)
} else {
ClickHelperAction::Continue
}
}
}
}
pub fn handle_backspace(&mut self) -> ClickHelperAction {
if let Some(ref mut overlay) = self.overlay {
if overlay.input_buffer().is_empty() {
self.deactivate();
return ClickHelperAction::Cancelled;
}
overlay.backspace();
}
ClickHelperAction::Continue
}
pub fn handle_escape(&mut self) -> ClickHelperAction {
self.deactivate();
ClickHelperAction::Cancelled
}
pub fn render(&self, ctx: &egui::Context) {
if let Some(ref overlay) = self.overlay {
overlay.render(ctx);
}
}
#[cfg(test)]
pub fn overlay(&self) -> Option<&ClickHelperOverlay> {
self.overlay.as_ref()
}
}
impl Default for ClickHelperMode {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_initial_state() {
let mode = ClickHelperMode::new();
assert_eq!(mode.state(), ClickHelperState::Inactive);
assert!(!mode.is_active());
}
#[test]
fn test_deactivate() {
let mut mode = ClickHelperMode::new();
mode.deactivate();
assert_eq!(mode.state(), ClickHelperState::Inactive);
}
}