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