Skip to content

Commit

Permalink
Merge pull request #441 from betrusted-io/busy-text
Browse files Browse the repository at this point in the history
implement an underlying graphics primitive for a busy bar animation
  • Loading branch information
bunnie authored Oct 19, 2023
2 parents 6b98322 + 6434652 commit 367ca1c
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 26 deletions.
2 changes: 1 addition & 1 deletion services/graphics-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@ debugprint = []
braille = []
gfx-testing = []
ditherpunk = []
default = []
default = ["gfx-testing"]
4 changes: 4 additions & 0 deletions services/graphics-server/src/api/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ pub struct TextView {
// this field specifies the beginning and end of a "selected" region of text
pub selected: Option<[u32; 2]>,

// this field tracks the state of a busy animation, if `Some`
pub busy_animation_state: Option<u32>,

pub text: String<3072>,
}
impl TextView {
Expand All @@ -139,6 +142,7 @@ impl TextView {
clear_area: true,
overflow: None,
dry_run: false,
busy_animation_state: None,
}
}
pub fn dry_run(&self) -> bool {
Expand Down
1 change: 1 addition & 0 deletions services/graphics-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ impl Gfx {
tv.bounds_computed = tvr.bounds_computed;
tv.cursor = tvr.cursor;
tv.overflow = tvr.overflow;
tv.busy_animation_state = tvr.busy_animation_state;
Ok(())
}

Expand Down
85 changes: 73 additions & 12 deletions services/graphics-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ fn wrapped_main(main_thread_token: backend::MainThreadToken) -> ! {
op::circle(display.native_buffer(), circ, Some(obj.clip));
}
ClipObjectType::Rect(rect) => {
op::rectangle(display.native_buffer(), rect, Some(obj.clip));
op::rectangle(display.native_buffer(), rect, Some(obj.clip), false);
}
ClipObjectType::RoundRect(rr) => {
op::rounded_rectangle(display.native_buffer(), rr, Some(obj.clip));
Expand Down Expand Up @@ -217,7 +217,7 @@ fn wrapped_main(main_thread_token: backend::MainThreadToken) -> ! {
op::circle(display.native_buffer(), circ, Some(obj.clip));
}
ClipObjectType::Rect(rect) => {
op::rectangle(display.native_buffer(), rect, Some(obj.clip));
op::rectangle(display.native_buffer(), rect, Some(obj.clip), false);
}
ClipObjectType::RoundRect(rr) => {
op::rounded_rectangle(display.native_buffer(), rr, Some(obj.clip));
Expand Down Expand Up @@ -323,13 +323,25 @@ fn wrapped_main(main_thread_token: backend::MainThreadToken) -> ! {
r
}
_ => {
// composition_top_left already had a screen_offset added when it was computed. just margin it out
let mut r = Rectangle::new(
composition_top_left,
composition_top_left.add(Point::new(composition.bb_width() as _, composition.bb_height() as _))
);
r.margin_out(tv.margin);
r
if tv.busy_animation_state.is_some() {
// we want to clear the entire potentially drawable region, not just the dirty box if we're
// doing a busy animation.
let r = Rectangle::new(
Point::new(screen_offset.x, composition_top_left.y),
Point::new(screen_offset.x, composition_top_left.y).add(
Point::new(typeset_extent.x, composition.bb_height())
)
);
r
} else {
// composition_top_left already had a screen_offset added when it was computed. just margin it out
let mut r = Rectangle::new(
composition_top_left,
composition_top_left.add(Point::new(composition.bb_width() as _, composition.bb_height() as _))
);
r.margin_out(tv.margin);
r
}
}
};

Expand Down Expand Up @@ -370,7 +382,7 @@ fn wrapped_main(main_thread_token: backend::MainThreadToken) -> ! {
tv.clip_rect,
);
} else {
op::rectangle(display.native_buffer(), clear_rect, tv.clip_rect);
op::rectangle(display.native_buffer(), clear_rect, tv.clip_rect, false);
}
}
// for now, if we're in braille mode, emit all text to the debug log so we can see it
Expand All @@ -384,6 +396,55 @@ fn wrapped_main(main_thread_token: backend::MainThreadToken) -> ! {
.unwrap_or(Rectangle::new(Point::new(0, 0), Point::new(0, 0,)));
composition.render(display.native_buffer(), composition_top_left, tv.invert, smallest_rect);
}

