use ferroui_controls::platform::{FeedbackAction, FeedbackType};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(isize)]
pub enum ImpactStyle {
Light = 0,
Medium = 1,
}
pub const DEFAULT_SOUND: u32 = 1104;
pub fn feedback_plan(feedback: FeedbackAction, type_: FeedbackType) -> (bool, Option<ImpactStyle>) {
let play_sound = matches!(type_, FeedbackType::Sound | FeedbackType::Auto);
let vibrate = matches!(type_, FeedbackType::Haptic | FeedbackType::Auto);
let sound = feedback == FeedbackAction::click() && play_sound;
let impact = if vibrate { feedback_to_impact_style(feedback) } else { None };
(sound, impact)
}
fn feedback_to_impact_style(feedback: FeedbackAction) -> Option<ImpactStyle> {
if feedback == FeedbackAction::click() {
Some(ImpactStyle::Light)
} else if feedback == FeedbackAction::hold() {
Some(ImpactStyle::Medium)
} else {
None
}
}
#[cfg(target_os = "ios")]
pub use uikit::IosPlatformFeedback;
#[cfg(target_os = "ios")]
mod uikit {
use super::{feedback_plan, ImpactStyle, DEFAULT_SOUND};
use crate::ferro_view::FerroView;
use ferroui_controls::platform::{FeedbackAction, FeedbackType, IPlatformFeedback};
use objc2::rc::Weak;
use objc2::{available, MainThreadMarker, MainThreadOnly};
use objc2_ui_kit::{UIImpactFeedbackGenerator, UIImpactFeedbackStyle};
const _: () = {
assert!(ImpactStyle::Light as isize == UIImpactFeedbackStyle::Light.0);
assert!(ImpactStyle::Medium as isize == UIImpactFeedbackStyle::Medium.0);
};
#[link(name = "AudioToolbox", kind = "framework")]
extern "C" {
fn AudioServicesPlaySystemSound(in_system_sound_id: u32);
}
pub struct IosPlatformFeedback {
view: Weak<FerroView>,
}
impl IosPlatformFeedback {
pub fn new(view: Weak<FerroView>) -> Self {
Self { view }
}
}
impl IPlatformFeedback for IosPlatformFeedback {
fn perform(&self, feedback: FeedbackAction, type_: FeedbackType) -> bool {
let mut performed_feedback = false;
let (play_sound, impact) = feedback_plan(feedback, type_);
if play_sound {
unsafe { AudioServicesPlaySystemSound(DEFAULT_SOUND) };
performed_feedback = true;
}
if let Some(impact) = impact {
let style = UIImpactFeedbackStyle(impact as isize);
let view = self.view.load();
let generator = match &view {
Some(view) if available!(ios = 17.5) => {
Some(UIImpactFeedbackGenerator::feedbackGeneratorWithStyle_forView(style, view))
}
#[allow(deprecated)]
Some(view) => Some(UIImpactFeedbackGenerator::initWithStyle(view.mtm().alloc(), style)),
#[allow(deprecated)]
None => MainThreadMarker::new()
.map(|mtm| UIImpactFeedbackGenerator::initWithStyle(mtm.alloc(), style)),
};
if let Some(generator) = generator {
generator.impactOccurred();
performed_feedback = true;
}
}
performed_feedback
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_click_sounds_and_taps_lightly() {
assert_eq!((true, Some(ImpactStyle::Light)), feedback_plan(FeedbackAction::click(), FeedbackType::Auto));
assert_eq!((true, None), feedback_plan(FeedbackAction::click(), FeedbackType::Sound));
assert_eq!((false, Some(ImpactStyle::Light)), feedback_plan(FeedbackAction::click(), FeedbackType::Haptic));
assert_eq!((false, None), feedback_plan(FeedbackAction::click(), FeedbackType::None));
}
#[test]
fn holding_taps_and_has_no_sound() {
assert_eq!((false, Some(ImpactStyle::Medium)), feedback_plan(FeedbackAction::hold(), FeedbackType::Auto));
assert_eq!((false, None), feedback_plan(FeedbackAction::hold(), FeedbackType::Sound));
assert_eq!((false, Some(ImpactStyle::Medium)), feedback_plan(FeedbackAction::hold(), FeedbackType::Haptic));
assert_eq!((false, None), feedback_plan(FeedbackAction::hold(), FeedbackType::None));
}
#[test]
fn the_sound_is_the_one_of_the_reference() {
assert_eq!(1104, DEFAULT_SOUND);
}
}