Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
d1617b1
reversed and hidden work now
gold-silver-copper Feb 22, 2026
64bae30
dimming works now
gold-silver-copper Feb 22, 2026
45c7464
blinking works now
gold-silver-copper Feb 22, 2026
769756f
blinking cursor works
gold-silver-copper Feb 22, 2026
472fd3c
typos lol
gold-silver-copper Feb 22, 2026
c4dab80
Update main.rs
gold-silver-copper Feb 22, 2026
2944165
Update colors.rs
gold-silver-copper Feb 22, 2026
1d977a1
better frame count semantics
gold-silver-copper Feb 22, 2026
6d1b4a5
Update backend.rs
gold-silver-copper Feb 22, 2026
54e2b84
blinking enum, cleaned blink toggle code
gold-silver-copper Feb 22, 2026
424c9cd
Update backend.rs
gold-silver-copper Feb 22, 2026
aaa11d9
inverse cursor, heapbuffer get_pixel
gold-silver-copper Feb 22, 2026
dd08fba
Added CursorStyle,CursorConfig,BlinkConfig
gold-silver-copper Feb 23, 2026
a823c50
Moved blink state to config
gold-silver-copper Feb 23, 2026
4769fe5
Inverse as default
gold-silver-copper Feb 23, 2026
aee110d
Japanese style cursor
gold-silver-copper Feb 23, 2026
e59c5e8
fmt
gold-silver-copper Feb 23, 2026
be9e55c
clippy
gold-silver-copper Feb 23, 2026
7dff734
better japanese cursor style
gold-silver-copper Feb 23, 2026
76aea8a
Merge branch 'ratatui:main' into main
gold-silver-copper Feb 23, 2026
23a5be9
feature flag blink, u16 frame count, removed floats, breaking changes md
gold-silver-copper Feb 25, 2026
94c6a02
wat?
gold-silver-copper Feb 25, 2026
e638536
feature gate blink in cursor config, consolidate cfg flagged code
gold-silver-copper Feb 25, 2026
115661b
md fmt
gold-silver-copper Feb 25, 2026
34c4a32
md fmt2
gold-silver-copper Feb 25, 2026
410cab6
one less cast in get_pixel
gold-silver-copper Feb 25, 2026
6693e0b
make dim a bit more bright
gold-silver-copper Feb 25, 2026
ea88a92
fmt
gold-silver-copper Feb 25, 2026
49d034d
Export all new configuration structs and enums, build docs with all-f…
gold-silver-copper Feb 25, 2026
ff55f0c
fmt
gold-silver-copper Feb 25, 2026
3ec593f
add readme section, removed unnecessary cursor option and fn
gold-silver-copper Feb 25, 2026
bc00434
Update README.md
gold-silver-copper Feb 25, 2026
8d8c7e1
ignore in the doc comments @_@
gold-silver-copper Feb 25, 2026
87bb6a7
Update README.md
gold-silver-copper Feb 25, 2026
857d94d
finale
gold-silver-copper Feb 25, 2026
9de18b6
Update mousefood/src/backend.rs
gold-silver-copper Feb 26, 2026
04d4e73
Update mousefood/src/backend.rs
gold-silver-copper Feb 26, 2026
09e4d1b
Update mousefood/src/colors.rs
gold-silver-copper Feb 26, 2026
ec4ee8e
Update mousefood/src/colors.rs
gold-silver-copper Feb 26, 2026
5a4c669
Update mousefood/src/backend.rs
gold-silver-copper Feb 26, 2026
4f5ca06
Update mousefood/src/backend.rs
gold-silver-copper Feb 26, 2026
3b90b06
Update mousefood/src/backend.rs
gold-silver-copper Feb 26, 2026
1f9b1a4
cursor module, pub type Result
gold-silver-copper Feb 26, 2026
a966992
Update colors.rs
gold-silver-copper Feb 26, 2026
f06c008
Apply suggestion from @orhun
gold-silver-copper Feb 26, 2026
ad099f0
Apply suggestion from @orhun
gold-silver-copper Feb 26, 2026
ad34db7
Apply suggestion from @orhun
gold-silver-copper Feb 26, 2026
2576b83
fmt
gold-silver-copper Feb 26, 2026
c487523
Merge branch 'main' into main
gold-silver-copper Feb 26, 2026
827642f
clean up
gold-silver-copper Mar 6, 2026
c7047ff
Replaced simulator image with blink demo gif
gold-silver-copper Mar 6, 2026
4e5073a
added modifiers demo, added modifiers gif to readme
gold-silver-copper Mar 6, 2026
573252d
Update stuff
gold-silver-copper Mar 9, 2026
f7024b5
Update BREAKING-CHANGES.md
gold-silver-copper Mar 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
226 changes: 160 additions & 66 deletions mousefood/src/backend.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use core::marker::PhantomData;
use embedded_graphics::prelude::RgbColor;

