Skip to main content

kas_core/
util.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! Utilities
7
8use crate::geom::Coord;
9use crate::{ChildIndices, Id, Tile, TileExt, autoimpl};
10use std::{error::Error, fmt, path::Path};
11
12enum IdentifyContents<'a> {
13    Simple(&'a Id),
14    Wrapping(&'a dyn Tile),
15}
16
17/// Helper to display widget identification (e.g. `MyWidget#01`)
18///
19/// Constructed by [`crate::Tile::identify`].
20pub struct IdentifyWidget<'a>(&'a str, IdentifyContents<'a>);
21impl<'a> IdentifyWidget<'a> {
22    /// Construct for a simple widget
23    pub fn simple(name: &'a str, id: &'a Id) -> Self {
24        IdentifyWidget(name, IdentifyContents::Simple(id))
25    }
26
27    /// Construct for a wrapping widget
28    pub fn wrapping(name: &'a str, inner: &'a dyn Tile) -> Self {
29        IdentifyWidget(name, IdentifyContents::Wrapping(inner))
30    }
31}
32impl<'a> fmt::Display for IdentifyWidget<'a> {
33    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
34        match self.1 {
35            IdentifyContents::Simple(id) => write!(f, "{}{}", self.0, id),
36            IdentifyContents::Wrapping(inner) => write!(f, "{}<{}>", self.0, inner.identify()),
37        }
38    }
39}
40
41struct Trail<'a> {
42    parent: Option<&'a Trail<'a>>,
43    trail: &'static str,
44}
45impl<'a> fmt::Display for Trail<'a> {
46    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
47        if let Some(p) = self.parent {
48            p.fmt(f)?;
49        }
50        write!(f, "{}", self.trail)
51    }
52}
53
54/// Helper to print widget heirarchy
55///
56/// Note: output starts with a new line.
57pub struct WidgetHierarchy<'a> {
58    widget: &'a dyn Tile,
59    filter: Option<Id>,
60    trail: Trail<'a>,
61    indent: usize,
62    have_next_sibling: bool,
63}
64impl<'a> WidgetHierarchy<'a> {
65    pub fn new(widget: &'a dyn Tile, filter: Option<Id>) -> Self {
66        WidgetHierarchy {
67            widget,
68            filter,
69            trail: Trail {
70                parent: None,
71                trail: "",
72            },
73            indent: 0,
74            have_next_sibling: false,
75        }
76    }
77}
78impl<'a> fmt::Display for WidgetHierarchy<'a> {
79    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
80        let len = 51 - 2 * self.indent;
81        let trail = &self.trail;
82        let (hook, trail_hook) = match self.indent >= 1 {
83            false => ("", ""),
84            true if self.have_next_sibling => ("├ ", "│ "),
85            true => ("└ ", "  "),
86        };
87        // Note: pre-format some items to ensure correct alignment
88        let identify = format!("{}", self.widget.identify());
89        let r = self.widget.rect();
90        let Coord(x1, y1) = r.pos;
91        let Coord(x2, y2) = r.pos + r.size;
92        let xr = format!("x={x1}..{x2}");
93        let xrlen = xr.len().max(12);
94        write!(
95            f,
96            "\n{trail}{hook}{identify:<len$} {xr:<xrlen$} y={y1}..{y2}"
97        )?;
98
99        let indent = self.indent + 1;
100
101        if let Some(id) = self.filter.as_ref()
102            && let Some(index) = self.widget.find_child_index(id)
103            && let Some(widget) = self.widget.get_child(index)
104        {
105            return write!(f, "{}", WidgetHierarchy {
106                widget,
107                filter: self.filter.clone(),
108                trail: Trail {
109                    parent: Some(trail),
110                    trail: trail_hook,
111                },
112                indent,
113                have_next_sibling: false,
114            });
115        }
116
117        let mut iter = self.widget.children();
118        let mut next = iter.next();
119        while let Some(widget) = next {
120            next = iter.next();
121
122            if !widget.id_ref().is_valid() {
123                continue;
124            }
125
126            write!(f, "{}", WidgetHierarchy {
127                widget,
128                filter: None,
129                trail: Trail {
130                    parent: Some(trail),
131                    trail: trail_hook,
132                },
133                indent,
134                have_next_sibling: next.is_some(),
135            })?;
136        }
137        Ok(())
138    }
139}
140
141/// Format for types supporting Debug
142///
143/// This requires the "spec" feature and nightly rustc to be useful.
144pub struct TryFormat<'a, T: ?Sized>(pub &'a T);
145
146#[cfg(not(feature = "spec"))]
147impl<'a, T: ?Sized> fmt::Debug for TryFormat<'a, T> {
148    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
149        write!(f, "{{{}}}", std::any::type_name::<T>())
150    }
151}
152
153#[cfg(feature = "spec")]
154impl<'a, T: ?Sized> fmt::Debug for TryFormat<'a, T> {
155    default fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
156        write!(f, "{{{}}}", std::any::type_name::<T>())
157    }
158}
159
160#[cfg(feature = "spec")]
161impl<'a, T: fmt::Debug + ?Sized> fmt::Debug for TryFormat<'a, T> {
162    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
163        write!(f, "{:?}", self.0)
164    }
165}
166
167/// Generic implementation of [`Tile::nav_next`]
168pub fn nav_next(reverse: bool, from: Option<usize>, indices: ChildIndices) -> Option<usize> {
169    let range = indices.as_range();
170    if range.is_empty() {
171        return None;
172    }
173    let (first, last) = (range.start, range.end - 1);
174
175    if let Some(index) = from {
176        match reverse {
177            false if index < last => Some(index + 1),
178            true if first < index => Some(index - 1),
179            _ => None,
180        }
181    } else {
182        match reverse {
183            false => Some(first),
184            true => Some(last),
185        }
186    }
187}
188
189/// A stack of undo states
190///
191/// Typically `T` will be either `State` or `(Id, State)`.
192#[autoimpl(Default)]
193#[derive(Clone, Debug)]
194pub struct UndoStack<T> {
195    states: Vec<T>,
196    head: usize,
197}
198
199impl<T: PartialEq> UndoStack<T> {
200    /// Construct a new instance (const)
201    #[inline]
202    pub const fn new() -> Self {
203        UndoStack {
204            states: vec![],
205            head: 0,
206        }
207    }
208
209    /// Clear all history
210    #[inline]
211    pub fn clear(&mut self) {
212        self.states.clear();
213        self.head = 0;
214    }
215
216    /// Push a new state
217    ///
218    /// Tries pushing a new state, failing if the new state is identical to the current one.
219    ///
220    /// On success this discards all states ahead of the current state (due to usage of undo).
221    pub fn try_push(&mut self, state: T) {
222        if self.head > 0 && self.states.get(self.head - 1) == Some(&state) {
223            return;
224        }
225
226        self.states.truncate(self.head);
227        self.states.push(state);
228        self.head = self.states.len();
229    }
230
231    /// Apply undo (or redo if `redo`), yielding the appropriate committed state (if any)
232    pub fn undo_or_redo(&mut self, redo: bool) -> Option<&T> {
233        let h = self.head;
234        let len = self.states.len();
235        let j = if redo && h < len {
236            h + 1
237        } else if !redo && h > 1 {
238            h - 1
239        } else {
240            return None;
241        };
242
243        self.head = j;
244        Some(&self.states[j - 1])
245    }
246}
247
248impl<K: Eq, S> UndoStack<(K, S)> {
249    /// Clear historical states for `key`
250    ///
251    /// Typically used for `K` = [`Id`].
252    pub fn clear_for_key(&mut self, key: K) {
253        let mut i = 0;
254        while i < self.states.len() {
255            if self.states[i].0 == key {
256                self.states.remove(i);
257                if i <= self.head {
258                    self.head -= 1;
259                }
260            } else {
261                i += 1;
262            }
263        }
264    }
265}
266
267/// Log a warning regarding an error message
268#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
269#[cfg_attr(docsrs, doc(cfg(internal_doc)))]
270pub fn warn_about_error(msg: &str, mut error: &dyn Error) {
271    log::warn!("{msg}: {error}");
272    while let Some(source) = error.source() {
273        log::warn!("Source: {source}");
274        error = source;
275    }
276}
277
278/// Log a warning regarding an error message with a path
279#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
280#[cfg_attr(docsrs, doc(cfg(internal_doc)))]
281pub fn warn_about_error_with_path(msg: &str, error: &dyn Error, path: &Path) {
282    warn_about_error(msg, error);
283    log::warn!("Path: {}", path.display());
284}