Rip out buffer and widgets

I've decided to use the "taffy" crate for layouting. To make the API
somewhat bearable to use, I've decided to perform all rendering in a
good ol' rgb image buffer (in this case, an ImageBuffer from the "image"
crate). The pixels will be converted to their final representation just
before being sent to whatever device will be displaying them.

I've removed the buffer and the color traits because they're no longer
necessary. I've also removed the existing widgets because the widget
system will have to be redesigned to work well with taffy.
This commit is contained in:
Joscha 2024-03-06 00:36:18 +01:00
parent 04ed092c4f
commit eb04b3fb50
13 changed files with 38 additions and 300 deletions

1
Cargo.lock generated
View file

@ -1025,6 +1025,7 @@ name = "showbits-common"
version = "0.0.0"
dependencies = [
"anyhow",
"image",
"palette",
]

View file

@ -8,6 +8,7 @@ edition = "2021"
[workspace.dependencies]
anyhow = "1.0.80"
image = "0.24.9"
palette = "0.7.5"
showbits-common.path = "./showbits-common"

View file

@ -5,6 +5,7 @@ edition.workspace = true
[dependencies]
anyhow.workspace = true
image.workspace = true
palette.workspace = true
[lints]

View file

@ -1,50 +0,0 @@
use crate::Vec2;
#[derive(Clone)]
pub struct Buffer<C> {
size: Vec2,
data: Vec<C>,
}
impl<C> Buffer<C> {
pub fn new(size: Vec2, color: C) -> Self
where
C: Copy,
{
assert!(size.x >= 0);
assert!(size.y >= 0);
let len = (size.x * size.y) as usize;
let data = vec![color; len];
Self { size, data }
}
pub fn size(&self) -> Vec2 {
self.size
}
fn index(&self, pos: Vec2) -> Option<usize> {
let in_bounds_x = pos.x >= 0 || pos.x < self.size.x;
let in_bounds_y = pos.y >= 0 || pos.y < self.size.y;
let in_bounds = in_bounds_x && in_bounds_y;
if in_bounds {
Some((pos.y * self.size.x + pos.x) as usize)
} else {
None
}
}
pub fn at(&self, pos: Vec2) -> Option<&C> {
let index = self.index(pos)?;
let pixel = self.data.get(index)?;
Some(pixel)
}
pub fn at_mut(&mut self, pos: Vec2) -> Option<&mut C> {
let index = self.index(pos)?;
let pixel = self.data.get_mut(index)?;
Some(pixel)
}
}

View file

@ -1,11 +0,0 @@
use palette::Srgb;
pub trait Color: Copy {
/// Convert to an sRGB color.
///
/// Useful for debugging or dithering.
fn to_srgb(self) -> Srgb;
/// Combine two colors by putting one "over" the other.
fn over(self, below: Self) -> Self;
}

View file

@ -1,9 +1,5 @@
pub use crate::{buffer::*, color::*, rect::*, vec2::*, view::*, widget::*};
pub use crate::{rect::*, vec2::*, view::*};
mod buffer;
mod color;
mod rect;
mod vec2;
mod view;
mod widget;
pub mod widgets;

View file

@ -20,6 +20,18 @@ impl Vec2 {
Self { x, y }
}
pub fn from_u32(x: u32, y: u32) -> Self {
let x: i32 = x.try_into().expect("x too large");
let y: i32 = y.try_into().expect("y too large");
Self::new(x, y)
}
pub fn to_u32(self) -> (u32, u32) {
let x: u32 = self.x.try_into().expect("x too small");
let y: u32 = self.y.try_into().expect("y too small");
(x, y)
}
/// The vector pointing from `self` to `other`.
///
/// ```

View file

