bevy_ratatui_camera 0.16.0

A bevy plugin for rendering your bevy app to the terminal using ratatui.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
use std::{fmt::Debug, sync::Arc};

use bevy::prelude::*;

use crate::color_support::ColorSupport;

/// Specify the strategy used for converting the camera's rendered image to unicode characters for
/// the terminal buffer. Insert a variant of this component alongside your `RatatuiCamera` to
/// change the default behavior.
///
#[derive(Component, Clone, Debug)]
pub enum RatatuiCameraStrategy {
    /// Print to the terminal using unicode halfblock characters. By using both the halfblock
    /// (foreground) color and the background color, we can draw two pixels per buffer cell.
    HalfBlocks(HalfBlocksConfig),

    /// Given a range of unicode characters sorted in increasing order of opacity, use each pixel's
    /// luminance to select a character from the range.
    Luminance(LuminanceConfig),

    /// Given a range of unicode characters sorted in increasing order of opacity, use each pixel's
    /// depth to select a character from the range.
    ///
    /// NOTE: The [RatatuiCameraDepthDetection](crate::RatatuiCameraDepthDetection) component is
    /// required on the same camera entity for this strategy to function, as it relies on the depth
    /// texture.
    Depth(DepthConfig),

    /// Does not print characters by itself, but edge detection will still print. Use with edge
    /// detection for a "wireframe".
    None,
}

impl RatatuiCameraStrategy {
    /// A range of braille unicode characters in increasing order of opacity.
    pub const CHARACTERS_BRAILLE: &'static [char] = &[' ', '', '', '', '', '', '', '', ''];

    /// A range of miscellaneous characters in increasing order of opacity.
    pub const CHARACTERS_MISC: &'static [char] =
        &[' ', '.', ':', '+', '=', '!', '*', '?', '#', '%', '&', '@'];

    /// A range of block characters in increasing order of opacity.
    pub const CHARACTERS_SHADING: &'static [char] = &[' ', '', '', '', ''];

    /// A range of block characters in increasing order of size.
    pub const CHARACTERS_BLOCKS: &'static [char] = &[' ', '', '', '', '', '', '', '', ''];
}

impl Default for RatatuiCameraStrategy {
    fn default() -> Self {
        Self::halfblocks()
    }
}

impl RatatuiCameraStrategy {
    /// Halfblocks strategy using unicode halfblock characters, and the foreground and background
    /// colors of each cell.
    pub fn halfblocks() -> Self {
        Self::HalfBlocks(HalfBlocksConfig::default())
    }

    /// Depth strategy with a provided list of characters.
    pub fn depth_with_characters(characters: &[char]) -> Self {
        Self::Depth(DepthConfig {
            characters: CharactersConfig {
                list: characters.into(),
                scale: DepthConfig::SCALE_DEFAULT,
            },
            ..default()
        })
    }

    /// Depth strategy with a range of braille unicode characters in increasing order of opacity.
    pub fn depth_braille() -> Self {
        Self::Depth(DepthConfig {
            characters: CharactersConfig {
                list: Self::CHARACTERS_BRAILLE.into(),
                scale: DepthConfig::SCALE_DEFAULT,
            },
            ..default()
        })
    }

    /// Depth strategy with a range of miscellaneous characters in increasing order of opacity.
    pub fn depth_misc() -> Self {
        Self::Depth(DepthConfig {
            characters: CharactersConfig {
                list: Self::CHARACTERS_MISC.into(),
                scale: DepthConfig::SCALE_DEFAULT,
            },
            ..default()
        })
    }

    /// Depth strategy with a range of block characters in increasing order of opacity.
    pub fn depth_shading() -> Self {
        Self::Depth(DepthConfig {
            characters: CharactersConfig {
                list: Self::CHARACTERS_SHADING.into(),
                scale: DepthConfig::SCALE_DEFAULT,
            },
            ..default()
        })
    }

