old school music tracker
at dev 363 lines 12 kB view raw
1use ascii::{AsciiChar, AsciiString}; 2use font8x8::UnicodeFonts; 3use winit::keyboard::{Key, NamedKey}; 4 5use crate::{ 6 EventQueue, 7 coordinates::{CharPosition, WINDOW_SIZE_CHARS}, 8 draw_buffer::DrawBuffer, 9}; 10 11use super::{NextWidget, Widget, WidgetResponse}; 12 13/// text has max_len of the rect that was given, because the text_in cannot scroll 14/// use text_in_scroll for that 15/// i only allow Ascii characters as i can only render ascii 16pub struct TextIn { 17 pos: CharPosition, 18 width: u8, 19 text: AsciiString, 20 next_widget: NextWidget, 21 // fixed width, so text length is also fixed 22 cursor_pos: u8, 23 #[cfg(feature = "accesskit")] 24 access: (accesskit::NodeId, &'static str), 25} 26 27impl Widget for TextIn { 28 fn draw(&self, draw_buffer: &mut DrawBuffer, selected: bool) { 29 draw_buffer.draw_string_length(self.text.as_str(), self.pos, self.width, 2, 0); 30 // draw the cursor by overdrawing a letter 31 if selected { 32 let cursor_char_pos = self.pos + CharPosition::new(self.cursor_pos, 0); 33 let upos = usize::from(self.cursor_pos); 34 if upos < self.text.len() { 35 draw_buffer.draw_char( 36 font8x8::BASIC_FONTS.get(self.text[upos].into()).unwrap(), 37 cursor_char_pos, 38 0, 39 3, 40 ); 41 } else { 42 draw_buffer.draw_rect(3, cursor_char_pos.into()); 43 } 44 } 45 } 46 47 fn process_input( 48 &mut self, 49 modifiers: &winit::event::Modifiers, 50 key_event: &winit::event::KeyEvent, 51 _events: &mut EventQueue<'_>, 52 ) -> WidgetResponse { 53 let mut pos = usize::from(self.cursor_pos); 54 let res = process_input( 55 &mut self.text, 56 self.width, 57 &self.next_widget, 58 &mut pos, 59 None, 60 modifiers, 61 key_event, 62 ); 63 self.cursor_pos = u8::try_from(pos).unwrap(); 64 res 65 } 66 67 #[cfg(feature = "accesskit")] 68 fn build_tree(&self, tree: &mut Vec<(accesskit::NodeId, accesskit::Node)>) { 69 use std::iter::repeat_n; 70 71 use accesskit::{Node, NodeId, Role, TextDirection, TextPosition, TextSelection}; 72 73 let mut root_node = Node::new(Role::TextInput); 74 root_node.set_label(self.access.1); 75 let mut text_node = Node::new(Role::TextRun); 76 let text_node_id = NodeId(self.access.0.0 + 1); 77 text_node.set_text_direction(TextDirection::LeftToRight); 78 text_node.set_character_lengths(repeat_n(1, self.text.len()).collect::<Box<[u8]>>()); 79 // text_node.set_character_widths(repeat_n(1., self.text.len()).collect::<Box<[f32]>>()); 80 // text_node.set_character_positions( 81 // (0..self.text.len()) 82 // .map(|n| n as f32) 83 // .collect::<Box<[f32]>>(), 84 // ); 85 text_node.set_value(self.text.as_str()); 86 // text_node.set_word_lengths( 87 // self.text 88 // .as_str() 89 // .split_whitespace() 90 // .map(|w| u8::try_from(w.len()).unwrap()) 91 // .collect::<Box<[u8]>>(), 92 // ); 93 let character_index = usize::from(self.cursor_pos); 94 root_node.set_text_selection(TextSelection { 95 anchor: TextPosition { 96 node: text_node_id, 97 character_index, 98 }, 99 focus: TextPosition { 100 node: text_node_id, 101 character_index, 102 // character_index: self.text.as_str().len().min(self.cursor_pos + 1), 103 }, 104 }); 105 root_node.push_child(text_node_id); 106 107 tree.push((self.access.0, root_node)); 108 tree.push((text_node_id, text_node)); 109 } 110} 111 112impl TextIn { 113 pub fn new( 114 pos: CharPosition, 115 width: u8, 116 next_widget: NextWidget, 117 #[cfg(feature = "accesskit")] access: (accesskit::NodeId, &'static str), 118 ) -> Self { 119 assert!(pos.x() + width < WINDOW_SIZE_CHARS.0); 120 // right and left keys are used in the widget itself. doeesnt make sense to put NextWidget there 121 assert!(next_widget.right.is_none()); 122 assert!(next_widget.left.is_none()); 123 124 TextIn { 125 pos, 126 width, 127 text: AsciiString::with_capacity(usize::from(width)), // allows to never allocate or deallocate in TextIn 128 next_widget, 129 cursor_pos: 0, 130 #[cfg(feature = "accesskit")] 131 access, 132 } 133 } 134 135 // not tested 136 // TODO: can panic if the string is too long 137 pub fn set_string(&mut self, new_str: String) -> Result<(), ascii::FromAsciiError<String>> { 138 self.text = AsciiString::from_ascii(new_str)?; 139 self.text.truncate(usize::from(self.width)); 140 self.cursor_pos = u8::try_from(self.text.len()).unwrap(); 141 142 Ok(()) 143 } 144 145 pub fn get_str(&self) -> &str { 146 self.text.as_str() 147 } 148} 149 150pub struct TextInScroll { 151 pos: CharPosition, 152 width: u8, 153 text: AsciiString, 154 next_widget: NextWidget, 155 cursor_pos: usize, 156 scroll_offset: usize, 157} 158 159impl Widget for TextInScroll { 160 fn draw(&self, draw_buffer: &mut DrawBuffer, selected: bool) { 161 draw_buffer.draw_string_length( 162 &self.text.as_str()[self.scroll_offset..], 163 self.pos, 164 self.width, 165 2, 166 0, 167 ); 168 169 if selected { 170 let cursor_char_pos = self.pos 171 + CharPosition::new( 172 // this minus should make this diff be less then self.width 173 u8::try_from(self.cursor_pos - self.scroll_offset).unwrap(), 174 0, 175 ); 176 if self.cursor_pos < self.text.len() { 177 draw_buffer.draw_char( 178 font8x8::BASIC_FONTS 179 .get(self.text[self.cursor_pos].into()) 180 .unwrap(), 181 cursor_char_pos, 182 0, 183 3, 184 ); 185 } else { 186 draw_buffer.draw_rect(3, cursor_char_pos.into()); 187 } 188 } 189 } 190 191 fn process_input( 192 &mut self, 193 modifiers: &winit::event::Modifiers, 194 key_event: &winit::event::KeyEvent, 195 _events: &mut EventQueue<'_>, 196 ) -> WidgetResponse { 197 process_input( 198 &mut self.text, 199 self.width, 200 &self.next_widget, 201 &mut self.cursor_pos, 202 Some(&mut self.scroll_offset), 203 modifiers, 204 key_event, 205 ) 206 } 207 208 #[cfg(feature = "accesskit")] 209 fn build_tree(&self, tree: &mut Vec<(accesskit::NodeId, accesskit::Node)>) { 210 todo!() 211 } 212} 213 214impl TextInScroll { 215 pub fn new(pos: CharPosition, width: u8, next_widget: NextWidget) -> Self { 216 assert!(next_widget.right.is_none()); 217 assert!(next_widget.left.is_none()); 218 assert!(pos.x() + width < WINDOW_SIZE_CHARS.0); 219 220 Self { 221 pos, 222 width, 223 text: AsciiString::new(), 224 next_widget, 225 cursor_pos: 0, 226 scroll_offset: 0, 227 } 228 } 229 230 pub fn get_str(&self) -> &str { 231 self.text.as_str() 232 } 233} 234 235pub fn process_input( 236 text: &mut AsciiString, 237 width: u8, 238 next: &NextWidget, 239 cursor_pos: &mut usize, 240 scroll_offset: Option<&mut usize>, 241 modifiers: &winit::event::Modifiers, 242 key_event: &winit::event::KeyEvent, 243) -> WidgetResponse { 244 /// returns true if the cursor moved 245 fn move_cursor_left( 246 cursor_pos: &mut usize, 247 scroll_offset: Option<&mut usize>, 248 ) -> WidgetResponse { 249 if *cursor_pos > 0 { 250 *cursor_pos -= 1; 251 if let Some(scroll) = scroll_offset 252 && *cursor_pos < *scroll 253 { 254 *scroll -= 1; 255 } 256 // redraw, but no state change 257 WidgetResponse::RequestRedraw(false) 258 } else { 259 WidgetResponse::None 260 } 261 } 262 263 /// returns true if the cursor moved 264 fn move_cursor_right( 265 text: &AsciiString, 266 cursor_pos: &mut usize, 267 width: u8, 268 scroll_offset: Option<&mut usize>, 269 ) -> WidgetResponse { 270 if *cursor_pos < text.len() { 271 *cursor_pos += 1; 272 if let Some(scroll) = scroll_offset 273 && *cursor_pos > *scroll + usize::from(width) 274 { 275 *scroll += 1; 276 } 277 // redraw, but no state change 278 WidgetResponse::RequestRedraw(false) 279 } else { 280 WidgetResponse::None 281 } 282 } 283 284 fn insert_char( 285 text: &mut AsciiString, 286 width: u8, 287 cursor_pos: &mut usize, 288 char: AsciiChar, 289 scroll_offset: Option<&mut usize>, 290 ) { 291 if scroll_offset.is_some() { 292 text.insert(*cursor_pos, char); 293 move_cursor_right(text, cursor_pos, width, scroll_offset); 294 } else { 295 if *cursor_pos < usize::from(width) { 296 *cursor_pos += 1; 297 } 298 text.insert(*cursor_pos - 1, char); 299 text.truncate(usize::from(width)); 300 } 301 } 302 303 if !key_event.state.is_pressed() { 304 return WidgetResponse::None; 305 } 306 307 if let Key::Character(str) = &key_event.logical_key { 308 let mut char_iter = str.chars(); 309 let first_char = char_iter.next().unwrap(); 310 assert!(char_iter.next().is_none()); 311 312 if let Ok(ascii_char) = AsciiChar::from_ascii(first_char) { 313 insert_char(text, width, cursor_pos, ascii_char, scroll_offset); 314 WidgetResponse::RequestRedraw(true) 315 } else { 316 WidgetResponse::None 317 } 318 } else if key_event.logical_key == Key::Named(NamedKey::Space) { 319 insert_char(text, width, cursor_pos, AsciiChar::Space, scroll_offset); 320 return WidgetResponse::RequestRedraw(true); 321 } else if key_event.logical_key == Key::Named(NamedKey::ArrowLeft) 322 && modifiers.state().is_empty() 323 { 324 move_cursor_left(cursor_pos, scroll_offset) 325 } else if key_event.logical_key == Key::Named(NamedKey::ArrowRight) 326 && modifiers.state().is_empty() 327 { 328 move_cursor_right(text, cursor_pos, width, scroll_offset) 329 // entf key on german keyboard 330 } else if key_event.logical_key == Key::Named(NamedKey::Delete) { 331 // can't delete if cursor is at the front of the text or the text is empty 332 if *cursor_pos < text.len() { 333 _ = text.remove(*cursor_pos); 334 WidgetResponse::RequestRedraw(true) 335 } else { 336 WidgetResponse::None 337 } 338 } else if key_event.logical_key == Key::Named(NamedKey::Backspace) { 339 // super + backspace clears the string 340 // if the text is already empty we don't need to do anything 341 if modifiers.state().super_key() && !text.is_empty() { 342 text.clear(); 343 *cursor_pos = 0; 344 if let Some(scroll) = scroll_offset { 345 *scroll = 0; 346 } 347 WidgetResponse::RequestRedraw(true) 348 } else if modifiers.state().is_empty() && !text.is_empty() { 349 if *cursor_pos == 0 { 350 _ = text.remove(0); 351 } else { 352 _ = text.remove(*cursor_pos - 1); 353 move_cursor_left(cursor_pos, scroll_offset); 354 } 355 WidgetResponse::RequestRedraw(true) 356 } else { 357 WidgetResponse::None 358 } 359 } else { 360 // next widget select 361 next.process_key_event(key_event, modifiers) 362 } 363}