use std::{
io::Write,
sync::atomic::{AtomicBool, Ordering},
};
use crossterm::{
cursor::{self, MoveTo, MoveToColumn},
queue,
style::ResetColor,
};
use duat_core::{
form,
ui::{Axis, SpawnId},
};
use kasuari::{Expression, Variable};
use self::{sync_solver::SyncSolver, variables::Variables};
use crate::{
AreaId, Coords, Equality, Mutex,
area::Coord,
layout::{Frame, Rect},
};
mod edges;
mod sync_solver;
mod variables;
pub use self::edges::{Border, BorderStyle};
#[allow(clippy::type_complexity)]
pub struct Printer {
sync_solver: Mutex<SyncSolver>,
vars: Mutex<Variables>,
old_lines: Mutex<Vec<Lines>>,
new_lines: Mutex<Vec<Lines>>,
spawned_lines: Mutex<Vec<(Vec<(AreaId, Lines)>, SpawnId, Frame)>>,
spawns_have_changed: AtomicBool,
max: VarPoint,
has_to_print_edges: AtomicBool,
}
impl Printer {
pub(crate) fn new() -> Self {
let (vars, sync_solver, max) = {
let mut vars = Variables::new();
let (width, height) = crossterm::terminal::size().unwrap();
let (width, height) = (width as f64, height as f64);
let max = vars.new_point();
let sync_solver = SyncSolver::new(max, width, height);
(vars, sync_solver, max)
};
Self {
sync_solver: Mutex::new(sync_solver),
vars: Mutex::new(vars),
old_lines: Mutex::new(Vec::new()),
new_lines: Mutex::new(Vec::new()),
spawned_lines: Mutex::new(Vec::new()),
spawns_have_changed: AtomicBool::new(false),
max,
has_to_print_edges: AtomicBool::new(false),
}
}
pub fn new_point(&self) -> VarPoint {
self.vars.lock().unwrap().new_point()
}
pub fn new_widget_spawn(
&self,
id: SpawnId,
deps: [VarPoint; 2],
len: Option<f32>,
axis: Axis,
(prefers_before, is_inside): (bool, bool),
parent_frame: Option<&Frame>,
) -> [Variable; 2] {
self.sync_solver.lock().unwrap().new_widget_spawn(
id,
&mut self.vars.lock().unwrap(),
deps,
(len, axis),
(prefers_before, is_inside),
parent_frame,
)
}
pub fn new_text_spawn(
&self,
id: SpawnId,
len: Option<f32>,
axis: Axis,
prefers_before: bool,
) -> ([Variable; 2], VarPoint) {
self.sync_solver.lock().unwrap().new_text_spawn(
id,
&mut self.vars.lock().unwrap(),
len,
axis,
prefers_before,
)
}
pub fn get_spawn_info(
&self,
id: SpawnId,
) -> Option<([Variable; 2], [Expression; 2], [Expression; 2])> {
self.sync_solver.lock().unwrap().get_spawn_info(id)
}
pub fn set_spawn_len(&self, id: SpawnId, len: Option<f64>) {
self.sync_solver.lock().unwrap().set_spawn_len(id, len);
if len == Some(0.0) {
self.spawned_lines
.lock()
.unwrap()
.retain(|(_, other, ..)| *other != id);
}
}
pub fn set_frame(&self, id: SpawnId, frame: &Frame, parent_frame: Option<&Frame>) {
self.sync_solver
.lock()
.unwrap()
.set_frame(id, frame, parent_frame);
}
pub fn set_edge(&self, lhs: VarPoint, rhs: VarPoint, axis: Axis, fr: Border) -> Variable {
self.vars.lock().unwrap().add_edge([lhs, rhs], axis, fr)
}
pub fn add_eqs(&self, eqs: impl IntoIterator<Item = Equality>) {
self.sync_solver.lock().unwrap().add_eqs(eqs);
}
pub fn remove_eqs(&self, eqs: impl IntoIterator<Item = Equality>) {
self.sync_solver.lock().unwrap().remove_eqs(eqs);
}
pub fn remove_edge(&self, edge: Variable) {
self.vars.lock().unwrap().remove_edge(edge);
}
pub fn remove_rect(&self, rect: &mut Rect) -> [Variable; 4] {
self.sync_solver
.lock()
.unwrap()
.remove_eqs(rect.drain_eqs());
let mut vars = self.vars.lock().unwrap();
let [tl, br] = rect.var_points();
if let Some(edge) = rect.edge() {
vars.remove_edge(edge);
}
for var in [tl.x(), tl.y(), br.x(), br.y()] {
vars.remove(var);
}
drop(vars);
self.spawned_lines.lock().unwrap().retain_mut(|(list, ..)| {
list.retain(|(id, _)| *id != rect.id());
!list.is_empty()
});
[tl.x(), tl.y(), br.x(), br.y()]
}
pub fn remove_spawn_info(&self, id: SpawnId) {
let Some(edit_vars) = self.sync_solver.lock().unwrap().remove_spawn_info(id) else {
return;
};
let mut vars = self.vars.lock().unwrap();
match edit_vars {
sync_solver::ReturnedEditVars::WidgetSpawned([center, len]) => {
vars.remove(center);
vars.remove(len);
}
sync_solver::ReturnedEditVars::TextSpawned([center, len, tl_x, tl_y]) => {
vars.remove(center);
vars.remove(len);
vars.remove(tl_x);
vars.remove(tl_y);
}
}
drop(vars);
}
pub fn insert_rect_vars(&self, new_vars: [Variable; 4]) {
let mut vars = self.vars.lock().unwrap();
for var in new_vars {
vars.insert(var);
}
}
pub fn update(&self, change_max: bool, assign_floating: bool) {
let changes = {
let mut ss = self.sync_solver.lock().unwrap();
ss.update(change_max, self.max, assign_floating).unwrap()
};
let mut vars = self.vars.lock().unwrap();
vars.update_variables(changes);
self.has_to_print_edges.store(true, Ordering::Relaxed);
}
pub fn clear_spawn(&self, area_id: AreaId) {
let mut spawned_lines = self.spawned_lines.lock().unwrap();
let old_len = spawned_lines.len();
spawned_lines.retain_mut(|(list, ..)| {
list.retain(|(id, _)| *id != area_id);
!list.is_empty()
});
if old_len != spawned_lines.len() {
self.has_to_print_edges.store(true, Ordering::Relaxed);
self.spawns_have_changed.store(true, Ordering::Relaxed);
}
}
pub fn replace(
&self,
old_eqs: impl IntoIterator<Item = Equality>,
new_eqs: impl IntoIterator<Item = Equality>,
) {
let mut ss = self.sync_solver.lock().unwrap();
ss.remove_eqs(old_eqs);
ss.add_eqs(new_eqs);
}
pub fn move_spawn_to(&self, id: SpawnId, coord: Coord, char_width: u32) {
self.sync_solver
.lock()
.unwrap()
.move_spawn_to(id, coord, char_width);
}
pub fn print(&self) {
static CURSOR_IS_REAL: AtomicBool = AtomicBool::new(false);
let new_lines = std::mem::take(&mut *self.new_lines.lock().unwrap());
let has_to_print_edges = self.has_to_print_edges.swap(false, Ordering::Relaxed);
let mut stdout = stdout::get();
queue!(stdout, cursor::Hide, ResetColor).unwrap();
write!(stdout, "\x1b[?2026h").unwrap();
if has_to_print_edges {
let edge_form = form::from_id(form::id_of!("terminal.border"));
self.vars
.lock()
.unwrap()
.print_edges(&mut stdout, edge_form);
}
let spawned_lines = self.spawned_lines.lock().unwrap();
let mut old_lines = self.old_lines.lock().unwrap();
let max = self.max_value();
let print_old_lines =
self.spawns_have_changed.load(Ordering::Relaxed) || !spawned_lines.is_empty();
self.spawns_have_changed.store(false, Ordering::Relaxed);
for y in 0..max.y {
write!(stdout, "\x1b[{}H", y + 1).unwrap();
let mut x = 0;
let mut old_iter = old_lines.iter().filter_map(|lines| lines.on(y));
let mut new_iter = new_lines.iter().filter_map(|lines| lines.on(y)).peekable();
let mut had_edge_ahead = false;
while let Some((bytes, [start, end], has_edge_ahead)) = new_iter
.next_if(|(_, [start, _], _)| {
!print_old_lines || *start == x + had_edge_ahead as u32
})
.or_else(|| {
if print_old_lines {
old_iter.find(|(_, [start, _], _)| *start >= x)
} else {
None
}
})
{
if x != start {
queue!(stdout, MoveToColumn(start as u16)).unwrap();
}
stdout.write_all(bytes).unwrap();
x = end;
had_edge_ahead = has_edge_ahead;
}
}
let cursor_was_real = if let Some(was_real) = new_lines
.iter()
.filter_map(|lines| lines.real_cursor)
.reduce(|prev, was_real| prev || was_real)
{
CURSOR_IS_REAL.store(was_real, Ordering::Relaxed);
was_real
} else {
CURSOR_IS_REAL.load(Ordering::Relaxed)
};
let frame_form = form::from_id(form::id_of!("terminal.frame"));
for (list, _, frame) in spawned_lines.iter() {
let tl = list.iter().map(|(_, lines)| lines.coords.tl).min().unwrap();
let br = list.iter().map(|(_, lines)| lines.coords.br).max().unwrap();
if tl.x == br.x
|| tl.y == br.y
|| br.x + frame.right as u32 > max.x
|| br.y + frame.below as u32 > max.y
{
continue;
}
for (_, lines) in list.iter() {
for y in lines.coords.tl.y..lines.coords.br.y {
queue!(stdout, MoveTo(lines.coords.tl.x as u16, y as u16)).unwrap();
let (bytes, ..) = lines.on(y).unwrap();
stdout.write_all(bytes).unwrap();
}
}
frame.draw(&mut stdout, Coords::new(tl, br), frame_form, max);
}
if cursor_was_real {
queue!(stdout, cursor::RestorePosition, cursor::Show).unwrap();
}
write!(stdout, "\x1b[?2026l").unwrap();
stdout.flush().unwrap();
for info in new_lines {
old_lines.retain(|l| !l.coords.intersects(info.coords));
let Err(i) = old_lines.binary_search_by_key(&info.coords, |lines| lines.coords) else {
unreachable!("Colliding Lines should have been removed already");
};
old_lines.insert(i, info);
}
}
pub fn clear(&self) {
*self.old_lines.lock().unwrap() = Vec::new();
*self.new_lines.lock().unwrap() = Vec::new();
*self.spawned_lines.lock().unwrap() = Vec::new();
}
pub fn send_lines(&self, lines: Lines) {
let mut new_lines = self.new_lines.lock().unwrap();
new_lines.retain(|l| !l.coords.intersects(lines.coords));
let i = new_lines
.binary_search_by_key(&lines.coords, |lines| lines.coords)
.unwrap_err();
new_lines.insert(i, lines);
}
pub fn send_spawn_lines(
&self,
area_id: AreaId,
spawn_id: SpawnId,
lines: Lines,
frame: &Frame,
) {
let mut spawned_lines = self.spawned_lines.lock().unwrap();
let list = if let Some((list, _, old_frame)) =
spawned_lines.iter_mut().find(|(_, id, _)| *id == spawn_id)
{
if old_frame != frame {
*old_frame = frame.clone()
}
list
} else {
spawned_lines.push((Vec::new(), spawn_id, frame.clone()));
&mut spawned_lines.last_mut().unwrap().0
};
if let Some((_, old_lines)) = list.iter_mut().find(|(id, _)| *id == area_id) {
*old_lines = lines;
} else {
list.push((area_id, lines));
}
self.spawns_have_changed.store(true, Ordering::Relaxed);
}
pub fn max(&self) -> &VarPoint {
&self.max
}
pub fn max_value(&self) -> Coord {
let mut vars = self.vars.lock().unwrap();
let (max, _) = vars.coord(self.max, false);
max
}
pub fn coords(&self, var_points: [VarPoint; 2], is_printing: bool) -> Coords {
let mut vars = self.vars.lock().unwrap();
let (tl, _) = vars.coord(var_points[0], is_printing);
let (br, _) = vars.coord(var_points[1], is_printing);
Coords::new(tl, br)
}
pub fn coords_have_changed(&self, [tl, br]: [VarPoint; 2]) -> bool {
let vars = self.vars.lock().unwrap();
[tl.x(), tl.y(), br.x(), br.y()]
.into_iter()
.any(|var| vars.has_changed(var))
}
}
unsafe impl Send for Printer {}
unsafe impl Sync for Printer {}
#[derive(Debug)]
pub struct Lines {
bytes: Vec<u8>,
offsets: Vec<usize>,
coords: Coords,
real_cursor: Option<bool>,
has_edge_ahead: bool,
}
impl Lines {
pub fn new(coords: Coords, has_edge_ahead: bool) -> Self {
let mut offsets = Vec::with_capacity(coords.height() as usize);
offsets.push(0);
Self {
bytes: Vec::with_capacity(2 * (coords.width() * coords.height()) as usize),
offsets,
coords,
real_cursor: None,
has_edge_ahead,
}
}
pub fn show_real_cursor(&mut self) {
self.real_cursor = Some(true);
}
pub fn hide_real_cursor(&mut self) {
self.real_cursor = Some(false);
}
pub fn on(&self, y: u32) -> Option<(&'_ [u8], [u32; 2], bool)> {
let (tl, br) = (self.coords.tl, self.coords.br);
let y = y.checked_sub(tl.y)? as usize;
self.offsets.get(y).and_then(|offset| {
let end = *self.offsets.get(y + 1)?;
Some((&self.bytes[*offset..end], [tl.x, br.x], self.has_edge_ahead))
})
}
pub fn coords(&self) -> Coords {
self.coords
}
}
impl std::io::Write for Lines {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.bytes.write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.offsets.push(self.bytes.len());
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct VarPoint {
y: Variable,
x: Variable,
}
impl VarPoint {
pub(super) fn new(x: Variable, y: Variable) -> Self {
Self { y, x }
}
pub fn x(&self) -> Variable {
self.x
}
pub fn y(&self) -> Variable {
self.y
}
pub fn on(&self, axis: Axis) -> Variable {
match axis {
Axis::Horizontal => self.x,
Axis::Vertical => self.y,
}
}
}
mod stdout {
use std::{
fs::File,
io::BufWriter,
sync::{LazyLock, Mutex, MutexGuard},
};
pub type Stdout = MutexGuard<'static, BufWriter<File>>;
pub fn get() -> Stdout {
#[cfg(not(windows))]
use unix::get_stdout;
#[cfg(windows)]
use windows::get_stdout;
const CAP: usize = usize::pow(2, 14);
static STDOUT: LazyLock<Mutex<BufWriter<File>>> =
LazyLock::new(|| Mutex::new(BufWriter::with_capacity(CAP, get_stdout())));
STDOUT.lock().unwrap()
}
#[cfg(not(windows))]
mod unix {
use std::{
fs::File,
io::stdout,
os::fd::{AsRawFd, FromRawFd},
};
pub fn get_stdout() -> File {
unsafe { File::from_raw_fd(stdout().as_raw_fd()) }
}
}
#[cfg(windows)]
mod windows {
use std::{
fs::File,
io::stdout,
os::windows::io::{AsRawHandle, FromRawHandle},
};
pub fn get_stdout() -> File {
unsafe { File::from_raw_handle(stdout().as_raw_handle()) }
}
}
}