ferroui_controls/
text_box_history.rs1use super::TextBox;
5use crate::presenters::TextStyleOverrides;
6use ferroui_base::media::text_formatting::TextParagraphStyles;
7use std::any::Any;
8use std::fmt;
9use std::rc::Rc;
10
11#[derive(Clone)]
14pub(crate) struct UndoRedoState {
15 pub(super) text: Option<String>,
16 caret_position: i32,
17 styles: Option<TextStyleOverrides>,
18 paragraph_styles: Option<TextParagraphStyles>,
19 application_state: Option<Rc<dyn Any>>,
20}
21
22impl UndoRedoState {
23 pub(super) fn new(text: Option<String>, caret_position: i32) -> Self {
24 Self {
25 text,
26 caret_position,
27 styles: None,
28 paragraph_styles: None,
29 application_state: None,
30 }
31 }
32}
33
34impl fmt::Debug for UndoRedoState {
35 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
36 formatter
37 .debug_struct("UndoRedoState")
38 .field("text", &self.text)
39 .field("caret_position", &self.caret_position)
40 .field(
41 "style_count",
42 &self.styles.as_ref().map_or(0, |styles| styles.len()),
43 )
44 .field(
45 "paragraph_count",
46 &self
47 .paragraph_styles
48 .as_ref()
49 .map_or(0, |styles| styles.len()),
50 )
51 .field("has_application_state", &self.application_state.is_some())
52 .finish()
53 }
54}
55
56impl PartialEq for UndoRedoState {
57 fn eq(&self, other: &Self) -> bool {
58 self.text == other.text
59 }
60}
61
62impl TextBox {
63 pub fn text_undo_state(&self) -> Option<Rc<dyn Any>> {
71 self.text_undo_state.borrow().clone()
72 }
73
74 pub fn set_text_undo_state(&self, value: Option<Rc<dyn Any>>) {
83 let previous = self.text_undo_state.replace(value);
85 drop(previous);
86 if !self.is_undo_enabled() {
87 return;
88 }
89 if let Some(last) = self.undo_redo_helper.try_get_last_state() {
93 let current = self.capture_text_history_state();
94 if last.text == current.text {
95 self.undo_redo_helper.update_last_state_with(current);
96 }
97 }
98 }
99
100 pub fn is_restoring_text_history(&self) -> bool {
105 self.is_undoing_redoing.get()
106 }
107
108 pub(super) fn capture_text_history_state(&self) -> UndoRedoState {
109 UndoRedoState {
110 text: self.text(),
111 caret_position: self.caret_index(),
112 styles: self.text_style_overrides(),
113 paragraph_styles: self.text_paragraph_styles(),
114 application_state: self.text_undo_state(),
115 }
116 }
117
118 pub(super) fn restore_text_history_state(&self, state: UndoRedoState) {
119 let generation = self.text_change_generation.get();
120 let _change = self.im_client.begin_change();
121
122 let previous = self.text_undo_state.replace(state.application_state);
125 drop(previous);
126 if self.text_change_generation.get() != generation {
127 return;
128 }
129 self.set_current_value(Self::text_style_overrides_property(), state.styles);
130 if self.text_change_generation.get() != generation {
131 return;
132 }
133 self.set_current_value(
134 Self::text_paragraph_styles_property(),
135 state.paragraph_styles,
136 );
137 if self.text_change_generation.get() != generation {
138 return;
139 }
140
141 self.set_current_value(Self::text_property(), state.text.clone());
146 if self.text_change_generation.get().wrapping_sub(generation) > 1
147 || self.text() != state.text
148 {
149 return;
150 }
151 let generation = self.text_change_generation.get();
152 self.set_current_value(Self::caret_index_property(), state.caret_position);
153 if self.text_change_generation.get() != generation {
154 return;
155 }
156 let caret = self.caret_index();
157 self.set_current_value(Self::selection_start_property(), caret);
158 if self.text_change_generation.get() != generation {
159 return;
160 }
161 self.set_current_value(Self::selection_end_property(), caret);
162 }
163}
164
165#[cfg(test)]
166#[path = "text_box_history_tests.rs"]
167mod tests;