use crate::geom::Coord;
use crate::{ChildIndices, Id, Tile, TileExt, autoimpl};
use std::{error::Error, fmt, path::Path};
enum IdentifyContents<'a> {
Simple(&'a Id),
Wrapping(&'a dyn Tile),
}
pub struct IdentifyWidget<'a>(&'a str, IdentifyContents<'a>);
impl<'a> IdentifyWidget<'a> {
pub fn simple(name: &'a str, id: &'a Id) -> Self {
IdentifyWidget(name, IdentifyContents::Simple(id))
}
pub fn wrapping(name: &'a str, inner: &'a dyn Tile) -> Self {
IdentifyWidget(name, IdentifyContents::Wrapping(inner))
}
}
impl<'a> fmt::Display for IdentifyWidget<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match self.1 {
IdentifyContents::Simple(id) => write!(f, "{}{}", self.0, id),
IdentifyContents::Wrapping(inner) => write!(f, "{}<{}>", self.0, inner.identify()),
}
}
}
struct Trail<'a> {
parent: Option<&'a Trail<'a>>,
trail: &'static str,
}
impl<'a> fmt::Display for Trail<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
if let Some(p) = self.parent {
p.fmt(f)?;
}
write!(f, "{}", self.trail)
}
}
pub struct WidgetHierarchy<'a> {
widget: &'a dyn Tile,
filter: Option<Id>,
trail: Trail<'a>,
indent: usize,
have_next_sibling: bool,
}
impl<'a> WidgetHierarchy<'a> {
pub fn new(widget: &'a dyn Tile, filter: Option<Id>) -> Self {
WidgetHierarchy {
widget,
filter,
trail: Trail {
parent: None,
trail: "",
},
indent: 0,
have_next_sibling: false,
}
}
}
impl<'a> fmt::Display for WidgetHierarchy<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
let len = 51 - 2 * self.indent;
let trail = &self.trail;
let (hook, trail_hook) = match self.indent >= 1 {
false => ("", ""),
true if self.have_next_sibling => ("├ ", "│ "),
true => ("â”” ", " "),
};
let identify = format!("{}", self.widget.identify());
let r = self.widget.rect();
let Coord(x1, y1) = r.pos;
let Coord(x2, y2) = r.pos + r.size;
let xr = format!("x={x1}..{x2}");
let xrlen = xr.len().max(12);
write!(
f,
"\n{trail}{hook}{identify:<len$} {xr:<xrlen$} y={y1}..{y2}"
)?;
let indent = self.indent + 1;
if let Some(id) = self.filter.as_ref()
&& let Some(index) = self.widget.find_child_index(id)
&& let Some(widget) = self.widget.get_child(index)
{
return write!(f, "{}", WidgetHierarchy {
widget,
filter: self.filter.clone(),
trail: Trail {
parent: Some(trail),
trail: trail_hook,
},
indent,
have_next_sibling: false,
});
}
let mut iter = self.widget.children();
let mut next = iter.next();
while let Some(widget) = next {
next = iter.next();
if !widget.id_ref().is_valid() {
continue;
}
write!(f, "{}", WidgetHierarchy {
widget,
filter: None,
trail: Trail {
parent: Some(trail),
trail: trail_hook,
},
indent,
have_next_sibling: next.is_some(),
})?;
}
Ok(())
}
}
pub struct TryFormat<'a, T: ?Sized>(pub &'a T);
#[cfg(not(feature = "spec"))]
impl<'a, T: ?Sized> fmt::Debug for TryFormat<'a, T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{{{}}}", std::any::type_name::<T>())
}
}
#[cfg(feature = "spec")]
impl<'a, T: ?Sized> fmt::Debug for TryFormat<'a, T> {
default fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{{{}}}", std::any::type_name::<T>())
}
}
#[cfg(feature = "spec")]
impl<'a, T: fmt::Debug + ?Sized> fmt::Debug for TryFormat<'a, T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.0)
}
}
pub fn nav_next(reverse: bool, from: Option<usize>, indices: ChildIndices) -> Option<usize> {
let range = indices.as_range();
if range.is_empty() {
return None;
}
let (first, last) = (range.start, range.end - 1);
if let Some(index) = from {
match reverse {
false if index < last => Some(index + 1),
true if first < index => Some(index - 1),
_ => None,
}
} else {
match reverse {
false => Some(first),
true => Some(last),
}
}
}
#[autoimpl(Default)]
#[derive(Clone, Debug)]
pub struct UndoStack<T> {
states: Vec<T>,
head: usize,
}
impl<T: PartialEq> UndoStack<T> {
#[inline]
pub const fn new() -> Self {
UndoStack {
states: vec![],
head: 0,
}
}
#[inline]
pub fn clear(&mut self) {
self.states.clear();
self.head = 0;
}
pub fn try_push(&mut self, state: T) {
if self.head > 0 && self.states.get(self.head - 1) == Some(&state) {
return;
}
self.states.truncate(self.head);
self.states.push(state);
self.head = self.states.len();
}
pub fn undo_or_redo(&mut self, redo: bool) -> Option<&T> {
let h = self.head;
let len = self.states.len();
let j = if redo && h < len {
h + 1
} else if !redo && h > 1 {
h - 1
} else {
return None;
};
self.head = j;
Some(&self.states[j - 1])
}
}
impl<K: Eq, S> UndoStack<(K, S)> {
pub fn clear_for_key(&mut self, key: K) {
let mut i = 0;
while i < self.states.len() {
if self.states[i].0 == key {
self.states.remove(i);
if i <= self.head {
self.head -= 1;
}
} else {
i += 1;
}
}
}
}
#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
#[cfg_attr(docsrs, doc(cfg(internal_doc)))]
pub fn warn_about_error(msg: &str, mut error: &dyn Error) {
log::warn!("{msg}: {error}");
while let Some(source) = error.source() {
log::warn!("Source: {source}");
error = source;
}
}
#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
#[cfg_attr(docsrs, doc(cfg(internal_doc)))]
pub fn warn_about_error_with_path(msg: &str, error: &dyn Error, path: &Path) {
warn_about_error(msg, error);
log::warn!("Path: {}", path.display());
}