cranpose_ui/widgets/
basic_text_field.rs1#![allow(non_snake_case)]
7
8use crate::composable;
9use crate::layout::policies::EmptyMeasurePolicy;
10use crate::modifier::Modifier;
11use crate::text_field_modifier_node::TextFieldElement;
12use crate::widgets::Layout;
13use cranpose_core::NodeId;
14use cranpose_foundation::modifier_element;
15use cranpose_foundation::text::{TextFieldLineLimits, TextFieldState};
16use cranpose_ui_graphics::Color;
17
18#[composable]
36pub fn BasicTextField(state: TextFieldState, modifier: Modifier) -> NodeId {
37 BasicTextFieldWithOptions(state, modifier, BasicTextFieldOptions::default())
38}
39
40#[derive(Debug, Clone, PartialEq)]
42pub struct BasicTextFieldOptions {
43 pub cursor_color: Color,
45 pub line_limits: TextFieldLineLimits,
47}
48
49impl Default for BasicTextFieldOptions {
50 fn default() -> Self {
51 Self {
52 cursor_color: Color(0.0, 0.0, 0.0, 1.0), line_limits: TextFieldLineLimits::default(),
54 }
55 }
56}
57
58#[composable]
62pub fn BasicTextFieldWithOptions(
63 state: TextFieldState,
64 modifier: Modifier,
65 options: BasicTextFieldOptions,
66) -> NodeId {
67 let _text = state.text();
71
72 let text_field_element = TextFieldElement::new(state)
74 .with_cursor_color(options.cursor_color)
75 .with_line_limits(options.line_limits);
76
77 let text_field_modifier = modifier_element(text_field_element);
79 let final_modifier = Modifier::from_parts(vec![text_field_modifier]);
80 let combined_modifier = modifier.then(final_modifier);
81
82 Layout(
85 combined_modifier,
86 EmptyMeasurePolicy,
87 || {}, )
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94 use cranpose_core::{location_key, Composition, DefaultScheduler, MemoryApplier, Runtime};
95 use std::sync::Arc;
96
97 fn with_test_runtime<T>(f: impl FnOnce() -> T) -> T {
99 let _runtime = Runtime::new(Arc::new(DefaultScheduler));
100 f()
101 }
102
103 #[test]
104 fn basic_text_field_creates_node() {
105 let mut composition = Composition::new(MemoryApplier::new());
106 let state = TextFieldState::new("Test content");
107
108 let result = composition.render(location_key(file!(), line!(), column!()), {
109 let state = state.clone();
110 move || {
111 BasicTextField(state.clone(), Modifier::empty());
112 }
113 });
114
115 assert!(result.is_ok());
116 assert!(composition.root().is_some());
117 }
118
119 #[test]
120 fn basic_text_field_state_updates() {
121 with_test_runtime(|| {
122 let state = TextFieldState::new("Hello");
123 assert_eq!(state.text(), "Hello");
124
125 state.edit(|buffer| {
126 buffer.place_cursor_at_end();
127 buffer.insert("!");
128 });
129
130 assert_eq!(state.text(), "Hello!");
131 });
132 }
133}