bevy_ui_builders/text_input/native_input/
components.rs

1//! Component definitions for native text input
2
3use bevy::prelude::*;
4use std::collections::VecDeque;
5use super::helpers::{char_to_byte_index};
6use super::types::{CursorStyle, OperationType, TabBehavior};
7
8/// The main text input component - this is all you need to spawn
9#[derive(Component, Default)]
10pub struct NativeTextInput;
11
12/// The internal text buffer and cursor management
13#[derive(Component, Default)]
14pub struct TextBuffer {
15    /// The actual text content
16    pub content: String,
17    /// Current cursor position (in chars, not bytes)
18    pub cursor_pos: usize,
19    /// Whether the input is currently focused
20    pub is_focused: bool,
21}
22
23/// Text selection state
24#[derive(Component, Default)]
25pub struct SelectionState {
26    /// Selection anchor (where selection started)
27    pub anchor: Option<usize>,
28    /// Current selection end (moves with cursor)
29    pub cursor: usize,
30}
31
32impl SelectionState {
33    /// Get the normalized selection range (start, end)
34    pub fn range(&self) -> Option<(usize, usize)> {
35        self.anchor.map(|anchor| {
36            if anchor < self.cursor {
37                (anchor, self.cursor)
38            } else {
39                (self.cursor, anchor)
40            }
41        })
42    }
43
44    /// Check if there's an active selection
45    pub fn has_selection(&self) -> bool {
46        self.anchor.is_some() && self.anchor != Some(self.cursor)
47    }
48
49    /// Clear the selection
50    pub fn clear(&mut self) {
51        self.anchor = None;
52    }
53
54    /// Start a new selection
55    pub fn start_selection(&mut self, pos: usize) {
56        self.anchor = Some(pos);
57        self.cursor = pos;
58    }
59
60    /// Update selection end
61    pub fn update_selection(&mut self, pos: usize) {
62        if self.anchor.is_none() {
63            self.anchor = Some(pos);
64        }
65        self.cursor = pos;
66    }
67
68    /// Get selected text from buffer
69    pub fn get_selected_text<'a>(&self, content: &'a str) -> Option<&'a str> {
70        self.range().map(|(start, end)| {
71            let start_byte = char_to_byte_index(content, start);
72            let end_byte = char_to_byte_index(content, end);
73            &content[start_byte..end_byte]
74        })
75    }
76}
77
78/// Visual settings for the input
79#[derive(Component)]
80pub struct TextInputVisual {
81    /// Font settings
82    pub font: TextFont,
83    /// Text color
84    pub text_color: Color,
85    /// Selection color
86    pub selection_color: Color,
87    /// Cursor color
88    pub cursor_color: Color,
89    /// Placeholder text
90    pub placeholder: String,
91    /// Placeholder color
92    pub placeholder_color: Color,
93    /// Whether to mask input (for passwords)
94    pub mask_char: Option<char>,
95}
96
97impl Default for TextInputVisual {
98    fn default() -> Self {
99        Self {
100            font: TextFont::default(),
101            text_color: Color::WHITE,
102            selection_color: Color::srgba(0.3, 0.5, 0.8, 0.3),
103            cursor_color: Color::WHITE,
104            placeholder: String::new(),
105            placeholder_color: Color::srgba(0.5, 0.5, 0.5, 0.5),
106            mask_char: None,
107        }
108    }
109}
110
111/// Cursor visual state
112#[derive(Component)]
113pub struct CursorVisual {
114    /// Timer for blinking animation
115    pub blink_timer: Timer,
116    /// Whether cursor is currently visible
117    pub visible: bool,
118    /// Cursor style
119    pub style: CursorStyle,
120    /// Entity of the visual cursor (if spawned)
121    pub cursor_entity: Option<Entity>,
122    /// Entities of selection overlays (for rendering selection highlights)
123    pub selection_entities: Vec<Entity>,
124}
125
126impl Default for CursorVisual {
127    fn default() -> Self {
128        Self {
129            blink_timer: Timer::from_seconds(0.5, TimerMode::Repeating),
130            visible: true,
131            style: CursorStyle::Line,
132            cursor_entity: None,
133            selection_entities: Vec::new(),
134        }
135    }
136}
137
138/// Scroll state for overflow handling
139#[derive(Component, Default)]
140pub struct ScrollViewport {
141    /// Horizontal scroll offset
142    pub offset_x: f32,
143    /// Vertical scroll offset (for multiline)
144    pub offset_y: f32,
145}
146
147/// Undo/redo history
148#[derive(Component)]
149pub struct UndoHistory {
150    /// Stack of undo operations
151    pub undo_stack: VecDeque<EditOperation>,
152    /// Stack of redo operations
153    pub redo_stack: VecDeque<EditOperation>,
154    /// Maximum history size
155    pub max_size: usize,
156}
157
158impl Default for UndoHistory {
159    fn default() -> Self {
160        Self {
161            undo_stack: VecDeque::new(),
162            redo_stack: VecDeque::new(),
163            max_size: 100,
164        }
165    }
166}
167
168/// Represents a single edit operation
169#[derive(Clone, Debug)]
170pub struct EditOperation {
171    /// The operation type
172    pub op_type: OperationType,
173    /// Cursor position before the operation
174    pub cursor_before: usize,
175    /// Cursor position after the operation
176    pub cursor_after: usize,
177}
178
179/// Inner text node marker
180#[derive(Component)]
181pub struct TextInputInner;
182
183/// Marker component for the selection overlay
184#[derive(Component)]
185pub struct TextInputSelection {
186    /// The input entity this selection belongs to
187    pub input_entity: Entity,
188}
189
190/// Text input settings/configuration
191#[derive(Component)]
192pub struct TextInputSettings {
193    /// Whether the input is multiline
194    pub multiline: bool,
195    /// Maximum length in characters
196    pub max_length: Option<usize>,
197    /// Whether to retain text on submit
198    pub retain_on_submit: bool,
199    /// Whether the input is read-only
200    pub read_only: bool,
201    /// Tab behavior
202    pub tab_behavior: TabBehavior,
203}
204
205impl Default for TextInputSettings {
206    fn default() -> Self {
207        Self {
208            multiline: false,
209            max_length: None,
210            retain_on_submit: false,
211            read_only: false,
212            tab_behavior: TabBehavior::NextField,
213        }
214    }
215}