add rounded rectangle drawing

This commit is contained in:
bunnie 2021-03-01 01:49:22 +08:00
parent 75b01ae400
commit bee1f796ee
10 changed files with 497 additions and 133 deletions

View file

@ -14,12 +14,7 @@ use log::{error, info};
/// the GAM first has to check that the textview is allowed to be updated, and then it will decide when
/// the actual screen update is allowed
pub fn post_textview(gam_cid: xous::CID, tv: &mut TextView) -> Result<(), xous::Error> {
// for testing only
tv.set_op(TextOp::Render);
tv.cursor.pt.x = 37;
tv.cursor.pt.y = 5;
// info!("tv before lend: {:?}", tv);
let mut rkyv_tv = api::Opcode::RenderTextView(*tv);
let mut writer = rkyv::ArchiveBuffer::new(xous::XousBuffer::new(4096));
let pos = writer.archive(&rkyv_tv).expect("couldn't archive textview");
@ -31,7 +26,7 @@ pub fn post_textview(gam_cid: xous::CID, tv: &mut TextView) -> Result<(), xous::
let returned = unsafe { rkyv::archived_value::<api::Opcode>(xous_buffer.as_ref(), pos)};
if let rkyv::Archived::<api::Opcode>::RenderTextView(result) = returned {
let tvr: TextView = result.unarchive();
tv.set_computed_bounds(tvr.get_bounds_computed());
tv.bounds_computed = tvr.bounds_computed;
tv.cursor = tvr.cursor;
} else {
let tvr = returned.unarchive();

View file

@ -107,41 +107,6 @@ fn add_chat_layout(gfx_conn: xous::CID, trng_conn: xous::CID, canvases: &mut Fnv
})
}
fn tv_draw(gfx_conn: xous::CID, trng_conn: xous::CID, canvases: &mut FnvIndexMap<Gid, Canvas, U32>, tv: &mut TextView) -> Result<(), xous::Error> {
/*
1. figure out text bounds
2. clear background, if requested
3. draw surrounding rectangle, if requested
4. draw text
*/
/*
info!("GAM: tv_draw canvas list");
for (&k, &v) in canvases.iter() {
info!("gid: {:?}, canvas: {:?}", k, v);
}*/
if let Some(canvas) = canvases.get_mut(&tv.get_canvas_gid()) {
// first, figure out if we should even be drawing to this canvas.
if canvas.is_drawable() {
// figure out text bounds: figure out how wide our text is, to start with.
graphics_server::set_string_clipping(gfx_conn, canvas.clip_rect().into()).expect("GAM: can't set clipping");
} else {
info!("GAM: attempt to draw TextView on non-drawable canvas. Not fatal, but request ignored.");
return Ok(())
}
} else {
info!("GAM: bogus GID in TextView, not doing anything in response to draw request.");
return Ok(()) // silently fail if a bogus Gid is given???
}
Ok(())
}
#[xous::xous_main]
fn xmain() -> ! {
log_server::init_wait().unwrap();
@ -203,16 +168,35 @@ fn xmain() -> ! {
archived_value_mut::<api::Opcode>(Pin::new(buf.as_mut()), m.id as usize)
};
use rkyv::Write;
//let mut writer: rkyv::ArchiveBuffer<xous::XousBuffer> = rkyv::ArchiveBuffer::new(buf);
let mut writer = rkyv::ArchiveBuffer::new(xous::XousBuffer::new(4096));
match &*value {
rkyv::Archived::<api::Opcode>::RenderTextView(rtv) => {
let mut tv = rtv.unarchive();
match tv.get_op() {
TextOp::Nop => (),
TextOp::Render => {
TextOp::Render | TextOp::ComputeBounds => {
info!("GAM: render request for {:?}", tv);
tv_draw(gfx_conn, trng_conn, &mut canvases, &mut tv).expect("GAM: can't render TextView");
if tv.get_op() == TextOp::ComputeBounds {
tv.dry_run = true;
} else {
tv.dry_run = false;
}
if let Some(canvas) = canvases.get_mut(&tv.get_canvas_gid()) {
// first, figure out if we should even be drawing to this canvas.
if canvas.is_drawable() {
// set the clip rectangle according to the canvas' location
tv.clip_rect = Some(canvas.clip_rect().into());
// issue the draw command
graphics_server::draw_textview(gfx_conn, &mut tv).expect("GAM: text view draw could not complete.");
} else {
info!("GAM: attempt to draw TextView on non-drawable canvas. Not fatal, but request ignored.");
}
} else {
info!("GAM: bogus GID in TextView, not doing anything in response to draw request.");
// silently fail if a bogus Gid is given???
}
},
TextOp::ComputeBounds => {

View file

@ -34,7 +34,7 @@ pub fn status_thread(canvas_gid: [u32; 4]) {
uptime_tv.untrusted = false;
uptime_tv.style = blitstr::GlyphStyle::Small;
uptime_tv.draw_border = false;
uptime_tv.x_margin = 0; uptime_tv.y_margin = 0;
uptime_tv.margin = Point::new(0, 0);
write!(uptime_tv, "Booting up...").expect("GAM|status: couldn't init uptime text");
info!("GAM|status: screensize as reported: {:?}", screensize);
info!("GAM|status: uptime initialized to '{:?}'", uptime_tv);
@ -45,8 +45,7 @@ pub fn status_thread(canvas_gid: [u32; 4]) {
Point::new(screensize.x, small_height))));
battstats_tv.style = blitstr::GlyphStyle::Small;
battstats_tv.draw_border = false;
battstats_tv.x_margin = 0; battstats_tv.y_margin = 0;
battstats_tv.alignment = TextAlignment::Right;
battstats_tv.margin = Point::new(0, 0);
let mut stats: BattStats = BattStats::default();
let mut last_time: u64 = ticktimer_server::elapsed_ms(ticktimer_conn).unwrap();
@ -82,7 +81,7 @@ pub fn status_thread(canvas_gid: [u32; 4]) {
if let Ok(elapsed_time) = ticktimer_server::elapsed_ms(ticktimer_conn) {
if elapsed_time - last_time > 500 {
info!("GAM|status: size of TextView type: {} bytes", core::mem::size_of::<TextView>());
//info!("GAM|status: size of TextView type: {} bytes", core::mem::size_of::<TextView>());
// info!("GAM|status: periodic tasks: updating uptime, requesting battstats");
last_time = elapsed_time;
com::get_batt_stats_nb(com_conn).expect("Can't get battery stats from COM");

View file

@ -49,7 +49,7 @@ impl hash32::Hash for Gid {
}
}
#[derive(Debug, rkyv::Archive)]
#[derive(Debug, rkyv::Archive, rkyv::Unarchive)]
pub enum Opcode {
/// Flush the buffer to the screen
Flush,
@ -97,7 +97,7 @@ pub enum Opcode {
QueryGlyphProps(GlyphStyle),
// draws a textview
// TextView(&'a mut TextView), // redo this as an rkyv object, which has no lifetime requirement
DrawTextView(TextView),
}
impl core::convert::TryFrom<& Message> for Opcode {

View file

@ -2,7 +2,7 @@ use crate::op::{HEIGHT, WIDTH};
use crate::api::{Point, DrawStyle};
use blitstr_ref as blitstr;
use blitstr::{ClipRect};
use core::cmp::{max, min};
use core::{cmp::{max, min}, ops::Add};
#[derive(Debug, Clone, Copy, rkyv::Archive, rkyv::Unarchive)]
pub struct Rectangle {
@ -71,6 +71,10 @@ impl Rectangle {
((other.br.x >= self.tl.x) && (other.br.x <= self.br.x)) &&
((other.br.y >= self.tl.y) && (other.br.y <= self.br.y))
}
pub fn intersects_point(&self, point: Point) -> bool {
((point.x >= self.tl.x) && (point.x <= self.br.x)) &&
((point.y >= self.tl.y) && (point.y <= self.br.y))
}
pub fn new_coords_with_style(
x0: i16,
y0: i16,
@ -104,6 +108,12 @@ impl Rectangle {
pub fn y1(&self) -> u32 {
self.br.y as u32
}
pub fn translate(&mut self, offset: Point) {
self.tl.x += offset.x;
self.br.x += offset.x;
self.tl.y += offset.y;
self.br.y += offset.y;
}
/// Make a rectangle of the full screen size
pub fn full_screen() -> Rectangle {
@ -186,4 +196,27 @@ impl Circle {
style,
}
}
}
}
//////////////////////// Rounded Rectangle
#[derive(Debug, Clone, Copy, rkyv::Archive, rkyv::Unarchive)]
pub struct RoundedRectangle {
pub border: Rectangle, // drawstyle is inherited from the Rectangle
pub radius: i16,
}
impl RoundedRectangle {
pub fn new(rr: Rectangle, r: i16) -> RoundedRectangle {
let mut r_adj = r;
// disallow radii that are greater than 2x the width of the rectangle
if r > (rr.br.x - rr.tl.x) {
r_adj = 0;
}
if r > (rr.br.y - rr.tl.y) {
r_adj = 0;
}
RoundedRectangle {
border: rr,
radius: r_adj,
}
}
}

View file

@ -4,13 +4,7 @@ use blitstr::{GlyphStyle, Cursor};
use log::info;
#[derive(Debug, Copy, Clone, rkyv::Archive, rkyv::Unarchive)]
pub enum TextAlignment {
Left,
Center,
Right,
}
/// coordinates are local to the canvas, not absolute to the screen
#[derive(Debug, Copy, Clone, rkyv::Archive, rkyv::Unarchive)]
pub enum TextBounds {
// fixed width and height in a rectangle
@ -23,12 +17,12 @@ pub enum TextBounds {
GrowableFromBl(Point, u16),
}
#[derive(Debug, Copy, Clone, rkyv::Archive, rkyv::Unarchive)]
#[derive(Debug, Copy, Clone, rkyv::Archive, rkyv::Unarchive, PartialEq)]
// operations that may be requested of a TextView when sent to GAM
pub enum TextOp {
Nop,
Render,
ComputeBounds,
ComputeBounds, // maybe we don't need this
}
impl Into<usize> for TextOp {
fn into(self) -> usize {
@ -72,26 +66,27 @@ pub struct TextView {
pub draw_order: u32,
// offsets for text drawing -- exactly one of the following options should be specified
// note that the TextBounds coordinate system is local to the canvas, not the screen
pub bounds_hint: TextBounds,
bounds_computed: Option<Rectangle>, // is Some(Rectangle) if bounds have been computed and text has not been modified
pub bounds_computed: Option<Rectangle>, // is Some(Rectangle) if bounds have been computed and text has not been modified
pub overflow: Option<bool>, // indicates if the text has overflowed the canvas, set by the drawing routine
pub dry_run: bool, // set to true if no drawing is desired and we just want to compute the bounds
pub style: GlyphStyle,
text: xous::String::<3072>,
length: u32,
pub alignment: TextAlignment,
pub text: xous::String::<3072>,
pub cursor: Cursor,
pub draw_border: bool,
pub clear_area: bool,
pub clear_area: bool, // you almost always want this to be true
pub border_width: u16,
pub rounded_border: bool,
pub x_margin: u16,
pub y_margin: u16,
pub rounded_border: Option<u16>, // radius of the rounded border, if applicable
pub margin: Point,
// this field specifies the beginning and end of a "selected" region of text
pub selected: Option<[u32; 2]>,
canvas: Gid, // GID of the canvas to draw on
pub clip_rect: Option<Rectangle>, // this is set by the GAM to the canvas' clip_rect; needed by gfx for drawing. Note this is in screen coordinates.
}
impl TextView {
pub fn new(canvas: Gid, draw_order: u32, bounds_hint: TextBounds) -> Self {
@ -101,41 +96,26 @@ impl TextView {
token: None,
invert: false,
draw_order,
clip_rect: None,
bounds_hint,
bounds_computed: None,
style: GlyphStyle::Regular,
text: xous::String::<3072>::new(),
length: 0,
alignment: TextAlignment::Left,
cursor: Cursor::new(0,0,0),
draw_border: true,
border_width: 1,
rounded_border: false,
x_margin: 4,
y_margin: 4,
rounded_border: None,
margin: Point { x: 4, y: 4 },
selected: None,
canvas,
clear_area: true,
overflow: None,
dry_run: false,
}
}
pub fn set_op(&mut self, op: TextOp) { self.operation = op; }
pub fn get_op(&self) -> TextOp { self.operation }
pub fn get_canvas_gid(&self) -> Gid { self.canvas }
pub fn get_bounds_computed(&self) -> Option<Rectangle> { self.bounds_computed }
pub fn compute_bounds(&mut self) -> Result<(), xous::Error> {
match self.bounds_hint {
TextBounds::BoundingBox(r) => {
info!("GFX/text - r {:?}", r);
self.bounds_computed = Some(r);
info!("GFX/text - bounds_computed {:?}", self.bounds_computed);
},
_=> todo!("other dynamic bounds computations not yet implemented"),
}
Ok(())
}
pub fn set_computed_bounds(&mut self, r: Option<Rectangle>) {
self.bounds_computed = r;
}
pub fn to_str(&self) -> &str {
self.text.as_str().unwrap()
@ -153,17 +133,17 @@ impl TextView {
self.bounds_computed = t.bounds_computed;
self.style = t.style;
self.text = t.text;
self.length = t.length;
self.alignment = t.alignment;
self.cursor = t.cursor;
self.draw_border = t.draw_border;
self.clear_area = t.clear_area;
self.border_width = t.border_width;
self.rounded_border = t.rounded_border;
self.x_margin = t.x_margin;
self.y_margin = t.y_margin;
self.margin = t.margin;
self.selected = t.selected;
self.canvas = t.canvas;
self.overflow = t.overflow;
self.clip_rect = t.clip_rect;
self.dry_run = t.dry_run;
}
}

View file

@ -2,7 +2,7 @@
// pub mod size;
pub mod api;
pub use api::{Circle, DrawStyle, Line, PixelColor, Point, Rectangle, TextView, TextBounds, Gid, TextOp, TextAlignment};
pub use api::{Circle, DrawStyle, Line, PixelColor, Point, Rectangle, TextView, TextBounds, Gid, TextOp};
use blitstr_ref as blitstr;
pub use blitstr::{ClipRect, Cursor, GlyphStyle};
use xous::String;
@ -109,10 +109,28 @@ pub fn glyph_height_hint(cid: CID, glyph: GlyphStyle) -> Result<usize, xous::Err
}
}
pub fn draw_textview(_cid: CID, _tv: &mut TextView) -> Result<(), xous::Error> {
/*/
let mut sendable_tv = Sendable::new(tv)
.expect("can't create sendable TextView structure");
sendable_tv.lend_mut(cid, 0x100).map(|_| ()) */
pub fn draw_textview(cid: CID, tv: &mut TextView) -> Result<(), xous::Error> {
use log::info;
use rkyv::Write;
use rkyv::Unarchive;
let mut rkyv_tv = api::Opcode::DrawTextView(*tv);
let mut writer = rkyv::ArchiveBuffer::new(xous::XousBuffer::new(4096));
let pos = writer.archive(&rkyv_tv).expect("couldn't archive textview");
let mut xous_buffer = writer.into_inner();
xous_buffer.lend_mut(cid, pos as u32).expect("draw_textview operation failure");
let returned = unsafe { rkyv::archived_value::<api::Opcode>(xous_buffer.as_ref(), pos)};
if let rkyv::Archived::<api::Opcode>::DrawTextView(result) = returned {
let tvr: TextView = result.unarchive();
tv.bounds_computed = tvr.bounds_computed;
tv.cursor = tvr.cursor;
} else {
let tvr = returned.unarchive();
info!("draw_textview saw a return of {:?}", tvr);
panic!("draw_textview got a return value from the server that isn't expected or handled");
}
Ok(())
}

View file

@ -8,7 +8,7 @@ use api::Opcode;
use xous::buffer;
use core::pin::Pin;
use rkyv::{archived_value, Unarchive};
use rkyv::{archived_value, Unarchive, archived_value_mut};
use core::convert::TryInto;
mod backend;
@ -20,7 +20,7 @@ use core::convert::TryFrom;
mod logo;
use api::{DrawStyle, PixelColor, Rectangle};
use api::{DrawStyle, PixelColor, Rectangle, TextBounds, RoundedRectangle};
use blitstr_ref as blitstr;
mod fontmap;
@ -76,6 +76,23 @@ fn xmain() -> ! {
let sid = xous_names::register_name(xous::names::SERVER_NAME_GFX).expect("GFX: can't register server");
info!("GFX: Server listening on address {:?}", sid);
if false {
// leave this test case around
// for some reason, the top right quadrant draws an extra pixel inside the fill area
// when a fill color of "Light" is specified. However, if `None` fill is specified, it
// works correctly. This is really puzzling, because the test for filled drawing happens
// after the test for border drawing.
use api::Point;
let mut r = Rectangle::new(Point::new(20, 200), Point::new(151, 301));
r.style = DrawStyle {
fill_color: Some(PixelColor::Light),
stroke_color: Some(PixelColor::Dark),
stroke_width: 1,
};
let rr = RoundedRectangle::new(r, 16);
op::rounded_rectangle(display.native_buffer(), rr);
}
display.redraw();
loop {
let msg = xous::receive_message(sid).unwrap();
@ -113,20 +130,108 @@ fn xmain() -> ! {
blitstr::xor_char
);
},
rkyv::Archived::<api::Opcode>::SimulateString(rkyv_s) => {
let s: xous::String<4096> = rkyv_s.unarchive();
blitstr::paint_str(
display.native_buffer(),
current_string_clip.into(),
&mut current_cursor,
current_glyph.into(),
s.as_str().unwrap(),
false,
blitstr::simulate_char
);
},
_ => panic!("GFX: invalid response from server -- corruption occurred in MemoryMessage")
};
} else if let xous::Message::MutableBorrow(m) = &msg.body {
let mut buf = unsafe { xous::XousBuffer::from_memory_message(m) };
let value = unsafe {
archived_value_mut::<api::Opcode>(Pin::new(buf.as_mut()), m.id as usize)
};
use rkyv::Write;
let mut writer = rkyv::ArchiveBuffer::new(xous::XousBuffer::new(4096));
let debugtv: bool = true;
match &*value {
rkyv::Archived::<api::Opcode>::DrawTextView(rtv) => {
let mut tv = rtv.unarchive();
let paintfn = if tv.dry_run {
if debugtv { info!("GFX(TV): doing dry run"); }
blitstr::simulate_char
} else {
if debugtv { info!("GFX(TV): drawing"); }
blitstr::xor_char
};
/*
1. figure out text bounds
2. clear background, if requested
3. draw surrounding rectangle, if requested
4. draw text
*/
// first compute the bounding box, if it isn't computed
if tv.bounds_computed.is_none() {
match tv.bounds_hint {
TextBounds::BoundingBox(r) => {
tv.bounds_computed = Some(r);
},
TextBounds::GrowableFromBr(br, width) => {
unimplemented!()
},
TextBounds::GrowableFromTl(tl, width) => {
unimplemented!()
},
TextBounds::GrowableFromBl(bl, width) => {
unimplemented!()
}
}
}
if debugtv { info!("GFX(TV): computed bounds {:?}", tv.bounds_computed); }
// clear the bounding box if requested
let mut clear_rect = tv.bounds_computed.unwrap();
clear_rect.translate(tv.clip_rect.unwrap().tl);
let bordercolor = if tv.draw_border {
Some(PixelColor::Dark)
} else {
None
};
let borderwidth: i16 = if tv.draw_border {
1
} else {
0
};
let fillcolor = if tv.clear_area {
Some(PixelColor::Light)
} else {
None
};
clear_rect.style = DrawStyle {
fill_color: fillcolor,
stroke_color: bordercolor,
stroke_width: borderwidth,
};
if tv.rounded_border.is_some() {
op::rounded_rectangle(display.native_buffer(),
RoundedRectangle::new(clear_rect, tv.rounded_border.unwrap() as _));
} else {
if debugtv { info!("GFX(TV): clearing rectangle {:?}", clear_rect); }
op::rectangle(display.native_buffer(), clear_rect);
}
// this is the actual draw operation
clear_rect.translate(tv.margin);
/////// TODO: I think we need to clip the clear_rect.tl.x/y to within the screen area for this to be valid?
let mut ref_cursor = blitstr::Cursor{
pt: blitstr::Pt{x: clear_rect.tl.x as u32, y: clear_rect.tl.y as u32},
line_height: 0,
};
if debugtv { info!("GFX(TV): paint_str with {:?} | {:?} | {:?} | {:?}", clear_rect, ref_cursor, tv.style, tv.text); }
blitstr::paint_str(
display.native_buffer(),
clear_rect.into(),
&mut ref_cursor,
tv.style.into(),
tv.text.as_str().unwrap(),
false,
paintfn
);
tv.cursor = ref_cursor;
},
_ => panic!("GFX: invalid mutable borrow message"),
}
} else if let Ok(opcode) = Opcode::try_from(&msg.body) {
// info!("GFX: Opcode: {:?}", opcode);
match opcode {

View file

@ -1,4 +1,4 @@
use crate::api::{Circle, DrawStyle, Line, Pixel, PixelColor, Point, Rectangle, TextView};
use crate::api::{Circle, DrawStyle, Line, Pixel, PixelColor, Point, Rectangle, TextView, RoundedRectangle};
use log::info;
use blitstr_ref as blitstr;
@ -25,7 +25,7 @@ fn put_pixel(fb: &mut LcdFB, x: i16, y: i16, color: PixelColor) {
clip_y = LCD_PX_PER_LINE - 1;
}
if color == PixelColor::Dark {
if color == PixelColor::Light {
fb[(clip_x + clip_y * LCD_WORDS_PER_LINE * 32) / 32] |= 1 << (clip_x % 32)
} else {
fb[(clip_x + clip_y * LCD_WORDS_PER_LINE * 32) / 32] &= !(1 << (clip_x % 32))
@ -233,21 +233,271 @@ pub fn rectangle(fb: &mut LcdFB, rect: Rectangle) {
}
}
/////////////////////////////////////////////////// rounded rectangle
pub fn textview(fb: &mut LcdFB, tv: &mut TextView) {
tv.compute_bounds().expect("GFX: couldn't compute bounds of textview");
let mut c = tv.cursor.clone();
// at this point, bounds_computed *must* be Some()
info!("GFX: painting tv with cursor {:?}, clip {:?}, text {}, style {:?}", c, tv.get_bounds_computed().unwrap(), tv, tv.style);
blitstr::paint_str(
fb,
tv.get_bounds_computed().unwrap().into(),
&mut c,
tv.style,
tv.to_str(),
false,
blitstr::xor_char
);
tv.cursor = c;
#[derive(Debug, Clone, Copy)]
pub enum Quadrant {
TopLeft,
TopRight,
BottomLeft,
BottomRight,
}
#[derive(Debug, Copy, Clone)]
pub struct QuadrantIterator {
center: Point,
radius: u16,
style: DrawStyle,
p: Point,
quad: Quadrant,
}
impl Iterator for QuadrantIterator {
type Item = Pixel;
// https://stackoverflow.com/questions/1201200/fast-algorithm-for-drawing-filled-circles
fn next(&mut self) -> Option<Self::Item> {
// If border or stroke colour is `None`, treat entire object as transparent and exit early
if self.style.stroke_color.is_none() && self.style.fill_color.is_none() {
return None;
}
let inner_radius = self.radius as i16 - self.style.stroke_width + 1;
let outer_radius = self.radius as i16;
let inner_radius_sq = inner_radius * inner_radius;
let outer_radius_sq = outer_radius * outer_radius;
loop {
let t = self.p;
let len = t.x * t.x + t.y * t.y;
let is_border = len > (inner_radius_sq - inner_radius) && len < (outer_radius_sq + inner_radius);
let is_fill = len <= outer_radius_sq + 1;
let item = if is_border && self.style.stroke_color.is_some() {
Some(Pixel(
self.center + t,
self.style.stroke_color.expect("Border color not defined"),
))
} else if is_fill && self.style.fill_color.is_some() {
Some(Pixel(
self.center + t,
self.style.fill_color.expect("Fill color not defined"),
))
} else {
None
};
self.p.x += 1;
match self.quad {
Quadrant::TopLeft => {
if self.p.x > 0 as i16 {
self.p.x = -(self.radius as i16);
self.p.y += 1;
}
if self.p.y > 0 as i16 {
break item;
}
if item.is_some() {
break item;
}
},
Quadrant::TopRight => {
if self.p.x > self.radius as i16 {
self.p.x = 0;
self.p.y += 1;
}
if self.p.y > 0 as i16 {
break item;
}
if item.is_some() {
break item;
}
},
Quadrant::BottomLeft => {
if self.p.x > 0 as i16 {
self.p.x = -(self.radius as i16);
self.p.y += 1;
}
if self.p.y > self.radius as i16 {
break item;
}
if item.is_some() {
break item;
}
},
Quadrant::BottomRight => {
if self.p.x > self.radius as i16 {
self.p.x = 0;
self.p.y += 1;
}
if self.p.y > self.radius as i16 {
break item;
}
if item.is_some() {
break item;
}
}
}
}
}
}
pub fn quadrant(fb: &mut LcdFB, circle: Circle, quad: Quadrant) {
let starting_pixel = match quad {
Quadrant::TopLeft => {
Point::new( -(circle.radius as i16), -(circle.radius as i16))
},
Quadrant::TopRight => {
Point::new( 0, -(circle.radius as i16))
},
Quadrant::BottomLeft => {
Point::new( -(circle.radius as i16), 0)
},
Quadrant::BottomRight => {
Point::new( 0, 0 )
}
};
let q = QuadrantIterator {
center: circle.center,
radius: circle.radius as _,
style: circle.style,
p: starting_pixel,
quad: quad,
};
for pixel in q {
put_pixel(fb, pixel.0.x, pixel.0.y, pixel.1);
}
}
/// Pixel iterator for each pixel in the rect border
/// lifted from embedded-graphics crate
#[derive(Debug, Clone, Copy)]
pub struct RoundedRectangleIterator {
top_left: Point,
bottom_right: Point,
style: DrawStyle,
radius: i16,
p: Point,
// the four quadrants for drawing the rounded corners
tlq: Rectangle,
trq: Rectangle,
blq: Rectangle,
brq: Rectangle,
}
impl Iterator for RoundedRectangleIterator {
type Item = Pixel;
fn next(&mut self) -> Option<Self::Item> {
// Don't render anything if the rectangle has no border or fill color.
if self.style.stroke_color.is_none() && self.style.fill_color.is_none() {
return None;
}
loop {
let mut out = None;
// Finished, i.e. we're below the rect
if self.p.y > self.bottom_right.y {
break None;
}
// suppress the output pixel if we happen to be in the corner quadrant area
if self.tlq.intersects_point(self.p) || self.trq.intersects_point(self.p) ||
self.blq.intersects_point(self.p) || self.brq.intersects_point(self.p) {
out = None
} else {
let border_width = self.style.stroke_width;
let tl = self.top_left;
let br = self.bottom_right;
// Border
if (
// Top border
(self.p.y >= tl.y && self.p.y < tl.y + border_width)
// Bottom border
|| (self.p.y <= br.y && self.p.y > br.y - border_width)
// Left border
|| (self.p.x >= tl.x && self.p.x < tl.x + border_width)
// Right border
|| (self.p.x <= br.x && self.p.x > br.x - border_width)
) && self.style.stroke_color.is_some()
{
out = Some(Pixel(
self.p,
self.style.stroke_color.expect("Expected stroke"),
));
}
// Fill
else if let Some(fill) = self.style.fill_color {
out = Some(Pixel(self.p, fill));
}
}
self.p.x += 1;
// Reached end of row? Jump down one line
if self.p.x > self.bottom_right.x {
self.p.x = self.top_left.x;
self.p.y += 1;
}
if out.is_some() {
break out;
}
}
}
}
pub fn rounded_rectangle(fb: &mut LcdFB, rr: RoundedRectangle) {
/// left off: compute the four quadrants
// call the rr iterator on the rectangle
// then call it on one each of the four circle quadrants
let rri = RoundedRectangleIterator {
top_left: rr.border.tl,
bottom_right: rr.border.br,
style: rr.border.style,
radius: rr.radius,
p: rr.border.tl,
tlq: Rectangle::new(
rr.border.tl,
Point::new(rr.border.tl.x + rr.radius, rr.border.tl.y + rr.radius)),
trq: Rectangle::new(
Point::new(rr.border.br.x - rr.radius, rr.border.tl.y),
Point::new(rr.border.br.x, rr.border.tl.y + rr.radius)),
blq: Rectangle::new(
Point::new(rr.border.tl.x, rr.border.br.y - rr.radius),
Point::new(rr.border.tl.x + rr.radius, rr.border.br.y)),
brq: Rectangle::new(
Point::new(rr.border.br.x - rr.radius, rr.border.br.y - rr.radius),
rr.border.br),
};
// draw the body
for pixel in rri {
put_pixel(fb, pixel.0.x, pixel.0.y, pixel.1);
}
// now draw the corners
quadrant(fb,
Circle::new_with_style(rri.tlq.br, rr.radius, rr.border.style),
Quadrant::TopLeft
);
quadrant(fb,
Circle::new_with_style(Point::new(rri.trq.tl.x, rri.trq.br.y), rr.radius, rr.border.style),
Quadrant::TopRight
);
quadrant(fb,
Circle::new_with_style(Point::new(rri.blq.br.x, rri.blq.tl.y), rr.radius, rr.border.style),
Quadrant::BottomLeft
);
quadrant(fb,
Circle::new_with_style(rri.brq.tl, rr.radius, rr.border.style),
Quadrant::BottomRight
);
}

View file

@ -186,8 +186,8 @@ fn renode_image(debug: bool, packages: &[&str]) -> Result<(), DynError> {
fn run(debug: bool) -> Result<(), DynError> {
let stream = if debug { "debug" } else { "release" };
let init = [
"shell",
//"gam",
//"shell",
"gam",
//"benchmark",
//"benchmark-target",
"log-server",