Working basics
This commit is contained in:
parent
7a68f5ad19
commit
e174ecdeaf
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -789,6 +789,7 @@ dependencies = [
|
|||||||
"profont",
|
"profont",
|
||||||
"rand",
|
"rand",
|
||||||
"shared",
|
"shared",
|
||||||
|
"strum 0.26.3",
|
||||||
"weact-studio-epd",
|
"weact-studio-epd",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ weact-studio-epd = { version = "0.1.2", features = ["blocking"] }
|
|||||||
|
|
||||||
shared = { path = "./shared", features = ['trng'] }
|
shared = { path = "./shared", features = ['trng'] }
|
||||||
maze = { path = "./maze" }
|
maze = { path = "./maze" }
|
||||||
|
strum = { version = "0.26.3", default-features = false, features = ["derive"] }
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
# Rust debug is too slow.
|
# Rust debug is too slow.
|
||||||
|
@ -315,7 +315,6 @@ fn choose_orientation(width: usize, height: usize, trng: &mut impl GenU32) -> Or
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use shared::GenU32;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn create_maze() {
|
fn create_maze() {
|
||||||
@ -485,19 +484,19 @@ impl<const X: usize, const Y: usize, const SIZE: usize> BinaryMap<X, Y, SIZE> {
|
|||||||
Self([MazePart::Noop; SIZE])
|
Self([MazePart::Noop; SIZE])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn at(&self, x: usize, y: usize) -> MazePart {
|
pub fn at(&self, x: u16, y: u16) -> MazePart {
|
||||||
self.0[y * X + x]
|
self.0[y as usize * X + x as usize]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn can_move(&self, player: (usize, usize), dir: Direction) -> bool {
|
pub fn can_move(&self, player: (u16, u16), dir: Direction) -> bool {
|
||||||
match dir {
|
match dir {
|
||||||
Direction::West if player.0 == 0 => false,
|
Direction::West if player.0 == 0 => false,
|
||||||
Direction::West => self.at(player.0 - 1, player.1) != MazePart::Wall,
|
Direction::West => self.at(player.0 - 1, player.1) != MazePart::Wall,
|
||||||
Direction::East if player.0 == X - 1 => false,
|
Direction::East if player.0 as usize == X - 1 => false,
|
||||||
Direction::East => self.at(player.0 + 1, player.1) != MazePart::Wall,
|
Direction::East => self.at(player.0 + 1, player.1) != MazePart::Wall,
|
||||||
Direction::North if player.1 == 0 => false,
|
Direction::North if player.1 == 0 => false,
|
||||||
Direction::North => self.at(player.0, player.1 - 1) != MazePart::Wall,
|
Direction::North => self.at(player.0, player.1 - 1) != MazePart::Wall,
|
||||||
Direction::South if player.1 == Y - 1 => false,
|
Direction::South if player.1 as usize == Y - 1 => false,
|
||||||
Direction::South => self.at(player.0, player.1 + 1) != MazePart::Wall,
|
Direction::South => self.at(player.0, player.1 + 1) != MazePart::Wall,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -624,8 +623,6 @@ pub enum MazePart {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod print_tests {
|
mod print_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::io::Cursor;
|
|
||||||
use std::io::Write;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn print() {
|
fn print() {
|
||||||
|
0
src/apps/guess_game.rs
Normal file
0
src/apps/guess_game.rs
Normal file
89
src/apps/maze_game.rs
Normal file
89
src/apps/maze_game.rs
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
use maze::{BinaryMapVisitor, Direction};
|
||||||
|
|
||||||
|
use crate::Button;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub struct MazeGame {
|
||||||
|
map: maze::BinaryMap<122, 122, 14884>,
|
||||||
|
player: (u16, u16),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MazeGame {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
map: maze::BinaryMap::new(),
|
||||||
|
player: (0, 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App for MazeGame {
|
||||||
|
fn start(&mut self, trng: &mut Trng) {
|
||||||
|
let mut grid = maze::Grid::<60, 60, 3600>::new();
|
||||||
|
maze::RecursiveDivision.generate(&mut grid, trng);
|
||||||
|
BinaryMapVisitor.format(&mut grid, &mut self.map.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(&self, display: &mut Display290TriColor) {
|
||||||
|
let wall_style = PrimitiveStyleBuilder::new()
|
||||||
|
.stroke_color(TriColor::Black)
|
||||||
|
.stroke_width(3)
|
||||||
|
.fill_color(TriColor::Black)
|
||||||
|
.build();
|
||||||
|
let player_style = PrimitiveStyleBuilder::new()
|
||||||
|
.stroke_color(TriColor::Red)
|
||||||
|
.stroke_width(3)
|
||||||
|
.fill_color(TriColor::Red)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
const X_OFFSET: i32 = 2;
|
||||||
|
const Y_OFFSET: i32 = 2;
|
||||||
|
|
||||||
|
for x in 0..122 {
|
||||||
|
for y in 0..122 {
|
||||||
|
match self.map.at(x, y) {
|
||||||
|
maze::MazePart::Wall => {
|
||||||
|
let p = Rectangle::new(
|
||||||
|
Point::new(x as i32 + X_OFFSET, y as i32 + Y_OFFSET),
|
||||||
|
Size::new(1, 1),
|
||||||
|
);
|
||||||
|
p.draw_styled(&wall_style, display).unwrap();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let p = Rectangle::new(
|
||||||
|
Point::new(
|
||||||
|
self.player.0 as i32 + X_OFFSET,
|
||||||
|
self.player.1 as i32 + Y_OFFSET,
|
||||||
|
),
|
||||||
|
Size::new(1, 1),
|
||||||
|
);
|
||||||
|
p.draw_styled(&player_style, display).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, ctx: &Context) -> Action {
|
||||||
|
let Some(button) = ctx.button_pressed else {
|
||||||
|
return Action::Noop;
|
||||||
|
};
|
||||||
|
match button {
|
||||||
|
Button::Up if self.map.can_move(self.player, Direction::North) => {
|
||||||
|
self.player.1 -= 1;
|
||||||
|
}
|
||||||
|
Button::Down if self.map.can_move(self.player, Direction::South) => {
|
||||||
|
self.player.1 += 1;
|
||||||
|
}
|
||||||
|
Button::Left if self.map.can_move(self.player, Direction::West) => {
|
||||||
|
self.player.0 -= 1;
|
||||||
|
}
|
||||||
|
Button::Right if self.map.can_move(self.player, Direction::East) => {
|
||||||
|
self.player.0 += 1;
|
||||||
|
}
|
||||||
|
Button::Back => return Action::GoToMenu,
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
Action::Noop
|
||||||
|
}
|
||||||
|
}
|
90
src/apps/menu.rs
Normal file
90
src/apps/menu.rs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
use crate::Button;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use profont::PROFONT_24_POINT;
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Default, PartialEq, strum::EnumIter)]
|
||||||
|
enum MenuEntry {
|
||||||
|
#[default]
|
||||||
|
Labirynth,
|
||||||
|
Pairs,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MenuEntry {
|
||||||
|
fn as_str(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
MenuEntry::Labirynth => "Labirynt",
|
||||||
|
MenuEntry::Pairs => "Pary",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn position(&self) -> Point {
|
||||||
|
match self {
|
||||||
|
MenuEntry::Labirynth => Point::new(8, 68),
|
||||||
|
MenuEntry::Pairs => Point::new(38, 68),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Menu {
|
||||||
|
selected: MenuEntry,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Menu {
|
||||||
|
pub fn new() -> Menu {
|
||||||
|
Menu {
|
||||||
|
selected: MenuEntry::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App for Menu {
|
||||||
|
fn start(&mut self, _trng: &mut Trng) {}
|
||||||
|
|
||||||
|
fn update(&mut self, ctx: &Context) -> Action {
|
||||||
|
let Some(button) = ctx.button_pressed else {
|
||||||
|
return Action::Noop;
|
||||||
|
};
|
||||||
|
match button {
|
||||||
|
Button::Up => match self.selected {
|
||||||
|
MenuEntry::Labirynth => return Action::Noop,
|
||||||
|
MenuEntry::Pairs => {
|
||||||
|
self.selected = MenuEntry::Labirynth;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Button::Down => match self.selected {
|
||||||
|
MenuEntry::Labirynth => {
|
||||||
|
self.selected = MenuEntry::Pairs;
|
||||||
|
}
|
||||||
|
MenuEntry::Pairs => return Action::Noop,
|
||||||
|
},
|
||||||
|
Button::Circle => match self.selected {
|
||||||
|
MenuEntry::Labirynth => return Action::StartMaze,
|
||||||
|
MenuEntry::Pairs => return Action::StartPairs,
|
||||||
|
},
|
||||||
|
_ => return Action::Noop,
|
||||||
|
};
|
||||||
|
Action::Noop
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(&self, display: &mut Display290TriColor) {
|
||||||
|
let style = MonoTextStyle::new(&PROFONT_24_POINT, TriColor::Black);
|
||||||
|
let selected_style = MonoTextStyle::new(&PROFONT_24_POINT, TriColor::Red);
|
||||||
|
|
||||||
|
MenuEntry::iter().for_each(|entry| {
|
||||||
|
let _ = Text::with_text_style(
|
||||||
|
entry.as_str(),
|
||||||
|
entry.position(),
|
||||||
|
if entry == self.selected {
|
||||||
|
selected_style
|
||||||
|
} else {
|
||||||
|
style
|
||||||
|
},
|
||||||
|
TextStyle::default(),
|
||||||
|
)
|
||||||
|
.draw(display)
|
||||||
|
.unwrap();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
67
src/apps/mod.rs
Normal file
67
src/apps/mod.rs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
use embedded_graphics::{
|
||||||
|
geometry::{Point, Size},
|
||||||
|
mono_font::MonoTextStyle,
|
||||||
|
primitives::{PrimitiveStyleBuilder, Rectangle, StyledDrawable},
|
||||||
|
text::{Text, TextStyle},
|
||||||
|
Drawable,
|
||||||
|
};
|
||||||
|
use esp_hal::rng::Trng;
|
||||||
|
use maze_game::MazeGame;
|
||||||
|
use menu::Menu;
|
||||||
|
use weact_studio_epd::{graphics::Display290TriColor, TriColor};
|
||||||
|
|
||||||
|
use crate::Context;
|
||||||
|
|
||||||
|
pub mod guess_game;
|
||||||
|
pub mod maze_game;
|
||||||
|
pub mod menu;
|
||||||
|
|
||||||
|
pub enum Action {
|
||||||
|
Noop,
|
||||||
|
GoToMenu,
|
||||||
|
StartMaze,
|
||||||
|
StartPairs,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Application {
|
||||||
|
Menu(Menu),
|
||||||
|
Maze(maze_game::MazeGame),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Application {
|
||||||
|
pub fn update(&mut self, ctx: &Context, trng: &mut Trng) {
|
||||||
|
let action = match self {
|
||||||
|
Self::Menu(menu) => menu.update(ctx),
|
||||||
|
Self::Maze(maze) => maze.update(ctx),
|
||||||
|
};
|
||||||
|
match action {
|
||||||
|
Action::StartMaze => {
|
||||||
|
let mut maze = MazeGame::new();
|
||||||
|
maze.start(trng);
|
||||||
|
*self = Application::Maze(maze);
|
||||||
|
}
|
||||||
|
Action::GoToMenu => {
|
||||||
|
let mut menu = Menu::new();
|
||||||
|
menu.start(trng);
|
||||||
|
*self = Application::Menu(menu);
|
||||||
|
}
|
||||||
|
Action::Noop => {}
|
||||||
|
Action::StartPairs => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(&self, display: &mut Display290TriColor) {
|
||||||
|
match self {
|
||||||
|
Self::Menu(menu) => menu.draw(display),
|
||||||
|
Self::Maze(maze) => maze.draw(display),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait App {
|
||||||
|
fn start(&mut self, trng: &mut Trng);
|
||||||
|
|
||||||
|
fn draw(&self, display: &mut Display290TriColor);
|
||||||
|
|
||||||
|
fn update(&mut self, ctx: &Context) -> Action;
|
||||||
|
}
|
117
src/main.rs
117
src/main.rs
@ -1,13 +1,11 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
use core::fmt::Write;
|
use apps::{menu::Menu, Application};
|
||||||
use display_interface_spi::SPIInterface;
|
use display_interface_spi::SPIInterface;
|
||||||
use embedded_graphics::{
|
use embedded_graphics::{
|
||||||
geometry::Point,
|
geometry::Point,
|
||||||
mono_font::MonoTextStyle,
|
mono_font::MonoTextStyle,
|
||||||
pixelcolor::Rgb565,
|
|
||||||
primitives::{Line, PrimitiveStyle},
|
|
||||||
text::{Text, TextStyle},
|
text::{Text, TextStyle},
|
||||||
Drawable,
|
Drawable,
|
||||||
};
|
};
|
||||||
@ -16,19 +14,22 @@ use esp_backtrace as _;
|
|||||||
use esp_hal::{
|
use esp_hal::{
|
||||||
clock::ClockControl,
|
clock::ClockControl,
|
||||||
delay::Delay,
|
delay::Delay,
|
||||||
gpio::{Input, Io, Level, Output, NO_PIN},
|
gpio::{
|
||||||
|
Gpio0, Gpio1, Gpio2, Gpio3, Gpio4, Gpio5, Gpio6, Gpio7, GpioPin, Input, Io, Level, Output,
|
||||||
|
NO_PIN,
|
||||||
|
},
|
||||||
peripherals::Peripherals,
|
peripherals::Peripherals,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
rng::Trng,
|
rng::Trng,
|
||||||
spi::{master::Spi, SpiMode},
|
spi::{master::Spi, SpiMode},
|
||||||
system::SystemControl,
|
system::SystemControl,
|
||||||
};
|
};
|
||||||
use heapless::String;
|
|
||||||
use maze::{BinaryMap, BinaryMapVisitor};
|
|
||||||
use profont::PROFONT_24_POINT;
|
use profont::PROFONT_24_POINT;
|
||||||
use weact_studio_epd::{graphics::Display290TriColor, TriColor};
|
use weact_studio_epd::{graphics::Display290TriColor, TriColor};
|
||||||
use weact_studio_epd::{graphics::DisplayRotation, WeActStudio290TriColorDriver};
|
use weact_studio_epd::{graphics::DisplayRotation, WeActStudio290TriColorDriver};
|
||||||
|
|
||||||
|
mod apps;
|
||||||
|
|
||||||
#[entry]
|
#[entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
let peripherals = Peripherals::take();
|
let peripherals = Peripherals::take();
|
||||||
@ -44,9 +45,9 @@ fn main() -> ! {
|
|||||||
|
|
||||||
log::info!("Intializing SPI Bus...");
|
log::info!("Intializing SPI Bus...");
|
||||||
|
|
||||||
// Pins for Seeedstudio XIAO ESP32-C6
|
// Pins for WeAct
|
||||||
let sclk = io.pins.gpio19; // D8 / GPIO19
|
|
||||||
let mosi = io.pins.gpio18; // D10 / GPIO18
|
let mosi = io.pins.gpio18; // D10 / GPIO18
|
||||||
|
let sclk = io.pins.gpio19; // D8 / GPIO19
|
||||||
let cs = io.pins.gpio20; // D9 / GPIO20
|
let cs = io.pins.gpio20; // D9 / GPIO20
|
||||||
let dc = io.pins.gpio21; // D3 / GPIO21
|
let dc = io.pins.gpio21; // D3 / GPIO21
|
||||||
let rst = io.pins.gpio22; // D4 / GPIO22
|
let rst = io.pins.gpio22; // D4 / GPIO22
|
||||||
@ -84,7 +85,7 @@ fn main() -> ! {
|
|||||||
|
|
||||||
let style = MonoTextStyle::new(&PROFONT_24_POINT, TriColor::Black);
|
let style = MonoTextStyle::new(&PROFONT_24_POINT, TriColor::Black);
|
||||||
let _ = Text::with_text_style(
|
let _ = Text::with_text_style(
|
||||||
"Hello World!",
|
"Welcome in Reddy",
|
||||||
Point::new(8, 68),
|
Point::new(8, 68),
|
||||||
style,
|
style,
|
||||||
TextStyle::default(),
|
TextStyle::default(),
|
||||||
@ -93,51 +94,95 @@ fn main() -> ! {
|
|||||||
|
|
||||||
driver.full_update(&display).unwrap();
|
driver.full_update(&display).unwrap();
|
||||||
|
|
||||||
log::info!("Sleeping for 5s...");
|
log::info!("Sleeping for 0.5s...");
|
||||||
driver.sleep().unwrap();
|
driver.sleep().unwrap();
|
||||||
delay.delay(5_000.millis());
|
delay.delay(500.millis());
|
||||||
|
|
||||||
|
let kbd = Keyboard {
|
||||||
|
gpio0: io.pins.gpio0,
|
||||||
|
gpio1: io.pins.gpio1,
|
||||||
|
gpio2: io.pins.gpio2,
|
||||||
|
gpio3: io.pins.gpio3,
|
||||||
|
gpio4: io.pins.gpio4,
|
||||||
|
gpio5: io.pins.gpio5,
|
||||||
|
gpio6: io.pins.gpio6,
|
||||||
|
gpio7: io.pins.gpio7,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut ctx = Context {
|
||||||
|
button_pressed: None,
|
||||||
|
};
|
||||||
|
let mut app = Application::Menu(Menu::new());
|
||||||
|
|
||||||
let mut n: u8 = 0;
|
|
||||||
loop {
|
loop {
|
||||||
log::info!("Wake up!");
|
log::info!("Wake up!");
|
||||||
driver.wake_up().unwrap();
|
driver.wake_up().unwrap();
|
||||||
|
|
||||||
display.clear(TriColor::White);
|
display.clear(TriColor::White);
|
||||||
|
|
||||||
let mut string_buf = String::<30>::new();
|
ctx.button_pressed = kbd.pressed();
|
||||||
write!(string_buf, "Hello World {}!", n).unwrap();
|
app.update(&ctx, &mut trng);
|
||||||
let _ = Text::with_text_style(&string_buf, Point::new(8, 68), style, TextStyle::default())
|
app.draw(&mut display);
|
||||||
.draw(&mut display)
|
|
||||||
.unwrap();
|
|
||||||
string_buf.clear();
|
|
||||||
|
|
||||||
// TODO: try fast update?
|
// TODO: try fast update?
|
||||||
driver.full_update(&display).unwrap();
|
driver.full_update(&display).unwrap();
|
||||||
|
|
||||||
n = n.wrapping_add(1); // Wrap from 0..255
|
|
||||||
|
|
||||||
log::info!("Sleeping for 5s...");
|
log::info!("Sleeping for 5s...");
|
||||||
driver.sleep().unwrap();
|
driver.sleep().unwrap();
|
||||||
delay.delay(5_000.millis());
|
delay.delay(5_000.millis());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MazeGame {
|
/// Buttons
|
||||||
map: maze::BinaryMap<122, 122, 14884>,
|
///
|
||||||
|
/// +-------------------------------+
|
||||||
|
/// | A | Up | B | X |
|
||||||
|
/// | Left | Circle | Right | Y |
|
||||||
|
/// | C | Down | D | Z |
|
||||||
|
/// | M1 | M2 | M3 | Back |
|
||||||
|
/// +-------------------------------+
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub enum Button {
|
||||||
|
// row 1
|
||||||
|
A,
|
||||||
|
Up,
|
||||||
|
B,
|
||||||
|
X,
|
||||||
|
// row 2
|
||||||
|
Left,
|
||||||
|
Circle,
|
||||||
|
Right,
|
||||||
|
Y,
|
||||||
|
// row 3
|
||||||
|
C,
|
||||||
|
Down,
|
||||||
|
D,
|
||||||
|
Z,
|
||||||
|
// row 4
|
||||||
|
M1,
|
||||||
|
M2,
|
||||||
|
M3,
|
||||||
|
Back,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MazeGame {
|
pub struct Context {
|
||||||
pub fn new() -> Self {
|
button_pressed: Option<Button>,
|
||||||
Self {
|
}
|
||||||
map: maze::BinaryMap::new(),
|
|
||||||
}
|
struct Keyboard {
|
||||||
}
|
gpio0: Gpio0, //
|
||||||
|
gpio1: Gpio1, //
|
||||||
pub fn start(&mut self, trng: &mut Trng) {
|
gpio2: Gpio2, //
|
||||||
let mut grid = maze::Grid::<60, 60, 3600>::new();
|
gpio3: Gpio3, //
|
||||||
maze::RecursiveDivision.generate(&mut grid, trng);
|
//
|
||||||
BinaryMapVisitor.format(&mut grid, &mut self.map.0);
|
gpio4: Gpio4, //
|
||||||
}
|
gpio5: Gpio5, //
|
||||||
|
gpio6: Gpio6, //
|
||||||
pub fn draw(&self, dispay: Display290TriColor) {}
|
gpio7: Gpio7, //
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Keyboard {
|
||||||
|
pub fn pressed(&self) -> Option<Button> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user