    /// Depth strategy with a range of block characters in increasing order of size.
    pub fn depth_blocks() -> Self {
        Self::Depth(DepthConfig {
            characters: CharactersConfig {
                list: Self::CHARACTERS_BLOCKS.into(),
                scale: DepthConfig::SCALE_DEFAULT,
            },
            ..default()
        })
    }

    /// Luminance strategy with a provided list of characters.
    pub fn luminance_with_characters(characters: &[char]) -> Self {
        Self::Luminance(LuminanceConfig {
            characters: CharactersConfig {
                list: characters.into(),
                scale: LuminanceConfig::SCALE_DEFAULT,
            },
            ..default()
        })
    }

    /// Luminance strategy with a range of braille unicode characters in increasing order of opacity.
    pub fn luminance_braille() -> Self {
        Self::Luminance(LuminanceConfig {
            characters: CharactersConfig {
                list: Self::CHARACTERS_BRAILLE.into(),
                scale: LuminanceConfig::SCALE_DEFAULT,
            },
            ..default()
        })
    }

    /// Luminance strategy with a range of miscellaneous characters in increasing order of opacity.
    pub fn luminance_misc() -> Self {
        Self::Luminance(LuminanceConfig {
            characters: CharactersConfig {
                list: Self::CHARACTERS_MISC.into(),
                scale: LuminanceConfig::SCALE_DEFAULT,
            },
            ..default()
        })
    }

    /// Luminance strategy with a range of block characters in increasing order of opacity.
    pub fn luminance_shading() -> Self {
        Self::Luminance(LuminanceConfig {
            characters: CharactersConfig {
                list: Self::CHARACTERS_SHADING.into(),
                scale: LuminanceConfig::SCALE_DEFAULT,
            },
            ..default()
        })
    }

    /// Luminance strategy with a range of block characters in increasing order of size.
    pub fn luminance_blocks() -> Self {
        Self::Luminance(LuminanceConfig {
            characters: CharactersConfig {
                list: Self::CHARACTERS_BLOCKS.into(),
                scale: LuminanceConfig::SCALE_DEFAULT,
            },
            ..default()
        })
    }
}

/// Configuration for the RatatuiCameraStrategy::HalfBlock terminal rendering strategy.
///
/// # Example:
///
/// The following would configure the widget to use ANSI colors.
///
/// ```no_run
/// # use bevy::prelude::*;
/// # use bevy_ratatui_camera::{
/// #   RatatuiCamera, RatatuiCameraStrategy, HalfBlocksConfig, ColorsConfig, ColorSupport
/// # };
/// #
/// # fn setup_scene_system(mut commands: Commands) {
/// # commands.spawn((
/// #     RatatuiCamera::default(),
///     RatatuiCameraStrategy::HalfBlocks(HalfBlocksConfig {
///         colors: ColorsConfig {
///             support: ColorSupport::ANSI16,
///             ..default()
///         },
///         ..default()
///     }),
/// # ));
/// # };
/// ```
///
#[derive(Clone, Debug, Default)]
pub struct HalfBlocksConfig {
    /// Configuration options common to all strategies.
    pub common: CommonConfig,

    /// Configuration for determining the resulting colors.
    pub colors: ColorsConfig,
}

/// Configuration for the RatatuiCameraStrategy::Depth terminal rendering strategy.
///
/// NOTE: The [RatatuiCameraDepthDetection](crate::RatatuiCameraDepthDetection) component is
/// required on the same camera entity for this strategy to function, as it relies on the depth
/// texture.
///
/// # Example:
///
/// The following configures the widget to use '@' for close surfaces and '+' for more distant
/// surfaces.
///
/// ```no_run
/// # use bevy::prelude::*;
/// # use bevy_ratatui_camera::{
/// #   RatatuiCamera, RatatuiCameraStrategy, DepthConfig, CharactersConfig
/// # };
/// #
/// # fn setup_scene_system(mut commands: Commands) {
/// # commands.spawn((
/// #     RatatuiCamera::default(),
///     RatatuiCameraStrategy::Depth(DepthConfig {
///         characters: CharactersConfig {
///             list: vec![' ', '+', '@'],
///             scale: DepthConfig::SCALE_DEFAULT,
///         },
///         ..default()
///     }),
/// # ));
/// # };
/// ```
#[derive(Clone, Debug)]
pub struct DepthConfig {
    /// Configuration options common to all strategies.
    pub common: CommonConfig,

