Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
12 kB · 363 lines
Rust
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364use ascii::{AsciiChar, AsciiString};use font8x8::UnicodeFonts;use winit::keyboard::{Key, NamedKey};
use crate::{ EventQueue, coordinates::{CharPosition, WINDOW_SIZE_CHARS}, draw_buffer::DrawBuffer,};
use super::{NextWidget, Widget, WidgetResponse};
/// text has max_len of the rect that was given, because the text_in cannot scroll/// use text_in_scroll for that/// i only allow Ascii characters as i can only render asciipub struct TextIn { pos: CharPosition, width: u8, text: AsciiString, next_widget: NextWidget, // fixed width, so text length is also fixed cursor_pos: u8, #[cfg(feature = "accesskit")] access: (accesskit::NodeId, &'static str),}
impl Widget for TextIn { fn draw(&self, draw_buffer: &mut DrawBuffer, selected: bool) { draw_buffer.draw_string_length(self.text.as_str(), self.pos, self.width, 2, 0); // draw the cursor by overdrawing a letter if selected { let cursor_char_pos = self.pos + CharPosition::new(self.cursor_pos, 0); let upos = usize::from(self.cursor_pos); if upos < self.text.len() { draw_buffer.draw_char( font8x8::BASIC_FONTS.get(self.text[upos].into()).unwrap(), cursor_char_pos, 0, 3, ); } else { draw_buffer.draw_rect(3, cursor_char_pos.into()); } } }
fn process_input( &mut self, modifiers: &winit::event::Modifiers, key_event: &winit::event::KeyEvent, _events: &mut EventQueue<'_>, ) -> WidgetResponse { let mut pos = usize::from(self.cursor_pos); let res = process_input( &mut self.text, self.width, &self.next_widget, &mut pos, None, modifiers, key_event, ); self.cursor_pos = u8::try_from(pos).unwrap(); res }
#[cfg(feature = "accesskit")] fn build_tree(&self, tree: &mut Vec<(accesskit::NodeId, accesskit::Node)>) { use std::iter::repeat_n;
use accesskit::{Node, NodeId, Role, TextDirection, TextPosition, TextSelection};
let mut root_node = Node::new(Role::TextInput); root_node.set_label(self.access.1); let mut text_node = Node::new(Role::TextRun); let text_node_id = NodeId(self.access.0.0 + 1); text_node.set_text_direction(TextDirection::LeftToRight); text_node.set_character_lengths(repeat_n(1, self.text.len()).collect::<Box<[u8]>>()); // text_node.set_character_widths(repeat_n(1., self.text.len()).collect::<Box<[f32]>>()); // text_node.set_character_positions( // (0..self.text.len()) // .map(|n| n as f32) // .collect::<Box<[f32]>>(), // ); text_node.set_value(self.text.as_str()); // text_node.set_word_lengths( // self.text // .as_str() // .split_whitespace() // .map(|w| u8::try_from(w.len()).unwrap()) // .collect::<Box<[u8]>>(), // ); let character_index = usize::from(self.cursor_pos); root_node.set_text_selection(TextSelection { anchor: TextPosition { node: text_node_id, character_index, }, focus: TextPosition { node: text_node_id, character_index, // character_index: self.text.as_str().len().min(self.cursor_pos + 1), }, }); root_node.push_child(text_node_id);
tree.push((self.access.0, root_node)); tree.push((text_node_id, text_node)); }}
impl TextIn { pub fn new( pos: CharPosition, width: u8, next_widget: NextWidget, #[cfg(feature = "accesskit")] access: (accesskit::NodeId, &'static str), ) -> Self { assert!(pos.x() + width < WINDOW_SIZE_CHARS.0); // right and left keys are used in the widget itself. doeesnt make sense to put NextWidget there assert!(next_widget.right.is_none()); assert!(next_widget.left.is_none());
TextIn { pos, width, text: AsciiString::with_capacity(usize::from(width)), // allows to never allocate or deallocate in TextIn next_widget, cursor_pos: 0, #[cfg(feature = "accesskit")] access, } }
// not tested // TODO: can panic if the string is too long pub fn set_string(&mut self, new_str: String) -> Result<(), ascii::FromAsciiError<String>> { self.text = AsciiString::from_ascii(new_str)?; self.text.truncate(usize::from(self.width)); self.cursor_pos = u8::try_from(self.text.len()).unwrap();
Ok(()) }
pub fn get_str(&self) -> &str { self.text.as_str() }}
pub struct TextInScroll { pos: CharPosition, width: u8, text: AsciiString, next_widget: NextWidget, cursor_pos: usize, scroll_offset: usize,}
impl Widget for TextInScroll { fn draw(&self, draw_buffer: &mut DrawBuffer, selected: bool) { draw_buffer.draw_string_length( &self.text.as_str()[self.scroll_offset..], self.pos, self.width, 2, 0, );
if selected { let cursor_char_pos = self.pos + CharPosition::new( // this minus should make this diff be less then self.width u8::try_from(self.cursor_pos - self.scroll_offset).unwrap(), 0, ); if self.cursor_pos < self.text.len() { draw_buffer.draw_char( font8x8::BASIC_FONTS .get(self.text[self.cursor_pos].into()) .unwrap(), cursor_char_pos, 0, 3, ); } else { draw_buffer.draw_rect(3, cursor_char_pos.into()); } } }
fn process_input( &mut self, modifiers: &winit::event::Modifiers, key_event: &winit::event::KeyEvent, _events: &mut EventQueue<'_>, ) -> WidgetResponse { process_input( &mut self.text, self.width, &self.next_widget, &mut self.cursor_pos, Some(&mut self.scroll_offset), modifiers, key_event, ) }
#[cfg(feature = "accesskit")] fn build_tree(&self, _tree: &mut Vec<(accesskit::NodeId, accesskit::Node)>) { todo!() }}
impl TextInScroll { pub fn new(pos: CharPosition, width: u8, next_widget: NextWidget) -> Self { assert!(next_widget.right.is_none()); assert!(next_widget.left.is_none()); assert!(pos.x() + width < WINDOW_SIZE_CHARS.0);
Self { pos, width, text: AsciiString::new(), next_widget, cursor_pos: 0, scroll_offset: 0, } }
pub fn get_str(&self) -> &str { self.text.as_str() }}
pub fn process_input( text: &mut AsciiString, width: u8, next: &NextWidget, cursor_pos: &mut usize, scroll_offset: Option<&mut usize>, modifiers: &winit::event::Modifiers, key_event: &winit::event::KeyEvent,) -> WidgetResponse { /// returns true if the cursor moved fn move_cursor_left( cursor_pos: &mut usize, scroll_offset: Option<&mut usize>, ) -> WidgetResponse { if *cursor_pos > 0 { *cursor_pos -= 1; if let Some(scroll) = scroll_offset && *cursor_pos < *scroll { *scroll -= 1; } // redraw, but no state change WidgetResponse::RequestRedraw(false) } else { WidgetResponse::None } }
/// returns true if the cursor moved fn move_cursor_right( text: &AsciiString, cursor_pos: &mut usize, width: u8, scroll_offset: Option<&mut usize>, ) -> WidgetResponse { if *cursor_pos < text.len() { *cursor_pos += 1; if let Some(scroll) = scroll_offset && *cursor_pos > *scroll + usize::from(width) { *scroll += 1; } // redraw, but no state change WidgetResponse::RequestRedraw(false) } else { WidgetResponse::None } }
fn insert_char( text: &mut AsciiString, width: u8, cursor_pos: &mut usize, char: AsciiChar, scroll_offset: Option<&mut usize>, ) { if scroll_offset.is_some() { text.insert(*cursor_pos, char); move_cursor_right(text, cursor_pos, width, scroll_offset); } else { if *cursor_pos < usize::from(width) { *cursor_pos += 1; } text.insert(*cursor_pos - 1, char); text.truncate(usize::from(width)); } }
if !key_event.state.is_pressed() { return WidgetResponse::None; }
if let Key::Character(str) = &key_event.logical_key { let mut char_iter = str.chars(); let first_char = char_iter.next().unwrap(); assert!(char_iter.next().is_none());
if let Ok(ascii_char) = AsciiChar::from_ascii(first_char) { insert_char(text, width, cursor_pos, ascii_char, scroll_offset); WidgetResponse::RequestRedraw(true) } else { WidgetResponse::None } } else if key_event.logical_key == Key::Named(NamedKey::Space) { insert_char(text, width, cursor_pos, AsciiChar::Space, scroll_offset); return WidgetResponse::RequestRedraw(true); } else if key_event.logical_key == Key::Named(NamedKey::ArrowLeft) && modifiers.state().is_empty() { move_cursor_left(cursor_pos, scroll_offset) } else if key_event.logical_key == Key::Named(NamedKey::ArrowRight) && modifiers.state().is_empty() { move_cursor_right(text, cursor_pos, width, scroll_offset) // entf key on german keyboard } else if key_event.logical_key == Key::Named(NamedKey::Delete) { // can't delete if cursor is at the front of the text or the text is empty if *cursor_pos < text.len() { _ = text.remove(*cursor_pos); WidgetResponse::RequestRedraw(true) } else { WidgetResponse::None } } else if key_event.logical_key == Key::Named(NamedKey::Backspace) { // super + backspace clears the string // if the text is already empty we don't need to do anything if modifiers.state().super_key() && !text.is_empty() { text.clear(); *cursor_pos = 0; if let Some(scroll) = scroll_offset { *scroll = 0; } WidgetResponse::RequestRedraw(true) } else if modifiers.state().is_empty() && !text.is_empty() { if *cursor_pos == 0 { _ = text.remove(0); } else { _ = text.remove(*cursor_pos - 1); move_cursor_left(cursor_pos, scroll_offset); } WidgetResponse::RequestRedraw(true) } else { WidgetResponse::None } } else { // next widget select next.process_key_event(key_event, modifiers) }}