mirror of
https://github.com/LegitCamper/picocalc-os-rs.git
synced 2025-12-27 07:45:28 +00:00
rename libraries
fix remove
This commit is contained in:
9
user_apps/calculator/Cargo.toml
Normal file
9
user_apps/calculator/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "calculator"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
userlib = { path = "../../userlib" }
|
||||
embedded-graphics = "0.8.1"
|
||||
embedded-layout = "0.4.2"
|
||||
28
user_apps/calculator/build.rs
Normal file
28
user_apps/calculator/build.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
//! This build script copies the `memory.x` file from the crate root into
|
||||
//! a directory where the linker can always find it at build time.
|
||||
//! For many projects this is optional, as the linker always searches the
|
||||
//! project root directory -- wherever `Cargo.toml` is. However, if you
|
||||
//! are using a workspace or have a more complicated build setup, this
|
||||
//! build script becomes required. Additionally, by requesting that
|
||||
//! Cargo re-run the build script whenever `memory.x` is changed,
|
||||
//! updating `memory.x` ensures a rebuild of the application with the
|
||||
//! new memory settings.
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
// Put `memory.x` in our output directory and ensure it's
|
||||
// on the linker search path.
|
||||
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
File::create(out.join("memory.x"))
|
||||
.unwrap()
|
||||
.write_all(include_bytes!("../memory.x"))
|
||||
.unwrap();
|
||||
println!("cargo:rustc-link-search={}", out.display());
|
||||
|
||||
println!("cargo:rerun-if-changed=memory.x");
|
||||
println!("cargo:rustc-link-arg-bins=-Tmemory.x");
|
||||
}
|
||||
200
user_apps/calculator/src/main.rs
Normal file
200
user_apps/calculator/src/main.rs
Normal file
@@ -0,0 +1,200 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
extern crate alloc;
|
||||
use alloc::{format, string::String, vec, vec::Vec};
|
||||
use core::panic::PanicInfo;
|
||||
use embedded_graphics::{
|
||||
Drawable,
|
||||
geometry::{Dimensions, Point},
|
||||
mono_font::{MonoTextStyle, ascii::FONT_7X14, iso_8859_1::FONT_10X20},
|
||||
pixelcolor::Rgb565,
|
||||
prelude::{Primitive, RgbColor},
|
||||
primitives::{PrimitiveStyle, Rectangle},
|
||||
text::Text,
|
||||
};
|
||||
use embedded_layout::{
|
||||
View,
|
||||
align::{Align, horizontal, vertical},
|
||||
layout::linear::LinearLayout,
|
||||
prelude::Chain,
|
||||
};
|
||||
use userlib::{
|
||||
display::Display,
|
||||
get_key,
|
||||
keyboard::{KeyCode, KeyState},
|
||||
println,
|
||||
};
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(info: &PanicInfo) -> ! {
|
||||
println!("user panic: {} @ {:?}", info.message(), info.location(),);
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "Rust" fn _start() {
|
||||
main()
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
println!("Starting Calculator app");
|
||||
let mut display = Display::take().unwrap();
|
||||
|
||||
let mut input = vec!['e', 'x', 'p', 'r', ':', ' '];
|
||||
let input_min = input.len();
|
||||
let mut dirty = true;
|
||||
let mut last_area: Option<(Rectangle, Rectangle)> = None;
|
||||
|
||||
LinearLayout::vertical(Chain::new(Text::new(
|
||||
"Calculator!",
|
||||
Point::zero(),
|
||||
MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE),
|
||||
)))
|
||||
.arrange()
|
||||
.align_to(&display.bounding_box(), horizontal::Center, vertical::Top)
|
||||
.draw(&mut display)
|
||||
.expect("Failed to draw title");
|
||||
|
||||
loop {
|
||||
if dirty {
|
||||
let style = PrimitiveStyle::with_fill(Rgb565::BLACK);
|
||||
if let Some(area) = last_area {
|
||||
Rectangle::new(area.0.top_left, area.0.size)
|
||||
.into_styled(style)
|
||||
.draw(&mut display)
|
||||
.unwrap();
|
||||
|
||||
Rectangle::new(area.1.top_left, area.1.size)
|
||||
.into_styled(style)
|
||||
.draw(&mut display)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let text = input.iter().cloned().collect::<String>();
|
||||
|
||||
let style = MonoTextStyle::new(&FONT_7X14, Rgb565::WHITE);
|
||||
let expr_layout = LinearLayout::vertical(Chain::new(Text::new(
|
||||
&text,
|
||||
display.bounding_box().center(),
|
||||
style,
|
||||
)))
|
||||
.arrange()
|
||||
.align_to(&display.bounding_box(), horizontal::Left, vertical::Center);
|
||||
|
||||
let result = if let Ok(result) = evaluate(&input[input_min..]) {
|
||||
&format!(" = {}", result)
|
||||
} else {
|
||||
" = Error"
|
||||
};
|
||||
|
||||
let eq_layout = LinearLayout::vertical(Chain::new(Text::new(
|
||||
result,
|
||||
display.bounding_box().center(),
|
||||
style,
|
||||
)))
|
||||
.arrange()
|
||||
.align_to(&display.bounding_box(), horizontal::Right, vertical::Center);
|
||||
|
||||
last_area = Some((expr_layout.bounds(), eq_layout.bounds()));
|
||||
expr_layout.draw(&mut display).unwrap();
|
||||
eq_layout.draw(&mut display).unwrap();
|
||||
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
let event = get_key();
|
||||
if event.state == KeyState::Released {
|
||||
match event.key {
|
||||
KeyCode::Char(ch) => {
|
||||
input.push(ch);
|
||||
}
|
||||
KeyCode::Del => {
|
||||
input.truncate(input_min);
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
if input.len() > input_min {
|
||||
input.pop();
|
||||
}
|
||||
}
|
||||
KeyCode::Esc => return,
|
||||
_ => (),
|
||||
}
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_int(int: &[char]) -> Result<i32, ()> {
|
||||
let mut output: i32 = 0;
|
||||
for &c in int {
|
||||
let digit = c.to_digit(10).ok_or(())? as i32;
|
||||
output = output
|
||||
.checked_mul(10)
|
||||
.and_then(|v| v.checked_add(digit))
|
||||
.ok_or(())?;
|
||||
}
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn primary(input: &[char], pos: &mut usize) -> Result<i32, ()> {
|
||||
let mut digits = Vec::new();
|
||||
while *pos < input.len() && input[*pos].is_ascii_digit() {
|
||||
digits.push(input[*pos]);
|
||||
*pos += 1;
|
||||
}
|
||||
if digits.is_empty() {
|
||||
return Err(());
|
||||
}
|
||||
get_int(&digits)
|
||||
}
|
||||
|
||||
fn mul_div(input: &[char], pos: &mut usize) -> Result<i32, ()> {
|
||||
let mut value = primary(input, pos)?;
|
||||
while *pos < input.len() {
|
||||
let op = input[*pos];
|
||||
if op != '*' && op != '/' {
|
||||
break;
|
||||
}
|
||||
*pos += 1;
|
||||
let rhs = primary(input, pos)?;
|
||||
value = match op {
|
||||
'*' => value.checked_mul(rhs).ok_or(())?,
|
||||
'/' => {
|
||||
if rhs == 0 {
|
||||
return Err(());
|
||||
}
|
||||
value.checked_div(rhs).ok_or(())?
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
fn add_sub(input: &[char], pos: &mut usize) -> Result<i32, ()> {
|
||||
let mut value = mul_div(input, pos)?;
|
||||
while *pos < input.len() {
|
||||
let op = input[*pos];
|
||||
if op != '+' && op != '-' {
|
||||
break;
|
||||
}
|
||||
*pos += 1;
|
||||
let rhs = mul_div(input, pos)?;
|
||||
value = match op {
|
||||
'+' => value.checked_add(rhs).ok_or(())?,
|
||||
'-' => value.checked_sub(rhs).ok_or(())?,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
fn evaluate(input: &[char]) -> Result<i32, ()> {
|
||||
let mut pos = 0;
|
||||
let result = add_sub(input, &mut pos)?;
|
||||
if pos != input.len() {
|
||||
return Err(());
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
9
user_apps/gallery/Cargo.toml
Normal file
9
user_apps/gallery/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "gallery"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
userlib = { path = "../../userlib" }
|
||||
embedded-graphics = "0.8.1"
|
||||
tinybmp = "0.6.0"
|
||||
28
user_apps/gallery/build.rs
Normal file
28
user_apps/gallery/build.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
//! This build script copies the `memory.x` file from the crate root into
|
||||
//! a directory where the linker can always find it at build time.
|
||||
//! For many projects this is optional, as the linker always searches the
|
||||
//! project root directory -- wherever `Cargo.toml` is. However, if you
|
||||
//! are using a workspace or have a more complicated build setup, this
|
||||
//! build script becomes required. Additionally, by requesting that
|
||||
//! Cargo re-run the build script whenever `memory.x` is changed,
|
||||
//! updating `memory.x` ensures a rebuild of the application with the
|
||||
//! new memory settings.
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
// Put `memory.x` in our output directory and ensure it's
|
||||
// on the linker search path.
|
||||
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
File::create(out.join("memory.x"))
|
||||
.unwrap()
|
||||
.write_all(include_bytes!("../memory.x"))
|
||||
.unwrap();
|
||||
println!("cargo:rustc-link-search={}", out.display());
|
||||
|
||||
println!("cargo:rerun-if-changed=memory.x");
|
||||
println!("cargo:rustc-link-arg-bins=-Tmemory.x");
|
||||
}
|
||||
95
user_apps/gallery/src/main.rs
Normal file
95
user_apps/gallery/src/main.rs
Normal file
@@ -0,0 +1,95 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![allow(static_mut_refs)]
|
||||
|
||||
extern crate alloc;
|
||||
use alloc::{format, vec};
|
||||
use core::panic::PanicInfo;
|
||||
use embedded_graphics::{
|
||||
Drawable, image::Image, mono_font::MonoTextStyle, mono_font::ascii::FONT_6X10,
|
||||
pixelcolor::Rgb565, prelude::*, text::Text,
|
||||
};
|
||||
use tinybmp::Bmp;
|
||||
use userlib::{
|
||||
display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH},
|
||||
fs::{Entries, list_dir, read_file},
|
||||
get_key,
|
||||
keyboard::{KeyCode, KeyState},
|
||||
println,
|
||||
};
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(info: &PanicInfo) -> ! {
|
||||
println!("user panic: {} @ {:?}", info.message(), info.location());
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "Rust" fn _start() {
|
||||
main()
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
println!("Starting Gallery app");
|
||||
let mut bmp_buf = vec![0_u8; 100_000];
|
||||
let mut display = Display::take().unwrap();
|
||||
|
||||
let grid_cols = 3;
|
||||
let grid_rows = 3;
|
||||
let cell_width = SCREEN_WIDTH as i32 / grid_cols;
|
||||
let cell_height = SCREEN_HEIGHT as i32 / grid_rows;
|
||||
|
||||
let mut images_drawn = 0;
|
||||
|
||||
let mut entries = Entries::new();
|
||||
let files_num = list_dir("/images", &mut entries);
|
||||
|
||||
for file in &entries.entries()[2..files_num] {
|
||||
if images_drawn >= grid_cols * grid_rows {
|
||||
break; // only draw 3x3
|
||||
}
|
||||
|
||||
println!("file: {}", file);
|
||||
if file.extension().unwrap_or("") == "bmp" || file.extension().unwrap_or("") == "BMP" {
|
||||
let file_path = format!("/images/{}", file);
|
||||
|
||||
let read = read_file(&file_path, 0, &mut &mut bmp_buf[..]);
|
||||
if read > 0 {
|
||||
let bmp = Bmp::from_slice(&bmp_buf).expect("failed to parse bmp");
|
||||
|
||||
let row = images_drawn / grid_cols;
|
||||
let col = images_drawn % grid_cols;
|
||||
let cell_x = col * cell_width;
|
||||
let cell_y = row * cell_height;
|
||||
|
||||
// Center image inside cell
|
||||
let bmp_w = bmp.size().width as i32;
|
||||
let bmp_h = bmp.size().height as i32;
|
||||
let x = cell_x + (cell_width - bmp_w) / 2;
|
||||
let y = cell_y + 5; // 5px top margin
|
||||
|
||||
Image::new(&bmp, Point::new(x, y))
|
||||
.draw(&mut display)
|
||||
.unwrap();
|
||||
|
||||
let text_style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE);
|
||||
let text_y = y + bmp_h + 2; // 2px gap under image
|
||||
Text::new(&file.base(), Point::new(cell_x + 2, text_y), text_style)
|
||||
.draw(&mut display)
|
||||
.unwrap();
|
||||
|
||||
images_drawn += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
let event = get_key();
|
||||
if event.state != KeyState::Idle {
|
||||
match event.key {
|
||||
KeyCode::Esc => return,
|
||||
_ => (),
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
10
user_apps/gif/Cargo.toml
Normal file
10
user_apps/gif/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "gif"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
userlib = { path = "../../userlib" }
|
||||
selection_ui = { path = "../../selection_ui" }
|
||||
embedded-graphics = "0.8.1"
|
||||
tinygif = { git = "https://github.com/LegitCamper/tinygif" }
|
||||
28
user_apps/gif/build.rs
Normal file
28
user_apps/gif/build.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
//! This build script copies the `memory.x` file from the crate root into
|
||||
//! a directory where the linker can always find it at build time.
|
||||
//! For many projects this is optional, as the linker always searches the
|
||||
//! project root directory -- wherever `Cargo.toml` is. However, if you
|
||||
//! are using a workspace or have a more complicated build setup, this
|
||||
//! build script becomes required. Additionally, by requesting that
|
||||
//! Cargo re-run the build script whenever `memory.x` is changed,
|
||||
//! updating `memory.x` ensures a rebuild of the application with the
|
||||
//! new memory settings.
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
// Put `memory.x` in our output directory and ensure it's
|
||||
// on the linker search path.
|
||||
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
File::create(out.join("memory.x"))
|
||||
.unwrap()
|
||||
.write_all(include_bytes!("../memory.x"))
|
||||
.unwrap();
|
||||
println!("cargo:rustc-link-search={}", out.display());
|
||||
|
||||
println!("cargo:rerun-if-changed=memory.x");
|
||||
println!("cargo:rustc-link-arg-bins=-Tmemory.x");
|
||||
}
|
||||
103
user_apps/gif/src/main.rs
Normal file
103
user_apps/gif/src/main.rs
Normal file
@@ -0,0 +1,103 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
extern crate alloc;
|
||||
use alloc::{format, vec, vec::Vec};
|
||||
use core::panic::PanicInfo;
|
||||
use embedded_graphics::{
|
||||
image::ImageDrawable,
|
||||
mono_font::{ascii::FONT_6X10, MonoTextStyle},
|
||||
pixelcolor::Rgb565,
|
||||
prelude::{Point, RgbColor},
|
||||
transform::Transform,
|
||||
};
|
||||
use selection_ui::{draw_text_center, SelectionUi, SelectionUiError};
|
||||
use tinygif::Gif;
|
||||
use userlib::{
|
||||
display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH},
|
||||
fs::{file_len, list_dir, read_file, Entries},
|
||||
get_key, get_ms,
|
||||
keyboard::{KeyCode, KeyState},
|
||||
println, sleep,
|
||||
};
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(info: &PanicInfo) -> ! {
|
||||
println!("user panic: {} @ {:?}", info.message(), info.location(),);
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "Rust" fn _start() {
|
||||
main()
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
println!("Starting Gif app");
|
||||
let mut display = Display::take().unwrap();
|
||||
|
||||
let mut entries = Entries::new();
|
||||
list_dir("/gifs", &mut entries);
|
||||
|
||||
let mut files = entries.entries();
|
||||
files.retain(|e| e.extension().unwrap_or("") == "gif");
|
||||
let mut gifs = files.iter().map(|e| e.full_name()).collect::<Vec<&str>>();
|
||||
gifs.sort();
|
||||
|
||||
let mut selection_ui = SelectionUi::new(&mut gifs);
|
||||
let selection = match selection_ui.run_selection_ui(&mut display) {
|
||||
Ok(maybe_sel) => maybe_sel,
|
||||
Err(e) => match e {
|
||||
SelectionUiError::SelectionListEmpty => {
|
||||
draw_text_center(
|
||||
&mut display,
|
||||
"No Gifs were found in /gifs",
|
||||
MonoTextStyle::new(&FONT_6X10, Rgb565::RED),
|
||||
)
|
||||
.expect("Display Error");
|
||||
None
|
||||
}
|
||||
SelectionUiError::DisplayError(_) => panic!("Display Error"),
|
||||
},
|
||||
};
|
||||
|
||||
assert!(selection.is_some());
|
||||
|
||||
let file_name = format!("/gifs/{}", gifs[selection.unwrap()]);
|
||||
let size = file_len(&file_name);
|
||||
let mut buf = vec![0_u8; size];
|
||||
let read = read_file(&file_name, 0, &mut buf);
|
||||
println!("read: {}, file size: {}", read, size);
|
||||
assert!(read == size);
|
||||
|
||||
let gif = Gif::<Rgb565>::from_slice(&buf).expect("Failed to parse gif");
|
||||
|
||||
let translation = Point::new(
|
||||
(SCREEN_WIDTH as i32 - gif.width() as i32) / 2,
|
||||
(SCREEN_HEIGHT as i32 - gif.height() as i32) / 2,
|
||||
);
|
||||
|
||||
let mut frame_num = 0;
|
||||
loop {
|
||||
for mut frame in gif.frames() {
|
||||
let start = get_ms();
|
||||
|
||||
frame.translate_mut(translation).draw(&mut display).unwrap();
|
||||
frame_num += 1;
|
||||
|
||||
if frame_num % 5 == 0 {
|
||||
let event = get_key();
|
||||
if event.state != KeyState::Idle {
|
||||
match event.key {
|
||||
KeyCode::Esc => {
|
||||
drop(buf);
|
||||
return;
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
};
|
||||
}
|
||||
sleep(((frame.delay_centis as u64) * 10).saturating_sub(start));
|
||||
}
|
||||
}
|
||||
}
|
||||
31
user_apps/memory.x
Normal file
31
user_apps/memory.x
Normal file
@@ -0,0 +1,31 @@
|
||||
MEMORY
|
||||
{
|
||||
RAM : ORIGIN = 0x0, LENGTH = 250K
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
.text : ALIGN(4)
|
||||
{
|
||||
*(.text .text.*);
|
||||
*(.rodata .rodata.*);
|
||||
} > RAM
|
||||
|
||||
.data : ALIGN(4)
|
||||
{
|
||||
*(.data .data.*);
|
||||
} > RAM
|
||||
|
||||
.bss : ALIGN(4)
|
||||
{
|
||||
*(.bss .bss.*);
|
||||
*(COMMON);
|
||||
} > RAM
|
||||
|
||||
.syscall_table (NOLOAD) : ALIGN(4)
|
||||
{
|
||||
__user_reloc_start = .;
|
||||
KEEP(*(.user_reloc*));
|
||||
__user_reloc_end = .;
|
||||
} > RAM
|
||||
}
|
||||
10
user_apps/snake/Cargo.toml
Normal file
10
user_apps/snake/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "snake"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
userlib = { path = "../../userlib" }
|
||||
embedded-graphics = "0.8.1"
|
||||
embedded-snake = { git = "https://github.com/LegitCamper/embedded-snake-rs" }
|
||||
rand = { version = "0.9.0", default-features = false }
|
||||
28
user_apps/snake/build.rs
Normal file
28
user_apps/snake/build.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
//! This build script copies the `memory.x` file from the crate root into
|
||||
//! a directory where the linker can always find it at build time.
|
||||
//! For many projects this is optional, as the linker always searches the
|
||||
//! project root directory -- wherever `Cargo.toml` is. However, if you
|
||||
//! are using a workspace or have a more complicated build setup, this
|
||||
//! build script becomes required. Additionally, by requesting that
|
||||
//! Cargo re-run the build script whenever `memory.x` is changed,
|
||||
//! updating `memory.x` ensures a rebuild of the application with the
|
||||
//! new memory settings.
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
// Put `memory.x` in our output directory and ensure it's
|
||||
// on the linker search path.
|
||||
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
File::create(out.join("memory.x"))
|
||||
.unwrap()
|
||||
.write_all(include_bytes!("../memory.x"))
|
||||
.unwrap();
|
||||
println!("cargo:rustc-link-search={}", out.display());
|
||||
|
||||
println!("cargo:rerun-if-changed=memory.x");
|
||||
println!("cargo:rustc-link-arg-bins=-Tmemory.x");
|
||||
}
|
||||
65
user_apps/snake/src/main.rs
Normal file
65
user_apps/snake/src/main.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
extern crate alloc;
|
||||
use core::panic::PanicInfo;
|
||||
use embedded_graphics::{pixelcolor::Rgb565, prelude::RgbColor};
|
||||
use embedded_snake::{Direction, SnakeGame};
|
||||
use userlib::{
|
||||
Rng,
|
||||
display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH},
|
||||
get_key,
|
||||
keyboard::{KeyCode, KeyState},
|
||||
println, sleep,
|
||||
};
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(info: &PanicInfo) -> ! {
|
||||
println!("user panic: {} @ {:?}", info.message(), info.location(),);
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "Rust" fn _start() {
|
||||
main()
|
||||
}
|
||||
|
||||
const CELL_SIZE: usize = 8;
|
||||
|
||||
pub fn main() {
|
||||
println!("Starting Snake app");
|
||||
let mut display = Display::take().unwrap();
|
||||
|
||||
let mut game = SnakeGame::<100, Rgb565, Rng>::new(
|
||||
SCREEN_WIDTH as u16,
|
||||
SCREEN_HEIGHT as u16,
|
||||
CELL_SIZE as u16,
|
||||
CELL_SIZE as u16,
|
||||
Rng,
|
||||
Rgb565::BLACK,
|
||||
Rgb565::GREEN,
|
||||
Rgb565::RED,
|
||||
50,
|
||||
);
|
||||
|
||||
loop {
|
||||
let event = get_key();
|
||||
if event.state != KeyState::Idle {
|
||||
let direction = match event.key {
|
||||
KeyCode::Up => Direction::Up,
|
||||
KeyCode::Down => Direction::Down,
|
||||
KeyCode::Right => Direction::Right,
|
||||
KeyCode::Left => Direction::Left,
|
||||
KeyCode::Esc => return,
|
||||
_ => Direction::None,
|
||||
};
|
||||
game.set_direction(direction);
|
||||
};
|
||||
|
||||
// ensure all draws show up at once
|
||||
game.pre_draw(&mut display);
|
||||
game.draw(&mut display);
|
||||
|
||||
sleep(15);
|
||||
}
|
||||
}
|
||||
11
user_apps/wav_player/Cargo.toml
Normal file
11
user_apps/wav_player/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "wav_player"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
userlib = { path = "../../userlib" }
|
||||
selection_ui = { path = "../../selection_ui" }
|
||||
embedded-graphics = "0.8.1"
|
||||
rand = { version = "0.9.0", default-features = false }
|
||||
embedded-audio = { git = "https://github.com/LegitCamper/embedded-audio" }
|
||||
28
user_apps/wav_player/build.rs
Normal file
28
user_apps/wav_player/build.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
//! This build script copies the `memory.x` file from the crate root into
|
||||
//! a directory where the linker can always find it at build time.
|
||||
//! For many projects this is optional, as the linker always searches the
|
||||
//! project root directory -- wherever `Cargo.toml` is. However, if you
|
||||
//! are using a workspace or have a more complicated build setup, this
|
||||
//! build script becomes required. Additionally, by requesting that
|
||||
//! Cargo re-run the build script whenever `memory.x` is changed,
|
||||
//! updating `memory.x` ensures a rebuild of the application with the
|
||||
//! new memory settings.
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
// Put `memory.x` in our output directory and ensure it's
|
||||
// on the linker search path.
|
||||
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
File::create(out.join("memory.x"))
|
||||
.unwrap()
|
||||
.write_all(include_bytes!("../memory.x"))
|
||||
.unwrap();
|
||||
println!("cargo:rustc-link-search={}", out.display());
|
||||
|
||||
println!("cargo:rerun-if-changed=memory.x");
|
||||
println!("cargo:rustc-link-arg-bins=-Tmemory.x");
|
||||
}
|
||||
146
user_apps/wav_player/src/main.rs
Normal file
146
user_apps/wav_player/src/main.rs
Normal file
@@ -0,0 +1,146 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
extern crate alloc;
|
||||
use alloc::{string::String, vec::Vec};
|
||||
use core::panic::PanicInfo;
|
||||
use embedded_audio::{wav::Wav, AudioFile, PlatformFile, PlatformFileError};
|
||||
use embedded_graphics::{
|
||||
mono_font::{ascii::FONT_6X10, MonoTextStyle},
|
||||
pixelcolor::Rgb565,
|
||||
prelude::RgbColor,
|
||||
};
|
||||
use selection_ui::{draw_text_center, SelectionUi, SelectionUiError};
|
||||
use userlib::{
|
||||
audio::{audio_buffer_ready, send_audio_buffer, AUDIO_BUFFER_LEN},
|
||||
display::Display,
|
||||
format,
|
||||
fs::{file_len, list_dir, read_file, Entries},
|
||||
get_key,
|
||||
keyboard::{KeyCode, KeyState},
|
||||
println,
|
||||
};
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(info: &PanicInfo) -> ! {
|
||||
println!("user panic: {} @ {:?}", info.message(), info.location(),);
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "Rust" fn _start() {
|
||||
main()
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
println!("Starting Wav player app");
|
||||
let mut display = Display::take().unwrap();
|
||||
|
||||
loop {
|
||||
let mut entries = Entries::new();
|
||||
list_dir("/music", &mut entries);
|
||||
|
||||
let mut files = entries.entries();
|
||||
files.retain(|e| e.extension().unwrap_or("") == "wav");
|
||||
let mut wavs = files.iter().map(|e| e.full_name()).collect::<Vec<&str>>();
|
||||
wavs.sort();
|
||||
|
||||
let mut selection_ui = SelectionUi::new(&mut wavs);
|
||||
let selection = match selection_ui.run_selection_ui(&mut display) {
|
||||
Ok(maybe_sel) => maybe_sel,
|
||||
Err(e) => match e {
|
||||
SelectionUiError::SelectionListEmpty => {
|
||||
draw_text_center(
|
||||
&mut display,
|
||||
"No Wavs were found in /music",
|
||||
MonoTextStyle::new(&FONT_6X10, Rgb565::RED),
|
||||
)
|
||||
.expect("Display Error");
|
||||
None
|
||||
}
|
||||
SelectionUiError::DisplayError(_) => panic!("Display Error"),
|
||||
},
|
||||
};
|
||||
|
||||
assert!(selection.is_some());
|
||||
|
||||
draw_text_center(
|
||||
&mut display,
|
||||
&format!("Now playing {}", wavs[selection.unwrap()]),
|
||||
MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE),
|
||||
)
|
||||
.expect("Display Error");
|
||||
|
||||
let file_name = format!("/music/{}", wavs[selection.unwrap()]);
|
||||
let file = File::new(String::from(file_name));
|
||||
let mut wav = Wav::new(file).unwrap();
|
||||
println!("sample rate: {}", wav.sample_rate());
|
||||
println!("channels: {:?}", wav.channels() as u8);
|
||||
|
||||
let mut buf = [0_u8; AUDIO_BUFFER_LEN];
|
||||
|
||||
loop {
|
||||
if audio_buffer_ready() {
|
||||
if wav.is_eof() {
|
||||
break;
|
||||
}
|
||||
|
||||
let _read = wav.read(&mut buf).unwrap();
|
||||
send_audio_buffer(&buf);
|
||||
}
|
||||
|
||||
let event = get_key();
|
||||
if event.state == KeyState::Released {
|
||||
match event.key {
|
||||
KeyCode::Esc => return,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct File {
|
||||
current_pos: usize,
|
||||
file: String,
|
||||
}
|
||||
|
||||
impl File {
|
||||
fn new(file: String) -> Self {
|
||||
Self {
|
||||
current_pos: 0,
|
||||
file,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformFile for File {
|
||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize, PlatformFileError> {
|
||||
let read = read_file(&self.file, self.current_pos, buf);
|
||||
self.current_pos += read;
|
||||
Ok(read)
|
||||
}
|
||||
|
||||
fn seek_from_current(&mut self, offset: i64) -> Result<(), PlatformFileError> {
|
||||
if offset.is_positive() {
|
||||
self.current_pos += offset as usize;
|
||||
} else {
|
||||
self.current_pos -= offset as usize;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn seek_from_start(&mut self, offset: usize) -> Result<(), PlatformFileError> {
|
||||
self.current_pos = offset;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn seek_from_end(&mut self, offset: usize) -> Result<(), PlatformFileError> {
|
||||
self.current_pos = self.length() - offset;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn length(&mut self) -> usize {
|
||||
file_len(&self.file)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user