Skip to main content

wxdragon/widgets/
textctrl.rs

1//!
2//! Safe wrapper for wxTextCtrl.
3
4use crate::event::TextEvents;
5use crate::event::{Event, EventType, WxEvtHandler};
6use crate::geometry::{Point, Size};
7use crate::id::Id;
8use crate::window::{WindowHandle, WxWidget};
9// Window is used by new_from_composition for backwards compatibility
10#[allow(unused_imports)]
11use crate::window::Window;
12use std::ffi::CString;
13use std::os::raw::c_char;
14use std::ptr::null_mut;
15use wxdragon_sys as ffi;
16
17// --- Text Control Styles ---
18widget_style_enum!(
19    name: TextCtrlStyle,
20    doc: "Style flags for TextCtrl widget.",
21    variants: {
22        Default: 0, "Default style (single line, editable, left-aligned).",
23        MultiLine: ffi::WXD_TE_MULTILINE, "Multi-line text control.",
24        Password: ffi::WXD_TE_PASSWORD, "Password entry control (displays characters as asterisks).",
25        ReadOnly: ffi::WXD_TE_READONLY, "Read-only text control.",
26        Rich: ffi::WXD_TE_RICH, "For rich text content (implies multiline). Use with care, may require specific handling.",
27        Rich2: ffi::WXD_TE_RICH2, "For more advanced rich text content (implies multiline). Use with care.",
28        AutoUrl: ffi::WXD_TE_AUTO_URL, "Automatically detect and make URLs clickable.",
29        ProcessEnter: ffi::WXD_TE_PROCESS_ENTER, "Generate an event when Enter key is pressed.",
30        ProcessTab: ffi::WXD_TE_PROCESS_TAB, "Process TAB key in the control instead of using it for navigation.",
31        NoHideSel: ffi::WXD_TE_NOHIDESEL, "Always show selection, even when control doesn't have focus (Windows only).",
32        Centre: ffi::WXD_TE_CENTRE, "Center-align text.",
33        Right: ffi::WXD_TE_RIGHT, "Right-align text.",
34        CharWrap: ffi::WXD_TE_CHARWRAP, "Wrap at any position, splitting words if necessary.",
35        WordWrap: ffi::WXD_TE_WORDWRAP, "Wrap at word boundaries.",
36        NoVScroll: ffi::WXD_TE_NO_VSCROLL, "No vertical scrollbar (multiline only).",
37        DontWrap: ffi::WXD_TE_DONTWRAP, "Don't wrap at all, show horizontal scrollbar instead."
38    },
39    default_variant: Default
40);
41
42/// Events emitted by TextCtrl
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum TextCtrlEvent {
45    /// Emitted when the text in the control changes
46    TextChanged,
47    /// Emitted when the user presses Enter in the control
48    TextEnter,
49}
50
51/// Event data for a TextCtrl event
52#[derive(Debug)]
53pub struct TextCtrlEventData {
54    event: Event,
55}
56
57impl TextCtrlEventData {
58    /// Create a new TextCtrlEventData from a generic Event
59    pub fn new(event: Event) -> Self {
60        Self { event }
61    }
62
63    /// Get the ID of the control that generated the event
64    pub fn get_id(&self) -> i32 {
65        self.event.get_id()
66    }
67
68    /// Skip this event (allow it to be processed by the parent window)
69    pub fn skip(&self, skip: bool) {
70        self.event.skip(skip);
71    }
72
73    /// Get the current text in the control
74    pub fn get_string(&self) -> Option<String> {
75        self.event.get_string()
76    }
77}
78
79/// Represents a wxTextCtrl widget.
80///
81/// TextCtrl uses `WindowHandle` internally for safe memory management.
82/// When the underlying window is destroyed (by calling `destroy()` or when
83/// its parent is destroyed), the handle becomes invalid and all operations
84/// become safe no-ops.
85///
86/// # Example
87/// ```ignore
88/// let textctrl = TextCtrl::builder(&frame).value("Enter text here").build();
89///
90/// // TextCtrl is Copy - no clone needed for closures!
91/// textctrl.bind_text_changed(move |_| {
92///     // Safe: if textctrl was destroyed, this is a no-op
93///     let text = textctrl.get_value();
94///     println!("Text changed: {}", text);
95/// });
96///
97/// // After parent destruction, textctrl operations are safe no-ops
98/// frame.destroy();
99/// assert!(!textctrl.is_valid());
100/// ```
101#[derive(Clone, Copy)]
102pub struct TextCtrl {
103    /// Safe handle to the underlying wxTextCtrl - automatically invalidated on destroy
104    handle: WindowHandle,
105}
106
107impl TextCtrl {
108    /// Creates a new TextCtrl builder.
109    pub fn builder(parent: &dyn WxWidget) -> TextCtrlBuilder<'_> {
110        TextCtrlBuilder::new(parent)
111    }
112
113    /// Creates a new TextCtrl wrapper from a raw pointer.
114    /// # Safety
115    /// The pointer must be a valid `wxd_TextCtrl_t` pointer.
116    pub(crate) unsafe fn from_ptr(ptr: *mut ffi::wxd_TextCtrl_t) -> Self {
117        TextCtrl {
118            handle: WindowHandle::new(ptr as *mut ffi::wxd_Window_t),
119        }
120    }
121
122    /// Creates a new TextCtrl from a raw window pointer.
123    /// This is for backwards compatibility with widgets that compose TextCtrl.
124    /// The parent_ptr parameter is ignored (kept for API compatibility).
125    #[allow(dead_code)]
126    pub(crate) fn new_from_composition(_window: Window, _parent_ptr: *mut ffi::wxd_Window_t) -> Self {
127        // Use the window's pointer to create a new WindowHandle
128        Self {
129            handle: WindowHandle::new(_window.as_ptr()),
130        }
131    }
132
133    /// Internal implementation used by the builder.
134    fn new_impl(parent_ptr: *mut ffi::wxd_Window_t, id: Id, value: &str, pos: Point, size: Size, style: i64) -> Self {
135        let c_value = CString::new(value).unwrap_or_default();
136
137        let ptr = unsafe {
138            ffi::wxd_TextCtrl_Create(
139                parent_ptr,
140                id,
141                c_value.as_ptr(),
142                pos.into(),
143                size.into(),
144                style as ffi::wxd_Style_t,
145            )
146        };
147
148        if ptr.is_null() {
149            panic!("Failed to create TextCtrl widget");
150        }
151
152        unsafe { TextCtrl::from_ptr(ptr) }
153    }
154
155    /// Helper to get raw textctrl pointer, returns null if widget has been destroyed
156    #[inline]
157    fn textctrl_ptr(&self) -> *mut ffi::wxd_TextCtrl_t {
158        self.handle
159            .get_ptr()
160            .map(|p| p as *mut ffi::wxd_TextCtrl_t)
161            .unwrap_or(null_mut())
162    }
163
164    fn read_string_with_retry(mut getter: impl FnMut(*mut c_char, i32) -> i32) -> String {
165        let mut buffer: Vec<c_char> = vec![0; 1024];
166        let mut len = getter(buffer.as_mut_ptr(), buffer.len() as i32);
167        if len < 0 {
168            return String::new();
169        }
170        if len as usize >= buffer.len() {
171            buffer = vec![0; len as usize + 1];
172            len = getter(buffer.as_mut_ptr(), buffer.len() as i32);
173            if len < 0 {
174                return String::new();
175            }
176        }
177        let byte_slice = unsafe { std::slice::from_raw_parts(buffer.as_ptr() as *const u8, len as usize) };
178        String::from_utf8_lossy(byte_slice).to_string()
179    }
180
181    /// Sets the text value of the control.
182    /// No-op if the control has been destroyed.
183    pub fn set_value(&self, value: &str) {
184        let ptr = self.textctrl_ptr();
185        if ptr.is_null() {
186            return;
187        }
188        let c_value = CString::new(value).unwrap_or_default();
189        unsafe { ffi::wxd_TextCtrl_SetValue(ptr, c_value.as_ptr()) };
190    }
191
192    /// Gets the current text value of the control.
193    /// Returns empty string if the control has been destroyed.
194    pub fn get_value(&self) -> String {
195        let ptr = self.textctrl_ptr();
196        if ptr.is_null() {
197            return String::new();
198        }
199        unsafe { Self::read_string_with_retry(|buf, len| ffi::wxd_TextCtrl_GetValue(ptr, buf, len)) }
200    }
201
202    /// Appends text to the end of the control.
203    /// No-op if the control has been destroyed.
204    pub fn append_text(&self, text: &str) {
205        let ptr = self.textctrl_ptr();
206        if ptr.is_null() {
207            return;
208        }
209        let c_text = CString::new(text).unwrap_or_default();
210        unsafe { ffi::wxd_TextCtrl_AppendText(ptr, c_text.as_ptr()) };
211    }
212
213    /// Clears the text in the control.
214    /// No-op if the control has been destroyed.
215    pub fn clear(&self) {
216        let ptr = self.textctrl_ptr();
217        if ptr.is_null() {
218            return;
219        }
220        unsafe {
221            ffi::wxd_TextCtrl_Clear(ptr);
222        }
223    }
224
225    /// Writes text into the control at the current insertion point.
226    /// No-op if the control has been destroyed.
227    pub fn write_text(&self, text: &str) {
228        let ptr = self.textctrl_ptr();
229        if ptr.is_null() {
230            return;
231        }
232        let c_text = CString::new(text).unwrap_or_default();
233        unsafe { ffi::wxd_TextCtrl_WriteText(ptr, c_text.as_ptr()) };
234    }
235
236    /// Sets the value of the control without generating a TextChanged event.
237    /// No-op if the control has been destroyed.
238    pub fn change_value(&self, value: &str) {
239        let ptr = self.textctrl_ptr();
240        if ptr.is_null() {
241            return;
242        }
243        let c_value = CString::new(value).unwrap_or_default();
244        unsafe { ffi::wxd_TextCtrl_ChangeValue(ptr, c_value.as_ptr()) };
245    }
246
247    /// Removes the text in the given range.
248    /// No-op if the control has been destroyed.
249    pub fn remove(&self, from: i64, to: i64) {
250        let ptr = self.textctrl_ptr();
251        if ptr.is_null() {
252            return;
253        }
254        unsafe { ffi::wxd_TextCtrl_Remove(ptr, from, to) };
255    }
256
257    /// Replaces the text in the given range with the new value.
258    /// No-op if the control has been destroyed.
259    pub fn replace(&self, from: i64, to: i64, value: &str) {
260        let ptr = self.textctrl_ptr();
261        if ptr.is_null() {
262            return;
263        }
264        let c_value = CString::new(value).unwrap_or_default();
265        unsafe { ffi::wxd_TextCtrl_Replace(ptr, from, to, c_value.as_ptr()) };
266    }
267
268    /// Converts given text position to client coordinates in pixels.
269    /// Returns (x, y) if successful, or None if the position is invalid or the control is destroyed.
270    pub fn position_to_xy(&self, pos: i64) -> Option<(i64, i64)> {
271        let ptr = self.textctrl_ptr();
272        if ptr.is_null() {
273            return None;
274        }
275        let mut x: i64 = 0;
276        let mut y: i64 = 0;
277        let result = unsafe { ffi::wxd_TextCtrl_PositionToXY(ptr, pos, &mut x, &mut y) };
278        if result { Some((x, y)) } else { None }
279    }
280
281    /// Converts given client coordinates in pixels to text position.
282    /// Returns 0 if the coordinates are invalid or the control is destroyed.
283    pub fn xy_to_position(&self, x: i64, y: i64) -> i64 {
284        let ptr = self.textctrl_ptr();
285        if ptr.is_null() {
286            return 0;
287        }
288        unsafe { ffi::wxd_TextCtrl_XYToPosition(ptr, x, y) }
289    }
290
291    /// Returns the number of lines in the text control.
292    /// Returns 0 if the control has been destroyed.
293    pub fn get_number_of_lines(&self) -> i32 {
294        let ptr = self.textctrl_ptr();
295        if ptr.is_null() {
296            return 0;
297        }
298        unsafe { ffi::wxd_TextCtrl_GetNumberOfLines(ptr) }
299    }
300
301    /// Returns the length of the specified line (not including the trailing newline character).
302    /// Returns 0 if the line number is invalid or the control is destroyed.
303    pub fn get_line_length(&self, line_no: i64) -> i32 {
304        let ptr = self.textctrl_ptr();
305        if ptr.is_null() {
306            return 0;
307        }
308        unsafe { ffi::wxd_TextCtrl_GetLineLength(ptr, line_no) }
309    }
310
311    /// Returns the contents of the given line.
312    /// Returns an empty string if the control has been destroyed.
313    pub fn get_line_text(&self, line_no: i64) -> String {
314        let ptr = self.textctrl_ptr();
315        if ptr.is_null() {
316            return String::new();
317        }
318        unsafe { Self::read_string_with_retry(|buf, len| ffi::wxd_TextCtrl_GetLineText(ptr, line_no, buf, len)) }
319    }
320
321    /// Returns whether the text control has been modified by the user since the last
322    /// time MarkDirty() or DiscardEdits() was called.
323    /// Returns false if the control has been destroyed.
324    pub fn is_modified(&self) -> bool {
325        let ptr = self.textctrl_ptr();
326        if ptr.is_null() {
327            return false;
328        }
329        unsafe { ffi::wxd_TextCtrl_IsModified(ptr) }
330    }
331
332    /// Marks the control as modified or unmodified.
333    /// No-op if the control has been destroyed.
334    pub fn set_modified(&self, modified: bool) {
335        let ptr = self.textctrl_ptr();
336        if ptr.is_null() {
337            return;
338        }
339        unsafe { ffi::wxd_TextCtrl_SetModified(ptr, modified) };
340    }
341
342    /// Makes the text control editable or read-only, overriding the style setting.
343    /// No-op if the control has been destroyed.
344    pub fn set_editable(&self, editable: bool) {
345        let ptr = self.textctrl_ptr();
346        if ptr.is_null() {
347            return;
348        }
349        unsafe { ffi::wxd_TextCtrl_SetEditable(ptr, editable) };
350    }
351
352    /// Returns true if the control is editable.
353    /// Returns false if the control has been destroyed.
354    pub fn is_editable(&self) -> bool {
355        let ptr = self.textctrl_ptr();
356        if ptr.is_null() {
357            return false;
358        }
359        unsafe { ffi::wxd_TextCtrl_IsEditable(ptr) }
360    }
361
362    /// Gets the insertion point of the control.
363    /// The insertion point is the position at which the caret is currently positioned.
364    /// Returns 0 if the control has been destroyed.
365    pub fn get_insertion_point(&self) -> i64 {
366        let ptr = self.textctrl_ptr();
367        if ptr.is_null() {
368            return 0;
369        }
370        unsafe { ffi::wxd_TextCtrl_GetInsertionPoint(ptr) }
371    }
372
373    /// Sets the insertion point of the control.
374    /// No-op if the control has been destroyed.
375    pub fn set_insertion_point(&self, pos: i64) {
376        let ptr = self.textctrl_ptr();
377        if ptr.is_null() {
378            return;
379        }
380        unsafe { ffi::wxd_TextCtrl_SetInsertionPoint(ptr, pos) };
381    }
382
383    /// Sets the maximum number of characters that may be entered in the control.
384    ///
385    /// If `len` is 0, the maximum length limit is removed.
386    /// No-op if the control has been destroyed.
387    pub fn set_max_length(&self, len: usize) {
388        let ptr = self.textctrl_ptr();
389        if ptr.is_null() {
390            return;
391        }
392        unsafe { ffi::wxd_TextCtrl_SetMaxLength(ptr, len as i64) };
393    }
394
395    /// Returns the last position in the control.
396    /// Returns 0 if the control has been destroyed.
397    pub fn get_last_position(&self) -> i64 {
398        let ptr = self.textctrl_ptr();
399        if ptr.is_null() {
400            return 0;
401        }
402        unsafe { ffi::wxd_TextCtrl_GetLastPosition(ptr) }
403    }
404
405    /// Returns true if this is a multi-line text control.
406    /// Returns false if the control has been destroyed.
407    pub fn is_multiline(&self) -> bool {
408        let ptr = self.textctrl_ptr();
409        if ptr.is_null() {
410            return false;
411        }
412        unsafe { ffi::wxd_TextCtrl_IsMultiLine(ptr) }
413    }
414
415    /// Returns true if this is a single-line text control.
416    /// Returns false if the control has been destroyed.
417    pub fn is_single_line(&self) -> bool {
418        let ptr = self.textctrl_ptr();
419        if ptr.is_null() {
420            return false;
421        }
422        unsafe { ffi::wxd_TextCtrl_IsSingleLine(ptr) }
423    }
424
425    // --- Selection Operations ---
426
427    /// Sets the selection in the text control.
428    ///
429    /// # Arguments
430    /// * `from` - The start position of the selection
431    /// * `to` - The end position of the selection
432    ///
433    /// No-op if the control has been destroyed.
434    pub fn set_selection(&self, from: i64, to: i64) {
435        let ptr = self.textctrl_ptr();
436        if ptr.is_null() {
437            return;
438        }
439        unsafe { ffi::wxd_TextCtrl_SetSelection(ptr, from, to) };
440    }
441
442    /// Gets the current selection range.
443    ///
444    /// Returns a tuple (from, to) representing the selection range.
445    /// If there's no selection, both values will be equal to the insertion point.
446    /// Returns (0, 0) if the control has been destroyed.
447    pub fn get_selection(&self) -> (i64, i64) {
448        let ptr = self.textctrl_ptr();
449        if ptr.is_null() {
450            return (0, 0);
451        }
452        let mut from = 0i64;
453        let mut to = 0i64;
454        unsafe { ffi::wxd_TextCtrl_GetSelection(ptr, &mut from, &mut to) };
455        (from, to)
456    }
457
458    /// Selects all text in the control.
459    /// No-op if the control has been destroyed.
460    pub fn select_all(&self) {
461        let ptr = self.textctrl_ptr();
462        if ptr.is_null() {
463            return;
464        }
465        unsafe { ffi::wxd_TextCtrl_SelectAll(ptr) };
466    }
467
468    /// Gets the currently selected text.
469    ///
470    /// Returns an empty string if no text is selected or if the control has been destroyed.
471    pub fn get_string_selection(&self) -> String {
472        let ptr = self.textctrl_ptr();
473        if ptr.is_null() {
474            return String::new();
475        }
476        unsafe { Self::read_string_with_retry(|buf, len| ffi::wxd_TextCtrl_GetStringSelection(ptr, buf, len)) }
477    }
478
479    /// Sets the insertion point to the end of the text control.
480    /// No-op if the control has been destroyed.
481    pub fn set_insertion_point_end(&self) {
482        let ptr = self.textctrl_ptr();
483        if ptr.is_null() {
484            return;
485        }
486        unsafe { ffi::wxd_TextCtrl_SetInsertionPointEnd(ptr) };
487    }
488
489    /// Sets the style for the given text range.
490    /// No-op if the control has been destroyed.
491    pub fn set_style(&self, start: i64, end: i64, style: &TextAttr) {
492        let ptr = self.textctrl_ptr();
493        if ptr.is_null() {
494            return;
495        }
496        unsafe { ffi::wxd_TextCtrl_SetStyle(ptr, start, end, style.as_ptr()) };
497    }
498
499    /// Returns the default style currently used for new text.
500    /// Returns None if the control has been destroyed.
501    pub fn get_default_style(&self) -> Option<TextAttr> {
502        let ptr = self.textctrl_ptr();
503        if ptr.is_null() {
504            return None;
505        }
506        let attr_ptr = unsafe { ffi::wxd_TextCtrl_GetDefaultStyle(ptr) };
507        if attr_ptr.is_null() {
508            return None;
509        }
510        Some(TextAttr { ptr: attr_ptr })
511    }
512
513    /// Sets the default style used for new text.
514    /// No-op if the control has been destroyed.
515    pub fn set_default_style(&self, style: &TextAttr) {
516        let ptr = self.textctrl_ptr();
517        if ptr.is_null() {
518            return;
519        }
520        unsafe { ffi::wxd_TextCtrl_SetDefaultStyle(ptr, style.as_ptr()) };
521    }
522
523    /// Returns the underlying WindowHandle for this textctrl.
524    pub fn window_handle(&self) -> WindowHandle {
525        self.handle
526    }
527}
528
529/// Represents text attributes such as colors and fonts.
530pub struct TextAttr {
531    ptr: *mut ffi::wxd_TextAttr_t,
532}
533
534impl Default for TextAttr {
535    fn default() -> Self {
536        Self::new()
537    }
538}
539
540impl TextAttr {
541    /// Creates a new default TextAttr.
542    pub fn new() -> Self {
543        Self {
544            ptr: unsafe { ffi::wxd_TextAttr_Create() },
545        }
546    }
547
548    /// Sets the text color.
549    pub fn set_text_colour(&mut self, color: crate::color::Colour) {
550        unsafe { ffi::wxd_TextAttr_SetTextColour(self.ptr, color.to_raw()) };
551    }
552
553    /// Sets the background color.
554    pub fn set_background_colour(&mut self, color: crate::color::Colour) {
555        unsafe { ffi::wxd_TextAttr_SetBackgroundColour(self.ptr, color.to_raw()) };
556    }
557
558    /// Sets the font.
559    pub fn set_font(&mut self, font: &crate::font::Font) {
560        unsafe { ffi::wxd_TextAttr_SetFont(self.ptr, font.as_ptr()) };
561    }
562
563    /// Sets the text alignment.
564    pub fn set_alignment(&mut self, alignment: i32) {
565        unsafe { ffi::wxd_TextAttr_SetAlignment(self.ptr, alignment) };
566    }
567
568    /// Sets the left indent and sub-indent in tenths of a millimetre.
569    pub fn set_left_indent(&mut self, indent: i32, sub_indent: i32) {
570        unsafe { ffi::wxd_TextAttr_SetLeftIndent(self.ptr, indent, sub_indent) };
571    }
572
573    /// Sets the right indent in tenths of a millimetre.
574    pub fn set_right_indent(&mut self, indent: i32) {
575        unsafe { ffi::wxd_TextAttr_SetRightIndent(self.ptr, indent) };
576    }
577
578    /// Sets the line spacing. 10 is normal, 15 is 1.5, 20 is double.
579    pub fn set_line_spacing(&mut self, spacing: i32) {
580        unsafe { ffi::wxd_TextAttr_SetLineSpacing(self.ptr, spacing) };
581    }
582
583    /// Sets the paragraph spacing after in tenths of a millimetre.
584    pub fn set_paragraph_spacing_after(&mut self, spacing: i32) {
585        unsafe { ffi::wxd_TextAttr_SetParagraphSpacingAfter(self.ptr, spacing) };
586    }
587
588    /// Sets the paragraph spacing before in tenths of a millimetre.
589    pub fn set_paragraph_spacing_before(&mut self, spacing: i32) {
590        unsafe { ffi::wxd_TextAttr_SetParagraphSpacingBefore(self.ptr, spacing) };
591    }
592
593    /// Sets the bullet style.
594    pub fn set_bullet_style(&mut self, style: i32) {
595        unsafe { ffi::wxd_TextAttr_SetBulletStyle(self.ptr, style) };
596    }
597
598    pub fn set_flags(&mut self, flags: i64) {
599        unsafe { ffi::wxd_TextAttr_SetFlags(self.ptr, flags) };
600    }
601    pub fn get_flags(&self) -> i64 {
602        unsafe { ffi::wxd_TextAttr_GetFlags(self.ptr) }
603    }
604    pub fn has_flag(&self, flag: i64) -> bool {
605        unsafe { ffi::wxd_TextAttr_HasFlag(self.ptr, flag) }
606    }
607
608    pub fn set_font_size(&mut self, point_size: i32) {
609        unsafe { ffi::wxd_TextAttr_SetFontSize(self.ptr, point_size) };
610    }
611    pub fn get_font_size(&self) -> i32 {
612        unsafe { ffi::wxd_TextAttr_GetFontSize(self.ptr) }
613    }
614
615    pub fn set_font_style(&mut self, font_style: crate::font::FontStyle) {
616        unsafe { ffi::wxd_TextAttr_SetFontStyle(self.ptr, font_style as i32) };
617    }
618    pub fn get_font_style(&self) -> crate::font::FontStyle {
619        unsafe { std::mem::transmute(ffi::wxd_TextAttr_GetFontStyle(self.ptr)) }
620    }
621
622    pub fn set_font_weight(&mut self, font_weight: crate::font::FontWeight) {
623        unsafe { ffi::wxd_TextAttr_SetFontWeight(self.ptr, font_weight as i32) };
624    }
625    pub fn get_font_weight(&self) -> crate::font::FontWeight {
626        unsafe { std::mem::transmute(ffi::wxd_TextAttr_GetFontWeight(self.ptr)) }
627    }
628
629    pub fn set_font_face_name(&mut self, face_name: &str) {
630        let c_str = std::ffi::CString::new(face_name).unwrap();
631        unsafe { ffi::wxd_TextAttr_SetFontFaceName(self.ptr, c_str.as_ptr()) };
632    }
633    pub fn set_font_underlined(&mut self, underlined: bool) {
634        unsafe { ffi::wxd_TextAttr_SetFontUnderlined(self.ptr, underlined) };
635    }
636    pub fn set_font_strikethrough(&mut self, strikethrough: bool) {
637        unsafe { ffi::wxd_TextAttr_SetFontStrikethrough(self.ptr, strikethrough) };
638    }
639    pub fn set_font_encoding(&mut self, encoding: i32) {
640        unsafe { ffi::wxd_TextAttr_SetFontEncoding(self.ptr, encoding) };
641    }
642    pub fn set_font_family(&mut self, family: crate::font::FontFamily) {
643        unsafe { ffi::wxd_TextAttr_SetFontFamily(self.ptr, family as i32) };
644    }
645
646    pub fn set_character_style_name(&mut self, name: &str) {
647        let c_str = std::ffi::CString::new(name).unwrap();
648        unsafe { ffi::wxd_TextAttr_SetCharacterStyleName(self.ptr, c_str.as_ptr()) };
649    }
650    pub fn set_paragraph_style_name(&mut self, name: &str) {
651        let c_str = std::ffi::CString::new(name).unwrap();
652        unsafe { ffi::wxd_TextAttr_SetParagraphStyleName(self.ptr, c_str.as_ptr()) };
653    }
654    pub fn set_list_style_name(&mut self, name: &str) {
655        let c_str = std::ffi::CString::new(name).unwrap();
656        unsafe { ffi::wxd_TextAttr_SetListStyleName(self.ptr, c_str.as_ptr()) };
657    }
658
659    pub fn set_bullet_number(&mut self, n: i32) {
660        unsafe { ffi::wxd_TextAttr_SetBulletNumber(self.ptr, n) };
661    }
662    pub fn set_bullet_text(&mut self, text: &str) {
663        let c_str = std::ffi::CString::new(text).unwrap();
664        unsafe { ffi::wxd_TextAttr_SetBulletText(self.ptr, c_str.as_ptr()) };
665    }
666    pub fn set_bullet_font(&mut self, bullet_font: &str) {
667        let c_str = std::ffi::CString::new(bullet_font).unwrap();
668        unsafe { ffi::wxd_TextAttr_SetBulletFont(self.ptr, c_str.as_ptr()) };
669    }
670    pub fn set_bullet_name(&mut self, name: &str) {
671        let c_str = std::ffi::CString::new(name).unwrap();
672        unsafe { ffi::wxd_TextAttr_SetBulletName(self.ptr, c_str.as_ptr()) };
673    }
674    pub fn set_url(&mut self, url: &str) {
675        let c_str = std::ffi::CString::new(url).unwrap();
676        unsafe { ffi::wxd_TextAttr_SetURL(self.ptr, c_str.as_ptr()) };
677    }
678    pub fn set_page_break(&mut self, page_break: bool) {
679        unsafe { ffi::wxd_TextAttr_SetPageBreak(self.ptr, page_break) };
680    }
681    pub fn set_text_effects(&mut self, effects: i32) {
682        unsafe { ffi::wxd_TextAttr_SetTextEffects(self.ptr, effects) };
683    }
684    pub fn set_text_effect_flags(&mut self, effects: i32) {
685        unsafe { ffi::wxd_TextAttr_SetTextEffectFlags(self.ptr, effects) };
686    }
687    pub fn set_outline_level(&mut self, level: i32) {
688        unsafe { ffi::wxd_TextAttr_SetOutlineLevel(self.ptr, level) };
689    }
690
691    pub fn get_text_colour(&self) -> crate::color::Colour {
692        crate::color::Colour::from(unsafe { ffi::wxd_TextAttr_GetTextColour(self.ptr) })
693    }
694    pub fn get_background_colour(&self) -> crate::color::Colour {
695        crate::color::Colour::from(unsafe { ffi::wxd_TextAttr_GetBackgroundColour(self.ptr) })
696    }
697    pub fn get_alignment(&self) -> i32 {
698        unsafe { ffi::wxd_TextAttr_GetAlignment(self.ptr) }
699    }
700    pub fn get_left_indent(&self) -> i32 {
701        unsafe { ffi::wxd_TextAttr_GetLeftIndent(self.ptr) }
702    }
703    pub fn get_left_sub_indent(&self) -> i32 {
704        unsafe { ffi::wxd_TextAttr_GetLeftSubIndent(self.ptr) }
705    }
706    pub fn get_right_indent(&self) -> i32 {
707        unsafe { ffi::wxd_TextAttr_GetRightIndent(self.ptr) }
708    }
709    pub fn get_line_spacing(&self) -> i32 {
710        unsafe { ffi::wxd_TextAttr_GetLineSpacing(self.ptr) }
711    }
712    pub fn get_paragraph_spacing_after(&self) -> i32 {
713        unsafe { ffi::wxd_TextAttr_GetParagraphSpacingAfter(self.ptr) }
714    }
715    pub fn get_paragraph_spacing_before(&self) -> i32 {
716        unsafe { ffi::wxd_TextAttr_GetParagraphSpacingBefore(self.ptr) }
717    }
718    pub fn get_bullet_style(&self) -> i32 {
719        unsafe { ffi::wxd_TextAttr_GetBulletStyle(self.ptr) }
720    }
721
722    pub(crate) fn as_ptr(&self) -> *mut ffi::wxd_TextAttr_t {
723        self.ptr
724    }
725}
726
727impl Drop for TextAttr {
728    fn drop(&mut self) {
729        if !self.ptr.is_null() {
730            unsafe { ffi::wxd_TextAttr_Delete(self.ptr) };
731            self.ptr = null_mut();
732        }
733    }
734}
735
736// Implement TextEvents trait for TextCtrl
737impl TextEvents for TextCtrl {}
738
739// Manual WxWidget implementation for TextCtrl (using WindowHandle)
740impl WxWidget for TextCtrl {
741    fn handle_ptr(&self) -> *mut ffi::wxd_Window_t {
742        self.handle.get_ptr().unwrap_or(null_mut())
743    }
744
745    fn is_valid(&self) -> bool {
746        self.handle.is_valid()
747    }
748}
749
750// Implement WxEvtHandler for event binding
751impl WxEvtHandler for TextCtrl {
752    unsafe fn get_event_handler_ptr(&self) -> *mut ffi::wxd_EvtHandler_t {
753        self.handle.get_ptr().unwrap_or(null_mut()) as *mut ffi::wxd_EvtHandler_t
754    }
755}
756
757// Implement common event traits that all Window-based widgets support
758impl crate::event::WindowEvents for TextCtrl {}
759
760// Implement scrolling functionality for TextCtrl (useful for multiline text)
761impl crate::scrollable::WxScrollable for TextCtrl {}
762
763// Use the widget_builder macro for TextCtrl
764widget_builder!(
765    name: TextCtrl,
766    parent_type: &'a dyn WxWidget,
767    style_type: TextCtrlStyle,
768    fields: {
769        value: String = String::new()
770    },
771    build_impl: |slf| {
772        TextCtrl::new_impl(
773            slf.parent.handle_ptr(),
774            slf.id,
775            &slf.value,
776            slf.pos,
777            slf.size,
778            slf.style.bits()
779        )
780    }
781);
782
783// Implement TextCtrl-specific event handlers using the standard macro
784crate::implement_widget_local_event_handlers!(
785    TextCtrl,
786    TextCtrlEvent,
787    TextCtrlEventData,
788    TextChanged => text_changed, EventType::TEXT,
789    TextEnter => text_enter, EventType::TEXT_ENTER
790);
791
792// XRC Support - enables TextCtrl to be created from XRC-managed pointers
793#[cfg(feature = "xrc")]
794impl crate::xrc::XrcSupport for TextCtrl {
795    unsafe fn from_xrc_ptr(ptr: *mut ffi::wxd_Window_t) -> Self {
796        TextCtrl {
797            handle: WindowHandle::new(ptr),
798        }
799    }
800}
801
802// Widget casting support for TextCtrl
803impl crate::window::FromWindowWithClassName for TextCtrl {
804    fn class_name() -> &'static str {
805        "wxTextCtrl"
806    }
807
808    unsafe fn from_ptr(ptr: *mut ffi::wxd_Window_t) -> Self {
809        TextCtrl {
810            handle: WindowHandle::new(ptr),
811        }
812    }
813}