mirror of
https://github.com/saymrwulf/xous-core.git
synced 2026-11-15 08:30:02 +00:00
add rounded rectangle drawing
This commit is contained in:
parent
75b01ae400
commit
bee1f796ee
10 changed files with 497 additions and 133 deletions
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 => {
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in a new issue