// run the busy animation
if let Some(state) = tv.busy_animation_state {
let total_width = typeset_extent.x as i16;
if total_width > op::BUSY_ANIMATION_RECT_WIDTH * 2 {
let step = state as i16 % (op::BUSY_ANIMATION_RECT_WIDTH * 2);
for offset in (0..(total_width + op::BUSY_ANIMATION_RECT_WIDTH * 2))
.step_by((op::BUSY_ANIMATION_RECT_WIDTH * 2) as usize) {
let left_x = offset + step + composition_top_left.x;
if offset == 0 && (step >= op::BUSY_ANIMATION_RECT_WIDTH) && (step < op::BUSY_ANIMATION_RECT_WIDTH * 2) {
// handle the truncated "left" rectangle
let mut trunc_rect = Rectangle::new(
Point::new(composition_top_left.x as i16, clear_rect.tl().y),
Point::new((step + composition_top_left.x - op::BUSY_ANIMATION_RECT_WIDTH) as i16, clear_rect.br().y)
);
trunc_rect.style = DrawStyle {
fill_color: Some(PixelColor::Light),
stroke_color: None,
stroke_width: 0,
};
op::rectangle(
display.native_buffer(),
trunc_rect,
tv.clip_rect,
true
);
} // the "right" rectangle is handled by the clipping mask
let mut xor_rect = Rectangle::new(
Point::new(left_x as i16, clear_rect.tl().y),
Point::new((left_x + op::BUSY_ANIMATION_RECT_WIDTH) as i16, clear_rect.br().y)
);
xor_rect.style = DrawStyle {
fill_color: Some(PixelColor::Light),
stroke_color: None,
stroke_width: 0,
};
op::rectangle(
display.native_buffer(),
xor_rect,
tv.clip_rect,
true
);
}
} else {
// don't do the animation, this could be abused to create inverted text
}
tv.busy_animation_state = Some(state + 2);
}

