1#[cfg(feature = "amethyst-system")]
92mod amethyst;
93
94#[cfg(feature = "amethyst-system")]
95pub use crate::amethyst::*;
96
97use imgui::im_str;
98use log::{Level, LevelFilter, Record};
99use std::sync::mpsc;
100
101pub struct LogLine {
106 pub level: log::Level,
107 pub text: String,
108}
109
110impl std::fmt::Display for LogLine {
111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112 write!(f, "{}", self.text)
113 }
114}
115
116fn default_formatter(record: &Record) -> String {
117 let msg = record.args().to_string();
118 if let (Some(file), Some(line)) = (record.file(), record.line()) {
119 format!("{}:{} --- {}: {}\n", file, line, record.level(), msg)
120 } else {
121 format!("{} --- {}: {}\n", record.target(), record.level(), msg)
122 }
123}
124
125pub struct ChanneledLogger {
130 channel: mpsc::SyncSender<LogLine>,
131 formatter: Box<dyn (Fn(&Record) -> String) + Send + Sync>,
132 stdout: bool,
133}
134
135impl log::Log for ChanneledLogger {
136 fn enabled(&self, metadata: &log::Metadata) -> bool {
137 metadata.level() <= Level::Debug
139 }
140
141 fn log(&self, record: &Record) {
142 if self.enabled(record.metadata()) {
143 let text = (self.formatter)(record);
144
145 if self.stdout {
146 print!("{}", text);
148 }
149
150 let line = LogLine {
153 text,
154 level: record.level(),
155 };
156 let _ = self.channel.try_send(line);
157 }
158 }
159
160 fn flush(&self) {}
161}
162
163#[derive(Clone, Copy)]
165pub struct LogColors {
166 pub trace: [f32; 4],
167 pub debug: [f32; 4],
168 pub info: [f32; 4],
169 pub warn: [f32; 4],
170 pub error: [f32; 4],
171}
172
173impl Default for LogColors {
174 fn default() -> Self {
175 LogColors {
176 trace: [0., 1., 0., 1.],
177 debug: [0., 0., 1., 1.],
178 info: [1., 1., 1., 1.],
179 warn: [1., 1., 0., 1.],
180 error: [1., 0., 0., 1.],
181 }
182 }
183}
184
185impl LogColors {
186 pub fn level(&self, level: Level) -> [f32; 4] {
187 match level {
188 Level::Trace => self.trace,
189 Level::Debug => self.debug,
190 Level::Info => self.info,
191 Level::Warn => self.warn,
192 Level::Error => self.error,
193 }
194 }
195}
196
197pub struct LogWindow {
200 buf: Vec<LogLine>,
201 channel: mpsc::Receiver<LogLine>,
202 autoscroll: bool,
203 colors: LogColors,
204}
205
206impl LogWindow {
207 pub fn new(channel: mpsc::Receiver<LogLine>) -> Self {
208 LogWindow {
209 buf: vec![],
210 channel,
211 autoscroll: false,
212 colors: LogColors::default(),
213 }
214 }
215}
216
217impl LogWindow {
218 fn sync(&mut self) {
219 while let Ok(line) = self.channel.try_recv() {
220 self.buf.push(line);
221 }
222 }
223
224 pub fn clear(&mut self) {
225 self.buf.clear();
226 }
227
228 pub fn set_colors(&mut self, colors: LogColors) {
229 self.colors = colors;
230 }
231
232 pub fn build(&mut self, ui: &imgui::Ui, window: imgui::Window) {
233 self.sync();
234 window.build(ui, || {
235 ui.popup(im_str!("Options"), || {
236 ui.checkbox(im_str!("Auto-scroll"), &mut self.autoscroll);
237 });
238
239 if ui.button(im_str!("Options"), [0., 0.]) {
240 ui.open_popup(im_str!("Options"));
241 }
242 ui.same_line(0.);
243 let clear = ui.button(im_str!("Clear"), [0., 0.]);
244 ui.same_line(0.);
245 let copy = ui.button(im_str!("Copy"), [0., 0.]);
246
247 ui.separator();
248 let child = imgui::ChildWindow::new(imgui::Id::Str("scrolling"))
249 .size([0., 0.])
250 .horizontal_scrollbar(true);
251 child.build(ui, || {
252 if clear {
253 self.clear();
254 }
255 let buf = &mut self.buf;
256 if copy {
257 ui.set_clipboard_text(&imgui::ImString::new(
258 buf.iter()
259 .map(|l| l.to_string())
260 .collect::<Vec<String>>()
261 .join("\n"),
262 ));
263 }
264
265 let style = ui.push_style_var(imgui::StyleVar::ItemSpacing([0., 0.]));
266
267 for record in buf {
268 ui.text_colored(self.colors.level(record.level), &record.text);
269 }
270
271 style.pop(ui);
272
273 if self.autoscroll || ui.scroll_y() >= ui.scroll_max_y() {
274 ui.set_scroll_here_y_with_ratio(1.0);
275 }
276 });
277 });
278 }
279}
280
281pub struct LoggerConfig {
287 formatter: Option<Box<dyn (Fn(&Record) -> String) + Send + Sync>>,
288 colors: Option<LogColors>,
289 stdout: bool,
290}
291
292impl Default for LoggerConfig {
293 fn default() -> Self {
294 LoggerConfig {
295 formatter: None,
296 colors: None,
297 stdout: true,
298 }
299 }
300}
301
302impl LoggerConfig {
303 pub fn formatter(mut self, formatter: fn(&Record) -> String) -> Self {
304 self.formatter = Some(Box::new(formatter));
305 self
306 }
307
308 pub fn colors(mut self, colors: LogColors) -> Self {
309 self.colors = Some(colors);
310 self
311 }
312
313 pub fn stdout(mut self, stdout: bool) -> Self {
314 self.stdout = stdout;
315 self
316 }
317
318 pub fn build(self, channel: mpsc::SyncSender<LogLine>) -> ChanneledLogger {
319 let formatter = {
320 if let Some(f) = self.formatter {
321 f
322 } else {
323 Box::new(default_formatter)
324 }
325 };
326
327 ChanneledLogger {
328 channel,
329 formatter,
330 stdout: self.stdout,
331 }
332 }
333}
334
335fn set_logger(logger: ChanneledLogger) -> Result<(), log::SetLoggerError> {
338 log::set_boxed_logger(Box::new(logger)).map(|()| log::set_max_level(LevelFilter::Debug))
339}
340
341pub fn init_with_config(config: LoggerConfig) -> LogWindow {
344 let (log_writer, log_reader) = mpsc::sync_channel(128);
345
346 let mut window = LogWindow::new(log_reader);
347 if let Some(colors) = config.colors {
348 window.set_colors(colors);
349 }
350
351 let logger = config.build(log_writer);
352 set_logger(logger).unwrap();
353
354 window
355}
356
357pub fn init() -> LogWindow {
360 init_with_config(LoggerConfig::default())
361}