mod cells;
mod dirty;
mod pixels;
use alloc::rc::{Rc, Weak};
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use core::cell::RefCell;
use core::marker::PhantomData;
pub use cells::Cell;
pub(crate) use cells::write_text_clipped;
use dirty::DirtyRects;
pub(crate) use pixels::Canvas;
use crate::error::{Error, Result};
use crate::geometry::{Geometry, Rect};
use crate::host;
use crate::local::instance_local;
pub const HEADER_LEN: u32 = 32;
pub const CELL_LEN: u32 = 16;
pub const PIXEL_LEN: u32 = 4;
const HEADER_WORDS: usize = (HEADER_LEN / 4) as usize;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Model {
Cells,
Pixels,
}
impl Model {
pub fn as_str(self) -> &'static str {
match self {
Self::Cells => "cells",
Self::Pixels => "pixels",
}
}
fn word(self) -> u32 {
match self {
Self::Cells => 0,
Self::Pixels => 1,
}
}
fn unit_words(self) -> usize {
match self {
Self::Cells => (CELL_LEN / 4) as usize,
Self::Pixels => 1,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct RegionLayout {
pub len: u32,
pub header_len: u32,
pub slot_len: u32,
pub model: Model,
pub geometry: Geometry,
}
impl RegionLayout {
pub(crate) fn new(model: Model, geometry: Geometry) -> Self {
let slot_len = match model {
Model::Cells => geometry.cols * geometry.rows * CELL_LEN,
Model::Pixels => geometry.px_w * geometry.px_h * PIXEL_LEN,
};
Self {
len: HEADER_LEN + 2 * slot_len,
header_len: HEADER_LEN,
slot_len,
model,
geometry,
}
}
fn extent(&self) -> (u32, u32) {
match self.model {
Model::Cells => (self.geometry.cols, self.geometry.rows),
Model::Pixels => (self.geometry.px_w, self.geometry.px_h),
}
}
fn slot_words(&self) -> usize {
(self.slot_len / 4) as usize
}
}
mod sealed {
pub trait Sealed {}
}
pub trait SurfaceModel: sealed::Sealed {
const MODEL: Model;
}
#[derive(Clone, Copy, Debug)]
pub enum Cells {}
#[derive(Clone, Copy, Debug)]
pub enum Pixels {}
impl sealed::Sealed for Cells {}
impl sealed::Sealed for Pixels {}
impl SurfaceModel for Cells {
const MODEL: Model = Model::Cells;
}
impl SurfaceModel for Pixels {
const MODEL: Model = Model::Pixels;
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CommitInfo {
pub seq: u32,
pub slot: u8,
pub dirty: Vec<Rect>,
pub full: bool,
}
#[derive(Debug)]
enum Backend {
Host,
Offscreen(Geometry),
}
#[derive(Debug)]
struct Binding {
layout: RegionLayout,
region: Vec<u32>,
back: u8,
}
#[derive(Debug)]
pub(crate) struct Inner {
id: String,
model: Model,
backend: Backend,
binding: Option<Binding>,
retired: Option<Vec<u32>>,
dirty: DirtyRects,
sync: Vec<Rect>,
full: bool,
seq: u32,
error: Option<Error>,
last_commit: Option<CommitInfo>,
}
instance_local! {
fn registry() -> Vec<Weak<RefCell<Inner>>> = Vec::new();
}
pub(crate) fn rebind_surfaces(id: &str) {
let surfaces: Vec<Rc<RefCell<Inner>>> = registry(|registry| {
registry.retain(|weak| weak.strong_count() > 0);
registry.iter().filter_map(Weak::upgrade).collect()
});
for surface in surfaces {
let mut inner = surface.borrow_mut();
if inner.id == id {
inner.rebind();
}
}
}
fn as_bytes(words: &[u32]) -> &[u8] {
unsafe { core::slice::from_raw_parts(words.as_ptr().cast::<u8>(), words.len() * 4) }
}
fn as_bytes_mut(words: &mut [u32]) -> &mut [u8] {
unsafe { core::slice::from_raw_parts_mut(words.as_mut_ptr().cast::<u8>(), words.len() * 4) }
}
impl Inner {
fn new(id: String, model: Model, backend: Backend) -> Self {
Self {
id,
model,
backend,
binding: None,
retired: None,
dirty: DirtyRects::default(),
sync: Vec::new(),
full: false,
seq: 0,
error: None,
last_commit: None,
}
}
fn rebind(&mut self) {
match self.bind() {
Ok(_) => self.error = None,
Err(error) => self.error = Some(error),
}
}
fn bind(&mut self) -> Result<bool> {
let layout = match &self.backend {
Backend::Host => host::surface_layout(&self.id)?,
Backend::Offscreen(geometry) => RegionLayout::new(self.model, *geometry),
};
if layout.model != self.model {
return Err(Error::WrongModel {
surface: self.id.clone(),
expected: self.model,
found: layout.model,
});
}
if layout.geometry.is_empty() {
return Ok(false);
}
let mut region = vec![0u32; (layout.len as usize).div_ceil(4)];
let g = layout.geometry;
region[..HEADER_WORDS].copy_from_slice(&[
self.seq,
layout.model.word(),
g.cols,
g.rows,
g.px_w,
g.px_h,
g.cell_px_w,
g.cell_px_h,
]);
if let Backend::Host = self.backend {
host::surface_attach(&self.id, &as_bytes(®ion)[..layout.len as usize])?;
}
if let Some(previous) = self.binding.take() {
if self.retired.is_none() {
self.retired = Some(previous.region);
}
}
self.binding = Some(Binding {
layout,
region,
back: 0,
});
self.full = true;
self.dirty.clear();
self.sync.clear();
Ok(true)
}
fn ensure_bound(&mut self) -> Result<&mut Binding> {
if self.binding.is_none() {
if let Some(error) = self.error.clone() {
return Err(error);
}
self.bind()?;
}
let id = &self.id;
self.binding.as_mut().ok_or_else(|| Error::NotAttached {
surface: id.clone(),
})
}
fn extent(&self) -> (u32, u32) {
self.binding
.as_ref()
.map_or((0, 0), |binding| binding.layout.extent())
}
fn apply_sync(&mut self) {
let Some(binding) = self.binding.as_mut() else {
self.sync.clear();
return;
};
if self.sync.is_empty() {
return;
}
let (width, _) = binding.layout.extent();
let unit = binding.layout.model.unit_words();
let slot_words = binding.layout.slot_words();
let front = HEADER_WORDS + usize::from(binding.back ^ 1) * slot_words;
let back = HEADER_WORDS + usize::from(binding.back) * slot_words;
for rect in self.sync.drain(..) {
for y in rect.y..rect.bottom() {
let start = (y as usize * width as usize + rect.x as usize) * unit;
let len = rect.w as usize * unit;
binding
.region
.copy_within(front + start..front + start + len, back + start);
}
}
}
fn back(&mut self) -> Option<(&mut [u32], RegionLayout, &mut DirtyRects)> {
self.apply_sync();
let binding = self.binding.as_mut()?;
let slot_words = binding.layout.slot_words();
let start = HEADER_WORDS + usize::from(binding.back) * slot_words;
Some((
&mut binding.region[start..start + slot_words],
binding.layout,
&mut self.dirty,
))
}
fn back_discard(&mut self) -> Option<(&mut [u32], RegionLayout)> {
self.sync.clear();
let binding = self.binding.as_mut()?;
let (width, height) = binding.layout.extent();
self.dirty
.add(Rect::new(0, 0, width, height), width, height);
let slot_words = binding.layout.slot_words();
let start = HEADER_WORDS + usize::from(binding.back) * slot_words;
Some((
&mut binding.region[start..start + slot_words],
binding.layout,
))
}
fn mark_dirty(&mut self, rect: Rect) {
self.apply_sync();
let (width, height) = self.extent();
self.dirty.add(rect, width, height);
}
fn commit(&mut self) -> Result<bool> {
self.ensure_bound()?;
let (width, height) = self.extent();
let full = self.full;
let rects = if full {
self.dirty.clear();
if width == 0 || height == 0 {
Vec::new()
} else {
vec![Rect::new(0, 0, width, height)]
}
} else {
self.dirty.take()
};
if rects.is_empty() {
return Ok(false);
}
let seq = self.seq.wrapping_add(1);
let Some(binding) = self.binding.as_mut() else {
return Ok(false);
};
binding.region[0] = seq;
let slot = binding.back;
if let Backend::Host = self.backend
&& let Err(error) = host::surface_commit(&self.id, slot, &rects)
{
for rect in rects {
self.dirty.add(rect, width, height);
}
return Err(error);
}
binding.back ^= 1;
self.seq = seq;
self.full = false;
self.retired = None;
self.sync = rects.clone();
self.last_commit = Some(CommitInfo {
seq,
slot,
dirty: rects,
full,
});
Ok(true)
}
}
#[derive(Debug)]
pub struct Surface<M: SurfaceModel> {
inner: Rc<RefCell<Inner>>,
model: PhantomData<M>,
}
impl<M: SurfaceModel> Surface<M> {
pub fn new(id: &str) -> Self {
let inner = Rc::new(RefCell::new(Inner::new(id.into(), M::MODEL, Backend::Host)));
registry(|registry| registry.push(Rc::downgrade(&inner)));
inner.borrow_mut().rebind();
Self {
inner,
model: PhantomData,
}
}
pub fn offscreen(geometry: Geometry) -> Self {
let mut inner = Inner::new("offscreen".into(), M::MODEL, Backend::Offscreen(geometry));
inner.rebind();
Self {
inner: Rc::new(RefCell::new(inner)),
model: PhantomData,
}
}
pub fn resize_offscreen(&mut self, geometry: Geometry) {
let mut inner = self.inner.borrow_mut();
if let Backend::Offscreen(current) = &mut inner.backend {
*current = geometry;
inner.rebind();
}
}
pub fn id(&self) -> String {
self.inner.borrow().id.clone()
}
pub fn geometry(&self) -> Option<Geometry> {
let inner = self.inner.borrow();
inner
.binding
.as_ref()
.map(|binding| binding.layout.geometry)
}
pub fn size(&self) -> (u32, u32) {
self.inner.borrow().extent()
}
pub fn request_size(&self, cols: u32, rows: u32) -> Result<()> {
crate::host::surface_request_size(&self.id(), cols, rows)
}
pub fn set_label(&self, label: Option<&str>) -> Result<()> {
crate::host::surface_set_label(&self.id(), label)
}
pub fn set_caret(&self, caret: Option<(u32, u32)>) -> Result<()> {
crate::host::surface_set_caret(&self.id(), caret)
}
pub fn is_attached(&self) -> bool {
self.inner.borrow().binding.is_some()
}
pub fn is_dirty(&self) -> bool {
let inner = self.inner.borrow();
inner.binding.is_some() && (inner.full || !inner.dirty.is_empty())
}
pub fn mark_dirty(&mut self, rect: Rect) {
self.inner.borrow_mut().mark_dirty(rect);
}
pub fn commit(&mut self) -> Result<bool> {
self.inner.borrow_mut().commit()
}
pub fn last_commit(&self) -> Option<CommitInfo> {
self.inner.borrow().last_commit.clone()
}
pub fn pending_dirty(&self) -> Vec<Rect> {
self.inner.borrow().dirty.rects().to_vec()
}
pub fn detach(&mut self) -> Result<()> {
let mut inner = self.inner.borrow_mut();
if inner.binding.take().is_some()
&& let Backend::Host = inner.backend
{
inner.retired = None;
return host::surface_detach(&inner.id);
}
Ok(())
}
}
impl<M: SurfaceModel> Drop for Surface<M> {
fn drop(&mut self) {
let _ = self.detach();
}
}
#[derive(Debug)]
pub struct AnySurface {
id: String,
surface: Option<AnySurfaceInner>,
}
#[derive(Debug)]
enum AnySurfaceInner {
Cells(Surface<Cells>),
Pixels(Surface<Pixels>),
}
#[derive(Debug)]
pub enum AnySurfaceMut<'a> {
Cells(&'a mut Surface<Cells>),
Pixels(&'a mut Surface<Pixels>),
}
impl AnySurface {
pub fn new(id: &str) -> Self {
let mut surface = Self {
id: id.into(),
surface: None,
};
surface.sync();
surface
}
pub fn id(&self) -> &str {
&self.id
}
pub fn model(&self) -> Model {
match self.surface {
Some(AnySurfaceInner::Pixels(_)) => Model::Pixels,
_ => Model::Cells,
}
}
fn current_model(&self) -> Option<Model> {
let inner = match self.surface.as_ref()? {
AnySurfaceInner::Cells(surface) => &surface.inner,
AnySurfaceInner::Pixels(surface) => &surface.inner,
};
let inner = inner.borrow();
match &inner.error {
Some(Error::WrongModel { found, .. }) => Some(*found),
_ => Some(inner.model),
}
}
pub fn sync(&mut self) -> bool {
let wanted = match self.current_model() {
Some(model) => model,
None => host::surface_layout(&self.id).map_or(Model::Cells, |layout| layout.model),
};
if self.surface.is_some() && Some(wanted) == self.surface.as_ref().map(|_| self.model()) {
return false;
}
self.surface = None;
self.surface = Some(match wanted {
Model::Cells => AnySurfaceInner::Cells(Surface::new(&self.id)),
Model::Pixels => AnySurfaceInner::Pixels(Surface::new(&self.id)),
});
true
}
pub fn get(&mut self) -> AnySurfaceMut<'_> {
self.sync();
match self.surface.as_mut() {
Some(AnySurfaceInner::Pixels(surface)) => AnySurfaceMut::Pixels(surface),
Some(AnySurfaceInner::Cells(surface)) => AnySurfaceMut::Cells(surface),
None => unreachable!("sync always leaves a surface"),
}
}
}
#[derive(Debug)]
pub struct Instances<M: SurfaceModel> {
surface: String,
panes: alloc::collections::BTreeMap<String, Surface<M>>,
}
pub fn instance_pane<'a>(surface: &str, id: &'a str) -> Option<&'a str> {
id.strip_prefix(surface)?.strip_prefix('@')
}
impl<M: SurfaceModel> Instances<M> {
pub fn new(surface: &str) -> Self {
Self {
surface: surface.into(),
panes: alloc::collections::BTreeMap::new(),
}
}
pub fn event(&mut self, event: &crate::Event) -> Option<String> {
let (id, removed) = match event {
crate::Event::Resize { surface, geometry } => (surface, geometry.is_empty()),
crate::Event::Visibility { surface, .. } => (surface, false),
_ => return None,
};
let pane = String::from(instance_pane(&self.surface, id)?);
if removed {
self.panes.remove(&pane);
} else if !self.panes.contains_key(&pane) {
self.panes.insert(pane.clone(), Surface::new(id));
}
Some(pane)
}
pub fn get_mut(&mut self, pane: &str) -> Option<&mut Surface<M>> {
self.panes.get_mut(pane)
}
pub fn instance(&mut self, owner: &str) -> &mut Surface<M> {
let surface = &self.surface;
self.panes
.entry(String::from(owner))
.or_insert_with(|| Surface::new(&alloc::format!("{surface}@{owner}")))
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut Surface<M>)> {
self.panes
.iter_mut()
.map(|(pane, surface)| (pane.as_str(), surface))
}
pub fn len(&self) -> usize {
self.panes.len()
}
pub fn is_empty(&self) -> bool {
self.panes.is_empty()
}
}