Implement From<char> for GString#1554
Conversation
|
Thanks for your contribution. Could you please elaborate where/how you've needed this? |
|
Recently I was working on an on-screen keyboard, and it has some places where single chars need to be used with Godot functions. For example, pressing a key inserts a char using Probably not something that would be used a ton, but it seems like a reasonable / intuitive conversion to be included. |
Can you provide some example code? I'm asking because there's (AFAIK) no API in Godot that returns Rust's |
|
Edit bromeon: rs codetags No, I'm not using chars returned from any Godot API. I have a Rust array of chars representing a keyboard layout. At startup, that array is used to instantiate a grid of KeyboardKey objects: #[derive(GodotClass)]
#[class(base=Button, no_init)]
pub struct OnScreenKeyboardKey {
ty: KeyType,
kb: Gd<OnScreenKeyboard>,
base: Base<Button>,
}where KeyType includes which char it corresponds to: enum KeyType {
Char(char, char), // lowercase, uppercase
Done,
Backspace,
MoveLeft,
MoveRight,
Shift,
Caps,
}Then, when a key is pressed, it inserts that stored char into the LineEdit. match key {
KeyType::Char(lower, upper) => {
let out_char = if uppercase_state { upper } else { lower };
self.shift_engaged = false;
self.text_preview
.insert_text_at_caret(&GString::from([out_char].as_slice()));
}
....
}Here's the full (unfinished) code Detailsuse godot::{
classes::{Button, GridContainer, IButton, IPanelContainer, LineEdit, PanelContainer, base_button::ActionMode},
prelude::*,
};
#[derive(GodotClass)]
#[class(base=PanelContainer, init)]
pub struct OnScreenKeyboard {
shift_engaged: bool,
caps_engaged: bool,
#[export]
text_preview: OnEditor<Gd<LineEdit>>,
key_buttons: Vec<Gd<OnScreenKeyboardKey>>,
base: Base<PanelContainer>,
}
#[derive(Copy, Clone)]
enum KeyType {
Char(char, char), // lowercase, uppercase
Done,
Backspace,
MoveLeft,
MoveRight,
Shift,
Caps,
}
#[godot_api]
impl IPanelContainer for OnScreenKeyboard {
fn ready(&mut self) {
let kb_layout = [
KeyType::Char('1', '1'),
KeyType::Char('2', '2'),
KeyType::Char('3', '3'),
KeyType::Char('4', '4'),
KeyType::Char('5', '5'),
KeyType::Char('6', '6'),
KeyType::Char('7', '7'),
KeyType::Char('8', '8'),
KeyType::Char('9', '9'),
KeyType::Char('0', '0'),
KeyType::Char('q', 'Q'),
KeyType::Char('w', 'W'),
KeyType::Char('e', 'E'),
KeyType::Char('r', 'R'),
KeyType::Char('t', 'T'),
KeyType::Char('y', 'Y'),
KeyType::Char('u', 'U'),
KeyType::Char('i', 'I'),
KeyType::Char('o', 'O'),
KeyType::Char('p', 'P'),
KeyType::Char('a', 'A'),
KeyType::Char('s', 'S'),
KeyType::Char('d', 'D'),
KeyType::Char('f', 'F'),
KeyType::Char('g', 'G'),
KeyType::Char('h', 'H'),
KeyType::Char('j', 'J'),
KeyType::Char('k', 'K'),
KeyType::Char('l', 'L'),
KeyType::Char('?', '?'),
KeyType::Char('z', 'Z'),
KeyType::Char('x', 'X'),
KeyType::Char('c', 'C'),
KeyType::Char('v', 'V'),
KeyType::Char('b', 'B'),
KeyType::Char('n', 'N'),
KeyType::Char('m', 'M'),
KeyType::Char('-', '-'),
KeyType::Char('_', '_'),
KeyType::Char('!', '!'),
KeyType::MoveLeft,
KeyType::MoveRight,
KeyType::Shift,
KeyType::Caps,
KeyType::Char(' ', ' '),
KeyType::Char('.', '.'),
KeyType::Char(',', ','),
KeyType::Char('#', '#'),
KeyType::Backspace,
KeyType::Done,
];
let mut grid_container = self.base().get_node_as::<GridContainer>("VBoxContainer/GridContainer");
self.key_buttons.reserve_exact(kb_layout.len());
for key in kb_layout {
let mut key_button = OnScreenKeyboardKey::new(key, self.to_gd());
key_button.set_custom_minimum_size(Vector2::new(50.0, 50.0));
key_button.set_action_mode(ActionMode::PRESS);
grid_container.add_child(&key_button);
self.key_buttons.push(key_button);
}
self.update_key_text();
}
}
#[godot_api]
impl OnScreenKeyboard {
fn using_uppercase(&self) -> bool {
self.caps_engaged ^ self.shift_engaged
}
fn update_key_text(&mut self) {
let uppercase = self.using_uppercase();
for key_button in &mut self.key_buttons {
key_button.bind_mut().update_text(uppercase);
}
}
fn key_pressed(&mut self, key: KeyType) {
let uppercase_state = self.using_uppercase();
match key {
KeyType::Char(lower, upper) => {
let out_char = if uppercase_state { upper } else { lower };
self.shift_engaged = false;
self.text_preview
.insert_text_at_caret(&GString::from([out_char].as_slice()));
}
KeyType::Done => (),
KeyType::Backspace => {
let caret_pos = self.text_preview.get_caret_column();
if caret_pos > 0 {
self.text_preview.delete_text(caret_pos - 1, caret_pos);
}
}
KeyType::MoveLeft => {
let caret_pos = self.text_preview.get_caret_column();
self.text_preview.set_caret_column(caret_pos - 1);
}
KeyType::MoveRight => {
let caret_pos = self.text_preview.get_caret_column();
self.text_preview.set_caret_column(caret_pos + 1);
}
KeyType::Caps => self.caps_engaged ^= true,
KeyType::Shift => self.shift_engaged ^= true,
}
if uppercase_state != self.using_uppercase() {
self.update_key_text();
}
}
}
#[derive(GodotClass)]
#[class(base=Button, no_init)]
pub struct OnScreenKeyboardKey {
ty: KeyType,
kb: Gd<OnScreenKeyboard>,
base: Base<Button>,
}
#[godot_api]
impl IButton for OnScreenKeyboardKey {
fn ready(&mut self) {
self.base().signals().pressed().connect_other(self, Self::pressed);
}
}
impl OnScreenKeyboardKey {
fn new(ty: KeyType, kb: Gd<OnScreenKeyboard>) -> Gd<Self> {
Gd::from_init_fn(|base| Self { ty, base, kb })
}
fn pressed(&mut self) {
// Needs to run deferred to avoid double-bind since it binds the keys
let ty = self.ty;
self.kb.bind_mut().run_deferred(move |s| s.key_pressed(ty));
}
fn update_text(&mut self, uppercase: bool) {
let txt = match self.ty {
KeyType::Char(lower, upper) => [if uppercase { upper } else { lower }].as_slice().into(),
KeyType::Done => GString::from("Done"),
KeyType::Backspace => GString::from("⌫"),
KeyType::MoveLeft => GString::from("←"),
KeyType::MoveRight => GString::from("→"),
KeyType::Shift => GString::from("Shift"),
KeyType::Caps => GString::from("Caps"),
};
self.base_mut().set_text(&txt);
}
} |
|
API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-1554 |
|
If we decide to proceed with it, shouldn't we implement gdext/godot-core/src/meta/args/as_arg.rs Lines 538 to 540 in ed460cd |
// now:
text_preview.insert_text_at_caret(&GString::from([out_char].as_slice()));
// with From:
text_preview.insert_text_at_caret(&GString::from(out_char));
// with AsArg:
text_preview.insert_text_at_caret(out_char);It looks definitely nice. But we also need to keep in mind that while convenient, implicit conversions (through One reason why I'm not 100% convinced by the change here is that I planned already a few times to add named conversion functions, most recently as a replacement for #1526. This could look like // with StringExt:
text_preview.insert_text_at_caret(out_char.to_gstring());So if we one day move away from |
|
Sorry for the lack of feedback on this; our plan is to experiment with an extension trait for string conversions and see if this could reasonably covered by it, and then re-evaluate. We'll post here once there's an update. |
Little convenience function I've needed a few times.