// type mismatch for now, replace this with a simple equals once we sort that out
tv.cursor.pt.x = composition.final_cursor().pt.x;
tv.cursor.pt.y = composition.final_cursor().pt.y;
Expand All @@ -404,7 +465,7 @@ fn wrapped_main(main_thread_token: backend::MainThreadToken) -> ! {
Some(Opcode::Clear) => {
let mut r = Rectangle::full_screen();
r.style = DrawStyle::new(PixelColor::Light, PixelColor::Light, 0);
op::rectangle(display.native_buffer(), r, screen_clip.into())
op::rectangle(display.native_buffer(), r, screen_clip.into(), false)
}
Some(Opcode::Line) => msg_scalar_unpack!(msg, p1, p2, style, _, {
let l =
Expand All @@ -417,7 +478,7 @@ fn wrapped_main(main_thread_token: backend::MainThreadToken) -> ! {
Point::from(br),
DrawStyle::from(style),
);
op::rectangle(display.native_buffer(), r, screen_clip.into());
op::rectangle(display.native_buffer(), r, screen_clip.into(), false);
}),
Some(Opcode::RoundedRectangle) => msg_scalar_unpack!(msg, tl, br, style, r, {
let rr = RoundedRectangle::new(
Expand Down
24 changes: 22 additions & 2 deletions services/graphics-server/src/op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ pub const HEIGHT: i16 = 536;
/// For passing frame buffer references
pub type LcdFB = [u32; LCD_FRAME_BUF_SIZE];

/// Set the expected rectangle length for the busy animation
pub const BUSY_ANIMATION_RECT_WIDTH: i16 = 32; // golden ratio off of a height of 20

fn put_pixel(fb: &mut LcdFB, x: i16, y: i16, color: PixelColor) {
let mut clip_y: usize = y as usize;
if clip_y >= LCD_LINES {
Expand Down Expand Up @@ -254,7 +257,7 @@ impl Iterator for RectangleIterator {
}
}

pub fn rectangle(fb: &mut LcdFB, rect: Rectangle, clip: Option<Rectangle>) {
pub fn rectangle(fb: &mut LcdFB, rect: Rectangle, clip: Option<Rectangle>, xor: bool) {
let r = RectangleIterator {
top_left: rect.tl,
bottom_right: rect.br,
Expand All @@ -264,7 +267,24 @@ pub fn rectangle(fb: &mut LcdFB, rect: Rectangle, clip: Option<Rectangle>) {
};

for pixel in r {
put_pixel(fb, pixel.0.x, pixel.0.y, pixel.1);
if !xor {
put_pixel(fb, pixel.0.x, pixel.0.y, pixel.1);
} else {
if rect.width() <= BUSY_ANIMATION_RECT_WIDTH as u32 {
// only allow xor of a rectangle if it is within the expected width for the busy animation
// This is to make it annoying for someone using an XOR rectangle to synthesize inverted
// text on an otherwise untrusted dialog box (I suspect it's not impossible, but introduces
// a likelihood of drawing artifacts especially across translations and font size selections).
if pixel.0.y % 20 < 18 {
xor_pixel(fb, pixel.0.x, pixel.0.y);
} else {
// don't allow XOR on some pixels to avoid this being used as a primitive to synthesize secure boxes
}
} else {
log::warn!("invalid xor rect width");
put_pixel(fb, pixel.0.x, pixel.0.y, pixel.1);
}
}
}
}

Expand Down
44 changes: 34 additions & 10 deletions services/graphics-server/src/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ use num_traits::*;

#[derive(Debug, num_derive::FromPrimitive, num_derive::ToPrimitive)]
enum TestType {
BoundingBox = 0,
LowerRight = 1,
LowerLeft = 2,
TopLeft = 3,
TopRight = 4,
Overflow = 5,
Insert = 6,
End = 7,
BusyAnimation = 9,
BoundingBox = 10,
LowerRight = 11,
LowerLeft = 12,
TopLeft = 13,
TopRight = 14,
Overflow = 15,
Insert = 16,
End = 17,
}
const TEST_STYLE: GlyphStyle = GlyphStyle::Tall;
pub fn tests() {
Expand All @@ -22,8 +23,8 @@ pub fn tests() {
let gfx = graphics_server::Gfx::new(&xns).unwrap();
let ticktimer = ticktimer_server::Ticktimer::new().expect("Couldn't connect to Ticktimer");

for index in TestType::BoundingBox.to_usize().unwrap()..TestType::End.to_usize().unwrap() {
// show a black screen
for index in TestType::BusyAnimation.to_usize().unwrap()..TestType::End.to_usize().unwrap() {
// pause between each tests
ticktimer.sleep_ms(1000).unwrap();
// draw a black screen
let screensize = gfx.screen_size().expect("Couldn't get screen size");
Expand All @@ -50,6 +51,29 @@ pub fn tests() {
gfx.flush().unwrap();

match FromPrimitive::from_usize(index) {
Some(TestType::BusyAnimation) => {
let anim_rect = Rectangle::new_coords(10, 10, 240, 30);
let mut tv = TextView::new(Gid::new([0, 0, 0, 0]),
TextBounds::BoundingBox(anim_rect));
tv.clip_rect = Some(clipping_area);
tv.style = TEST_STYLE;
tv.ellipsis = false;
write!(tv, "Test of busy animation").unwrap();
tv.insertion = None;
tv.draw_border = false;
tv.busy_animation_state = Some(0);
for y in 0..30 {
gfx.draw_rectangle(checkbound).unwrap();
tv.bounds_hint = TextBounds::BoundingBox(Rectangle::new_coords(
10, 10 + y, 240, 30 + y
));
for _ in 0..5 {
gfx.draw_textview(&mut tv).unwrap();
gfx.flush().unwrap();
ticktimer.sleep_ms(100).unwrap();
}
}
}
Some(TestType::BoundingBox) => {
let mut tv = TextView::new(Gid::new([0, 0, 0, 0]),
TextBounds::BoundingBox(
Expand Down
2 changes: 1 addition & 1 deletion xtask/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
builder.target_hosted()
.add_services(&gfx_base_pkgs.into_iter().map(String::from).collect())
.add_services(&get_cratespecs())
.add_feature("graphics-server/testing");
.add_feature("graphics-server/gfx-testing");
},
Some("hosted-ci") => {
builder.target_hosted()
Expand Down

0 comments on commit 367ca1c

Please sign in to comment.