use azul_core::{
dom::DomNodeId,
events::{
EventData, EventProvider, EventSource as CoreEventSource, EventType, SyntheticEvent,
TextInputEventData,
},
task::Instant,
};
use azul_css::corety::AzString;
#[derive(Debug, Clone)]
#[repr(C)]
pub struct PendingTextEdit {
pub node: DomNodeId,
pub inserted_text: AzString,
pub old_text: AzString,
}
impl PendingTextEdit {
#[must_use] pub fn resulting_text(&self) -> AzString {
let mut result = self.old_text.as_str().to_string();
result.push_str(self.inserted_text.as_str());
result.into()
}
}
#[allow(variant_size_differences)] #[derive(Debug, Clone)]
#[repr(C, u8)]
pub enum OptionPendingTextEdit {
None,
Some(PendingTextEdit),
}
impl OptionPendingTextEdit {
#[must_use] pub fn into_option(self) -> Option<PendingTextEdit> {
match self {
Self::None => None,
Self::Some(t) => Some(t),
}
}
}
impl From<Option<PendingTextEdit>> for OptionPendingTextEdit {
fn from(o: Option<PendingTextEdit>) -> Self {
o.map_or_else(|| Self::None, Self::Some)
}
}
impl<'a> From<Option<&'a PendingTextEdit>> for OptionPendingTextEdit {
fn from(o: Option<&'a PendingTextEdit>) -> Self {
o.map_or_else(|| Self::None, |v| Self::Some(v.clone()))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TextInputSource {
Keyboard,
Ime,
Accessibility,
Programmatic,
}
#[derive(Debug)]
pub struct TextInputManager {
pub pending_changeset: Option<PendingTextEdit>,
pub input_source: Option<TextInputSource>,
}
impl TextInputManager {
#[must_use] pub const fn new() -> Self {
Self {
pending_changeset: None,
input_source: None,
}
}
pub fn record_input(
&mut self,
node: DomNodeId,
inserted_text: String,
old_text: String,
source: TextInputSource,
) -> DomNodeId {
self.pending_changeset = Some(PendingTextEdit {
node,
inserted_text: inserted_text.into(),
old_text: old_text.into(),
});
self.input_source = Some(source);
node
}
#[must_use] pub const fn get_pending_changeset(&self) -> Option<&PendingTextEdit> {
self.pending_changeset.as_ref()
}
pub fn clear_changeset(&mut self) {
self.pending_changeset = None;
self.input_source = None;
}
}
impl Default for TextInputManager {
fn default() -> Self {
Self::new()
}
}
impl EventProvider for TextInputManager {
#[allow(clippy::match_same_arms)] fn get_pending_events(&self, timestamp: Instant) -> Vec<SyntheticEvent> {
let mut events = Vec::new();
if let Some(changeset) = &self.pending_changeset {
let event_source = match self.input_source {
Some(TextInputSource::Keyboard | TextInputSource::Ime) => {
CoreEventSource::User
}
Some(TextInputSource::Accessibility) => CoreEventSource::User,
Some(TextInputSource::Programmatic) => CoreEventSource::Programmatic,
None => CoreEventSource::User,
};
events.push(SyntheticEvent::new(
EventType::Input,
event_source,
changeset.node,
timestamp,
EventData::TextInput(TextInputEventData {
inserted_text: changeset.inserted_text.as_str().to_string(),
old_text: changeset.old_text.as_str().to_string(),
}),
));
}
events
}
}
impl crate::managers::NodeIdRemap for TextInputManager {
fn remap_node_ids(&mut self, dom: azul_core::dom::DomId, map: &crate::managers::NodeIdMap) {
let Some(ref mut pending) = self.pending_changeset else {
return;
};
match map.resolve_dom_node_id(dom, pending.node) {
Some(new_id) => pending.node = new_id,
None => {
self.pending_changeset = None;
self.input_source = None;
}
}
}
}