@ -1,21 +1,21 @@
use crate::{Buffer, Color, Rect, Vec2};
use image::RgbImage;
use palette::Srgb;
// TODO Add Orientation (from inkfo)
use crate::{Rect, Vec2};
pub struct View<'a, C> {
pub struct View<'a> {
area: Rect,
buffer: &'a mut Buffer<C>,
buffer: &'a mut RgbImage,
}
impl<'a, C> View<'a, C> {
pub fn new(buffer: &'a mut Buffer<C>) -> Self {
Self {
area: Rect::from_nw(Vec2::ZERO, buffer.size()),
buffer,
}
impl<'a> View<'a> {
pub fn new(buffer: &'a mut RgbImage) -> Self {
let size = Vec2::from_u32(buffer.width(), buffer.height());
let area = Rect::from_nw(Vec2::ZERO, size);
Self { area, buffer }
}
pub fn dup(&mut self) -> View<'_, C> {
pub fn dup(&mut self) -> View<'_> {
View {
area: self.area,
buffer: self.buffer,
@ -35,15 +35,19 @@ impl<'a, C> View<'a, C> {
pos + self.area.corner_nw()
}
pub fn at(&self, pos: Vec2) -> Option<&C> {
self.buffer.at(self.pos_to_buffer_pos(pos))
}
pub fn get(&self, pos: Vec2) -> Option<Srgb> {
let (x, y) = self.pos_to_buffer_pos(pos).to_u32();
let pixel = self.buffer.get_pixel_checked(x, y)?;
let [r, g, b] = pixel.0;
let color = Srgb::new(r, g, b);
Some(color.into_format())
}
impl<C: Color> View<'_, C> {
pub fn set(&mut self, pos: Vec2, color: C) {
if let Some(pixel) = self.buffer.at_mut(self.pos_to_buffer_pos(pos)) {
*pixel = color.over(*pixel);
pub fn set(&mut self, pos: Vec2, color: Srgb) {
let (x, y) = self.pos_to_buffer_pos(pos).to_u32();
if let Some(pixel) = self.buffer.get_pixel_mut_checked(x, y) {
let color = color.into_format::<u8>();
pixel.0 = [color.red, color.green, color.blue];
}
}
}

View file

@ -1,122 +0,0 @@
use crate::{widgets::Background, Rect, Vec2, View};
pub trait Widget<C> {
/// Size that the widget wants to be, given the width and height
/// constraints.
fn size(&self, max_width: Option<i32>, max_height: Option<i32>) -> Vec2;
/// Recalculate the area (size and position) of all inner widgets given the
/// widget's own area.
///
/// # Implement if...
///
/// - There are inner widgets
fn set_area(&mut self, _area: Rect) {}
/// Perform any updates (e.g. fetching map tiles) that require the widget's
/// area and may fail.
///
/// # Implement if...
///
/// - There are inner widgets
/// - This widget needs to perform updates
fn update(&mut self, _area: Rect) -> anyhow::Result<()> {
Ok(())
}
fn draw(self, view: &mut View<'_, C>);
}
/// Extension trait for [`Widget`]s.
pub trait WidgetExt<C> {
fn boxed(self) -> BoxedWidget<C>;
}
impl<C, W> WidgetExt<C> for W
where
W: Widget<C> + 'static,
{
fn boxed(self) -> BoxedWidget<C> {
BoxedWidget::new(self)
}
}
/// Wrapper trait around [`Widget`] that turns `Box<Self>` into a `Self` to get
/// around the "size cannot be statically determined" error with the naïve
/// approach of `Box<Widget>`.
trait WidgetWrapper<C> {
fn size(&self, max_width: Option<i32>, max_height: Option<i32>) -> Vec2;
fn set_area(&mut self, _area: Rect);
fn update(&mut self, _area: Rect) -> anyhow::Result<()>;
fn draw(self: Box<Self>, view: &mut View<'_, C>);
}
impl<C, W: Widget<C>> WidgetWrapper<C> for W {
// These implementations explicitly use `Widget::*` to call the widget
// methods even though they have priority over the `WidgetWrapper::*`
// methods for some reason. Just a bit of rustc magic, I guess.
fn size(&self, max_width: Option<i32>, max_height: Option<i32>) -> Vec2 {
Widget::size(self, max_width, max_height)
}
fn set_area(&mut self, area: Rect) {
// Widget::set_area(self, area);
self.set_area(area);
}
fn update(&mut self, area: Rect) -> anyhow::Result<()> {
Widget::update(self, area)
}
fn draw(self: Box<Self>, view: &mut View<'_, C>) {
Widget::draw(*self, view);
}
}
pub struct BoxedWidget<C> {
area: Rect,
widget: Box<dyn WidgetWrapper<C>>,
}
impl<C> BoxedWidget<C> {
pub fn new<W>(widget: W) -> Self
where
W: Widget<C> + 'static,
{
Self {
area: Rect::ZERO,
widget: Box::new(widget),
}
}
pub fn area(&self) -> Rect {
self.area
}
// Widget-like functions
pub fn size(&self, max_width: Option<i32>, max_height: Option<i32>) -> Vec2 {
self.widget.size(max_width, max_height)
}
pub fn set_area(&mut self, area: Rect) {
self.area = area;
self.widget.set_area(area);
}
pub fn update(&mut self) -> anyhow::Result<()> {
self.widget.update(self.area)
}
pub fn draw(self, view: &mut View<'_, C>) {
let mut view = view.dup().zoom(self.area);
self.widget.draw(&mut view);
}
// Widget constructors
pub fn background(self, color: C) -> Background<C> {
Background::new(self, color)
}
}

View file

@ -1,5 +0,0 @@
pub use background::*;
pub use empty::*;
mod background;
mod empty;

View file

@ -1,34 +0,0 @@
use crate::{BoxedWidget, Color, Vec2, Widget};
pub struct Background<C> {
inner: BoxedWidget<C>,
color: C,
}
impl<C> Background<C> {
pub fn new(inner: BoxedWidget<C>, color: C) -> Self {
Self { inner, color }
}
}
impl<C: Color> Widget<C> for Background<C> {
fn size(&self, max_width: Option<i32>, max_height: Option<i32>) -> crate::Vec2 {
self.inner.size(max_width, max_height)
}
fn set_area(&mut self, area: crate::Rect) {
self.inner.set_area(area);
}
fn update(&mut self, _area: crate::Rect) -> anyhow::Result<()> {
Ok(())
}
fn draw(self, view: &mut crate::View<'_, C>) {
for y in 0..view.size().y {
for x in 0..view.size().x {
view.set(Vec2::new(x, y), self.color);
}
}
}
}

View file

@ -1,23 +0,0 @@
use crate::{Vec2, View, Widget};
pub struct Empty {}
impl Empty {
pub fn new() -> Self {
Self {}
}
}
impl Default for Empty {
fn default() -> Self {
Self::new()
}
}
impl<C> Widget<C> for Empty {
fn size(&self, _max_width: Option<i32>, _max_height: Option<i32>) -> Vec2 {
Vec2::ZERO
}
fn draw(self, _view: &mut View<'_, C>) {}
}

View file

@ -1,32 +0,0 @@
use palette::Srgb;
use showbits_common::Color;
#[derive(Clone, Copy)]
pub enum PixelBw {
Black,
White,
Transparent,
Invert,
}
impl Color for PixelBw {
fn to_srgb(self) -> Srgb {
match self {
Self::Black => Srgb::new(0.0, 0.0, 0.0),
Self::White => Srgb::new(1.0, 1.0, 1.0),
_ => Srgb::new(1.0, 0.0, 1.0),
}
}
fn over(self, other: Self) -> Self {
match (self, other) {
(Self::Black, _) => Self::Black,
(Self::White, _) => Self::White,
(Self::Transparent, p) => p,
(Self::Invert, Self::Black) => Self::White,
(Self::Invert, Self::White) => Self::Black,
(Self::Invert, Self::Invert) => Self::Transparent,
(Self::Invert, Self::Transparent) => Self::Invert,
}
}
}