use crate::colors::*;
use crate::default_font;
Expand Down Expand Up @@ -116,6 +118,12 @@ where
columns_rows: layout::Size,
pixels: layout::Size,
color_theme: ColorTheme,
cursor_visible: bool,
cursor_position: layout::Position,
blink_counter: u8,
blinking_fast: bool,
blinking_slow: bool,
blink_cells: BTreeMap<(u16, u16), ratatui_core::buffer::Cell>,
}

impl<'display, D, C> EmbeddedBackend<'display, D, C>
Expand Down Expand Up @@ -173,6 +181,12 @@ where
},
pixels,
color_theme,
cursor_visible: false,
cursor_position: layout::Position::new(0, 0),
blink_counter: 0,
blinking_fast: false,
blinking_slow: false,
blink_cells: BTreeMap::new(),
}
}

Expand Down Expand Up @@ -208,91 +222,54 @@ where
where
I: Iterator<Item = (u16, u16, &'a ratatui_core::buffer::Cell)>,
{
for (x, y, cell) in content {
let position = geometry::Point::new(
x as i32 * self.font_regular.character_size.width as i32,
y as i32 * self.font_regular.character_size.height as i32,
);
self.blink_counter = (self.blink_counter + 1) % 60;
let prev_slow = self.blinking_slow;
let prev_fast = self.blinking_fast;
self.blinking_slow = matches!(self.blink_counter, 20..=25);
self.blinking_fast = matches!(self.blink_counter % 20, 0..=5);
let blink_toggled = self.blinking_slow != prev_slow || self.blinking_fast != prev_fast;

let mut style_builder = MonoTextStyleBuilder::new()
.font(&self.font_regular)
.text_color(
TermColor::new(cell.fg, TermColorType::Foreground, &self.color_theme).into(),
)
.background_color(
TermColor::new(cell.bg, TermColorType::Background, &self.color_theme).into(),
);

for modifier in cell.modifier.iter() {
style_builder = match modifier {
style::Modifier::BOLD => match &self.font_bold {
None => style_builder,
Some(font) => style_builder.font(font),
},
style::Modifier::DIM => style_builder, // TODO
style::Modifier::ITALIC => match &self.font_italic {
None => style_builder,
Some(font) => style_builder.font(font),
},
style::Modifier::UNDERLINED => style_builder.underline(),
style::Modifier::SLOW_BLINK => style_builder, // TODO
style::Modifier::RAPID_BLINK => style_builder, // TODO
style::Modifier::REVERSED => style_builder, // TODO
style::Modifier::HIDDEN => style_builder, // TODO
style::Modifier::CROSSED_OUT => style_builder.strikethrough(),
_ => style_builder,
}
for (x, y, cell) in content {
if cell.modifier.contains(style::Modifier::SLOW_BLINK)
|| cell.modifier.contains(style::Modifier::RAPID_BLINK)
{
self.blink_cells.insert((x, y), cell.clone());
} else {
self.blink_cells.remove(&(x, y));
}

#[cfg(feature = "underline-color")]
if cell.underline_color != style::Color::Reset {
style_builder = style_builder.underline_with_color(
TermColor::new(
cell.underline_color,
TermColorType::Foreground,
&self.color_theme,
)
.into(),
);
}
self.draw_cell(x, y, cell)?;
}

Text::with_baseline(
cell.symbol(),
position + self.char_offset,
style_builder.build(),
embedded_graphics::text::Baseline::Top,
)
.draw(
#[cfg(feature = "framebuffer")]
&mut self.buffer,
#[cfg(not(feature = "framebuffer"))]
self.display,
)
.map_err(|_| crate::error::Error::DrawError)?;
// Avoids redrawing unnecessarily
if blink_toggled && !self.blink_cells.is_empty() {
// Necessary to mem::take otherwise compiler complains about mutating self while iterating
let cells = core::mem::take(&mut self.blink_cells);
for (&(x, y), cell) in &cells {
self.draw_cell(x, y, cell)?;
}
self.blink_cells = cells;
}

Ok(())
}

fn hide_cursor(&mut self) -> Result<()> {
// TODO
self.cursor_visible = false;
Ok(())
}

fn show_cursor(&mut self) -> Result<()> {
// TODO
self.cursor_visible = true;
Ok(())
}

fn get_cursor_position(&mut self) -> Result<layout::Position> {
// TODO
Ok(layout::Position::new(0, 0))
Ok(self.cursor_position)
}

fn set_cursor_position<P: Into<layout::Position>>(
&mut self,
#[allow(unused_variables)] position: P,
) -> Result<()> {
// TODO
fn set_cursor_position<P: Into<layout::Position>>(&mut self, position: P) -> Result<()> {
self.cursor_position = position.into();
Ok(())
}

Expand Down Expand Up @@ -352,11 +329,128 @@ where
self.display
.fill_contiguous(&self.display.bounding_box(), &self.buffer)
.map_err(|_| crate::error::Error::DrawError)?;
// Draw cursor after buffer is copied to display
if self.cursor_visible && self.blinking_fast {
self.draw_cursor()?;
}

(self.flush_callback)(self.display);
Ok(())
}
}

impl<D, C> EmbeddedBackend<'_, D, C>
where
D: DrawTarget<Color = C> + 'static,
C: PixelColor + Into<Rgb888> + From<Rgb888> + for<'a> From<TermColor<'a>> + 'static,
{
fn draw_cell(&mut self, x: u16, y: u16, cell: &ratatui_core::buffer::Cell) -> Result<()> {
let position = geometry::Point::new(
x as i32 * self.font_regular.character_size.width as i32,
y as i32 * self.font_regular.character_size.height as i32,
);
let mut fg_color: C =
TermColor::new(cell.fg, TermColorType::Foreground, &self.color_theme).into();
let mut bg_color: C =
TermColor::new(cell.bg, TermColorType::Background, &self.color_theme).into();
let mut style_builder = MonoTextStyleBuilder::new()
.font(&self.font_regular)
.text_color(fg_color)
.background_color(bg_color);

for modifier in cell.modifier.iter() {
style_builder = match modifier {
style::Modifier::BOLD => match &self.font_bold {
None => style_builder,
Some(font) => style_builder.font(font),
},
style::Modifier::DIM => {
fg_color = dim_color(fg_color);
style_builder
}
style::Modifier::ITALIC => match &self.font_italic {
None => style_builder,
Some(font) => style_builder.font(font),
},
style::Modifier::UNDERLINED => style_builder.underline(),
style::Modifier::SLOW_BLINK => {
if self.blinking_slow {
fg_color = bg_color;
}
style_builder
}
style::Modifier::RAPID_BLINK => {
if self.blinking_fast {
fg_color = bg_color;
}
style_builder
}
style::Modifier::REVERSED => {
core::mem::swap(&mut fg_color, &mut bg_color);
style_builder
}
style::Modifier::HIDDEN => {
fg_color = bg_color;
style_builder
}
style::Modifier::CROSSED_OUT => style_builder.strikethrough(),
_ => style_builder,
}
}

style_builder = style_builder
.text_color(fg_color)
.background_color(bg_color);

#[cfg(feature = "underline-color")]
if cell.underline_color != style::Color::Reset {
style_builder = style_builder.underline_with_color(
TermColor::new(
cell.underline_color,
TermColorType::Foreground,
&self.color_theme,
)
.into(),
);
}

Text::with_baseline(
cell.symbol(),
position + self.char_offset,
style_builder.build(),
embedded_graphics::text::Baseline::Top,
)
.draw(
#[cfg(feature = "framebuffer")]
&mut self.buffer,
#[cfg(not(feature = "framebuffer"))]
self.display,
)
.map_err(|_| crate::error::Error::DrawError)?;

Ok(())
}
fn draw_cursor(&mut self) -> Result<()> {
let char_w = self.font_regular.character_size.width as i32;
let char_h = self.font_regular.character_size.height as i32;

let top_left = geometry::Point::new(
self.cursor_position.x as i32 * char_w,
self.cursor_position.y as i32 * char_h,
) + self.char_offset;

self.display
.fill_solid(
&embedded_graphics::primitives::Rectangle::new(
geometry::Point::new(top_left.x, top_left.y + char_h - 2),
geometry::Size::new(char_w as u32, 1),
),
Rgb888::WHITE.into(),
)
.map_err(|_| crate::error::Error::DrawError)
}
}

#[cfg(test)]
mod tests {
use {
Expand Down
12 changes: 11 additions & 1 deletion mousefood/src/colors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,17 @@ impl<'a> From<TermColor<'a>> for BinaryColor {
}
}
}

pub fn dim_color<C>(color: C) -> C
where
C: Into<Rgb888> + From<Rgb888>,
{
let rgb: Rgb888 = color.into();
let factor = 77u32; // ~30% brightness
let r = ((rgb.r() as u32 * factor + 127) / 255) as u8;
let g = ((rgb.g() as u32 * factor + 127) / 255) as u8;
let b = ((rgb.b() as u32 * factor + 127) / 255) as u8;
Rgb888::new(r, g, b).into()
}
#[cfg(feature = "epd-weact")]
impl<'a> From<TermColor<'a>> for weact_studio_epd::Color {
fn from(color: TermColor<'a>) -> Self {
Expand Down