mirror of
https://github.com/LegitCamper/picocalc-os-rs.git
synced 2025-12-27 15:55:25 +00:00
Compare commits
2 Commits
macros
...
userland-a
| Author | SHA1 | Date | |
|---|---|---|---|
| 49b6a99ea8 | |||
| 718bcd6b5b |
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -1941,7 +1941,6 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "st7365p-lcd"
|
name = "st7365p-lcd"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
source = "git+https://github.com/legitcamper/st7365p-lcd-rs?rev=87abf450404865dcb535292e9e1a6a2457fd4599#87abf450404865dcb535292e9e1a6a2457fd4599"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitvec",
|
"bitvec",
|
||||||
"embedded-graphics-core",
|
"embedded-graphics-core",
|
||||||
|
|||||||
@@ -70,7 +70,8 @@ defmt = { version = "0.3", optional = true }
|
|||||||
defmt-rtt = "0.4.2"
|
defmt-rtt = "0.4.2"
|
||||||
|
|
||||||
embedded-sdmmc = { version = "0.9", default-features = false }
|
embedded-sdmmc = { version = "0.9", default-features = false }
|
||||||
st7365p-lcd = { git = "https://github.com/legitcamper/st7365p-lcd-rs", rev = "87abf450404865dcb535292e9e1a6a2457fd4599" } # async branch
|
# st7365p-lcd = { git = "https://github.com/legitcamper/st7365p-lcd-rs", rev = "87abf450404865dcb535292e9e1a6a2457fd4599" } # async branch
|
||||||
|
st7365p-lcd = { path = "../../ST7365P-lcd-rs" } # async branch
|
||||||
embedded-graphics = { version = "0.8.1" }
|
embedded-graphics = { version = "0.8.1" }
|
||||||
embedded-text = "0.7.2"
|
embedded-text = "0.7.2"
|
||||||
embedded-layout = "0.4.2"
|
embedded-layout = "0.4.2"
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ use core::pin::Pin;
|
|||||||
use abi_sys::{DrawIterAbi, GetKeyAbi, Pixel, PrintAbi};
|
use abi_sys::{DrawIterAbi, GetKeyAbi, Pixel, PrintAbi};
|
||||||
use alloc::boxed::Box;
|
use alloc::boxed::Box;
|
||||||
use defmt::info;
|
use defmt::info;
|
||||||
use embassy_futures::block_on;
|
|
||||||
use embedded_graphics::{
|
use embedded_graphics::{
|
||||||
Drawable,
|
Drawable,
|
||||||
draw_target::DrawTarget,
|
draw_target::DrawTarget,
|
||||||
@@ -13,7 +12,7 @@ use embedded_graphics::{
|
|||||||
};
|
};
|
||||||
use shared::keyboard::KeyEvent;
|
use shared::keyboard::KeyEvent;
|
||||||
|
|
||||||
use crate::{KEY_CACHE, display::FRAMEBUFFER};
|
use crate::{KEY_CACHE, display::framebuffer_mut};
|
||||||
|
|
||||||
// ensure the abi and the kernel fn signatures are the same
|
// ensure the abi and the kernel fn signatures are the same
|
||||||
const _: PrintAbi = print;
|
const _: PrintAbi = print;
|
||||||
@@ -26,19 +25,8 @@ pub extern "Rust" fn print(msg: &str) {
|
|||||||
|
|
||||||
// TODO: maybe return result
|
// TODO: maybe return result
|
||||||
pub extern "Rust" fn draw_iter(pixels: &[Pixel<Rgb565>]) {
|
pub extern "Rust" fn draw_iter(pixels: &[Pixel<Rgb565>]) {
|
||||||
for _ in 0..10 {
|
let fb = framebuffer_mut();
|
||||||
if let Some(mut framebuffer) = FRAMEBUFFER.try_lock().ok() {
|
fb.draw_iter(pixels.iter().copied()).unwrap();
|
||||||
for _ in 0..10 {
|
|
||||||
// kernel takes() framebuffer
|
|
||||||
if let Some(framebuffer) = framebuffer.as_mut() {
|
|
||||||
framebuffer.draw_iter(pixels.iter().copied()).unwrap();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
cortex_m::asm::nop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub extern "Rust" fn get_key() -> Option<KeyEvent> {
|
pub extern "Rust" fn get_key() -> Option<KeyEvent> {
|
||||||
|
|||||||
@@ -20,8 +20,11 @@ pub const SCREEN_WIDTH: usize = 320;
|
|||||||
pub const SCREEN_HEIGHT: usize = 320;
|
pub const SCREEN_HEIGHT: usize = 320;
|
||||||
|
|
||||||
type FB = FrameBuffer<SCREEN_WIDTH, SCREEN_HEIGHT, { SCREEN_WIDTH * SCREEN_HEIGHT }>;
|
type FB = FrameBuffer<SCREEN_WIDTH, SCREEN_HEIGHT, { SCREEN_WIDTH * SCREEN_HEIGHT }>;
|
||||||
static FRAMEBUFFER_CELL: StaticCell<FB> = StaticCell::new();
|
static mut FRAMEBUFFER: Option<FB> = None;
|
||||||
pub static FRAMEBUFFER: Mutex<CriticalSectionRawMutex, Option<&'static mut FB>> = Mutex::new(None);
|
|
||||||
|
pub fn framebuffer_mut() -> &'static mut FB {
|
||||||
|
unsafe { FRAMEBUFFER.as_mut().unwrap() }
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn init_display(
|
pub async fn init_display(
|
||||||
spi: Spi<'static, SPI1, Async>,
|
spi: Spi<'static, SPI1, Async>,
|
||||||
@@ -38,28 +41,20 @@ pub async fn init_display(
|
|||||||
true,
|
true,
|
||||||
Delay,
|
Delay,
|
||||||
);
|
);
|
||||||
let framebuffer = FRAMEBUFFER_CELL.init(FrameBuffer::new());
|
|
||||||
display.init().await.unwrap();
|
display.init().await.unwrap();
|
||||||
display.set_custom_orientation(0x40).await.unwrap();
|
display.set_custom_orientation(0x40).await.unwrap();
|
||||||
framebuffer.draw(&mut display).await.unwrap();
|
unsafe { FRAMEBUFFER.replace(FrameBuffer::new()) };
|
||||||
|
display.draw(framebuffer_mut()).await.unwrap();
|
||||||
display.set_on().await.unwrap();
|
display.set_on().await.unwrap();
|
||||||
FRAMEBUFFER.lock().await.replace(framebuffer);
|
|
||||||
|
|
||||||
display
|
display
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn display_handler(mut display: DISPLAY) {
|
pub async fn display_handler(mut display: DISPLAY) {
|
||||||
|
let fb = framebuffer_mut();
|
||||||
loop {
|
loop {
|
||||||
let fb: &mut FB = {
|
display.partial_draw_batched(fb).await.unwrap();
|
||||||
let mut guard = FRAMEBUFFER.lock().await;
|
embassy_time::Timer::after_millis(32).await; // 30 fps
|
||||||
guard.take().unwrap() // take ownership
|
|
||||||
}; // guard dropped
|
|
||||||
|
|
||||||
fb.partial_draw_batched(&mut display).await.unwrap();
|
|
||||||
|
|
||||||
// Put it back
|
|
||||||
FRAMEBUFFER.lock().await.replace(fb);
|
|
||||||
|
|
||||||
Timer::after_millis(32).await; // 30 fps
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,11 +17,8 @@ mod ui;
|
|||||||
mod usb;
|
mod usb;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use core::sync::atomic::Ordering;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
display::{display_handler, init_display},
|
display::{display_handler, framebuffer_mut, init_display},
|
||||||
elf::load_binary,
|
|
||||||
peripherals::{
|
peripherals::{
|
||||||
conf_peripherals,
|
conf_peripherals,
|
||||||
keyboard::{KeyCode, KeyState, read_keyboard_fifo},
|
keyboard::{KeyCode, KeyState, read_keyboard_fifo},
|
||||||
@@ -30,11 +27,7 @@ use crate::{
|
|||||||
ui::{SELECTIONS, ui_handler},
|
ui::{SELECTIONS, ui_handler},
|
||||||
usb::usb_handler,
|
usb::usb_handler,
|
||||||
};
|
};
|
||||||
use abi_sys::EntryFn;
|
use abi_sys::{EntryFn, Rgb565, RgbColor};
|
||||||
use alloc::vec::Vec;
|
|
||||||
|
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
|
||||||
|
|
||||||
use defmt::unwrap;
|
use defmt::unwrap;
|
||||||
use embassy_executor::{Executor, Spawner};
|
use embassy_executor::{Executor, Spawner};
|
||||||
use embassy_futures::join::{join, join3, join4, join5};
|
use embassy_futures::join::{join, join3, join4, join5};
|
||||||
@@ -49,14 +42,18 @@ use embassy_rp::{
|
|||||||
spi::{self, Spi},
|
spi::{self, Spi},
|
||||||
usb as embassy_rp_usb,
|
usb as embassy_rp_usb,
|
||||||
};
|
};
|
||||||
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, channel::Channel, mutex::Mutex};
|
use embassy_sync::{
|
||||||
|
blocking_mutex::raw::CriticalSectionRawMutex, channel::Channel, mutex::Mutex, signal::Signal,
|
||||||
|
};
|
||||||
use embassy_time::{Delay, Timer};
|
use embassy_time::{Delay, Timer};
|
||||||
|
use embedded_graphics::draw_target::DrawTarget;
|
||||||
use embedded_hal_bus::spi::ExclusiveDevice;
|
use embedded_hal_bus::spi::ExclusiveDevice;
|
||||||
use embedded_sdmmc::SdCard as SdmmcSdCard;
|
use embedded_sdmmc::SdCard as SdmmcSdCard;
|
||||||
use heapless::spsc::Queue;
|
use heapless::spsc::Queue;
|
||||||
use shared::keyboard::KeyEvent;
|
use shared::keyboard::KeyEvent;
|
||||||
use static_cell::StaticCell;
|
use static_cell::StaticCell;
|
||||||
use talc::*;
|
use talc::*;
|
||||||
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
embassy_rp::bind_interrupts!(struct Irqs {
|
embassy_rp::bind_interrupts!(struct Irqs {
|
||||||
I2C1_IRQ => i2c::InterruptHandler<I2C1>;
|
I2C1_IRQ => i2c::InterruptHandler<I2C1>;
|
||||||
@@ -75,7 +72,9 @@ static ALLOCATOR: Talck<spin::Mutex<()>, ClaimOnOom> =
|
|||||||
.lock();
|
.lock();
|
||||||
|
|
||||||
static TASK_STATE: Mutex<CriticalSectionRawMutex, TaskState> = Mutex::new(TaskState::Ui);
|
static TASK_STATE: Mutex<CriticalSectionRawMutex, TaskState> = Mutex::new(TaskState::Ui);
|
||||||
|
static TASK_STATE_CHANGED: Signal<CriticalSectionRawMutex, ()> = Signal::new();
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
enum TaskState {
|
enum TaskState {
|
||||||
Ui,
|
Ui,
|
||||||
Kernel,
|
Kernel,
|
||||||
@@ -131,20 +130,28 @@ async fn userland_task() {
|
|||||||
let recv = BINARY_CH.receiver();
|
let recv = BINARY_CH.receiver();
|
||||||
loop {
|
loop {
|
||||||
let entry = recv.receive().await;
|
let entry = recv.receive().await;
|
||||||
|
defmt::info!("got bin");
|
||||||
|
|
||||||
// disable kernel ui
|
// disable kernel ui
|
||||||
{
|
{
|
||||||
let mut state = TASK_STATE.lock().await;
|
let mut state = TASK_STATE.lock().await;
|
||||||
*state = TaskState::Kernel;
|
*state = TaskState::Kernel;
|
||||||
|
TASK_STATE_CHANGED.signal(());
|
||||||
}
|
}
|
||||||
|
|
||||||
defmt::info!("Executing Binary");
|
{
|
||||||
|
let fb = framebuffer_mut();
|
||||||
|
fb.clear(Rgb565::BLACK).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
defmt::info!("running entry");
|
||||||
entry().await;
|
entry().await;
|
||||||
|
|
||||||
// enable kernel ui
|
// enable kernel ui
|
||||||
{
|
{
|
||||||
let mut state = TASK_STATE.lock().await;
|
let mut state = TASK_STATE.lock().await;
|
||||||
*state = TaskState::Ui;
|
*state = TaskState::Ui;
|
||||||
|
TASK_STATE_CHANGED.signal(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
135
kernel/src/ui.rs
135
kernel/src/ui.rs
@@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
BINARY_CH, TASK_STATE, TaskState,
|
BINARY_CH, TASK_STATE, TaskState,
|
||||||
display::{FRAMEBUFFER, SCREEN_HEIGHT, SCREEN_WIDTH},
|
display::{SCREEN_HEIGHT, SCREEN_WIDTH, framebuffer_mut},
|
||||||
elf::load_binary,
|
elf::load_binary,
|
||||||
format,
|
format,
|
||||||
peripherals::keyboard,
|
peripherals::keyboard,
|
||||||
@@ -47,36 +47,51 @@ use shared::keyboard::{KeyCode, KeyState};
|
|||||||
pub static SELECTIONS: Mutex<CriticalSectionRawMutex, SelectionList> =
|
pub static SELECTIONS: Mutex<CriticalSectionRawMutex, SelectionList> =
|
||||||
Mutex::new(SelectionList::new());
|
Mutex::new(SelectionList::new());
|
||||||
|
|
||||||
|
pub async fn run_selected() {
|
||||||
|
info!("Getting selections lock");
|
||||||
|
let selections = SELECTIONS.lock().await;
|
||||||
|
info!("Got selections lock");
|
||||||
|
let selection = selections.selections[selections.current_selection as usize - 1].clone();
|
||||||
|
|
||||||
|
let entry = unsafe { load_binary(&selection.short_name).await.unwrap() };
|
||||||
|
info!("loaded binary");
|
||||||
|
BINARY_CH.send(entry).await;
|
||||||
|
info!("sent bin");
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn ui_handler() {
|
pub async fn ui_handler() {
|
||||||
loop {
|
loop {
|
||||||
if let TaskState::Ui = *TASK_STATE.lock().await {
|
let state = *TASK_STATE.lock().await;
|
||||||
|
if let TaskState::Ui = state {
|
||||||
|
let mut redraw = false;
|
||||||
|
|
||||||
if let Some(event) = keyboard::read_keyboard_fifo().await {
|
if let Some(event) = keyboard::read_keyboard_fifo().await {
|
||||||
if let KeyState::Pressed = event.state {
|
if event.state == KeyState::Pressed {
|
||||||
match event.key {
|
match event.key {
|
||||||
KeyCode::JoyUp => {
|
KeyCode::JoyUp => {
|
||||||
let mut selections = SELECTIONS.lock().await;
|
let mut selections = SELECTIONS.lock().await;
|
||||||
selections.up();
|
selections.up();
|
||||||
|
redraw = true;
|
||||||
}
|
}
|
||||||
KeyCode::JoyDown => {
|
KeyCode::JoyDown => {
|
||||||
let mut selections = SELECTIONS.lock().await;
|
let mut selections = SELECTIONS.lock().await;
|
||||||
selections.down();
|
selections.down();
|
||||||
|
redraw = true;
|
||||||
}
|
}
|
||||||
KeyCode::Enter | KeyCode::JoyRight => {
|
KeyCode::Enter | KeyCode::JoyRight => run_selected().await,
|
||||||
let selections = SELECTIONS.lock().await;
|
_ => {}
|
||||||
let selection = selections.selections
|
|
||||||
[selections.current_selection as usize - 1]
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
let entry =
|
|
||||||
unsafe { load_binary(&selection.short_name).await.unwrap() };
|
|
||||||
BINARY_CH.send(entry).await;
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
draw_selection().await;
|
{
|
||||||
|
let mut selections = SELECTIONS.lock().await;
|
||||||
|
redraw |= selections.take_changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
if redraw {
|
||||||
|
draw_selection().await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,58 +102,57 @@ async fn draw_selection() {
|
|||||||
guard.selections.clone()
|
guard.selections.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut fb_lock = FRAMEBUFFER.lock().await;
|
let fb = framebuffer_mut();
|
||||||
if let Some(fb) = fb_lock.as_mut() {
|
let text_style = MonoTextStyle::new(&FONT_9X15, Rgb565::WHITE);
|
||||||
let text_style = MonoTextStyle::new(&FONT_9X15, Rgb565::WHITE);
|
let display_area = fb.bounding_box();
|
||||||
let display_area = fb.bounding_box();
|
|
||||||
|
|
||||||
const NO_BINS: &str = "No Programs found on SD Card. Ensure programs end with '.bin', and are located in the root directory";
|
const NO_BINS: &str = "No Programs found on SD Card. Ensure programs end with '.bin', and are located in the root directory";
|
||||||
let no_bins = String::from_str(NO_BINS).unwrap();
|
let no_bins = String::from_str(NO_BINS).unwrap();
|
||||||
|
|
||||||
if file_names.is_empty() {
|
if file_names.is_empty() {
|
||||||
TextBox::new(
|
TextBox::new(
|
||||||
&no_bins,
|
&no_bins,
|
||||||
Rectangle::new(
|
Rectangle::new(
|
||||||
Point::new(25, 25),
|
Point::new(25, 25),
|
||||||
Size::new(display_area.size.width - 50, display_area.size.width - 50),
|
Size::new(display_area.size.width - 50, display_area.size.width - 50),
|
||||||
),
|
),
|
||||||
text_style,
|
text_style,
|
||||||
)
|
)
|
||||||
.draw(*fb)
|
.draw(fb)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
} else {
|
} else {
|
||||||
let mut file_names = file_names.iter();
|
let mut file_names = file_names.iter();
|
||||||
let Some(first) = file_names.next() else {
|
let Some(first) = file_names.next() else {
|
||||||
Text::new("No Programs found on SD Card\nEnsure programs end with '.bin',\nand are located in the root directory",
|
Text::new("No Programs found on SD Card\nEnsure programs end with '.bin',\nand are located in the root directory",
|
||||||
Point::zero(), text_style).draw(*fb).unwrap();
|
Point::zero(), text_style).draw(fb).unwrap();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
};
|
|
||||||
|
|
||||||
let chain = Chain::new(Text::new(&first.long_name, Point::zero(), text_style));
|
|
||||||
|
|
||||||
// for _ in 0..file_names.len() {
|
|
||||||
// let chain = chain.append(Text::new(
|
|
||||||
// file_names.next().unwrap(),
|
|
||||||
// Point::zero(),
|
|
||||||
// text_style,
|
|
||||||
// ));
|
|
||||||
// }
|
|
||||||
|
|
||||||
LinearLayout::vertical(chain)
|
|
||||||
.with_alignment(horizontal::Center)
|
|
||||||
.arrange()
|
|
||||||
.align_to(&display_area, horizontal::Center, vertical::Center)
|
|
||||||
.draw(*fb)
|
|
||||||
.unwrap();
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
let chain = Chain::new(Text::new(&first.long_name, Point::zero(), text_style));
|
||||||
|
|
||||||
|
// for _ in 0..file_names.len() {
|
||||||
|
// let chain = chain.append(Text::new(
|
||||||
|
// file_names.next().unwrap(),
|
||||||
|
// Point::zero(),
|
||||||
|
// text_style,
|
||||||
|
// ));
|
||||||
|
// }
|
||||||
|
|
||||||
|
LinearLayout::vertical(chain)
|
||||||
|
.with_alignment(horizontal::Center)
|
||||||
|
.arrange()
|
||||||
|
.align_to(&display_area, horizontal::Center, vertical::Center)
|
||||||
|
.draw(fb)
|
||||||
|
.unwrap();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SelectionList {
|
pub struct SelectionList {
|
||||||
current_selection: u16,
|
current_selection: u16,
|
||||||
pub selections: Vec<FileName>,
|
pub selections: Vec<FileName>,
|
||||||
|
has_changed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SelectionList {
|
impl SelectionList {
|
||||||
@@ -146,6 +160,7 @@ impl SelectionList {
|
|||||||
Self {
|
Self {
|
||||||
selections: Vec::new(),
|
selections: Vec::new(),
|
||||||
current_selection: 0,
|
current_selection: 0,
|
||||||
|
has_changed: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,4 +179,10 @@ impl SelectionList {
|
|||||||
self.current_selection -= 1
|
self.current_selection -= 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn take_changed(&mut self) -> bool {
|
||||||
|
let changed = self.has_changed;
|
||||||
|
self.has_changed = false;
|
||||||
|
changed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,29 +27,29 @@ pub async fn main() {
|
|||||||
|
|
||||||
let mut text = vec!['H', 'E', 'L', 'L', 'O'];
|
let mut text = vec!['H', 'E', 'L', 'L', 'O'];
|
||||||
|
|
||||||
loop {
|
// loop {
|
||||||
Text::with_alignment(
|
Text::with_alignment(
|
||||||
&text.iter().cloned().collect::<String>(),
|
&text.iter().cloned().collect::<String>(),
|
||||||
display.bounding_box().center() + Point::new(0, 15),
|
display.bounding_box().center() + Point::new(0, 15),
|
||||||
character_style,
|
character_style,
|
||||||
Alignment::Center,
|
Alignment::Center,
|
||||||
)
|
)
|
||||||
.draw(&mut display)
|
.draw(&mut display)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
if let Some(event) = get_key() {
|
if let Some(event) = get_key() {
|
||||||
print("User got event");
|
print("User got event");
|
||||||
match event.key {
|
match event.key {
|
||||||
KeyCode::Char(ch) => {
|
KeyCode::Char(ch) => {
|
||||||
text.push(ch);
|
text.push(ch);
|
||||||
}
|
|
||||||
KeyCode::Backspace => {
|
|
||||||
text.pop();
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
|
KeyCode::Backspace => {
|
||||||
|
text.pop();
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
|
|||||||
Reference in New Issue
Block a user