Conversation
|
Current summary of my thoughts and notes: Adding frame_count field to the backend is neccesary for blinking. ratatui::Frame.count() exists but we cannot use it from the backend. Perhaps it would be nice in the future for ratatui to support exposing a general frame count on the backend, but thats a change to core beyond this PR. I added Cursor style and configuration structs, as well as Blink timing and config structs. Should make code cleaner and allow future customization options. Four cursor types are currently provided, inverse cursor fallbacks to underline when framebuffer unavailable. The blink configs hold some state. Should probably add tests, tests are always nice. Performance should also be checked to avoid regression. Feature gating blinking is an option if it makes perf really bad. |
j-g00da
left a comment
There was a problem hiding this comment.
Few thoughts:
I would lock blinking behind a feature flag anyway, it's additional computation and memory usage that in most cases won't be used.
I would also try to avoid using floats if not necessary, maybe rounding it to full ints would be good enough?
frame_count probably doesn't have to be usize, u8 may be enough.
EmbeddedBackendConfig is exhaustive, so this should be marked as a breaking change (and described in BREAKING_CHANGES.md)
|
Added a Removed use of floats frame_count changed to u16, the blink algorithm requires multiplication that would overflow on u8, I think storing as u16 is better than casting to u16 every update, imo. frame_count is also feature flagged Entry added to breaking changes md, with migration guide. |
Co-authored-by: Orhun Parmaksız <[email protected]>
Co-authored-by: Orhun Parmaksız <[email protected]>
3b90b06 to
1f9b1a4
Compare
Co-authored-by: Orhun Parmaksız <[email protected]>
Co-authored-by: Orhun Parmaksız <[email protected]>
Co-authored-by: Orhun Parmaksız <[email protected]>
|
Created cursor struct, cursor.rs module, moved Result type to error.rs for shared usage. I will work on condensing the demo I have and creating a nice gif for it. Currently the simulator screenshot inside the README is very similar to the screenshot right above it of the italics font. I am thinking of replacing the README simulator screenshot with a small gif showcasing blinking and cursor moving around, inside the simulator. But I am not sure if we should modify the code within the simulator demo, since having a bare minimum example is kind of nice, but idk, thoughts? |
|
gif and some sample code //! # Simulator
//!
//! A window will open with the simulator running.
//! Use arrow keys or WASD to move the cursor.
use std::cell::RefCell;
use std::rc::Rc;
use embedded_graphics_simulator::{
OutputSettings, SimulatorDisplay, SimulatorEvent, Window, sdl2::Keycode,
};
use mousefood::embedded_graphics::geometry;
use mousefood::embedded_graphics::prelude::RgbColor;
use mousefood::error::Error;
use mousefood::{CursorConfig, CursorStyle, prelude::*};
use ratatui::backend::Backend;
use ratatui::widgets::{Block, Paragraph, Wrap};
use ratatui::{Frame, Terminal, style::*};
fn main() -> Result<(), Error> {
let mut simulator_window = Window::new(
"mousefood simulator",
&OutputSettings {
scale: 4,
..Default::default()
},
);
simulator_window.set_max_fps(30);
let mut display = SimulatorDisplay::<Bgr565>::new(geometry::Size::new(128, 64));
let events: Rc<RefCell<Vec<SimulatorEvent>>> = Rc::new(RefCell::new(Vec::new()));
let events_cb = events.clone();
let backend_config = EmbeddedBackendConfig {
flush_callback: Box::new(move |display| {
simulator_window.update(display);
let mut ev = events_cb.borrow_mut();
ev.clear();
ev.extend(simulator_window.events());
}),
color_theme: ColorTheme::tokyo_night(),
cursor: CursorConfig {
style: CursorStyle::Inverse,
blink: true,
color: Rgb888::WHITE,
},
#[cfg(feature = "blink")]
blink: BlinkConfig {
fps: 30,
slow: BlinkTiming {
blinks_per_sec: 1,
duty_percent: 15,
},
fast: BlinkTiming {
blinks_per_sec: 3,
duty_percent: 50,
},
},
..Default::default()
};
let backend: EmbeddedBackend<SimulatorDisplay<_>, _> =
EmbeddedBackend::new(&mut display, backend_config);
let mut terminal = Terminal::new(backend)?;
let mut frame_count: usize = 0;
let mut cursor_x: u16 = 1;
let mut cursor_y: u16 = 1;
loop {
let count = frame_count;
let cx = cursor_x;
let cy = cursor_y;
terminal.draw(|frame| draw2(frame, count, cx, cy))?;
frame_count = frame_count.wrapping_add(1);
for event in events.borrow().iter() {
match event {
SimulatorEvent::KeyDown { keycode, .. } => match *keycode {
Keycode::Up | Keycode::W => {
cursor_y = cursor_y.saturating_sub(1);
}
Keycode::Down | Keycode::S => {
cursor_y = cursor_y.saturating_add(1);
}
Keycode::Left | Keycode::A => {
cursor_x = cursor_x.saturating_sub(1);
}
Keycode::Right | Keycode::D => {
cursor_x = cursor_x.saturating_add(1);
}
_ => {}
},
SimulatorEvent::Quit => return Ok(()),
_ => {}
}
}
if let Ok(size) = terminal.backend_mut().size() {
cursor_x = cursor_x.min(size.width.saturating_sub(1));
cursor_y = cursor_y.min(size.height.saturating_sub(1));
}
}
}
fn draw2(frame: &mut Frame, count: usize, cursor_x: u16, cursor_y: u16) {
use ratatui::style::Modifier;
use ratatui::text::{Line, Span};
let line = Line::from(vec![
Span::styled(format!("F:{count} "), Style::new().yellow()),
Span::styled("RED ", Style::new().fg(Color::Red)),
Span::styled(
"DIM ",
Style::new().fg(Color::Red).add_modifier(Modifier::DIM),
),
Span::styled("UNDR ", Style::new().add_modifier(Modifier::UNDERLINED)),
Span::styled("SLOW ", Style::new().add_modifier(Modifier::SLOW_BLINK)),
Span::styled("FAST ", Style::new().add_modifier(Modifier::RAPID_BLINK)),
Span::styled("REV ", Style::new().add_modifier(Modifier::REVERSED)),
Span::styled("HIDE ", Style::new().add_modifier(Modifier::HIDDEN)),
Span::styled("XOUT ", Style::new().add_modifier(Modifier::CROSSED_OUT)),
Span::styled(
"D+U ",
Style::new().add_modifier(Modifier::DIM | Modifier::UNDERLINED),
),
// combos
Span::styled(
"GHOST ",
Style::new()
.fg(Color::DarkGray)
.add_modifier(Modifier::DIM | Modifier::ITALIC),
),
Span::styled(
"ALARM ",
Style::new()
.fg(Color::Red)
.add_modifier(Modifier::RAPID_BLINK | Modifier::REVERSED),
),
Span::styled(
"DEAD ",
Style::new()
.fg(Color::Gray)
.add_modifier(Modifier::CROSSED_OUT | Modifier::DIM),
),
Span::styled(
"SHOUT ",
Style::new()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD | Modifier::UNDERLINED),
),
Span::styled(
"HAUNT ",
Style::new()
.fg(Color::Magenta)
.add_modifier(Modifier::SLOW_BLINK | Modifier::DIM),
),
Span::styled(
"CRIT",
Style::new()
.fg(Color::White)
.bg(Color::Red)
.add_modifier(Modifier::BOLD | Modifier::RAPID_BLINK),
),
]);
let paragraph = Paragraph::new(vec![line]).wrap(Wrap { trim: true });
let bordered_block = Block::bordered()
.border_style(Style::new().yellow())
.title("Mods");
frame.render_widget(paragraph.block(bordered_block), frame.area());
frame.set_cursor_position((cursor_x, cursor_y));
}[package]
name = "simulator"
edition.workspace = true
license.workspace = true
publish = false
[dependencies]
mousefood = { path = "../../mousefood/" , features = ["blink"]}
embedded-graphics-simulator.workspace = true
ratatui.workspace = true
|
|
I like how the demo you posted looks like!
Agreed. One option is extracting all the demo UI elements into a new module and simply calling a function from the main file. Or we could have multiple binaries in the simulator app, so that the user can run |
|
Applied suggested changes by orhun |


This PR implements all modifier types, adds cursor and blinking support, along with configuration structs for blinking and cursor.
I split the current draw function into draw and draw_cell, to be able to reuse the draw_cell logic for blinking. Blinking requires keeping track of cells which must be redrawn, as far as I am aware this is what terminal emulators do on their side. It is necessary because the draw function only draws changed cells, blinking cells arent fundamentally changed per blink, so they dont get caught by the draw function. The cursor drawing is done inside flush() due to the way ratatui handles drawing, adding the cursor to the draw() function did not work. I am using a BTreeMap cause normal Hashmap not available in alloc.
Something to discuss about Blink implementations: we need to support some sort of timing. There are crates such as embedded_time which looks pretty good but hasn't been updated in almost 5 years and would be an extra dependency. And the user/us would have to provide an impl for every supported device.
An alternative to using proper timing, we can add a frame counter field to the backend struct. This is what this PR uses.
PS: I stole some of the cursor code from jagoda's cursor PR, im not very good at git stuff so i didnt know how to merge it into my branch oops
This PR closes:
#58
#10
#35