    /// Configuration for determining the resulting characters.
    pub characters: CharactersConfig,

    /// Configuration for determining the resulting colors.
    pub colors: ColorsConfig,
}

impl DepthConfig {
    /// The default scaling value to multiply pixel depth by.
    pub const SCALE_DEFAULT: f32 = 30.;
}

impl Default for DepthConfig {
    fn default() -> Self {
        Self {
            common: CommonConfig::default(),
            characters: CharactersConfig {
                list: RatatuiCameraStrategy::CHARACTERS_MISC.into(),
                scale: DepthConfig::SCALE_DEFAULT,
            },
            colors: ColorsConfig::default(),
        }
    }
}

/// Configuration for the RatatuiCameraStrategy::Luminance terminal rendering strategy.
///
/// # Example:
///
/// The following configures the widget to multiply each pixel's luminance value by 5.0, and use
/// ' ' and '.' for dimmer areas, use '+' and '#' for brighter areas.
///
/// ```no_run
/// # use bevy::prelude::*;
/// # use bevy_ratatui_camera::{
/// #   RatatuiCamera, RatatuiCameraStrategy, LuminanceConfig, CharactersConfig
/// # };
/// #
/// # fn setup_scene_system(mut commands: Commands) {
/// # commands.spawn((
/// #     RatatuiCamera::default(),
///     RatatuiCameraStrategy::Luminance(LuminanceConfig {
///         characters: CharactersConfig {
///             list: vec![' ', '.', '+', '#'],
///             scale: 5.0,
///         },
///         ..default()
///     }),
/// # ));
/// # };
/// ```
///
#[derive(Clone, Debug)]
pub struct LuminanceConfig {
    /// Configuration options common to all strategies.
    pub common: CommonConfig,

    /// Configuration for determining the resulting characters.
    pub characters: CharactersConfig,

    /// Configuration for determining the resulting colors.
    pub colors: ColorsConfig,
}

impl LuminanceConfig {
    /// The default scaling value to multiply pixel luminance by.
    pub const SCALE_DEFAULT: f32 = 10.;
}

impl Default for LuminanceConfig {
    fn default() -> Self {
        Self {
            common: CommonConfig::default(),
            characters: CharactersConfig {
                list: RatatuiCameraStrategy::CHARACTERS_MISC.into(),
                scale: LuminanceConfig::SCALE_DEFAULT,
            },
            colors: ColorsConfig::default(),
        }
    }
}

/// General configuration not specific to particular strategies.
#[derive(Clone, Debug)]
pub struct CommonConfig {
    /// If the alpha value of a rendered pixel is zero, skip writing that character to the ratatui
    /// buffer. Useful for compositing camera images together.
    ///
    /// Normally if two camera widgets are rendered in the same buffer area, the first image will
    /// be completely overwritten by the background of the second, even if the background is empty.
    /// But, with this option enabled, transparent pixels in the second image will skip being drawn
    /// and will leave the first layer as-is.
    ///
    /// Make sure to set the `Camera` component's `clear_color` to fully transparent for your
    /// transparent camera entity. Only fully transparent pixels will be skipped. See the
    /// `transparency` example for more detail.
    pub transparent: bool,
}

impl Default for CommonConfig {
    fn default() -> Self {
        Self { transparent: true }
    }
}

/// Configuration pertaining to character selection, based on criteria determined by the strategy.
#[derive(Clone, Debug)]
pub struct CharactersConfig {
    /// The list of characters, in increasing order of opacity, to use for printing. For example,
    /// put an '@' symbol after a '+' symbol because it is more "opaque", taking up more space in
    /// the cell it is printed in, and so when printed in bright text on a dark background, it
    /// appears to be "brighter".
    pub list: Vec<char>,

