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;
9#[cfg(feature = "image")] use crate::window::Icon;
10use crate::{ChildIndices, Id, Tile, TileExt};
11use std::{error::Error, fmt, path::Path};
12
13enum IdentifyContents<'a> {
14    Simple(&'a Id),
15    Wrapping(&'a dyn Tile),
16}
17
18/// Helper to display widget identification (e.g. `MyWidget#01`)
19///
20/// Constructed by [`crate::Tile::identify`].
21pub struct IdentifyWidget<'a>(&'a str, IdentifyContents<'a>);
22impl<'a> IdentifyWidget<'a> {
23    /// Construct for a simple widget
24    pub fn simple(name: &'a str, id: &'a Id) -> Self {
25        IdentifyWidget(name, IdentifyContents::Simple(id))
26    }
27
28    /// Construct for a wrapping widget
29    pub fn wrapping(name: &'a str, inner: &'a dyn Tile) -> Self {
30        IdentifyWidget(name, IdentifyContents::Wrapping(inner))
31    }
32}
33impl<'a> fmt::Display for IdentifyWidget<'a> {
34    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
35        match self.1 {
36            IdentifyContents::Simple(id) => write!(f, "{}{}", self.0, id),
37            IdentifyContents::Wrapping(inner) => write!(f, "{}<{}>", self.0, inner.identify()),
38        }
39    }
40}
41
42struct Trail<'a> {
43    parent: Option<&'a Trail<'a>>,
44    trail: &'static str,
45}
46impl<'a> fmt::Display for Trail<'a> {
47    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
48        if let Some(p) = self.parent {
49            p.fmt(f)?;
50        }
51        write!(f, "{}", self.trail)
52    }
53}
54
55/// Helper to print widget heirarchy
56///
57/// Note: output starts with a new line.
58pub struct WidgetHierarchy<'a> {
59    widget: &'a dyn Tile,
60    filter: Option<Id>,
61    trail: Trail<'a>,
62    indent: usize,
63    have_next_sibling: bool,
64}
65impl<'a> WidgetHierarchy<'a> {
66    pub fn new(widget: &'a dyn Tile, filter: Option<Id>) -> Self {
67        WidgetHierarchy {
68            widget,
69            filter,
70            trail: Trail {
71                parent: None,
72                trail: "",
73            },
74            indent: 0,
75            have_next_sibling: false,
76        }
77    }
78}
79impl<'a> fmt::Display for WidgetHierarchy<'a> {
80    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
81        let len = 51 - 2 * self.indent;
82        let trail = &self.trail;
83        let (hook, trail_hook) = match self.indent >= 1 {
84            false => ("", ""),
85            true if self.have_next_sibling => ("├ ", "│ "),
86            true => ("└ ", "  "),
87        };
88        // Note: pre-format some items to ensure correct alignment
89        let identify = format!("{}", self.widget.identify());
90        let r = self.widget.rect();
91        let Coord(x1, y1) = r.pos;
92        let Coord(x2, y2) = r.pos + r.size;
93        let xr = format!("x={x1}..{x2}");
94        let xrlen = xr.len().max(12);
95        write!(
96            f,
97            "\n{trail}{hook}{identify:<len$} {xr:<xrlen$} y={y1}..{y2}"
98        )?;
99
100        let indent = self.indent + 1;
101
102        if let Some(id) = self.filter.as_ref()
103            && let Some(index) = self.widget.find_child_index(id)
104            && let Some(widget) = self.widget.get_child(index)
105        {
106            return write!(f, "{}", WidgetHierarchy {
107                widget,
108                filter: self.filter.clone(),
109                trail: Trail {
110                    parent: Some(trail),
111                    trail: trail_hook,
112                },
113                indent,
114                have_next_sibling: false,
115            });
116        }
117
118        let mut iter = self.widget.children();
119        let mut next = iter.next();
120        while let Some(widget) = next {
121            next = iter.next();
122
123            if !widget.id_ref().is_valid() {
124                continue;
125            }
126
127            write!(f, "{}", WidgetHierarchy {
128                widget,
129                filter: None,
130                trail: Trail {
131                    parent: Some(trail),
132                    trail: trail_hook,
133                },
134                indent,
135                have_next_sibling: next.is_some(),
136            })?;
137        }
138        Ok(())
139    }
140}
141
142/// Format for types supporting Debug
143///
144/// This requires the "spec" feature and nightly rustc to be useful.
145pub struct TryFormat<'a, T: ?Sized>(pub &'a T);
146
147#[cfg(not(feature = "spec"))]
148impl<'a, T: ?Sized> fmt::Debug for TryFormat<'a, T> {
149    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
150        write!(f, "{{{}}}", std::any::type_name::<T>())
151    }
152}
153
154#[cfg(feature = "spec")]
155impl<'a, T: ?Sized> fmt::Debug for TryFormat<'a, T> {
156    default fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
157        write!(f, "{{{}}}", std::any::type_name::<T>())
158    }
159}
160
161#[cfg(feature = "spec")]
162impl<'a, T: fmt::Debug + ?Sized> fmt::Debug for TryFormat<'a, T> {
163    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
164        write!(f, "{:?}", self.0)
165    }
166}
167
168/// Generic implementation of [`Tile::nav_next`]
169pub fn nav_next(reverse: bool, from: Option<usize>, indices: ChildIndices) -> Option<usize> {
170    let range = indices.as_range();
171    if range.is_empty() {
172        return None;
173    }
174    let (first, last) = (range.start, range.end - 1);
175
176    if let Some(index) = from {
177        match reverse {
178            false if index < last => Some(index + 1),
179            true if first < index => Some(index - 1),
180            _ => None,
181        }
182    } else {
183        match reverse {
184            false => Some(first),
185            true => Some(last),
186        }
187    }
188}
189
190/// Load a window icon from a path
191#[cfg(feature = "image")]
192pub fn load_icon_from_path<P: AsRef<std::path::Path>>(path: P) -> Result<Icon, Box<dyn Error>> {
193    // TODO(opt): image loading could be de-duplicated with
194    // DrawShared::image_from_path, but this may not be worthwhile.
195    let im = image::ImageReader::open(path)?
196        .with_guessed_format()?
197        .decode()?
198        .into_rgba8();
199    let (w, h) = im.dimensions();
200    Ok(Icon::from_rgba(im.into_vec(), w, h)?)
201}
202
203/// Log a warning regarding an error message
204#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
205#[cfg_attr(docsrs, doc(cfg(internal_doc)))]
206pub fn warn_about_error(msg: &str, mut error: &dyn Error) {
207    log::warn!("{msg}: {error}");
208    while let Some(source) = error.source() {
209        log::warn!("Source: {source}");
210        error = source;
211    }
212}
213
214/// Log a warning regarding an error message with a path
215#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
216#[cfg_attr(docsrs, doc(cfg(internal_doc)))]
217pub fn warn_about_error_with_path(msg: &str, error: &dyn Error, path: &Path) {
218    warn_about_error(msg, error);
219    log::warn!("Path: {}", path.display());
220}