use std::{
cell::RefCell,
io::Write,
rc::Rc,
sync::{Arc, Mutex},
};
use crossterm::{
cursor::MoveTo,
queue,
style::{ContentStyle, SetStyle},
};
use duat_core::{
form::{Form, FormId},
opts::PrintOpts,
text::{Text, TwoPoints},
ui::{Axis, DynSpawnSpecs, Orientation, PushSpecs, Side, SpawnId, StaticSpawnSpecs},
};
use kasuari::{Constraint, WeightedRelation::*};
pub use self::rect::{Deletion, Rect, recurse_length, transfer_vars};
use crate::{
AreaId, Border, CONS_SPAWN_LEN_PRIO, Coords, HIDDEN_PRIO, LEN_PRIO, MANUAL_LEN_PRIO,
area::{Coord, PrintInfo, print_text},
printer::{Lines, Printer},
};
mod rect;
#[derive(Clone, Default)]
pub(crate) struct Layouts(Rc<RefCell<InnerLayouts>>);
impl Layouts {
pub fn new_layout(&self, printer: Arc<Printer>, border: Border, cache: PrintInfo) -> AreaId {
let layout = Layout::new(printer, border, cache);
let main_id = layout.main_id();
let mut layouts = self.0.borrow_mut();
layouts.list.push(layout);
if layouts.active_id.is_none() {
layouts.active_id = Some(main_id);
}
main_id
}
pub fn push(
&self,
target: AreaId,
specs: PushSpecs,
on_buffers: bool,
cache: PrintInfo,
) -> Option<(AreaId, Option<AreaId>)> {
let mut layouts = self.0.borrow_mut();
layouts
.list
.iter_mut()
.find_map(|l| l.push(target, specs, on_buffers, cache))
}
pub fn spawn_on_widget(
&self,
target: AreaId,
spawn_id: SpawnId,
specs: DynSpawnSpecs,
cache: PrintInfo,
) -> Option<AreaId> {
let mut layouts = self.0.borrow_mut();
layouts
.list
.iter_mut()
.find_map(|layout| layout.spawn_on_widget(target, spawn_id, specs, cache))
}
pub fn spawn_on_text(
&self,
id: SpawnId,
specs: DynSpawnSpecs,
cache: PrintInfo,
win: usize,
) -> AreaId {
let mut layouts = self.0.borrow_mut();
layouts.list[win].spawn_on_text(id, specs, cache)
}
pub fn spawn_static(
&self,
id: SpawnId,
specs: StaticSpawnSpecs,
cache: PrintInfo,
win: usize,
) -> AreaId {
let mut layouts = self.0.borrow_mut();
layouts.list[win].spawn_static(id, specs, cache)
}
pub fn delete(&self, id: AreaId) -> (bool, Vec<AreaId>) {
let mut layouts = self.0.borrow_mut();
if let Some((i, rm_areas)) = layouts
.list
.iter_mut()
.enumerate()
.find_map(|(i, layout)| Some(i).zip(layout.delete(id)))
{
if rm_areas.contains(&layouts.list[i].main.id()) {
layouts.list.remove(i);
(true, rm_areas)
} else {
layouts.list[i].printer.update(false, false);
(false, rm_areas)
}
} else {
(false, Vec::new())
}
}
pub fn swap(&self, lhs: AreaId, rhs: AreaId) -> bool {
let mut inner = self.0.borrow_mut();
let list = &mut inner.list;
let (Some((l_layout_i, l_main_id)), Some((r_layout_i, r_main_id))) = (
list.iter()
.enumerate()
.find_map(|(i, layout)| Some(i).zip(layout.get_main_id(lhs))),
list.iter()
.enumerate()
.find_map(|(i, layout)| Some(i).zip(layout.get_main_id(rhs))),
) else {
return false;
};
if l_main_id == r_main_id {
let layout = &mut list[l_layout_i];
let l_id = layout.get_cluster_master(lhs).unwrap_or(lhs);
let r_id = layout.get_cluster_master(rhs).unwrap_or(rhs);
if l_id == r_id {
return false;
}
layout.swap(l_id, r_id);
} else {
let l_p = list[l_layout_i].printer.clone();
let r_p = list[r_layout_i].printer.clone();
let (l_main, r_main) = if l_layout_i == r_layout_i {
list[l_layout_i]
.get_disjoint_mains_mut(l_main_id, r_main_id)
.unwrap()
} else {
let layouts_i = [l_layout_i, r_layout_i];
let [l_layout, r_layout] = list.get_disjoint_mut(layouts_i).unwrap();
let l_main = l_layout.get_mut(l_main_id).unwrap();
let r_main = r_layout.get_mut(r_main_id).unwrap();
(l_main, r_main)
};
let l_id = l_main.get_cluster_master(lhs).unwrap_or(lhs);
let r_id = r_main.get_cluster_master(rhs).unwrap_or(rhs);
let l_rect = l_main.get_mut(l_id).unwrap();
let r_rect = r_main.get_mut(r_id).unwrap();
transfer_vars(&l_p, &r_p, l_rect);
transfer_vars(&r_p, &l_p, r_rect);
std::mem::swap(l_rect, r_rect);
list[l_layout_i].reset_eqs(r_id);
list[r_layout_i].reset_eqs(l_id);
}
for layout_i in [l_layout_i, r_layout_i] {
list[layout_i].printer.update(false, false);
}
true
}
pub fn set_constraints(
&self,
id: AreaId,
width: Option<f32>,
height: Option<f32>,
is_hidden: Option<bool>,
) -> bool {
self.0
.borrow_mut()
.list
.iter_mut()
.any(|layout| layout.set_constraints(id, width, height, is_hidden))
}
pub fn update(&self, id: AreaId) -> bool {
let inner = self.0.borrow();
if let Some(layout) = inner.list.iter().find(|layout| layout.get(id).is_some()) {
layout.printer.update(false, false);
true
} else {
false
}
}
pub fn set_frame(&self, id: AreaId, mut frame: Frame) -> bool {
let mut inner = self.0.borrow_mut();
let frame_was_set = inner
.list
.iter_mut()
.any(|layout| layout.set_frame(id, &mut frame));
frame_was_set
|| inner
.list
.iter()
.any(|layout| layout.main.get(id).is_some())
}
pub fn reset(&self) {
let mut inner = self.0.borrow_mut();
inner.list.clear();
inner.active_id = None;
}
pub fn remove_window(&self, win: usize) {
self.0.borrow_mut().list.remove(win);
}
pub fn set_info_of(&self, id: AreaId, new: PrintInfo) -> bool {
let mut inner = self.0.borrow_mut();
inner.list.iter_mut().any(|layout| {
layout
.get_mut(id)
.and_then(|rect| rect.print_info_mut())
.map(|info| *info = new)
.is_some()
})
}
pub fn send_lines(
&self,
area_id: AreaId,
lines: Lines,
spawns: impl Iterator<Item = SpawnId>,
observed_spawns: &[(SpawnId, Coord, u32)],
) {
let mut inner = self.0.borrow_mut();
let layout = inner
.list
.iter_mut()
.find(|layout| layout.get(area_id).is_some())
.unwrap();
let mut revealed_at_least_one = false;
for spawn_id in spawns {
if let Some((_, rect)) = layout.spawned.iter().find(|(info, _)| info.id == spawn_id) {
let hidden = !observed_spawns.iter().any(|(id, ..)| id == &spawn_id);
recurse_set_hidden(layout, rect.id(), hidden);
revealed_at_least_one = !hidden;
}
}
if revealed_at_least_one {
layout.printer.update(false, false);
}
if let Some((info, _)) = layout
.spawned
.iter()
.find(|(_, rect)| rect.get(area_id).is_some())
{
layout
.printer
.send_spawn_lines(area_id, info.id, lines, &info.frame);
} else {
layout.printer.send_lines(lines);
}
for (id, coord, len) in observed_spawns.iter().copied() {
if let Some((i, _rect)) = inner.list.iter_mut().enumerate().find_map(|(i, layout)| {
layout
.spawned
.iter_mut()
.find_map(|(info, rect)| (info.id == id).then_some((i, rect)))
}) {
inner.list[i].printer.move_spawn_to(id, coord, len);
inner.list[i].printer.update(false, false);
}
}
}
pub fn set_active_id(&self, id: AreaId) {
let mut inner = self.0.borrow_mut();
if inner.list.iter().any(|layout| layout.get(id).is_some()) {
inner.active_id = Some(id);
}
}
pub fn get_active_id(&self) -> AreaId {
self.0.borrow().active_id.unwrap()
}
pub fn inspect<Ret>(&self, id: AreaId, f: impl FnOnce(&Rect, &Layout) -> Ret) -> Option<Ret> {
let inner = self.0.borrow();
inner
.list
.iter()
.find_map(|layout| layout.get(id).zip(Some(layout)))
.map(|(rect, layout)| f(rect, layout))
}
pub fn coords_of(&self, id: AreaId, is_printing: bool) -> Option<Coords> {
let inner = self.0.borrow();
inner
.list
.iter()
.find_map(|layout| layout.coords_of(id, is_printing))
}
pub fn get_info_of(&self, id: AreaId) -> Option<PrintInfo> {
let inner = self.0.borrow();
inner
.list
.iter()
.find_map(|layout| layout.get(id))
.and_then(|rect| rect.print_info())
.cloned()
}
}
#[derive(Default)]
struct InnerLayouts {
list: Vec<Layout>,
active_id: Option<AreaId>,
}
pub struct Layout {
main: Rect,
spawned: Vec<(SpawnInfo, Rect)>,
printer: Arc<Printer>,
}
impl Layout {
pub fn new(printer: Arc<Printer>, border: Border, cache: PrintInfo) -> Self {
let main = Rect::new_main(&printer, border, cache);
Layout { main, spawned: Vec::new(), printer }
}
pub fn main_id(&self) -> AreaId {
self.main.id()
}
fn push(
&mut self,
target: AreaId,
specs: PushSpecs,
on_buffers: bool,
cache: PrintInfo,
) -> Option<(AreaId, Option<AreaId>)> {
self.main
.push(&self.printer, specs, target, on_buffers, cache, None)
.or_else(|| {
self.spawned.iter_mut().find_map(|(info, rect)| {
rect.push(&self.printer, specs, target, on_buffers, cache, Some(info))
})
})
}
fn delete(&mut self, id: AreaId) -> Option<Vec<AreaId>> {
let ret = if let Some(deletion) = self.main.delete(&self.printer, id) {
if let Deletion::Child(rect, cons, rm_list) = deletion {
(rect, cons, rm_list)
} else {
self.printer.remove_rect(&mut self.main);
return Some(remove_dependents(
&mut self.main,
&mut self.spawned,
&self.printer,
Vec::new(),
));
}
} else if let Some((i, deletion)) = self
.spawned
.iter_mut()
.enumerate()
.find_map(|(i, (_, rect))| Some(i).zip(rect.delete(&self.printer, id)))
{
if let Deletion::Child(rect, cons, rm_list) = deletion {
(rect, cons, rm_list)
} else {
let (mut info, mut rect) = self.spawned.remove(i);
self.printer.remove_rect(&mut rect);
self.printer.remove_eqs(info.cons.drain());
self.printer.remove_spawn_info(info.id);
return Some(remove_dependents(
&mut rect,
&mut self.spawned,
&self.printer,
Vec::new(),
));
}
} else {
return None;
};
let (mut rect, mut cons, rm_list) = ret;
self.printer.remove_eqs(cons.drain());
Some(remove_dependents(
&mut rect,
&mut self.spawned,
&self.printer,
rm_list,
))
}
fn swap(&mut self, id0: AreaId, id1: AreaId) {
self.main.swap(&self.printer, id0, id1);
}
fn spawn_on_widget(
&mut self,
target: AreaId,
id: SpawnId,
specs: DynSpawnSpecs,
cache: PrintInfo,
) -> Option<AreaId> {
let ((rect, cons), target_spawn_id) = [(&mut self.main, None, None)]
.into_iter()
.chain(
self.spawned
.iter_mut()
.map(|(info, rect)| (rect, Some(&info.frame), Some(info.id))),
)
.find_map(|(rect, frame, target_spawn_id)| {
rect.new_spawned_on_widget(id, specs, target, &self.printer, cache, frame)
.map(|ret| (ret, target_spawn_id))
})?;
let area_id = rect.id();
let orientation = specs.orientation;
self.spawned.push((
SpawnInfo {
id,
spec: SpawnSpec::Dynamic(orientation, target_spawn_id),
cons,
frame: Default::default(),
},
rect,
));
Some(area_id)
}
fn spawn_on_text(&mut self, id: SpawnId, specs: DynSpawnSpecs, cache: PrintInfo) -> AreaId {
let (rect, cons) =
Rect::new_spawned_on_text(&self.printer, id, self.main.border(), cache, specs);
let rect_id = rect.id();
self.spawned.push((
SpawnInfo {
id,
spec: SpawnSpec::Dynamic(specs.orientation, None),
cons,
frame: Default::default(),
},
rect,
));
rect_id
}
fn spawn_static(&mut self, id: SpawnId, specs: StaticSpawnSpecs, cache: PrintInfo) -> AreaId {
let (rect, cons, orig_max) =
Rect::new_static_spawned(&self.printer, id, self.main.border(), cache, specs);
let rect_id = rect.id();
self.spawned.push((
SpawnInfo {
id,
spec: SpawnSpec::Static {
top_left: specs.top_left,
fractional_repositioning: specs.fractional_repositioning,
orig_max,
},
cons,
frame: Default::default(),
},
rect,
));
rect_id
}
pub fn set_frame(&mut self, id: AreaId, frame: &mut Frame) -> bool {
let Some((info, rect)) = self
.spawned
.iter_mut()
.find(|(_, rect)| rect.get(id).is_some())
else {
return false;
};
if info.frame == *frame {
return true;
}
info.frame = std::mem::take(frame);
match info.spec {
SpawnSpec::Static {
top_left,
fractional_repositioning,
orig_max,
} => {
let width = recurse_length(rect, &info.cons, Axis::Horizontal).unwrap() as f32;
let height = recurse_length(rect, &info.cons, Axis::Vertical).unwrap() as f32;
rect.set_static_spawned_eqs(
&self.printer,
orig_max,
StaticSpawnSpecs {
top_left,
size: duat_core::ui::Coord::new(width, height),
hidden: false,
fractional_repositioning,
},
&info.frame,
);
}
SpawnSpec::Dynamic(orientation, parent_spawn_id) => {
let specs = DynSpawnSpecs { orientation, ..Default::default() };
let (deps, tl, br) = self.printer.get_spawn_info(info.id).unwrap();
rect.set_dyn_spawned_eqs(&self.printer, specs, deps, tl, br, &info.frame);
let info = self
.spawned
.iter()
.find_map(|(info, rect)| rect.get(id).and(Some(info)))
.unwrap();
let parent_frame = self.spawned.iter().find_map(|(info, _)| {
(Some(info.id) == parent_spawn_id).then_some(&info.frame)
});
self.printer.set_frame(info.id, &info.frame, parent_frame);
}
}
self.printer.update(false, true);
true
}
pub fn set_constraints(
&mut self,
id: AreaId,
width: Option<f32>,
height: Option<f32>,
is_hidden: Option<bool>,
) -> bool {
let is_eq = |cons: &mut Constraints| {
width.is_none_or(|w| Some(w) == cons.on(Axis::Horizontal))
&& height.is_none_or(|h| Some(h) == cons.on(Axis::Vertical))
&& is_hidden.is_none_or(|ih| ih == cons.is_hidden)
};
let get_new_cons = |main: &Rect, mut cons: Constraints| {
let old_eqs = cons.replace(width, height, is_hidden);
let rect = main.get(id).unwrap();
let (_, parent) = main.get_parent(id).unzip();
let new_eqs = cons.apply(rect, parent);
self.printer.replace(old_eqs, new_eqs);
cons
};
if let Some(cons) = self.main.get_constraints_mut(id) {
if is_eq(cons) {
return true;
}
let cons = cons.clone();
*self.main.get_constraints_mut(id).unwrap() = get_new_cons(&self.main, cons);
self.printer.update(false, false);
true
} else if let Some((i, cons)) =
self.spawned
.iter_mut()
.enumerate()
.find_map(|(i, (info, rect))| {
(rect.id() == id)
.then_some((i, &mut info.cons))
.or_else(|| Some(i).zip(rect.get_constraints_mut(id)))
})
{
if is_eq(cons) {
return true;
}
let cons = cons.clone();
let (SpawnInfo { cons: main_cons, .. }, main) = &mut self.spawned[i];
if main.id() == id {
*main_cons = get_new_cons(main, cons);
} else {
*main.get_constraints_mut(id).unwrap() = get_new_cons(main, cons);
}
if is_hidden == Some(true) || [width, height].contains(&Some(0.0)) {
self.printer.clear_spawn(id);
}
let (SpawnInfo { id, spec: orientation, cons, .. }, rect) = &self.spawned[i];
if let SpawnSpec::Dynamic(orientation, _) = orientation {
let len = recurse_length(rect, cons, orientation.axis());
self.printer.set_spawn_len(*id, len.map(|len| len as f64));
}
self.printer.update(false, true);
true
} else {
false
}
}
fn reset_eqs(&mut self, id: AreaId) {
[&mut self.main]
.into_iter()
.chain(self.spawned.iter_mut().map(|(_, rect)| rect))
.any(|rect| rect.reset_eqs(&self.printer, id));
}
pub fn get(&self, id: AreaId) -> Option<&Rect> {
[&self.main]
.into_iter()
.chain(self.spawned.iter().map(|(_, rect)| rect))
.find_map(|rect| rect.get(id))
}
pub fn get_parent(&self, id: AreaId) -> Option<(usize, &Rect)> {
[&self.main]
.into_iter()
.chain(self.spawned.iter().map(|(_, rect)| rect))
.find_map(|rect| rect.get_parent(id))
}
pub fn get_cluster_master(&self, id: AreaId) -> Option<AreaId> {
[&self.main]
.into_iter()
.chain(self.spawned.iter().map(|(_, rect)| rect))
.find_map(|rect| rect.get_cluster_master(id))
}
fn get_main_id(&self, id: AreaId) -> Option<AreaId> {
[&self.main]
.into_iter()
.chain(self.spawned.iter().map(|(_, rect)| rect))
.find_map(|rect| rect.get(id).is_some().then_some(rect.id()))
}
fn get_mut(&mut self, id: AreaId) -> Option<&mut Rect> {
[&mut self.main]
.into_iter()
.chain(self.spawned.iter_mut().map(|(_, rect)| rect))
.find_map(|rect| rect.get_mut(id))
}
pub fn max_value(&self) -> crate::area::Coord {
self.printer.max_value()
}
fn coords_of(&self, id: AreaId, is_printing: bool) -> Option<Coords> {
let rect = self.get(id)?;
Some(self.printer.coords(rect.var_points(), is_printing))
}
fn get_disjoint_mains_mut(
&mut self,
l_main_id: AreaId,
r_main_id: AreaId,
) -> Option<(&mut Rect, &mut Rect)> {
let l_con = |rect: &Rect| rect.id() == l_main_id;
let r_con = |rect: &Rect| rect.id() == r_main_id;
if l_main_id == self.main.id() {
let (_, r_main) = self.spawned.iter_mut().find(|(_, rect)| r_con(rect))?;
Some((&mut self.main, r_main))
} else if r_main_id == self.main.id() {
let (_, l_main) = self.spawned.iter_mut().find(|(_, rect)| l_con(rect))?;
Some((l_main, &mut self.main))
} else {
let l_i = self.spawned.iter().position(|(_, rect)| l_con(rect))?;
let r_i = self.spawned.iter().position(|(_, rect)| r_con(rect))?;
let [(_, l_rect), (_, r_rect)] = self.spawned.get_disjoint_mut([l_i, r_i]).ok()?;
Some((l_rect, r_rect))
}
}
}
#[derive(Clone)]
struct SpawnInfo {
id: SpawnId,
spec: SpawnSpec,
cons: Constraints,
frame: Frame,
}
#[derive(Debug, Clone, Copy)]
enum SpawnSpec {
Static {
top_left: duat_core::ui::Coord,
fractional_repositioning: Option<bool>,
orig_max: Coord,
},
Dynamic(Orientation, Option<SpawnId>),
}
#[derive(Default, Debug, Clone)]
pub struct Constraints {
hor_con: Option<Constraint>,
ver_con: Option<Constraint>,
width: Option<(f32, bool)>,
height: Option<(f32, bool)>,
is_hidden: bool,
}
impl Constraints {
fn new(
p: &Printer,
[width, height]: [Option<f32>; 2],
is_hidden: bool,
rect: &Rect,
parent: Option<&Rect>,
) -> Self {
let width = width.zip(Some(false));
let height = height.zip(Some(false));
let is_spawned = rect.spawn_id().is_some();
let [ver_con, hor_con] = get_cons([width, height], rect, is_hidden, is_spawned, parent);
p.add_eqs(ver_con.clone().into_iter().chain(hor_con.clone()));
Self {
hor_con,
ver_con,
width,
height,
is_hidden,
}
}
pub fn replace(
&mut self,
width: Option<f32>,
height: Option<f32>,
is_hidden: Option<bool>,
) -> Vec<Constraint> {
let hor_con = self.hor_con.take();
let ver_con = self.ver_con.take();
self.width = width.map(|w| (w, true)).or(self.width);
self.height = height.map(|h| (h, true)).or(self.height);
self.is_hidden = is_hidden.unwrap_or(self.is_hidden);
hor_con.into_iter().chain(ver_con).collect()
}
pub fn apply(&mut self, rect: &Rect, parent: Option<&Rect>) -> Vec<Constraint> {
let constraints = [self.width, self.height];
let is_spawned = rect.spawn_id().is_some();
let [ver_con, hor_con] = get_cons(constraints, rect, self.is_hidden, is_spawned, parent);
let new_eqs = ver_con.clone().into_iter().chain(hor_con.clone());
self.ver_con = ver_con;
self.hor_con = hor_con;
new_eqs.collect()
}
pub fn drain(&mut self) -> impl Iterator<Item = Constraint> {
self.ver_con.take().into_iter().chain(self.hor_con.take())
}
pub fn on(&self, axis: Axis) -> Option<f32> {
match axis {
Axis::Horizontal => self.width.map(|(w, _)| w),
Axis::Vertical => self.height.map(|(h, _)| h),
}
}
fn is_resizable_on(&self, axis: Axis) -> bool {
self.on(axis).is_none()
}
}
#[derive(Default, Clone)]
#[allow(clippy::type_complexity)]
pub struct Frame {
pub above: bool,
pub below: bool,
pub left: bool,
pub right: bool,
pub style: Option<FrameStyle>,
pub form: Option<FormId>,
#[doc(hidden)]
pub side_texts: [Option<Arc<dyn Fn(usize) -> Text>>; 4],
}
impl Frame {
pub fn sides(&self) -> impl Iterator<Item = bool> {
[self.above, self.below, self.left, self.right].into_iter()
}
pub fn draw(
&self,
stdout: &mut std::io::BufWriter<std::fs::File>,
coords: Coords,
form: Form,
max: Coord,
) {
let above = self.above && coords.tl.y > 0;
let right = self.right && coords.br.x < max.x;
let below = self.below && coords.br.y < max.y;
let left = self.left && coords.tl.x > 0;
let sides = [
above.then_some(Side::Above),
right.then_some(Side::Right),
below.then_some(Side::Below),
left.then_some(Side::Left),
];
let style = self
.style
.clone()
.unwrap_or_else(|| DEFAULT_FRAME_STYLE.lock().unwrap().clone());
for side in sides.into_iter().flatten() {
style.draw_side(stdout, coords, form.style, side);
}
let corners = [
(above && right).then_some((Coord::new(coords.br.x, coords.tl.y - 1), [
Side::Above,
Side::Right,
])),
(below && right).then_some((Coord::new(coords.br.x, coords.br.y), [
Side::Below,
Side::Right,
])),
(below && left).then_some((Coord::new(coords.tl.x - 1, coords.br.y), [
Side::Below,
Side::Left,
])),
(above && left).then_some((Coord::new(coords.tl.x - 1, coords.tl.y - 1), [
Side::Above,
Side::Left,
])),
];
for (coord, sides) in corners.into_iter().flatten() {
style.draw_corner(stdout, coord, form.style, sides);
}
let text_fn = |tl_x: u32, tl_y: u32, br_x: u32, br_y: u32| {
move |text_fn: &Arc<dyn Fn(usize) -> Text>| {
let text = text_fn((br_x - tl_x).max(br_y - tl_y) as usize);
(
Coords::new(Coord::new(tl_x, tl_y), Coord::new(br_x, br_y)),
text,
)
}
};
let texts = [
self.side_texts[0].as_ref().filter(|_| above).map(text_fn(
coords.tl.x,
coords.tl.y - 1,
coords.br.x,
coords.tl.y,
)),
self.side_texts[1].as_ref().filter(|_| right).map(text_fn(
coords.br.x,
coords.tl.y,
coords.br.x + 1,
coords.br.y,
)),
self.side_texts[2].as_ref().filter(|_| below).map(text_fn(
coords.tl.x,
coords.br.y,
coords.br.x,
coords.br.y + 1,
)),
self.side_texts[3].as_ref().filter(|_| left).map(text_fn(
coords.tl.x - 1,
coords.tl.y,
coords.tl.x,
coords.br.y,
)),
];
let opts = PrintOpts { wrap_lines: true, ..PrintOpts::default() };
for (coords, text) in texts.into_iter().flatten() {
let move_fwd = |lines: &mut Lines, len| {
if len > 0 {
write!(lines, "\x1b[{len}C").unwrap()
}
};
let painter = &mut duat_core::form::painter();
if let Some((lines, ..)) = print_text(
(&text, opts, painter),
(coords, max, false),
(false, TwoPoints::default(), 0),
move_fwd,
|_, _, _| {},
move_fwd,
) {
for y in coords.tl.y..coords.br.y {
let (line, ..) = lines.on(y).unwrap();
queue!(stdout, MoveTo(coords.tl.x as u16, y as u16)).unwrap();
stdout.write_all(line).unwrap();
}
}
}
}
pub fn set_text(&mut self, side: Side, text_fn: impl Fn(usize) -> Text + 'static) {
match side {
Side::Above => self.side_texts[0] = Some(Arc::new(text_fn)),
Side::Right => self.side_texts[1] = Some(Arc::new(text_fn)),
Side::Below => self.side_texts[2] = Some(Arc::new(text_fn)),
Side::Left => self.side_texts[3] = Some(Arc::new(text_fn)),
}
}
}
impl PartialEq for Frame {
fn eq(&self, other: &Self) -> bool {
self.above == other.above
&& self.below == other.below
&& self.left == other.left
&& self.right == other.right
&& self.style == other.style
&& self.form == other.form
&& self
.side_texts
.iter()
.zip(other.side_texts.iter())
.all(|(lhs, rhs)| match (lhs, rhs) {
(None, None) => true,
(Some(lhs), Some(rhs)) => Arc::ptr_eq(lhs, rhs),
_ => false,
})
}
}
impl Eq for Frame {}
#[derive(Default, Clone, PartialEq, Eq)]
pub enum FrameStyle {
#[default]
Regular,
Thick,
Dashed,
ThickDashed,
Double,
Rounded,
Halved,
ThinBlock,
Ascii,
Custom {
sides: [char; 4],
corners: [char; 4],
t_mergers: Option<[char; 4]>,
x_merger: Option<char>,
},
}
impl FrameStyle {
pub fn draw_side(
&self,
stdout: &mut std::io::BufWriter<std::fs::File>,
coords: Coords,
style: ContentStyle,
side: Side,
) {
let mut char_str = [b'\0'; 4];
let char = match (side, self) {
(Side::Above | Side::Below, Self::Regular | Self::Rounded) => "─",
(Side::Above | Side::Below, Self::Thick) => "━",
(Side::Above | Side::Below, Self::Dashed) => "┄",
(Side::Above | Side::Below, Self::ThickDashed) => "┅",
(Side::Above | Side::Below, Self::Double) => "═",
(Side::Above | Side::Below, Self::Ascii) => "-",
(Side::Right | Side::Left, Self::Regular | Self::Rounded) => "│",
(Side::Right | Side::Left, Self::Thick) => "┃",
(Side::Right | Side::Left, Self::Dashed) => "┆",
(Side::Right | Side::Left, Self::ThickDashed) => "┇",
(Side::Right | Side::Left, Self::Double) => "║",
(Side::Right | Side::Left, Self::Ascii) => "|",
(Side::Right | Side::Left, Self::Halved) => "█",
(Side::Above, Self::Halved) => "▄",
(Side::Below, Self::Halved) => "▀",
(Side::Above, Self::ThinBlock) => "▔",
(Side::Right, Self::ThinBlock) => "▕",
(Side::Below, Self::ThinBlock) => "▁",
(Side::Left, Self::ThinBlock) => "▏",
(side, Self::Custom { sides, .. }) => match side {
Side::Above => sides[0].encode_utf8(&mut char_str),
Side::Right => sides[1].encode_utf8(&mut char_str),
Side::Below => sides[2].encode_utf8(&mut char_str),
Side::Left => sides[3].encode_utf8(&mut char_str),
},
};
match side {
Side::Left | Side::Right => {
let x = if side == Side::Left {
coords.tl.x as u16 - 1
} else {
coords.br.x as u16
};
for y in coords.tl.y as u16..coords.br.y as u16 {
queue!(stdout, MoveTo(x, y), SetStyle(style),).unwrap();
write!(stdout, "{char}").unwrap();
}
}
Side::Above | Side::Below => {
let y = if side == Side::Above {
coords.tl.y as u16 - 1
} else {
coords.br.y as u16
};
queue!(stdout, MoveTo(coords.tl.x as u16, y), SetStyle(style),).unwrap();
for _ in 0..(coords.br.x - coords.tl.x) {
write!(stdout, "{char}").unwrap();
}
}
}
}
pub fn draw_corner(
&self,
stdout: &mut std::io::BufWriter<std::fs::File>,
coord: Coord,
style: ContentStyle,
sides: [Side; 2],
) {
use Side::*;
let mut char_str = [b'\0'; 4];
let char = match (sides, self) {
([Above, Right] | [Right, Above], Self::Regular | Self::Dashed) => "┐",
([Above, Right] | [Right, Above], Self::Thick | Self::ThickDashed) => "┓",
([Above, Right] | [Right, Above], Self::Double) => "╗",
([Above, Right] | [Right, Above], Self::Rounded) => "╮",
([Above, Right | Left] | [Right | Left, Above], Self::Halved) => "▄",
([Above, Right] | [Right, Above], Self::ThinBlock) => "🭾",
([Above, Left] | [Left, Above], Self::Regular) => "┌",
([Above, Left] | [Left, Above], Self::Thick | Self::ThickDashed) => "┏",
([Above, Left] | [Left, Above], Self::Double) => "╔",
([Above, Left] | [Left, Above], Self::Rounded) => "╭",
([Above, Left] | [Left, Above], Self::ThinBlock) => "🭽",
([Right, Below] | [Below, Right], Self::Regular) => "┘",
([Right, Below] | [Below, Right], Self::Thick | Self::ThickDashed) => "┛",
([Right, Below] | [Below, Right], Self::Double) => "╝",
([Right, Below] | [Below, Right], Self::Rounded) => "╯",
([Right | Left, Below] | [Below, Right | Left], Self::Halved) => "▀",
([Right, Below] | [Below, Right], Self::ThinBlock) => "🭿",
([Below, Left] | [Left, Below], Self::Regular) => "└",
([Below, Left] | [Left, Below], Self::Thick | Self::ThickDashed) => "┗",
([Below, Left] | [Left, Below], Self::Double) => "╚",
([Below, Left] | [Left, Below], Self::Rounded) => "╰",
([Below, Left] | [Left, Below], Self::ThinBlock) => "🭼",
(sides, Self::Custom { corners, .. }) => match sides {
[Above, Right] | [Right, Above] => corners[0].encode_utf8(&mut char_str),
[Above, Left] | [Left, Above] => corners[1].encode_utf8(&mut char_str),
[Right, Below] | [Below, Right] => corners[2].encode_utf8(&mut char_str),
[Below, Left] | [Left, Below] => corners[3].encode_utf8(&mut char_str),
_ => return,
},
(_, Self::Ascii) => "+",
_ => return,
};
queue!(
stdout,
MoveTo(coord.x as u16, coord.y as u16),
SetStyle(style)
)
.unwrap();
stdout.write_all(char.as_bytes()).unwrap();
}
}
fn get_cons(
[width, height]: [Option<(f32, bool)>; 2],
child: &Rect,
is_hidden: bool,
is_spawned: bool,
parent: Option<&Rect>,
) -> [Option<Constraint>; 2] {
if is_hidden {
let hor_con = child.len(Axis::Horizontal) | EQ(HIDDEN_PRIO) | 0.0;
let ver_con = child.len(Axis::Vertical) | EQ(HIDDEN_PRIO) | 0.0;
if let Some(parent) = parent {
if parent.aligns_with(Axis::Horizontal) {
[
Some(hor_con),
height.map(|(h, _)| child.len(Axis::Vertical) | EQ(LEN_PRIO) | h),
]
} else {
[
width.map(|(w, _)| child.len(Axis::Horizontal) | EQ(LEN_PRIO) | w),
Some(ver_con),
]
}
} else {
[Some(hor_con), Some(ver_con)]
}
} else {
[(width, Axis::Horizontal), (height, Axis::Vertical)].map(|(constraint, axis)| {
let (len, is_manual) = constraint?;
let strength = match (is_spawned, is_manual) {
(true, _) => CONS_SPAWN_LEN_PRIO,
(false, true) => MANUAL_LEN_PRIO,
(false, false) => LEN_PRIO,
};
Some(child.len(axis) | EQ(strength) | len)
})
}
}
fn remove_dependents(
rect: &mut Rect,
spawned: &mut Vec<(SpawnInfo, Rect)>,
p: &Printer,
mut rm_list: Vec<AreaId>,
) -> Vec<AreaId> {
rm_list.push(rect.id());
let vars = {
let [tl, br] = rect.var_points();
[tl.x(), tl.y(), br.x(), br.y()]
};
let rm_spawned: Vec<(SpawnInfo, Rect)> = spawned
.extract_if(.., |(info, _)| {
let Some((_, tl, br)) = p.get_spawn_info(info.id) else {
return false;
};
if tl
.iter()
.chain(br.iter())
.any(|expr| expr.terms.iter().any(|term| vars.contains(&term.variable)))
{
p.remove_spawn_info(info.id);
true
} else {
false
}
})
.collect();
for (mut info, mut rect) in rm_spawned {
p.remove_rect(&mut rect);
p.remove_eqs(info.cons.drain());
rm_list = remove_dependents(&mut rect, spawned, p, rm_list);
}
for (rect, cons) in rect.children_mut().into_iter().flat_map(|c| c.iter_mut()) {
p.remove_rect(rect);
p.remove_eqs(cons.drain());
rm_list = remove_dependents(rect, spawned, p, rm_list);
}
rm_list
}
fn recurse_set_hidden(layout: &mut Layout, id: AreaId, hidden: bool) {
let Some(rect) = layout.get(id) else { return };
if layout.get_parent(id).is_none() {
let [tl, _] = rect.var_points();
for i in 0..layout.spawned.len() {
let (info, rect) = &layout.spawned[i];
if let Some((_, [tl_x, _], _)) = layout.printer.get_spawn_info(info.id)
&& tl_x.terms.iter().any(|term| term.variable == tl.x())
{
recurse_set_hidden(layout, rect.id(), hidden);
}
}
}
let Some(rect) = layout.get(id) else { return };
if let Some(children) = rect.children() {
let children: Vec<_> = children.iter().map(|(rect, _)| rect.id()).collect();
for child_id in children {
recurse_set_hidden(layout, child_id, hidden);
}
}
if hidden {
layout.printer.clear_spawn(id);
}
layout.set_constraints(id, None, None, Some(hidden));
}
static DEFAULT_FRAME_STYLE: Mutex<FrameStyle> = Mutex::new(FrameStyle::Regular);