    /// The number that each value used for character selection is multiplied by before being used
    /// to select a character. This is useful because many combinations of scenes and character
    /// selection metrics will not occupy the full range between 0.0 and 1.0, and so each luminance
    /// value can be multiplied by a scaling value first to tune the character selection.
    pub scale: f32,
}

/// Configuration pertaining to color selection.
#[derive(Clone, Debug, Default)]
pub struct ColorsConfig {
    /// If present, customizes how the foreground color should be chosen per character.
    pub foreground: Option<ColorChoice>,

    /// If present, customizes how the background color should be chosen per character.
    pub background: Option<ColorChoice>,

    /// The sets of terminal colors to convert to. Many terminals support 24-bit RGB "true color",
    /// but some only support pre-defined sets of 16 or 256 ANSI colors. By default the `RGB` enum
    /// variant will be used, which transparently uses the rgb u8 triplet to create a ratatui
    /// `Color::RGB` color. If set to the `ANSI16` or `ANSI256` enum variants, this strategy will
    /// find the ANSI color within those sets closest to the original rgb color (by Euclidean
    /// distance), and then convert to the corresponding ratatui `Color::Indexed` (for 256 colors)
    /// or named ANSI color, like `Color::Cyan` (for 16 colors).
    ///
    /// Colors that are from a more limited set will not be converted "upwards" to the more
    /// expansive set- for example, if you set an edge detection color of `Color::Cyan` and the
    /// `ColorSupport::ANSI256` variant, the color will be left as-is rather than being converted
    /// to `Color::Indexed(6)` (the equivalent indexed color for cyan).
    ///
    /// Reference for terminal color support:
    /// https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
    pub support: ColorSupport,
}

/// Options for customizing a terminal buffer color (foreground or background). Customization
/// happens after depth detection and edge detection, and before the conversion for color support
/// and the transparency check.
#[derive(Clone)]
pub enum ColorChoice {
    /// Overrides the color with a single provided color.
    Color(ratatui::style::Color),

    /// Color will be determined by scaling the foreground color by the provided value. For
    /// example, `ColorChoice::Scale(0.5)` will be half as bright as the calculated foreground
    /// color.
    Scale(f32),

    /// Provide a callback that will be used to determine the color. When the callback is called,
    /// the first argument is the foreground color, and the second argument is the background
    /// color, as determined by the conversion strategy. Both are an `Option`, as they may be
    /// `None` in cases where the strategy has determined it should skip drawing that pixel or cell
    /// (e.g. if the alpha for that pixel is zero). The result is also an
    /// `Option<ratatui::style::Color>`, as you can signal that drawing the foreground or
    /// background should be skipped by conditionally returning `None` from the callback. Your
    /// callback needs to be wrapped in an `Arc` as `RatatuiCameraStrategy` is cloned during
    /// render (or you can use the `from_callback()` convenience method which wraps it for you).
    Callback(
        Arc<
            dyn Fn(
                    Option<ratatui::style::Color>,
                    Option<ratatui::style::Color>,
                ) -> Option<ratatui::style::Color>
                + Send
                + Sync
                + 'static,
        >,
    ),
}

impl Default for ColorChoice {
    fn default() -> Self {
        Self::Scale(0.5)
    }
}

impl Debug for ColorChoice {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ColorChoice::Color(color) => write!(f, "ColorChoice::Color({:?})", color),
            ColorChoice::Scale(scale) => write!(f, "ColorChoice::Scale({})", scale),
            ColorChoice::Callback(_) => write!(f, "ColorChoice::Callback(...)"),
        }
    }
}

impl ColorChoice {
    /// See [ColorChoice::Callback]. This convenience method creates a `ColorChoice::Callback` enum
    /// variant by wrapping the provided callback in an `Arc`.
    pub fn from_callback<F>(callback: F) -> Self
    where
        F: Fn(
                Option<ratatui::style::Color>,
                Option<ratatui::style::Color>,
            ) -> Option<ratatui::style::Color>
            + Send
            + Sync
            + 'static,
    {
        Self::Callback(Arc::new(callback))
    }
}