Compare commits
131 commits
measure-wi
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 57aa8c5930 | |||
| e3af509358 | |||
| 89b4595ed9 | |||
| 96b2e13c4a | |||
| 712c1537ad | |||
| d28ce90ec7 | |||
| 423dd100c1 | |||
| be7eff0979 | |||
| 77a02116a6 | |||
| 1618264cb7 | |||
| 73a0268dfd | |||
| 65f31a2697 | |||
| 3a5ce3832b | |||
| b1d7221bae | |||
| 0f7505ebb4 | |||
| ef6d75c23a | |||
| 8556fd8176 | |||
| 94052c5a65 | |||
| 761e8baeba | |||
| 2d604d606c | |||
| b01ee297d5 | |||
| 44512f1088 | |||
| b757f1be03 | |||
| 2714deeafb | |||
| 77b4f825c9 | |||
| 2c7888fa41 | |||
| f6cbba5231 | |||
| 87723840df | |||
| a4ec64aa57 | |||
| 6eb853e313 | |||
| 3b9ffe8715 | |||
| f005ec10fe | |||
| 8bfb4b2dc3 | |||
| f414db40d5 | |||
| 968dbe501f | |||
| 4179e7f56c | |||
| 57788a9dd9 | |||
| 59710c8162 | |||
| 242a1aed29 | |||
| d0b3b9edd4 | |||
| 810524325e | |||
| 35aa70de4b | |||
| 7c6e651f88 | |||
| 007493f136 | |||
| 77e72de9ad | |||
| 88e66e17ec | |||
| ea6be7bf32 | |||
| 542ea7bc66 | |||
| 0573fcec77 | |||
| 417f33cc24 | |||
| 607c11fea4 | |||
| cb483431cc | |||
| 397d3a6eac | |||
| 783e57a9ab | |||
| 8f155dc6a2 | |||
| b1c276ec38 | |||
| a8876e94f3 | |||
| bdc1549268 | |||
| 204540f375 | |||
| b27cb81642 | |||
| ba716dd089 | |||
| 3fb3a7b92b | |||
| 42d22e2a49 | |||
| d449c61f27 | |||
| f581fa6c47 | |||
| 828bba464a | |||
| 15e30dfdb2 | |||
| e666d5c092 | |||
| caca3b6ef1 | |||
| f25ce49e77 | |||
| 95a01d5fc8 | |||
| 8834bb6d9d | |||
| 7c3277a822 | |||
| 72b44fb3fc | |||
| ba6ee45110 | |||
| fae12a4b9f | |||
| b2d87543d7 | |||
| ac2546ba97 | |||
| ed14ea9023 | |||
| e3365fdc02 | |||
| 2dee39c03c | |||
| 5a15838989 | |||
| 845d88c93f | |||
| c689d97974 | |||
| 9ff8007cae | |||
| 4c304ffe79 | |||
| 67f703cf68 | |||
| eb36bfa2ea | |||
| 3f7e985b3f | |||
| b327dee3c3 | |||
| 47df35d9db | |||
| 575faf9bbf | |||
| bcc07dc9ba | |||
| dbafc40700 | |||
| 964f3bf011 | |||
| 6a0c0474ec | |||
| f793ec79ac | |||
| 70d33d4d5d | |||
| 904f5c16fa | |||
| 4ffaae067e | |||
| 0d59116012 | |||
| 0a3b193f79 | |||
| 8942b381f5 | |||
| 06aefd562b | |||
| f48901f543 | |||
| 6ed47ad916 | |||
| f258c84094 | |||
| 24fd0050fb | |||
| 45ece466c2 | |||
| 7e42913245 | |||
| fbe9e065fc | |||
| 5957e8e550 | |||
| 3b2a2105fe | |||
| 31bb2de87b | |||
| d186291ef7 | |||
| dfc10f9d09 | |||
| 26a8936cf5 | |||
| 464aefa6d7 | |||
| c1907bb8ee | |||
| 53b2728c82 | |||
| 14aedaf252 | |||
| e4e1454e80 | |||
| d693712dab | |||
| f0af4ddc40 | |||
| 26bf89023e | |||
| ee9d6018c0 | |||
| 11b2211fad | |||
| 9b0d80873f | |||
| 761519c1a7 | |||
| a0602a941c | |||
| 333cf74fba |
46 changed files with 4140 additions and 10743 deletions
8
.vscode/settings.json
vendored
Normal file
8
.vscode/settings.json
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"files.insertFinalNewline": true,
|
||||
"rust-analyzer.cargo.features": "all",
|
||||
"rust-analyzer.imports.granularity.enforce": true,
|
||||
"rust-analyzer.imports.granularity.group": "module",
|
||||
"rust-analyzer.imports.group.enable": true,
|
||||
"evenBetterToml.formatter.columnWidth": 100,
|
||||
}
|
||||
70
CHANGELOG.md
Normal file
70
CHANGELOG.md
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
Procedure when bumping the version number:
|
||||
1. Update dependencies in a separate commit
|
||||
2. Set version number in `Cargo.toml`
|
||||
3. Add new section in this changelog
|
||||
4. Commit with message `Bump version to X.Y.Z`
|
||||
5. Create tag named `vX.Y.Z`
|
||||
6. Push `master` and the new tag
|
||||
|
||||
## Unreleased
|
||||
|
||||
## v0.3.4 - 2025-03-8
|
||||
|
||||
### Added
|
||||
- `Frame::set_bell` to print a bell character when the frame is displayed
|
||||
- `widgets::bell`
|
||||
|
||||
## v0.3.3 - 2025-02-28
|
||||
|
||||
### Fixed
|
||||
- Rendering glitches in unicode-based with estimation
|
||||
|
||||
## v0.3.2 - 2025-02-23
|
||||
|
||||
### Added
|
||||
- Unicode-based grapheme width estimation method
|
||||
|
||||
## v0.3.1 - 2025-02-21
|
||||
|
||||
### Fixed
|
||||
- Rendering glitches, mainly related to emoji
|
||||
|
||||
## v0.3.0 - 2024-11-06
|
||||
|
||||
### Added
|
||||
- `Terminal::mark_dirty`
|
||||
|
||||
### Changed
|
||||
- **(breaking)** Updated dependencies
|
||||
|
||||
## v0.2.3 - 2024-04-25
|
||||
|
||||
### Fixed
|
||||
- Width measurements of ASCII control characters
|
||||
- Toss messing up the terminal state
|
||||
|
||||
## v0.2.2 - 2024-01-14
|
||||
|
||||
### Fixed
|
||||
- Crash when drawing `widgets::Predrawn` with width 0
|
||||
|
||||
## v0.2.1 - 2024-01-05
|
||||
|
||||
### Added
|
||||
- `Frame::set_title`
|
||||
- `WidgetExt::title`
|
||||
- `widgets::title`
|
||||
|
||||
## v0.2.0 - 2023-08-31
|
||||
|
||||
### Changed
|
||||
- **(breaking)** Updated dependencies
|
||||
|
||||
## v0.1.0 - 2023-05-14
|
||||
|
||||
Initial release
|
||||
14
Cargo.toml
14
Cargo.toml
|
|
@ -1,13 +1,11 @@
|
|||
[package]
|
||||
name = "toss"
|
||||
version = "0.1.0"
|
||||
version = "0.3.4"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
crossterm = "0.23.2"
|
||||
unicode-linebreak = "0.1.2"
|
||||
unicode-segmentation = "1.9.0"
|
||||
unicode-width = "0.1.9"
|
||||
|
||||
[dev-dependencies]
|
||||
unicode-blocks = "0.1.4"
|
||||
async-trait = "0.1.83"
|
||||
crossterm = "0.28.1"
|
||||
unicode-linebreak = "0.1.5"
|
||||
unicode-segmentation = "1.12.0"
|
||||
unicode-width = "0.2.0"
|
||||
|
|
|
|||
|
|
@ -1,39 +1,30 @@
|
|||
use crossterm::event::Event;
|
||||
use crossterm::style::{ContentStyle, Stylize};
|
||||
use toss::frame::{Frame, Pos};
|
||||
use toss::terminal::{Redraw, Terminal};
|
||||
use crossterm::style::Stylize;
|
||||
use toss::{Frame, Pos, Style, Terminal};
|
||||
|
||||
fn draw(f: &mut Frame) {
|
||||
f.write(
|
||||
Pos::new(0, 0),
|
||||
"Hello world!",
|
||||
ContentStyle::default().green(),
|
||||
);
|
||||
f.write(Pos::new(0, 0), ("Hello world!", Style::new().green()));
|
||||
f.write(
|
||||
Pos::new(0, 1),
|
||||
"Press any key to exit",
|
||||
ContentStyle::default().on_dark_blue(),
|
||||
("Press any key to exit", Style::new().on_dark_blue()),
|
||||
);
|
||||
f.show_cursor(Pos::new(16, 0));
|
||||
}
|
||||
|
||||
fn render_frame(term: &mut Terminal) {
|
||||
loop {
|
||||
// Must be called before rendering, otherwise the terminal has out-of-date
|
||||
// size information and will present garbage.
|
||||
let mut dirty = true;
|
||||
while dirty {
|
||||
term.autoresize().unwrap();
|
||||
|
||||
draw(term.frame());
|
||||
|
||||
if term.present().unwrap() == Redraw::NotRequired {
|
||||
break;
|
||||
}
|
||||
term.present().unwrap();
|
||||
dirty = term.measure_widths().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Automatically enters alternate screen and enables raw mode
|
||||
let mut term = Terminal::new().unwrap();
|
||||
term.set_measuring(true);
|
||||
|
||||
loop {
|
||||
// Render and display a frame. A full frame is displayed on the terminal
|
||||
|
|
|
|||
47
examples/hello_world_widgets.rs
Normal file
47
examples/hello_world_widgets.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
use std::io;
|
||||
|
||||
use crossterm::event::Event;
|
||||
use crossterm::style::Stylize;
|
||||
use toss::widgets::{BorderLook, Text};
|
||||
use toss::{Style, Styled, Terminal, Widget, WidgetExt};
|
||||
|
||||
fn widget() -> impl Widget<io::Error> {
|
||||
let styled = Styled::new("Hello world!", Style::new().dark_green())
|
||||
.then_plain("\n")
|
||||
.then("Press any key to exit", Style::new().on_dark_blue());
|
||||
Text::new(styled)
|
||||
.padding()
|
||||
.with_horizontal(1)
|
||||
.border()
|
||||
.with_look(BorderLook::LINE_DOUBLE)
|
||||
.with_style(Style::new().dark_red())
|
||||
.background()
|
||||
.with_style(Style::new().on_yellow().opaque())
|
||||
.float()
|
||||
.with_all(0.5)
|
||||
}
|
||||
|
||||
fn render_frame(term: &mut Terminal) {
|
||||
let mut dirty = true;
|
||||
while dirty {
|
||||
term.present_widget(widget()).unwrap();
|
||||
dirty = term.measure_widths().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Automatically enters alternate screen and enables raw mode
|
||||
let mut term = Terminal::new().unwrap();
|
||||
term.set_measuring(true);
|
||||
|
||||
loop {
|
||||
// Render and display a frame. A full frame is displayed on the terminal
|
||||
// once this function exits.
|
||||
render_frame(&mut term);
|
||||
|
||||
// Exit if the user presses any buttons
|
||||
if !matches!(crossterm::event::read().unwrap(), Event::Resize(_, _)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,375 +0,0 @@
|
|||
use std::io;
|
||||
|
||||
use crossterm::cursor::MoveTo;
|
||||
use crossterm::execute;
|
||||
use crossterm::style::Print;
|
||||
use crossterm::terminal::{Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen};
|
||||
use unicode_blocks::UnicodeBlock;
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
|
||||
use unicode_blocks as ub;
|
||||
|
||||
fn measure_width(c: char) {
|
||||
if let Some(predicted_width) = c.width() {
|
||||
execute!(
|
||||
io::stdout(),
|
||||
Clear(ClearType::CurrentLine),
|
||||
MoveTo(0, 1),
|
||||
Print(c),
|
||||
)
|
||||
.unwrap();
|
||||
let actual_width: usize = crossterm::cursor::position().unwrap().0.into();
|
||||
if predicted_width != actual_width {
|
||||
eprintln!(
|
||||
"{}: actual {actual_width}, expected {predicted_width}",
|
||||
c as u32
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn measure_widths(block: UnicodeBlock) {
|
||||
execute!(
|
||||
io::stdout(),
|
||||
Clear(ClearType::All),
|
||||
MoveTo(0, 0),
|
||||
Print(block.name()),
|
||||
MoveTo(0, 1),
|
||||
)
|
||||
.unwrap();
|
||||
for c in block.start()..=block.end() {
|
||||
if let Some(c) = char::from_u32(c) {
|
||||
measure_width(c);
|
||||
} else {
|
||||
eprintln!("{c}: error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen).unwrap();
|
||||
|
||||
measure_widths(ub::ADLAM);
|
||||
measure_widths(ub::AEGEAN_NUMBERS);
|
||||
measure_widths(ub::AHOM);
|
||||
measure_widths(ub::ALCHEMICAL_SYMBOLS);
|
||||
measure_widths(ub::ALPHABETIC_PRESENTATION_FORMS);
|
||||
measure_widths(ub::ANATOLIAN_HIEROGLYPHS);
|
||||
measure_widths(ub::ANCIENT_GREEK_MUSICAL_NOTATION);
|
||||
measure_widths(ub::ANCIENT_GREEK_NUMBERS);
|
||||
measure_widths(ub::ANCIENT_SYMBOLS);
|
||||
measure_widths(ub::ARABIC);
|
||||
measure_widths(ub::ARABIC_EXTENDED_A);
|
||||
measure_widths(ub::ARABIC_EXTENDED_B);
|
||||
measure_widths(ub::ARABIC_MATHEMATICAL_ALPHABETIC_SYMBOLS);
|
||||
measure_widths(ub::ARABIC_PRESENTATION_FORMS_A);
|
||||
measure_widths(ub::ARABIC_PRESENTATION_FORMS_B);
|
||||
measure_widths(ub::ARABIC_SUPPLEMENT);
|
||||
measure_widths(ub::ARMENIAN);
|
||||
measure_widths(ub::ARROWS);
|
||||
measure_widths(ub::AVESTAN);
|
||||
measure_widths(ub::BALINESE);
|
||||
measure_widths(ub::BAMUM);
|
||||
measure_widths(ub::BAMUM_SUPPLEMENT);
|
||||
measure_widths(ub::BASIC_LATIN);
|
||||
measure_widths(ub::BASSA_VAH);
|
||||
measure_widths(ub::BATAK);
|
||||
measure_widths(ub::BENGALI);
|
||||
measure_widths(ub::BHAIKSUKI);
|
||||
measure_widths(ub::BLOCK_ELEMENTS);
|
||||
measure_widths(ub::BOPOMOFO);
|
||||
measure_widths(ub::BOPOMOFO_EXTENDED);
|
||||
measure_widths(ub::BOX_DRAWING);
|
||||
measure_widths(ub::BRAHMI);
|
||||
measure_widths(ub::BRAILLE_PATTERNS);
|
||||
measure_widths(ub::BUGINESE);
|
||||
measure_widths(ub::BUHID);
|
||||
measure_widths(ub::BYZANTINE_MUSICAL_SYMBOLS);
|
||||
measure_widths(ub::CARIAN);
|
||||
measure_widths(ub::CAUCASIAN_ALBANIAN);
|
||||
measure_widths(ub::CHAKMA);
|
||||
measure_widths(ub::CHAM);
|
||||
measure_widths(ub::CHEROKEE);
|
||||
measure_widths(ub::CHEROKEE_SUPPLEMENT);
|
||||
measure_widths(ub::CHESS_SYMBOLS);
|
||||
measure_widths(ub::CHORASMIAN);
|
||||
measure_widths(ub::CJK_COMPATIBILITY);
|
||||
measure_widths(ub::CJK_COMPATIBILITY_FORMS);
|
||||
measure_widths(ub::CJK_COMPATIBILITY_IDEOGRAPHS);
|
||||
measure_widths(ub::CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT);
|
||||
measure_widths(ub::CJK_RADICALS_SUPPLEMENT);
|
||||
measure_widths(ub::CJK_STROKES);
|
||||
measure_widths(ub::CJK_SYMBOLS_AND_PUNCTUATION);
|
||||
measure_widths(ub::CJK_UNIFIED_IDEOGRAPHS);
|
||||
measure_widths(ub::CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A);
|
||||
measure_widths(ub::CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B);
|
||||
measure_widths(ub::CJK_UNIFIED_IDEOGRAPHS_EXTENSION_C);
|
||||
measure_widths(ub::CJK_UNIFIED_IDEOGRAPHS_EXTENSION_D);
|
||||
measure_widths(ub::CJK_UNIFIED_IDEOGRAPHS_EXTENSION_E);
|
||||
measure_widths(ub::CJK_UNIFIED_IDEOGRAPHS_EXTENSION_F);
|
||||
measure_widths(ub::CJK_UNIFIED_IDEOGRAPHS_EXTENSION_G);
|
||||
measure_widths(ub::COMBINING_DIACRITICAL_MARKS);
|
||||
measure_widths(ub::COMBINING_DIACRITICAL_MARKS_EXTENDED);
|
||||
measure_widths(ub::COMBINING_DIACRITICAL_MARKS_FOR_SYMBOLS);
|
||||
measure_widths(ub::COMBINING_DIACRITICAL_MARKS_SUPPLEMENT);
|
||||
measure_widths(ub::COMBINING_HALF_MARKS);
|
||||
measure_widths(ub::COMMON_INDIC_NUMBER_FORMS);
|
||||
measure_widths(ub::CONTROL_PICTURES);
|
||||
measure_widths(ub::COPTIC);
|
||||
measure_widths(ub::COPTIC_EPACT_NUMBERS);
|
||||
measure_widths(ub::COUNTING_ROD_NUMERALS);
|
||||
measure_widths(ub::CUNEIFORM);
|
||||
measure_widths(ub::CUNEIFORM_NUMBERS_AND_PUNCTUATION);
|
||||
measure_widths(ub::CURRENCY_SYMBOLS);
|
||||
measure_widths(ub::CYPRIOT_SYLLABARY);
|
||||
measure_widths(ub::CYPRO_MINOAN);
|
||||
measure_widths(ub::CYRILLIC);
|
||||
measure_widths(ub::CYRILLIC_EXTENDED_A);
|
||||
measure_widths(ub::CYRILLIC_EXTENDED_B);
|
||||
measure_widths(ub::CYRILLIC_EXTENDED_C);
|
||||
measure_widths(ub::CYRILLIC_SUPPLEMENT);
|
||||
measure_widths(ub::DESERET);
|
||||
measure_widths(ub::DEVANAGARI);
|
||||
measure_widths(ub::DEVANAGARI_EXTENDED);
|
||||
measure_widths(ub::DINGBATS);
|
||||
measure_widths(ub::DIVES_AKURU);
|
||||
measure_widths(ub::DOGRA);
|
||||
measure_widths(ub::DOMINO_TILES);
|
||||
measure_widths(ub::DUPLOYAN);
|
||||
measure_widths(ub::EARLY_DYNASTIC_CUNEIFORM);
|
||||
measure_widths(ub::EGYPTIAN_HIEROGLYPHS);
|
||||
measure_widths(ub::EGYPTIAN_HIEROGLYPH_FORMAT_CONTROLS);
|
||||
measure_widths(ub::ELBASAN);
|
||||
measure_widths(ub::ELYMAIC);
|
||||
measure_widths(ub::EMOTICONS);
|
||||
measure_widths(ub::ENCLOSED_ALPHANUMERICS);
|
||||
measure_widths(ub::ENCLOSED_ALPHANUMERIC_SUPPLEMENT);
|
||||
measure_widths(ub::ENCLOSED_CJK_LETTERS_AND_MONTHS);
|
||||
measure_widths(ub::ENCLOSED_IDEOGRAPHIC_SUPPLEMENT);
|
||||
measure_widths(ub::ETHIOPIC);
|
||||
measure_widths(ub::ETHIOPIC_EXTENDED);
|
||||
measure_widths(ub::ETHIOPIC_EXTENDED_A);
|
||||
measure_widths(ub::ETHIOPIC_EXTENDED_B);
|
||||
measure_widths(ub::ETHIOPIC_SUPPLEMENT);
|
||||
measure_widths(ub::GENERAL_PUNCTUATION);
|
||||
measure_widths(ub::GEOMETRIC_SHAPES);
|
||||
measure_widths(ub::GEOMETRIC_SHAPES_EXTENDED);
|
||||
measure_widths(ub::GEORGIAN);
|
||||
measure_widths(ub::GEORGIAN_EXTENDED);
|
||||
measure_widths(ub::GEORGIAN_SUPPLEMENT);
|
||||
measure_widths(ub::GLAGOLITIC);
|
||||
measure_widths(ub::GLAGOLITIC_SUPPLEMENT);
|
||||
measure_widths(ub::GOTHIC);
|
||||
measure_widths(ub::GRANTHA);
|
||||
measure_widths(ub::GREEK_AND_COPTIC);
|
||||
measure_widths(ub::GREEK_EXTENDED);
|
||||
measure_widths(ub::GUJARATI);
|
||||
measure_widths(ub::GUNJALA_GONDI);
|
||||
measure_widths(ub::GURMUKHI);
|
||||
measure_widths(ub::HALFWIDTH_AND_FULLWIDTH_FORMS);
|
||||
measure_widths(ub::HANGUL_COMPATIBILITY_JAMO);
|
||||
measure_widths(ub::HANGUL_JAMO);
|
||||
measure_widths(ub::HANGUL_JAMO_EXTENDED_A);
|
||||
measure_widths(ub::HANGUL_JAMO_EXTENDED_B);
|
||||
measure_widths(ub::HANGUL_SYLLABLES);
|
||||
measure_widths(ub::HANIFI_ROHINGYA);
|
||||
measure_widths(ub::HANUNOO);
|
||||
measure_widths(ub::HATRAN);
|
||||
measure_widths(ub::HEBREW);
|
||||
// measure_widths(ub::HIGH_PRIVATE_USE_SURROGATES);
|
||||
// measure_widths(ub::HIGH_SURROGATES);
|
||||
measure_widths(ub::HIRAGANA);
|
||||
measure_widths(ub::IDEOGRAPHIC_DESCRIPTION_CHARACTERS);
|
||||
measure_widths(ub::IDEOGRAPHIC_SYMBOLS_AND_PUNCTUATION);
|
||||
measure_widths(ub::IMPERIAL_ARAMAIC);
|
||||
measure_widths(ub::INDIC_SIYAQ_NUMBERS);
|
||||
measure_widths(ub::INSCRIPTIONAL_PAHLAVI);
|
||||
measure_widths(ub::INSCRIPTIONAL_PARTHIAN);
|
||||
measure_widths(ub::IPA_EXTENSIONS);
|
||||
measure_widths(ub::JAVANESE);
|
||||
measure_widths(ub::KAITHI);
|
||||
measure_widths(ub::KANA_EXTENDED_A);
|
||||
measure_widths(ub::KANA_EXTENDED_B);
|
||||
measure_widths(ub::KANA_SUPPLEMENT);
|
||||
measure_widths(ub::KANBUN);
|
||||
measure_widths(ub::KANGXI_RADICALS);
|
||||
measure_widths(ub::KANNADA);
|
||||
measure_widths(ub::KATAKANA);
|
||||
measure_widths(ub::KATAKANA_PHONETIC_EXTENSIONS);
|
||||
measure_widths(ub::KAYAH_LI);
|
||||
measure_widths(ub::KHAROSHTHI);
|
||||
measure_widths(ub::KHITAN_SMALL_SCRIPT);
|
||||
measure_widths(ub::KHMER);
|
||||
measure_widths(ub::KHMER_SYMBOLS);
|
||||
measure_widths(ub::KHOJKI);
|
||||
measure_widths(ub::KHUDAWADI);
|
||||
measure_widths(ub::LAO);
|
||||
measure_widths(ub::LATIN_1_SUPPLEMENT);
|
||||
measure_widths(ub::LATIN_EXTENDED_A);
|
||||
measure_widths(ub::LATIN_EXTENDED_ADDITIONAL);
|
||||
measure_widths(ub::LATIN_EXTENDED_B);
|
||||
measure_widths(ub::LATIN_EXTENDED_C);
|
||||
measure_widths(ub::LATIN_EXTENDED_D);
|
||||
measure_widths(ub::LATIN_EXTENDED_E);
|
||||
measure_widths(ub::LATIN_EXTENDED_F);
|
||||
measure_widths(ub::LATIN_EXTENDED_G);
|
||||
measure_widths(ub::LEPCHA);
|
||||
measure_widths(ub::LETTERLIKE_SYMBOLS);
|
||||
measure_widths(ub::LIMBU);
|
||||
measure_widths(ub::LINEAR_A);
|
||||
measure_widths(ub::LINEAR_B_IDEOGRAMS);
|
||||
measure_widths(ub::LINEAR_B_SYLLABARY);
|
||||
measure_widths(ub::LISU);
|
||||
measure_widths(ub::LISU_SUPPLEMENT);
|
||||
// measure_widths(ub::LOW_SURROGATES);
|
||||
measure_widths(ub::LYCIAN);
|
||||
measure_widths(ub::LYDIAN);
|
||||
measure_widths(ub::MAHAJANI);
|
||||
measure_widths(ub::MAHJONG_TILES);
|
||||
measure_widths(ub::MAKASAR);
|
||||
measure_widths(ub::MALAYALAM);
|
||||
measure_widths(ub::MANDAIC);
|
||||
measure_widths(ub::MANICHAEAN);
|
||||
measure_widths(ub::MARCHEN);
|
||||
measure_widths(ub::MASARAM_GONDI);
|
||||
measure_widths(ub::MATHEMATICAL_ALPHANUMERIC_SYMBOLS);
|
||||
measure_widths(ub::MATHEMATICAL_OPERATORS);
|
||||
measure_widths(ub::MAYAN_NUMERALS);
|
||||
measure_widths(ub::MEDEFAIDRIN);
|
||||
measure_widths(ub::MEETEI_MAYEK);
|
||||
measure_widths(ub::MEETEI_MAYEK_EXTENSIONS);
|
||||
measure_widths(ub::MENDE_KIKAKUI);
|
||||
measure_widths(ub::MEROITIC_CURSIVE);
|
||||
measure_widths(ub::MEROITIC_HIEROGLYPHS);
|
||||
measure_widths(ub::MIAO);
|
||||
measure_widths(ub::MISCELLANEOUS_MATHEMATICAL_SYMBOLS_A);
|
||||
measure_widths(ub::MISCELLANEOUS_MATHEMATICAL_SYMBOLS_B);
|
||||
measure_widths(ub::MISCELLANEOUS_SYMBOLS);
|
||||
measure_widths(ub::MISCELLANEOUS_SYMBOLS_AND_ARROWS);
|
||||
measure_widths(ub::MISCELLANEOUS_SYMBOLS_AND_PICTOGRAPHS);
|
||||
measure_widths(ub::MISCELLANEOUS_TECHNICAL);
|
||||
measure_widths(ub::MODI);
|
||||
measure_widths(ub::MODIFIER_TONE_LETTERS);
|
||||
measure_widths(ub::MONGOLIAN);
|
||||
measure_widths(ub::MONGOLIAN_SUPPLEMENT);
|
||||
measure_widths(ub::MRO);
|
||||
measure_widths(ub::MULTANI);
|
||||
measure_widths(ub::MUSICAL_SYMBOLS);
|
||||
measure_widths(ub::MYANMAR);
|
||||
measure_widths(ub::MYANMAR_EXTENDED_A);
|
||||
measure_widths(ub::MYANMAR_EXTENDED_B);
|
||||
measure_widths(ub::NABATAEAN);
|
||||
measure_widths(ub::NANDINAGARI);
|
||||
measure_widths(ub::NEWA);
|
||||
measure_widths(ub::NEW_TAI_LUE);
|
||||
measure_widths(ub::NKO);
|
||||
measure_widths(ub::NUMBER_FORMS);
|
||||
measure_widths(ub::NUSHU);
|
||||
measure_widths(ub::NYIAKENG_PUACHUE_HMONG);
|
||||
measure_widths(ub::OGHAM);
|
||||
measure_widths(ub::OLD_HUNGARIAN);
|
||||
measure_widths(ub::OLD_ITALIC);
|
||||
measure_widths(ub::OLD_NORTH_ARABIAN);
|
||||
measure_widths(ub::OLD_PERMIC);
|
||||
measure_widths(ub::OLD_PERSIAN);
|
||||
measure_widths(ub::OLD_SOGDIAN);
|
||||
measure_widths(ub::OLD_SOUTH_ARABIAN);
|
||||
measure_widths(ub::OLD_TURKIC);
|
||||
measure_widths(ub::OLD_UYGHUR);
|
||||
measure_widths(ub::OL_CHIKI);
|
||||
measure_widths(ub::OPTICAL_CHARACTER_RECOGNITION);
|
||||
measure_widths(ub::ORIYA);
|
||||
measure_widths(ub::ORNAMENTAL_DINGBATS);
|
||||
measure_widths(ub::OSAGE);
|
||||
measure_widths(ub::OSMANYA);
|
||||
measure_widths(ub::OTTOMAN_SIYAQ_NUMBERS);
|
||||
measure_widths(ub::PAHAWH_HMONG);
|
||||
measure_widths(ub::PALMYRENE);
|
||||
measure_widths(ub::PAU_CIN_HAU);
|
||||
measure_widths(ub::PHAGS_PA);
|
||||
measure_widths(ub::PHAISTOS_DISC);
|
||||
measure_widths(ub::PHOENICIAN);
|
||||
measure_widths(ub::PHONETIC_EXTENSIONS);
|
||||
measure_widths(ub::PHONETIC_EXTENSIONS_SUPPLEMENT);
|
||||
measure_widths(ub::PLAYING_CARDS);
|
||||
measure_widths(ub::PRIVATE_USE_AREA);
|
||||
measure_widths(ub::PSALTER_PAHLAVI);
|
||||
measure_widths(ub::REJANG);
|
||||
measure_widths(ub::RUMI_NUMERAL_SYMBOLS);
|
||||
measure_widths(ub::RUNIC);
|
||||
measure_widths(ub::SAMARITAN);
|
||||
measure_widths(ub::SAURASHTRA);
|
||||
measure_widths(ub::SHARADA);
|
||||
measure_widths(ub::SHAVIAN);
|
||||
measure_widths(ub::SHORTHAND_FORMAT_CONTROLS);
|
||||
measure_widths(ub::SIDDHAM);
|
||||
measure_widths(ub::SINHALA);
|
||||
measure_widths(ub::SINHALA_ARCHAIC_NUMBERS);
|
||||
measure_widths(ub::SMALL_FORM_VARIANTS);
|
||||
measure_widths(ub::SMALL_KANA_EXTENSION);
|
||||
measure_widths(ub::SOGDIAN);
|
||||
measure_widths(ub::SORA_SOMPENG);
|
||||
measure_widths(ub::SOYOMBO);
|
||||
measure_widths(ub::SPACING_MODIFIER_LETTERS);
|
||||
measure_widths(ub::SPECIALS);
|
||||
measure_widths(ub::SUNDANESE);
|
||||
measure_widths(ub::SUNDANESE_SUPPLEMENT);
|
||||
measure_widths(ub::SUPERSCRIPTS_AND_SUBSCRIPTS);
|
||||
measure_widths(ub::SUPPLEMENTAL_ARROWS_A);
|
||||
measure_widths(ub::SUPPLEMENTAL_ARROWS_B);
|
||||
measure_widths(ub::SUPPLEMENTAL_ARROWS_C);
|
||||
measure_widths(ub::SUPPLEMENTAL_MATHEMATICAL_OPERATORS);
|
||||
measure_widths(ub::SUPPLEMENTAL_PUNCTUATION);
|
||||
measure_widths(ub::SUPPLEMENTAL_SYMBOLS_AND_PICTOGRAPHS);
|
||||
measure_widths(ub::SUPPLEMENTARY_PRIVATE_USE_AREA_A);
|
||||
measure_widths(ub::SUPPLEMENTARY_PRIVATE_USE_AREA_B);
|
||||
measure_widths(ub::SUTTON_SIGNWRITING);
|
||||
measure_widths(ub::SYLOTI_NAGRI);
|
||||
measure_widths(ub::SYMBOLS_AND_PICTOGRAPHS_EXTENDED_A);
|
||||
measure_widths(ub::SYMBOLS_FOR_LEGACY_COMPUTING);
|
||||
measure_widths(ub::SYRIAC);
|
||||
measure_widths(ub::SYRIAC_SUPPLEMENT);
|
||||
measure_widths(ub::TAGALOG);
|
||||
measure_widths(ub::TAGBANWA);
|
||||
measure_widths(ub::TAGS);
|
||||
measure_widths(ub::TAI_LE);
|
||||
measure_widths(ub::TAI_THAM);
|
||||
measure_widths(ub::TAI_VIET);
|
||||
measure_widths(ub::TAI_XUAN_JING_SYMBOLS);
|
||||
measure_widths(ub::TAKRI);
|
||||
measure_widths(ub::TAMIL);
|
||||
measure_widths(ub::TAMIL_SUPPLEMENT);
|
||||
measure_widths(ub::TANGSA);
|
||||
measure_widths(ub::TANGUT);
|
||||
measure_widths(ub::TANGUT_COMPONENTS);
|
||||
measure_widths(ub::TANGUT_SUPPLEMENT);
|
||||
measure_widths(ub::TELUGU);
|
||||
measure_widths(ub::THAANA);
|
||||
measure_widths(ub::THAI);
|
||||
measure_widths(ub::TIBETAN);
|
||||
measure_widths(ub::TIFINAGH);
|
||||
measure_widths(ub::TIRHUTA);
|
||||
measure_widths(ub::TOTO);
|
||||
measure_widths(ub::TRANSPORT_AND_MAP_SYMBOLS);
|
||||
measure_widths(ub::UGARITIC);
|
||||
measure_widths(ub::UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS);
|
||||
measure_widths(ub::UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS_EXTENDED);
|
||||
measure_widths(ub::UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS_EXTENDED_A);
|
||||
measure_widths(ub::VAI);
|
||||
measure_widths(ub::VARIATION_SELECTORS);
|
||||
measure_widths(ub::VARIATION_SELECTORS_SUPPLEMENT);
|
||||
measure_widths(ub::VEDIC_EXTENSIONS);
|
||||
measure_widths(ub::VERTICAL_FORMS);
|
||||
measure_widths(ub::VITHKUQI);
|
||||
measure_widths(ub::WANCHO);
|
||||
measure_widths(ub::WARANG_CITI);
|
||||
measure_widths(ub::YEZIDI);
|
||||
measure_widths(ub::YIJING_HEXAGRAM_SYMBOLS);
|
||||
measure_widths(ub::YI_RADICALS);
|
||||
measure_widths(ub::YI_SYLLABLES);
|
||||
measure_widths(ub::ZANABAZAR_SQUARE);
|
||||
measure_widths(ub::ZNAMENNY_MUSICAL_NOTATION);
|
||||
|
||||
execute!(stdout, LeaveAlternateScreen).unwrap();
|
||||
}
|
||||
|
|
@ -1,79 +1,67 @@
|
|||
use crossterm::event::Event;
|
||||
use crossterm::style::{ContentStyle, Stylize};
|
||||
use toss::frame::{Frame, Pos};
|
||||
use toss::terminal::{Redraw, Terminal};
|
||||
use crossterm::style::Stylize;
|
||||
use toss::{Frame, Pos, Style, Terminal};
|
||||
|
||||
fn draw(f: &mut Frame) {
|
||||
f.write(
|
||||
Pos::new(0, 0),
|
||||
"Writing over wide graphemes removes the entire overwritten grapheme.",
|
||||
ContentStyle::default(),
|
||||
);
|
||||
let under = ContentStyle::default().white().on_dark_blue();
|
||||
let over = ContentStyle::default().black().on_dark_yellow();
|
||||
let under = Style::new().white().on_dark_blue();
|
||||
let over = Style::new().black().on_dark_yellow();
|
||||
for i in 0..6 {
|
||||
let delta = i - 2;
|
||||
f.write(Pos::new(2 + i * 7, 2), "a😀", under);
|
||||
f.write(Pos::new(2 + i * 7, 3), "a😀", under);
|
||||
f.write(Pos::new(2 + i * 7, 4), "a😀", under);
|
||||
f.write(Pos::new(2 + i * 7 + delta, 3), "b", over);
|
||||
f.write(Pos::new(2 + i * 7 + delta, 4), "😈", over);
|
||||
f.write(Pos::new(2 + i * 7, 2), ("a😀", under));
|
||||
f.write(Pos::new(2 + i * 7, 3), ("a😀", under));
|
||||
f.write(Pos::new(2 + i * 7, 4), ("a😀", under));
|
||||
f.write(Pos::new(2 + i * 7 + delta, 3), ("b", over));
|
||||
f.write(Pos::new(2 + i * 7 + delta, 4), ("😈", over));
|
||||
}
|
||||
|
||||
f.write(
|
||||
Pos::new(0, 6),
|
||||
"Wide graphemes at the edges of the screen apply their style, but are not",
|
||||
ContentStyle::default(),
|
||||
);
|
||||
f.write(
|
||||
Pos::new(0, 7),
|
||||
"actually rendered.",
|
||||
ContentStyle::default(),
|
||||
);
|
||||
f.write(Pos::new(0, 7), "actually rendered.");
|
||||
let x1 = -1;
|
||||
let x2 = f.size().width as i32 / 2 - 3;
|
||||
let x3 = f.size().width as i32 - 5;
|
||||
f.write(Pos::new(x1, 9), "123456", under);
|
||||
f.write(Pos::new(x1, 10), "😀😀😀", under);
|
||||
f.write(Pos::new(x2, 9), "123456", under);
|
||||
f.write(Pos::new(x2, 10), "😀😀😀", under);
|
||||
f.write(Pos::new(x3, 9), "123456", under);
|
||||
f.write(Pos::new(x3, 10), "😀😀😀", under);
|
||||
f.write(Pos::new(x1, 9), ("123456", under));
|
||||
f.write(Pos::new(x1, 10), ("😀😀😀", under));
|
||||
f.write(Pos::new(x2, 9), ("123456", under));
|
||||
f.write(Pos::new(x2, 10), ("😀😀😀", under));
|
||||
f.write(Pos::new(x3, 9), ("123456", under));
|
||||
f.write(Pos::new(x3, 10), ("😀😀😀", under));
|
||||
|
||||
let scientist = "👩🔬";
|
||||
f.write(
|
||||
Pos::new(0, 12),
|
||||
"Most terminals ignore the zero width joiner and display this female",
|
||||
ContentStyle::default(),
|
||||
);
|
||||
f.write(
|
||||
Pos::new(0, 13),
|
||||
"scientist emoji as a woman and a microscope: 👩🔬",
|
||||
ContentStyle::default(),
|
||||
);
|
||||
for i in 0..(f.width(scientist) + 4) {
|
||||
f.write(Pos::new(2, 15 + i as i32), scientist, under);
|
||||
f.write(Pos::new(i as i32, 15 + i as i32), "x", over);
|
||||
for i in 0..(f.widthdb().width(scientist) + 4) {
|
||||
f.write(Pos::new(2, 15 + i as i32), (scientist, under));
|
||||
f.write(Pos::new(i as i32, 15 + i as i32), ("x", over));
|
||||
}
|
||||
}
|
||||
|
||||
fn render_frame(term: &mut Terminal) {
|
||||
loop {
|
||||
// Must be called before rendering, otherwise the terminal has out-of-date
|
||||
// size information and will present garbage.
|
||||
let mut dirty = true;
|
||||
while dirty {
|
||||
term.autoresize().unwrap();
|
||||
|
||||
draw(term.frame());
|
||||
|
||||
if term.present().unwrap() == Redraw::NotRequired {
|
||||
break;
|
||||
}
|
||||
term.present().unwrap();
|
||||
dirty = term.measure_widths().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Automatically enters alternate screen and enables raw mode
|
||||
let mut term = Terminal::new().unwrap();
|
||||
term.set_measuring(true);
|
||||
|
||||
loop {
|
||||
// Render and display a frame. A full frame is displayed on the terminal
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
use crossterm::event::Event;
|
||||
use crossterm::style::ContentStyle;
|
||||
use toss::frame::{Frame, Pos};
|
||||
use toss::terminal::{Redraw, Terminal};
|
||||
use toss::{Frame, Pos, Styled, Terminal};
|
||||
|
||||
fn draw(f: &mut Frame) {
|
||||
let text = concat!(
|
||||
|
|
@ -14,37 +12,46 @@ fn draw(f: &mut Frame) {
|
|||
"This\u{00a0}sentence\u{00a0}is\u{00a0}separated\u{00a0}by\u{00a0}non-\u{2060}breaking\u{00a0}spaces.\n",
|
||||
"\n",
|
||||
"It can also properly handle wide graphemes (like emoji 🤔), ",
|
||||
"including ones usually displayed incorrectly by terminal emulators, like 👩🔬 (a female scientist emoji).",
|
||||
"including ones usually displayed incorrectly by terminal emulators, like 👩🔬 (a female scientist emoji).\n",
|
||||
"\n",
|
||||
"Finally, tabs are supported as well. ",
|
||||
"The following text is rendered with a tab width of 4:\n",
|
||||
"\tx\n",
|
||||
"1\tx\n",
|
||||
"12\tx\n",
|
||||
"123\tx\n",
|
||||
"1234\tx\n",
|
||||
"12345\tx\n",
|
||||
"123456\tx\n",
|
||||
"1234567\tx\n",
|
||||
"12345678\tx\n",
|
||||
"123456789\tx\n",
|
||||
);
|
||||
|
||||
let breaks = f.wrap(text, f.size().width.into());
|
||||
let lines = toss::split_at_indices(text, &breaks);
|
||||
for (i, line) in lines.iter().enumerate() {
|
||||
f.write(
|
||||
Pos::new(0, i as i32),
|
||||
line.trim_end(),
|
||||
ContentStyle::default(),
|
||||
);
|
||||
let width = f.size().width.into();
|
||||
let breaks = f.widthdb().wrap(text, width);
|
||||
let lines = Styled::new_plain(text).split_at_indices(&breaks);
|
||||
for (i, mut line) in lines.into_iter().enumerate() {
|
||||
line.trim_end();
|
||||
f.write(Pos::new(0, i as i32), line);
|
||||
}
|
||||
}
|
||||
|
||||
fn render_frame(term: &mut Terminal) {
|
||||
loop {
|
||||
// Must be called before rendering, otherwise the terminal has out-of-date
|
||||
// size information and will present garbage.
|
||||
let mut dirty = true;
|
||||
while dirty {
|
||||
term.autoresize().unwrap();
|
||||
|
||||
draw(term.frame());
|
||||
|
||||
if term.present().unwrap() == Redraw::NotRequired {
|
||||
break;
|
||||
}
|
||||
term.present().unwrap();
|
||||
dirty = term.measure_widths().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Automatically enters alternate screen and enables raw mode
|
||||
let mut term = Terminal::new().unwrap();
|
||||
term.set_measuring(true);
|
||||
term.set_tab_width(4);
|
||||
|
||||
loop {
|
||||
// Render and display a frame. A full frame is displayed on the terminal
|
||||
|
|
|
|||
245
src/buffer.rs
245
src/buffer.rs
|
|
@ -1,34 +1,8 @@
|
|||
use std::ops::Range;
|
||||
|
||||
use crossterm::style::ContentStyle;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use crate::widthdb::WidthDB;
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Size {
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
}
|
||||
|
||||
impl Size {
|
||||
pub const ZERO: Self = Self {
|
||||
width: 0,
|
||||
height: 0,
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Pos {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
}
|
||||
|
||||
impl Pos {
|
||||
pub const ZERO: Self = Self { x: 0, y: 0 };
|
||||
|
||||
pub fn new(x: i32, y: i32) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
}
|
||||
use crate::{Pos, Size, Style, Styled, WidthDb};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Cell {
|
||||
|
|
@ -49,13 +23,93 @@ impl Default for Cell {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct StackFrame {
|
||||
pub pos: Pos,
|
||||
pub size: Size,
|
||||
pub drawable_area: Option<(Pos, Size)>,
|
||||
}
|
||||
|
||||
impl StackFrame {
|
||||
fn intersect_areas(
|
||||
a_start: Pos,
|
||||
a_size: Size,
|
||||
b_start: Pos,
|
||||
b_size: Size,
|
||||
) -> Option<(Pos, Size)> {
|
||||
// The first row/column that is not part of the area any more
|
||||
let a_end = a_start + a_size;
|
||||
let b_end = b_start + b_size;
|
||||
|
||||
let x_start = a_start.x.max(b_start.x);
|
||||
let x_end = a_end.x.min(b_end.x);
|
||||
let y_start = a_start.y.max(b_start.y);
|
||||
let y_end = a_end.y.min(b_end.y);
|
||||
|
||||
if x_start < x_end && y_start < y_end {
|
||||
let start = Pos::new(x_start, y_start);
|
||||
let size = Size::new((x_end - x_start) as u16, (y_end - y_start) as u16);
|
||||
Some((start, size))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn then(&self, pos: Pos, size: Size) -> Self {
|
||||
let pos = self.local_to_global(pos);
|
||||
|
||||
let drawable_area = self
|
||||
.drawable_area
|
||||
.and_then(|(da_pos, da_size)| Self::intersect_areas(da_pos, da_size, pos, size));
|
||||
|
||||
Self {
|
||||
pos,
|
||||
size,
|
||||
drawable_area,
|
||||
}
|
||||
}
|
||||
|
||||
fn local_to_global(&self, local_pos: Pos) -> Pos {
|
||||
local_pos + self.pos
|
||||
}
|
||||
|
||||
fn global_to_local(&self, global_pos: Pos) -> Pos {
|
||||
global_pos - self.pos
|
||||
}
|
||||
|
||||
/// Ranges along the x and y axis where drawing is allowed, in global
|
||||
/// coordinates.
|
||||
fn legal_ranges(&self) -> Option<(Range<i32>, Range<i32>)> {
|
||||
if let Some((pos, size)) = self.drawable_area {
|
||||
let xrange = pos.x..pos.x + size.width as i32;
|
||||
let yrange = pos.y..pos.y + size.height as i32;
|
||||
Some((xrange, yrange))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Buffer {
|
||||
size: Size,
|
||||
data: Vec<Cell>,
|
||||
cursor: Option<Pos>,
|
||||
|
||||
/// A stack of rectangular drawing areas.
|
||||
///
|
||||
/// When rendering to the buffer with a nonempty stack, it behaves as if it
|
||||
/// was the size of the topmost stack element, and characters are translated
|
||||
/// by the position of the topmost stack element. No characters can be
|
||||
/// placed outside the area described by the topmost stack element.
|
||||
stack: Vec<StackFrame>,
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
/// Index in `data` of the cell at the given position. The position must
|
||||
/// be inside the buffer.
|
||||
///
|
||||
/// Ignores the stack.
|
||||
fn index(&self, x: u16, y: u16) -> usize {
|
||||
assert!(x < self.size.width);
|
||||
assert!(y < self.size.height);
|
||||
|
|
@ -67,30 +121,65 @@ impl Buffer {
|
|||
y * width + x
|
||||
}
|
||||
|
||||
pub(crate) fn at(&self, x: u16, y: u16) -> &Cell {
|
||||
/// A reference to the cell at the given position. The position must be
|
||||
/// inside the buffer.
|
||||
///
|
||||
/// Ignores the stack.
|
||||
pub fn at(&self, x: u16, y: u16) -> &Cell {
|
||||
assert!(x < self.size.width);
|
||||
assert!(y < self.size.height);
|
||||
|
||||
let i = self.index(x, y);
|
||||
&self.data[i]
|
||||
}
|
||||
|
||||
/// A mutable reference to the cell at the given position. The position must
|
||||
/// be inside the buffer.
|
||||
///
|
||||
/// Ignores the stack.
|
||||
fn at_mut(&mut self, x: u16, y: u16) -> &mut Cell {
|
||||
assert!(x < self.size.width);
|
||||
assert!(y < self.size.height);
|
||||
|
||||
let i = self.index(x, y);
|
||||
&mut self.data[i]
|
||||
}
|
||||
|
||||
fn current_frame(&self) -> StackFrame {
|
||||
self.stack.last().copied().unwrap_or(StackFrame {
|
||||
pos: Pos::ZERO,
|
||||
size: self.size,
|
||||
drawable_area: Some((Pos::ZERO, self.size)),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn push(&mut self, pos: Pos, size: Size) {
|
||||
self.stack.push(self.current_frame().then(pos, size));
|
||||
}
|
||||
|
||||
pub fn pop(&mut self) {
|
||||
self.stack.pop();
|
||||
}
|
||||
|
||||
/// Size of the current drawable area, respecting the stack.
|
||||
pub fn size(&self) -> Size {
|
||||
self.size
|
||||
self.current_frame().size
|
||||
}
|
||||
|
||||
pub fn cursor(&self) -> Option<Pos> {
|
||||
self.cursor.map(|p| self.current_frame().global_to_local(p))
|
||||
}
|
||||
|
||||
pub fn set_cursor(&mut self, pos: Option<Pos>) {
|
||||
self.cursor = pos.map(|p| self.current_frame().local_to_global(p));
|
||||
}
|
||||
|
||||
/// Resize the buffer and reset its contents.
|
||||
///
|
||||
/// The buffer's contents are reset even if the buffer is already the
|
||||
/// correct size.
|
||||
/// correct size. The stack is reset as well.
|
||||
pub fn resize(&mut self, size: Size) {
|
||||
if size == self.size() {
|
||||
if size == self.size {
|
||||
self.data.fill_with(Cell::default);
|
||||
} else {
|
||||
let width: usize = size.width.into();
|
||||
|
|
@ -101,58 +190,86 @@ impl Buffer {
|
|||
self.data.clear();
|
||||
self.data.resize_with(len, Cell::default);
|
||||
}
|
||||
|
||||
self.cursor = None;
|
||||
|
||||
self.stack.clear();
|
||||
}
|
||||
|
||||
/// Reset the contents of the buffer.
|
||||
/// Reset the contents and stack of the buffer.
|
||||
///
|
||||
/// `buf.reset()` is equivalent to `buf.resize(buf.size())`.
|
||||
pub fn reset(&mut self) {
|
||||
self.data.fill_with(Cell::default);
|
||||
self.resize(self.size);
|
||||
}
|
||||
|
||||
/// Remove the grapheme at the specified coordinates from the buffer.
|
||||
///
|
||||
/// Removes the entire grapheme, not just the cell at the coordinates.
|
||||
/// Preserves the style of the affected cells. Works even if the coordinates
|
||||
/// don't point to the beginning of the grapheme.
|
||||
/// Preserves the style of the affected cells. Preserves the cursor. Works
|
||||
/// even if the coordinates don't point to the beginning of the grapheme.
|
||||
///
|
||||
/// Ignores the stack.
|
||||
fn erase(&mut self, x: u16, y: u16) {
|
||||
let cell = self.at(x, y);
|
||||
let width: u16 = cell.width.into();
|
||||
let offset: u16 = cell.offset.into();
|
||||
|
||||
for x in (x - offset)..(x - offset + width) {
|
||||
let cell = self.at_mut(x, y);
|
||||
let style = cell.style;
|
||||
|
||||
*cell = Cell::default();
|
||||
cell.style = style;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(
|
||||
&mut self,
|
||||
widthdb: &mut WidthDB,
|
||||
mut pos: Pos,
|
||||
content: &str,
|
||||
style: ContentStyle,
|
||||
) {
|
||||
// If we're not even visible, there's nothing to do
|
||||
if pos.y < 0 || pos.y >= self.size.height as i32 {
|
||||
return;
|
||||
/// Write styled text to the buffer, respecting the width of individual
|
||||
/// graphemes.
|
||||
///
|
||||
/// The initial x position is considered the first column for tab width
|
||||
/// calculations.
|
||||
pub fn write(&mut self, widthdb: &mut WidthDb, pos: Pos, styled: &Styled) {
|
||||
let frame = self.current_frame();
|
||||
let (xrange, yrange) = match frame.legal_ranges() {
|
||||
Some(ranges) => ranges,
|
||||
None => return, // No drawable area
|
||||
};
|
||||
let pos = frame.local_to_global(pos);
|
||||
if !yrange.contains(&pos.y) {
|
||||
return; // Outside of drawable area
|
||||
}
|
||||
let y = pos.y as u16;
|
||||
|
||||
for grapheme in content.graphemes(true) {
|
||||
let width = widthdb.grapheme_width(grapheme);
|
||||
if width > 0 {
|
||||
self.write_grapheme(pos.x, y, width, grapheme, style);
|
||||
let mut col: usize = 0;
|
||||
for (_, style, grapheme) in styled.styled_grapheme_indices() {
|
||||
let x = pos.x + col as i32;
|
||||
let width = widthdb.grapheme_width(grapheme, col);
|
||||
col += width as usize;
|
||||
if grapheme == "\t" {
|
||||
for dx in 0..width {
|
||||
self.write_grapheme(&xrange, x + dx as i32, y, 1, " ", style);
|
||||
}
|
||||
} else if width > 0 {
|
||||
self.write_grapheme(&xrange, x, y, width, grapheme, style);
|
||||
}
|
||||
pos.x += width as i32;
|
||||
}
|
||||
}
|
||||
|
||||
/// Write a single grapheme to the buffer, respecting its width.
|
||||
///
|
||||
/// Assumes that `pos.y` is in range.
|
||||
fn write_grapheme(&mut self, x: i32, y: u16, width: u8, grapheme: &str, style: ContentStyle) {
|
||||
let min_x = 0;
|
||||
let max_x = self.size.width as i32 - 1; // Last possible cell
|
||||
fn write_grapheme(
|
||||
&mut self,
|
||||
xrange: &Range<i32>,
|
||||
x: i32,
|
||||
y: u16,
|
||||
width: u8,
|
||||
grapheme: &str,
|
||||
style: Style,
|
||||
) {
|
||||
let min_x = xrange.start;
|
||||
let max_x = xrange.end - 1; // Last possible cell
|
||||
|
||||
let start_x = x;
|
||||
let end_x = x + width as i32 - 1; // Coordinate of last cell
|
||||
|
|
@ -163,12 +280,13 @@ impl Buffer {
|
|||
|
||||
if start_x >= min_x && end_x <= max_x {
|
||||
// Fully visible, write actual grapheme
|
||||
let base_style = self.at(start_x as u16, y).style;
|
||||
for offset in 0..width {
|
||||
let x = start_x as u16 + offset as u16;
|
||||
self.erase(x, y);
|
||||
*self.at_mut(x, y) = Cell {
|
||||
content: grapheme.to_string().into_boxed_str(),
|
||||
style,
|
||||
style: style.cover(base_style),
|
||||
width,
|
||||
offset,
|
||||
};
|
||||
|
|
@ -178,13 +296,21 @@ impl Buffer {
|
|||
let start_x = start_x.max(0) as u16;
|
||||
let end_x = end_x.min(max_x) as u16;
|
||||
for x in start_x..=end_x {
|
||||
let base_style = self.at(x, y).style;
|
||||
self.erase(x, y);
|
||||
*self.at_mut(x, y) = Cell {
|
||||
style,
|
||||
style: style.cover(base_style),
|
||||
..Default::default()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(pos) = self.cursor {
|
||||
if pos.y == y as i32 && start_x <= pos.x && pos.x <= end_x {
|
||||
// The cursor lies within the bounds of the current grapheme and
|
||||
self.cursor = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cells(&self) -> Cells<'_> {
|
||||
|
|
@ -206,6 +332,9 @@ impl<'a> Iterator for Cells<'a> {
|
|||
type Item = (u16, u16, &'a Cell);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.x >= self.buffer.size.width {
|
||||
return None;
|
||||
}
|
||||
if self.y >= self.buffer.size.height {
|
||||
return None;
|
||||
}
|
||||
|
|
|
|||
153
src/coords.rs
Normal file
153
src/coords.rs
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
use std::ops::{Add, AddAssign, Neg, Sub, SubAssign};
|
||||
|
||||
/// Size in screen cells.
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Size {
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
}
|
||||
|
||||
impl Size {
|
||||
pub const ZERO: Self = Self::new(0, 0);
|
||||
|
||||
pub const fn new(width: u16, height: u16) -> Self {
|
||||
Self { width, height }
|
||||
}
|
||||
|
||||
/// Add two [`Size`]s using [`u16::saturating_add`].
|
||||
pub const fn saturating_add(self, rhs: Self) -> Self {
|
||||
Self::new(
|
||||
self.width.saturating_add(rhs.width),
|
||||
self.height.saturating_add(rhs.height),
|
||||
)
|
||||
}
|
||||
|
||||
/// Subtract two [`Size`]s using [`u16::saturating_sub`].
|
||||
pub const fn saturating_sub(self, rhs: Self) -> Self {
|
||||
Self::new(
|
||||
self.width.saturating_sub(rhs.width),
|
||||
self.height.saturating_sub(rhs.height),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Size {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Self) -> Self {
|
||||
Self::new(self.width + rhs.width, self.height + rhs.height)
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for Size {
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
self.width += rhs.width;
|
||||
self.height += rhs.height;
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Size {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self {
|
||||
Self::new(self.width - rhs.width, self.height - rhs.height)
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign for Size {
|
||||
fn sub_assign(&mut self, rhs: Self) {
|
||||
self.width -= rhs.width;
|
||||
self.height -= rhs.height;
|
||||
}
|
||||
}
|
||||
|
||||
/// Position in screen cell coordinates.
|
||||
///
|
||||
/// The x axis points to the right. The y axis points down.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Pos {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
}
|
||||
|
||||
impl Pos {
|
||||
pub const ZERO: Self = Self::new(0, 0);
|
||||
|
||||
pub const fn new(x: i32, y: i32) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Size> for Pos {
|
||||
fn from(s: Size) -> Self {
|
||||
Self::new(s.width.into(), s.height.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Pos {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Self) -> Self {
|
||||
Self::new(self.x + rhs.x, self.y + rhs.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Size> for Pos {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Size) -> Self {
|
||||
Self::new(self.x + rhs.width as i32, self.y + rhs.height as i32)
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for Pos {
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
self.x += rhs.x;
|
||||
self.y += rhs.y;
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<Size> for Pos {
|
||||
fn add_assign(&mut self, rhs: Size) {
|
||||
self.x += rhs.width as i32;
|
||||
self.y += rhs.height as i32;
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Pos {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self {
|
||||
Self::new(self.x - rhs.x, self.y - rhs.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Size> for Pos {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, rhs: Size) -> Self {
|
||||
Self::new(self.x - rhs.width as i32, self.y - rhs.height as i32)
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign for Pos {
|
||||
fn sub_assign(&mut self, rhs: Self) {
|
||||
self.x -= rhs.x;
|
||||
self.y -= rhs.y;
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign<Size> for Pos {
|
||||
fn sub_assign(&mut self, rhs: Size) {
|
||||
self.x -= rhs.width as i32;
|
||||
self.y -= rhs.height as i32;
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Pos {
|
||||
type Output = Self;
|
||||
|
||||
fn neg(self) -> Self {
|
||||
Self::new(-self.x, -self.y)
|
||||
}
|
||||
}
|
||||
49
src/frame.rs
49
src/frame.rs
|
|
@ -1,35 +1,40 @@
|
|||
//! Rendering the next frame.
|
||||
|
||||
use crossterm::style::ContentStyle;
|
||||
|
||||
use crate::buffer::Buffer;
|
||||
pub use crate::buffer::{Pos, Size};
|
||||
use crate::widthdb::WidthDB;
|
||||
use crate::wrap;
|
||||
use crate::{Pos, Size, Styled, WidthDb};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Frame {
|
||||
pub(crate) widthdb: WidthDB,
|
||||
pub(crate) widthdb: WidthDb,
|
||||
pub(crate) buffer: Buffer,
|
||||
cursor: Option<Pos>,
|
||||
pub(crate) title: Option<String>,
|
||||
pub(crate) bell: bool,
|
||||
}
|
||||
|
||||
impl Frame {
|
||||
pub fn push(&mut self, pos: Pos, size: Size) {
|
||||
self.buffer.push(pos, size);
|
||||
}
|
||||
|
||||
pub fn pop(&mut self) {
|
||||
self.buffer.pop();
|
||||
}
|
||||
|
||||
pub fn size(&self) -> Size {
|
||||
self.buffer.size()
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.buffer.reset();
|
||||
self.cursor = None;
|
||||
self.title = None;
|
||||
}
|
||||
|
||||
pub fn cursor(&self) -> Option<Pos> {
|
||||
self.cursor
|
||||
self.buffer.cursor()
|
||||
}
|
||||
|
||||
pub fn set_cursor(&mut self, pos: Option<Pos>) {
|
||||
self.cursor = pos;
|
||||
self.buffer.set_cursor(pos);
|
||||
}
|
||||
|
||||
pub fn show_cursor(&mut self, pos: Pos) {
|
||||
|
|
@ -40,27 +45,19 @@ impl Frame {
|
|||
self.set_cursor(None);
|
||||
}
|
||||
|
||||
/// Determine the width of a grapheme.
|
||||
///
|
||||
/// If the width has not been measured yet, it is estimated using the
|
||||
/// Unicode Standard Annex #11.
|
||||
pub fn grapheme_width(&mut self, grapheme: &str) -> u8 {
|
||||
self.widthdb.grapheme_width(grapheme)
|
||||
pub fn set_title(&mut self, title: Option<String>) {
|
||||
self.title = title;
|
||||
}
|
||||
|
||||
/// Determine the width of a string based on its graphemes.
|
||||
///
|
||||
/// If the width of a grapheme has not been measured yet, it is estimated
|
||||
/// using the Unicode Standard Annex #11.
|
||||
pub fn width(&mut self, s: &str) -> usize {
|
||||
self.widthdb.width(s)
|
||||
pub fn set_bell(&mut self, bell: bool) {
|
||||
self.bell = bell;
|
||||
}
|
||||
|
||||
pub fn wrap(&mut self, text: &str, width: usize) -> Vec<usize> {
|
||||
wrap::wrap(text, width, &mut self.widthdb)
|
||||
pub fn widthdb(&mut self) -> &mut WidthDb {
|
||||
&mut self.widthdb
|
||||
}
|
||||
|
||||
pub fn write(&mut self, pos: Pos, content: &str, style: ContentStyle) {
|
||||
self.buffer.write(&mut self.widthdb, pos, content, style);
|
||||
pub fn write<S: Into<Styled>>(&mut self, pos: Pos, styled: S) {
|
||||
self.buffer.write(&mut self.widthdb, pos, &styled.into());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
28
src/lib.rs
28
src/lib.rs
|
|
@ -1,7 +1,29 @@
|
|||
#![forbid(unsafe_code)]
|
||||
// Rustc lint groups
|
||||
#![warn(future_incompatible)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![warn(unused)]
|
||||
// Rustc lints
|
||||
#![warn(noop_method_call)]
|
||||
#![warn(single_use_lifetimes)]
|
||||
// Clippy lints
|
||||
#![warn(clippy::use_self)]
|
||||
|
||||
mod buffer;
|
||||
pub mod frame;
|
||||
pub mod terminal;
|
||||
mod coords;
|
||||
mod frame;
|
||||
mod style;
|
||||
mod styled;
|
||||
mod terminal;
|
||||
mod widget;
|
||||
pub mod widgets;
|
||||
mod widthdb;
|
||||
mod wrap;
|
||||
|
||||
pub use wrap::split_at_indices;
|
||||
pub use coords::*;
|
||||
pub use frame::*;
|
||||
pub use style::*;
|
||||
pub use styled::*;
|
||||
pub use terminal::*;
|
||||
pub use widget::*;
|
||||
pub use widthdb::*;
|
||||
|
|
|
|||
60
src/style.rs
Normal file
60
src/style.rs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
use crossterm::style::{ContentStyle, Stylize};
|
||||
|
||||
fn merge_cs(base: ContentStyle, cover: ContentStyle) -> ContentStyle {
|
||||
ContentStyle {
|
||||
foreground_color: cover.foreground_color.or(base.foreground_color),
|
||||
background_color: cover.background_color.or(base.background_color),
|
||||
underline_color: cover.underline_color.or(base.underline_color),
|
||||
attributes: cover.attributes,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct Style {
|
||||
pub content_style: ContentStyle,
|
||||
pub opaque: bool,
|
||||
}
|
||||
|
||||
impl Style {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn transparent(mut self) -> Self {
|
||||
self.opaque = false;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn opaque(mut self) -> Self {
|
||||
self.opaque = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn cover(self, base: ContentStyle) -> ContentStyle {
|
||||
if self.opaque {
|
||||
return self.content_style;
|
||||
}
|
||||
|
||||
merge_cs(base, self.content_style)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<ContentStyle> for Style {
|
||||
fn as_ref(&self) -> &ContentStyle {
|
||||
&self.content_style
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<ContentStyle> for Style {
|
||||
fn as_mut(&mut self) -> &mut ContentStyle {
|
||||
&mut self.content_style
|
||||
}
|
||||
}
|
||||
|
||||
impl Stylize for Style {
|
||||
type Styled = Self;
|
||||
|
||||
fn stylize(self) -> Self::Styled {
|
||||
self
|
||||
}
|
||||
}
|
||||
195
src/styled.rs
Normal file
195
src/styled.rs
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
use std::iter::Peekable;
|
||||
use std::slice;
|
||||
|
||||
use unicode_segmentation::{GraphemeIndices, Graphemes, UnicodeSegmentation};
|
||||
|
||||
use crate::Style;
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Styled {
|
||||
text: String,
|
||||
/// List of `(style, until)` tuples. The style should be applied to all
|
||||
/// chars in the range `prev_until..until`.
|
||||
styles: Vec<(Style, usize)>,
|
||||
}
|
||||
|
||||
impl Styled {
|
||||
pub fn new<S: AsRef<str>>(text: S, style: Style) -> Self {
|
||||
Self::default().then(text, style)
|
||||
}
|
||||
|
||||
pub fn new_plain<S: AsRef<str>>(text: S) -> Self {
|
||||
Self::default().then_plain(text)
|
||||
}
|
||||
|
||||
pub fn then<S: AsRef<str>>(mut self, text: S, style: Style) -> Self {
|
||||
let text = text.as_ref();
|
||||
if !text.is_empty() {
|
||||
self.text.push_str(text);
|
||||
self.styles.push((style, self.text.len()));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn then_plain<S: AsRef<str>>(self, text: S) -> Self {
|
||||
self.then(text, Style::new())
|
||||
}
|
||||
|
||||
pub fn and_then(mut self, mut other: Self) -> Self {
|
||||
let delta = self.text.len();
|
||||
for (_, until) in &mut other.styles {
|
||||
*until += delta;
|
||||
}
|
||||
|
||||
self.text.push_str(&other.text);
|
||||
self.styles.extend(other.styles);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn text(&self) -> &str {
|
||||
&self.text
|
||||
}
|
||||
|
||||
pub fn split_at(self, mid: usize) -> (Self, Self) {
|
||||
let (left_text, right_text) = self.text.split_at(mid);
|
||||
|
||||
let mut left_styles = vec![];
|
||||
let mut right_styles = vec![];
|
||||
let mut from = 0;
|
||||
for (style, until) in self.styles {
|
||||
if from < mid {
|
||||
left_styles.push((style, until.min(mid)));
|
||||
}
|
||||
if mid < until {
|
||||
right_styles.push((style, until.saturating_sub(mid)));
|
||||
}
|
||||
from = until;
|
||||
}
|
||||
|
||||
let left = Self {
|
||||
text: left_text.to_string(),
|
||||
styles: left_styles,
|
||||
};
|
||||
|
||||
let right = Self {
|
||||
text: right_text.to_string(),
|
||||
styles: right_styles,
|
||||
};
|
||||
|
||||
(left, right)
|
||||
}
|
||||
|
||||
pub fn split_at_indices(self, indices: &[usize]) -> Vec<Self> {
|
||||
let mut lines = vec![];
|
||||
|
||||
let mut rest = self;
|
||||
let mut offset = 0;
|
||||
|
||||
for i in indices {
|
||||
let (left, right) = rest.split_at(i - offset);
|
||||
lines.push(left);
|
||||
rest = right;
|
||||
offset = *i;
|
||||
}
|
||||
|
||||
lines.push(rest);
|
||||
|
||||
lines
|
||||
}
|
||||
|
||||
pub fn trim_end(&mut self) {
|
||||
self.text = self.text.trim_end().to_string();
|
||||
|
||||
let text_len = self.text.len();
|
||||
let mut styles_len = 0;
|
||||
for (_, until) in &mut self.styles {
|
||||
styles_len += 1;
|
||||
if *until >= text_len {
|
||||
*until = text_len;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while self.styles.len() > styles_len {
|
||||
self.styles.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////
|
||||
// Iterating over graphemes //
|
||||
//////////////////////////////
|
||||
|
||||
pub struct StyledGraphemeIndices<'a> {
|
||||
text: GraphemeIndices<'a>,
|
||||
styles: Peekable<slice::Iter<'a, (Style, usize)>>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for StyledGraphemeIndices<'a> {
|
||||
type Item = (usize, Style, &'a str);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let (gi, grapheme) = self.text.next()?;
|
||||
let (mut style, mut until) = **self.styles.peek().expect("styles cover entire text");
|
||||
while gi >= until {
|
||||
self.styles.next();
|
||||
(style, until) = **self.styles.peek().expect("styles cover entire text");
|
||||
}
|
||||
Some((gi, style, grapheme))
|
||||
}
|
||||
}
|
||||
|
||||
impl Styled {
|
||||
pub fn graphemes(&self) -> Graphemes<'_> {
|
||||
self.text.graphemes(true)
|
||||
}
|
||||
|
||||
pub fn grapheme_indices(&self) -> GraphemeIndices<'_> {
|
||||
self.text.grapheme_indices(true)
|
||||
}
|
||||
|
||||
pub fn styled_grapheme_indices(&self) -> StyledGraphemeIndices<'_> {
|
||||
StyledGraphemeIndices {
|
||||
text: self.grapheme_indices(),
|
||||
styles: self.styles.iter().peekable(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
// Converting to Styled //
|
||||
//////////////////////////
|
||||
|
||||
impl From<&str> for Styled {
|
||||
fn from(text: &str) -> Self {
|
||||
Self::new_plain(text)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Styled {
|
||||
fn from(text: String) -> Self {
|
||||
Self::new_plain(text)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: AsRef<str>> From<(S,)> for Styled {
|
||||
fn from((text,): (S,)) -> Self {
|
||||
Self::new_plain(text)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: AsRef<str>> From<(S, Style)> for Styled {
|
||||
fn from((text, style): (S, Style)) -> Self {
|
||||
Self::new(text, style)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: AsRef<str>> From<&[(S, Style)]> for Styled {
|
||||
fn from(segments: &[(S, Style)]) -> Self {
|
||||
let mut result = Self::default();
|
||||
for (text, style) in segments {
|
||||
result = result.then(text, *style);
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
256
src/terminal.rs
256
src/terminal.rs
|
|
@ -1,22 +1,28 @@
|
|||
//! Displaying frames on a terminal.
|
||||
|
||||
use std::io::Write;
|
||||
use std::{io, mem};
|
||||
use std::io::{self, Write};
|
||||
use std::mem;
|
||||
|
||||
use crossterm::cursor::{Hide, MoveTo, Show};
|
||||
use crossterm::style::{PrintStyledContent, StyledContent};
|
||||
use crossterm::terminal::{Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen};
|
||||
use crossterm::event::{
|
||||
DisableBracketedPaste, EnableBracketedPaste, KeyboardEnhancementFlags,
|
||||
PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags,
|
||||
};
|
||||
use crossterm::style::{Print, PrintStyledContent, StyledContent};
|
||||
use crossterm::terminal::{
|
||||
BeginSynchronizedUpdate, Clear, ClearType, EndSynchronizedUpdate, EnterAlternateScreen,
|
||||
LeaveAlternateScreen, SetTitle,
|
||||
};
|
||||
use crossterm::{ExecutableCommand, QueueableCommand};
|
||||
|
||||
use crate::buffer::{Buffer, Size};
|
||||
use crate::frame::Frame;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Redraw {
|
||||
Required,
|
||||
NotRequired,
|
||||
}
|
||||
use crate::buffer::Buffer;
|
||||
use crate::{AsyncWidget, Frame, Size, Widget, WidthDb, WidthEstimationMethod};
|
||||
|
||||
/// Wrapper that manages terminal output.
|
||||
///
|
||||
/// This struct (usually) wraps around stdout and handles showing things on the
|
||||
/// terminal. It cleans up after itself when droppped, so it shouldn't leave the
|
||||
/// terminal in a weird state even if your program crashes.
|
||||
pub struct Terminal {
|
||||
/// Render target.
|
||||
out: Box<dyn Write>,
|
||||
|
|
@ -31,17 +37,17 @@ pub struct Terminal {
|
|||
|
||||
impl Drop for Terminal {
|
||||
fn drop(&mut self) {
|
||||
let _ = crossterm::terminal::disable_raw_mode();
|
||||
let _ = self.out.execute(LeaveAlternateScreen);
|
||||
let _ = self.out.execute(Show);
|
||||
let _ = self.suspend();
|
||||
}
|
||||
}
|
||||
|
||||
impl Terminal {
|
||||
/// Create a new [`Terminal`] that wraps stdout.
|
||||
pub fn new() -> io::Result<Self> {
|
||||
Self::with_target(Box::new(io::stdout()))
|
||||
}
|
||||
|
||||
/// Create a new terminal wrapping a custom output.
|
||||
pub fn with_target(out: Box<dyn Write>) -> io::Result<Self> {
|
||||
let mut result = Self {
|
||||
out,
|
||||
|
|
@ -49,13 +55,136 @@ impl Terminal {
|
|||
prev_frame_buffer: Buffer::default(),
|
||||
full_redraw: true,
|
||||
};
|
||||
crossterm::terminal::enable_raw_mode()?;
|
||||
result.out.execute(EnterAlternateScreen)?;
|
||||
result.unsuspend()?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Temporarily restore the terminal state to normal.
|
||||
///
|
||||
/// This is useful when running external programs the user should interact
|
||||
/// with directly, for example a text editor.
|
||||
///
|
||||
/// Call [`Self::unsuspend`] to return the terminal state before drawing and
|
||||
/// presenting the next frame.
|
||||
pub fn suspend(&mut self) -> io::Result<()> {
|
||||
crossterm::terminal::disable_raw_mode()?;
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
self.out.execute(PopKeyboardEnhancementFlags)?;
|
||||
self.out.execute(DisableBracketedPaste)?;
|
||||
}
|
||||
self.out.execute(LeaveAlternateScreen)?;
|
||||
self.out.execute(Show)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Restore the terminal state after calling [`Self::suspend`].
|
||||
///
|
||||
/// After calling this function, a new frame needs to be drawn and presented
|
||||
/// by the application. The previous screen contents are **not** restored.
|
||||
pub fn unsuspend(&mut self) -> io::Result<()> {
|
||||
crossterm::terminal::enable_raw_mode()?;
|
||||
self.out.execute(EnterAlternateScreen)?;
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
self.out.execute(EnableBracketedPaste)?;
|
||||
self.out.execute(PushKeyboardEnhancementFlags(
|
||||
KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES,
|
||||
))?;
|
||||
}
|
||||
self.full_redraw = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the tab width in columns.
|
||||
///
|
||||
/// For more details, see [`Self::tab_width`].
|
||||
pub fn set_tab_width(&mut self, tab_width: u8) {
|
||||
self.frame.widthdb.tab_width = tab_width;
|
||||
}
|
||||
|
||||
/// The tab width in columns.
|
||||
///
|
||||
/// For accurate width calculations and consistency across terminals, tabs
|
||||
/// are not printed to the terminal directly, but instead converted into
|
||||
/// spaces.
|
||||
pub fn tab_width(&self) -> u8 {
|
||||
self.frame.widthdb.tab_width
|
||||
}
|
||||
|
||||
/// Set the grapheme width estimation method.
|
||||
///
|
||||
/// For more details, see [`WidthEstimationMethod`].
|
||||
pub fn set_width_estimation_method(&mut self, method: WidthEstimationMethod) {
|
||||
self.frame.widthdb.estimate = method;
|
||||
}
|
||||
|
||||
/// The grapheme width estimation method.
|
||||
///
|
||||
/// For more details, see [`WidthEstimationMethod`].
|
||||
pub fn width_estimation_method(&mut self) -> WidthEstimationMethod {
|
||||
self.frame.widthdb.estimate
|
||||
}
|
||||
|
||||
/// Enable or disable grapheme width measurements.
|
||||
///
|
||||
/// For more details, see [`Self::measuring`].
|
||||
pub fn set_measuring(&mut self, active: bool) {
|
||||
self.frame.widthdb.measure = active;
|
||||
}
|
||||
|
||||
/// Whether grapheme widths should be measured or estimated.
|
||||
///
|
||||
/// Handling of wide characters is inconsistent from terminal emulator to
|
||||
/// terminal emulator, and may even depend on the font the user is using.
|
||||
///
|
||||
/// When enabled, any newly encountered graphemes are measured whenever
|
||||
/// [`Self::measure_widths`] is called. This is done by clearing the screen,
|
||||
/// printing the grapheme and measuring the resulting cursor position.
|
||||
/// Because of this, the screen will flicker occasionally. However, grapheme
|
||||
/// widths will always be accurate independent of the terminal
|
||||
/// configuration.
|
||||
///
|
||||
/// When disabled, the width of graphemes is estimated using the Unicode
|
||||
/// Standard Annex #11. This usually works fine, but may break on some emoji
|
||||
/// or other less commonly used character sequences.
|
||||
pub fn measuring(&self) -> bool {
|
||||
self.frame.widthdb.measure
|
||||
}
|
||||
|
||||
/// Whether any unmeasured graphemes were seen since the last call to
|
||||
/// [`Self::measure_widths`].
|
||||
///
|
||||
/// Returns `true` whenever [`Self::measure_widths`] would return `true`.
|
||||
pub fn measuring_required(&self) -> bool {
|
||||
self.frame.widthdb.measuring_required()
|
||||
}
|
||||
|
||||
/// Measure widths of all unmeasured graphemes.
|
||||
///
|
||||
/// If width measurements are disabled, this function does nothing. For more
|
||||
/// info, see [`Self::measuring`].
|
||||
///
|
||||
/// Returns `true` if any new graphemes were measured and the screen must be
|
||||
/// redrawn. Keep in mind that after redrawing the screen, graphemes may
|
||||
/// have become visible that have not yet been measured. You should keep
|
||||
/// re-measuring and re-drawing until this function returns `false`.
|
||||
pub fn measure_widths(&mut self) -> io::Result<bool> {
|
||||
if self.frame.widthdb.measuring_required() {
|
||||
self.full_redraw = true;
|
||||
self.frame.widthdb.measure_widths(&mut self.out)?;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Resize the frame and other internal buffers if the terminal size has
|
||||
/// changed.
|
||||
///
|
||||
/// Should be called before drawing a frame and presenting it with
|
||||
/// [`Self::present`]. It is not necessary to call this when using
|
||||
/// [`Self::present_widget`] or [`Self::present_async_widget`].
|
||||
pub fn autoresize(&mut self) -> io::Result<()> {
|
||||
let (width, height) = crossterm::terminal::size()?;
|
||||
let size = Size { width, height };
|
||||
|
|
@ -68,42 +197,86 @@ impl Terminal {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// The current frame.
|
||||
pub fn frame(&mut self) -> &mut Frame {
|
||||
&mut self.frame
|
||||
}
|
||||
|
||||
/// A database of grapheme widths.
|
||||
pub fn widthdb(&mut self) -> &mut WidthDb {
|
||||
&mut self.frame.widthdb
|
||||
}
|
||||
|
||||
/// Mark the terminal as dirty, forcing a full redraw whenever any variant
|
||||
/// of [`Self::present`] is called.
|
||||
pub fn mark_dirty(&mut self) {
|
||||
self.full_redraw = true;
|
||||
}
|
||||
|
||||
/// Display the current frame on the screen and prepare the next frame.
|
||||
/// Returns `true` if an immediate redraw is required.
|
||||
///
|
||||
/// Before drawing and presenting a frame, [`Self::measure_widths`] and
|
||||
/// [`Self::autoresize`] should be called.
|
||||
///
|
||||
/// After calling this function, the frame returned by [`Self::frame`] will
|
||||
/// be empty again and have no cursor position.
|
||||
pub fn present(&mut self) -> io::Result<Redraw> {
|
||||
if self.frame.widthdb.measuring_required() {
|
||||
self.frame.widthdb.measure_widths(&mut self.out)?;
|
||||
// Since we messed up the screen by measuring widths, we'll need to
|
||||
// do a full redraw the next time around.
|
||||
self.full_redraw = true;
|
||||
// Throwing away the current frame because its content were rendered
|
||||
// with unconfirmed width data. Also, this function guarantees that
|
||||
// after it is called, the frame is empty.
|
||||
pub fn present(&mut self) -> io::Result<()> {
|
||||
self.out.queue(BeginSynchronizedUpdate)?;
|
||||
let result = self.draw_to_screen();
|
||||
self.out.queue(EndSynchronizedUpdate)?;
|
||||
result?;
|
||||
|
||||
self.out.flush()?;
|
||||
|
||||
mem::swap(&mut self.prev_frame_buffer, &mut self.frame.buffer);
|
||||
self.frame.reset();
|
||||
return Ok(Redraw::Required);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Display a [`Widget`] on the screen.
|
||||
///
|
||||
/// Before creating and presenting a widget, [`Self::measure_widths`] should
|
||||
/// be called. There is no need to call [`Self::autoresize`].
|
||||
pub fn present_widget<E, W>(&mut self, widget: W) -> Result<(), E>
|
||||
where
|
||||
E: From<io::Error>,
|
||||
W: Widget<E>,
|
||||
{
|
||||
self.autoresize()?;
|
||||
widget.draw(self.frame())?;
|
||||
self.present()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Display an [`AsyncWidget`] on the screen.
|
||||
///
|
||||
/// Before creating and presenting a widget, [`Self::measure_widths`] should
|
||||
/// be called. There is no need to call [`Self::autoresize`].
|
||||
pub async fn present_async_widget<E, W>(&mut self, widget: W) -> Result<(), E>
|
||||
where
|
||||
E: From<io::Error>,
|
||||
W: AsyncWidget<E>,
|
||||
{
|
||||
self.autoresize()?;
|
||||
widget.draw(self.frame()).await?;
|
||||
self.present()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw_to_screen(&mut self) -> io::Result<()> {
|
||||
if self.full_redraw {
|
||||
io::stdout().queue(Clear(ClearType::All))?;
|
||||
self.out.queue(Clear(ClearType::All))?;
|
||||
self.prev_frame_buffer.reset(); // Because the screen is now empty
|
||||
self.full_redraw = false;
|
||||
}
|
||||
|
||||
self.draw_differences()?;
|
||||
self.update_cursor()?;
|
||||
self.out.flush()?;
|
||||
self.update_title()?;
|
||||
self.ring_bell()?;
|
||||
|
||||
mem::swap(&mut self.prev_frame_buffer, &mut self.frame.buffer);
|
||||
self.frame.reset();
|
||||
|
||||
Ok(Redraw::NotRequired)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw_differences(&mut self) -> io::Result<()> {
|
||||
|
|
@ -136,4 +309,19 @@ impl Terminal {
|
|||
self.out.queue(Hide)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_title(&mut self) -> io::Result<()> {
|
||||
if let Some(title) = &self.frame.title {
|
||||
self.out.queue(SetTitle(title.clone()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ring_bell(&mut self) -> io::Result<()> {
|
||||
if self.frame.bell {
|
||||
self.out.queue(Print('\x07'))?;
|
||||
}
|
||||
self.frame.bell = false;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
127
src/widget.rs
Normal file
127
src/widget.rs
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
use async_trait::async_trait;
|
||||
|
||||
use crate::widgets::{
|
||||
Background, Border, Boxed, BoxedAsync, BoxedSendSync, Desync, Either2, Either3, Float,
|
||||
JoinSegment, Layer2, Padding, Resize, Title,
|
||||
};
|
||||
use crate::{Frame, Size, WidthDb};
|
||||
|
||||
// TODO Feature-gate these traits
|
||||
|
||||
pub trait Widget<E> {
|
||||
fn size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E>;
|
||||
|
||||
fn draw(self, frame: &mut Frame) -> Result<(), E>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait AsyncWidget<E> {
|
||||
async fn size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E>;
|
||||
|
||||
async fn draw(self, frame: &mut Frame) -> Result<(), E>;
|
||||
}
|
||||
|
||||
pub trait WidgetExt: Sized {
|
||||
fn background(self) -> Background<Self> {
|
||||
Background::new(self)
|
||||
}
|
||||
|
||||
fn border(self) -> Border<Self> {
|
||||
Border::new(self)
|
||||
}
|
||||
|
||||
fn boxed<'a, E>(self) -> Boxed<'a, E>
|
||||
where
|
||||
Self: Widget<E> + 'a,
|
||||
{
|
||||
Boxed::new(self)
|
||||
}
|
||||
|
||||
fn boxed_send_sync<'a, E>(self) -> BoxedSendSync<'a, E>
|
||||
where
|
||||
Self: Widget<E> + Send + Sync + 'a,
|
||||
{
|
||||
BoxedSendSync::new(self)
|
||||
}
|
||||
|
||||
fn boxed_async<'a, E>(self) -> BoxedAsync<'a, E>
|
||||
where
|
||||
Self: AsyncWidget<E> + Send + Sync + 'a,
|
||||
{
|
||||
BoxedAsync::new(self)
|
||||
}
|
||||
|
||||
fn desync(self) -> Desync<Self> {
|
||||
Desync(self)
|
||||
}
|
||||
|
||||
fn first2<W2>(self) -> Either2<Self, W2> {
|
||||
Either2::First(self)
|
||||
}
|
||||
|
||||
fn second2<W1>(self) -> Either2<W1, Self> {
|
||||
Either2::Second(self)
|
||||
}
|
||||
|
||||
fn first3<W2, W3>(self) -> Either3<Self, W2, W3> {
|
||||
Either3::First(self)
|
||||
}
|
||||
|
||||
fn second3<W1, W3>(self) -> Either3<W1, Self, W3> {
|
||||
Either3::Second(self)
|
||||
}
|
||||
|
||||
fn third3<W1, W2>(self) -> Either3<W1, W2, Self> {
|
||||
Either3::Third(self)
|
||||
}
|
||||
|
||||
fn float(self) -> Float<Self> {
|
||||
Float::new(self)
|
||||
}
|
||||
|
||||
fn segment(self) -> JoinSegment<Self> {
|
||||
JoinSegment::new(self)
|
||||
}
|
||||
|
||||
fn below<W>(self, above: W) -> Layer2<Self, W> {
|
||||
Layer2::new(self, above)
|
||||
}
|
||||
|
||||
fn above<W>(self, below: W) -> Layer2<W, Self> {
|
||||
Layer2::new(below, self)
|
||||
}
|
||||
|
||||
fn padding(self) -> Padding<Self> {
|
||||
Padding::new(self)
|
||||
}
|
||||
|
||||
fn resize(self) -> Resize<Self> {
|
||||
Resize::new(self)
|
||||
}
|
||||
|
||||
fn title<S: ToString>(self, title: S) -> Title<Self> {
|
||||
Title::new(self, title)
|
||||
}
|
||||
}
|
||||
|
||||
// It would be nice if this could be restricted to types implementing Widget.
|
||||
// However, Widget (and AsyncWidget) have the E type parameter, which WidgetExt
|
||||
// doesn't have. We sadly can't have unconstrained type parameters like that in
|
||||
// impl blocks.
|
||||
//
|
||||
// If WidgetExt had a type parameter E, we'd need to specify that parameter
|
||||
// everywhere we use the trait. This is less ergonomic than just constructing
|
||||
// the types manually.
|
||||
//
|
||||
// Blanket-implementing this trait is not great, but usually works fine.
|
||||
impl<W> WidgetExt for W {}
|
||||
35
src/widgets.rs
Normal file
35
src/widgets.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
pub mod background;
|
||||
pub mod bell;
|
||||
pub mod border;
|
||||
pub mod boxed;
|
||||
pub mod cursor;
|
||||
pub mod desync;
|
||||
pub mod editor;
|
||||
pub mod either;
|
||||
pub mod empty;
|
||||
pub mod float;
|
||||
pub mod join;
|
||||
pub mod layer;
|
||||
pub mod padding;
|
||||
pub mod predrawn;
|
||||
pub mod resize;
|
||||
pub mod text;
|
||||
pub mod title;
|
||||
|
||||
pub use background::*;
|
||||
pub use bell::*;
|
||||
pub use border::*;
|
||||
pub use boxed::*;
|
||||
pub use cursor::*;
|
||||
pub use desync::*;
|
||||
pub use editor::*;
|
||||
pub use either::*;
|
||||
pub use empty::*;
|
||||
pub use float::*;
|
||||
pub use join::*;
|
||||
pub use layer::*;
|
||||
pub use padding::*;
|
||||
pub use predrawn::*;
|
||||
pub use resize::*;
|
||||
pub use text::*;
|
||||
pub use title::*;
|
||||
71
src/widgets/background.rs
Normal file
71
src/widgets/background.rs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
use async_trait::async_trait;
|
||||
|
||||
use crate::{AsyncWidget, Frame, Pos, Size, Style, Widget, WidthDb};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Background<I> {
|
||||
pub inner: I,
|
||||
pub style: Style,
|
||||
}
|
||||
|
||||
impl<I> Background<I> {
|
||||
pub fn new(inner: I) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
style: Style::new().opaque(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_style(mut self, style: Style) -> Self {
|
||||
self.style = style;
|
||||
self
|
||||
}
|
||||
|
||||
fn fill(&self, frame: &mut Frame) {
|
||||
let size = frame.size();
|
||||
for dy in 0..size.height {
|
||||
for dx in 0..size.width {
|
||||
frame.write(Pos::new(dx.into(), dy.into()), (" ", self.style));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, I> Widget<E> for Background<I>
|
||||
where
|
||||
I: Widget<E>,
|
||||
{
|
||||
fn size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
self.inner.size(widthdb, max_width, max_height)
|
||||
}
|
||||
|
||||
fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
self.fill(frame);
|
||||
self.inner.draw(frame)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<E, I> AsyncWidget<E> for Background<I>
|
||||
where
|
||||
I: AsyncWidget<E> + Send + Sync,
|
||||
{
|
||||
async fn size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
self.inner.size(widthdb, max_width, max_height).await
|
||||
}
|
||||
|
||||
async fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
self.fill(frame);
|
||||
self.inner.draw(frame).await
|
||||
}
|
||||
}
|
||||
55
src/widgets/bell.rs
Normal file
55
src/widgets/bell.rs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
use crate::{Frame, Size, Widget, WidthDb};
|
||||
|
||||
///////////
|
||||
// State //
|
||||
///////////
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct BellState {
|
||||
// Whether the bell should be rung the next time the widget is displayed.
|
||||
pub ring: bool,
|
||||
}
|
||||
|
||||
impl BellState {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn widget(&mut self) -> Bell<'_> {
|
||||
Bell { state: self }
|
||||
}
|
||||
}
|
||||
|
||||
////////////
|
||||
// Widget //
|
||||
////////////
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Bell<'a> {
|
||||
state: &'a mut BellState,
|
||||
}
|
||||
|
||||
impl Bell<'_> {
|
||||
pub fn state(&mut self) -> &mut BellState {
|
||||
self.state
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Widget<E> for Bell<'_> {
|
||||
fn size(
|
||||
&self,
|
||||
_widthdb: &mut WidthDb,
|
||||
_max_width: Option<u16>,
|
||||
_max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
Ok(Size::ZERO)
|
||||
}
|
||||
|
||||
fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
if self.state.ring {
|
||||
frame.set_bell(true);
|
||||
self.state.ring = false
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
201
src/widgets/border.rs
Normal file
201
src/widgets/border.rs
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
use async_trait::async_trait;
|
||||
|
||||
use crate::{AsyncWidget, Frame, Pos, Size, Style, Widget, WidthDb};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BorderLook {
|
||||
pub top_left: &'static str,
|
||||
pub top_right: &'static str,
|
||||
pub bottom_left: &'static str,
|
||||
pub bottom_right: &'static str,
|
||||
pub top: &'static str,
|
||||
pub bottom: &'static str,
|
||||
pub left: &'static str,
|
||||
pub right: &'static str,
|
||||
}
|
||||
|
||||
impl BorderLook {
|
||||
/// ```text
|
||||
/// +-------+
|
||||
/// | Hello |
|
||||
/// +-------+
|
||||
/// ```
|
||||
pub const ASCII: Self = Self {
|
||||
top_left: "+",
|
||||
top_right: "+",
|
||||
bottom_left: "+",
|
||||
bottom_right: "+",
|
||||
top: "-",
|
||||
bottom: "-",
|
||||
left: "|",
|
||||
right: "|",
|
||||
};
|
||||
|
||||
/// ```text
|
||||
/// ┌───────┐
|
||||
/// │ Hello │
|
||||
/// └───────┘
|
||||
/// ```
|
||||
pub const LINE: Self = Self {
|
||||
top_left: "┌",
|
||||
top_right: "┐",
|
||||
bottom_left: "└",
|
||||
bottom_right: "┘",
|
||||
top: "─",
|
||||
bottom: "─",
|
||||
left: "│",
|
||||
right: "│",
|
||||
};
|
||||
|
||||
/// ```text
|
||||
/// ┏━━━━━━━┓
|
||||
/// ┃ Hello ┃
|
||||
/// ┗━━━━━━━┛
|
||||
/// ```
|
||||
pub const LINE_HEAVY: Self = Self {
|
||||
top_left: "┏",
|
||||
top_right: "┓",
|
||||
bottom_left: "┗",
|
||||
bottom_right: "┛",
|
||||
top: "━",
|
||||
bottom: "━",
|
||||
left: "┃",
|
||||
right: "┃",
|
||||
};
|
||||
|
||||
/// ```text
|
||||
/// ╔═══════╗
|
||||
/// ║ Hello ║
|
||||
/// ╚═══════╝
|
||||
/// ```
|
||||
pub const LINE_DOUBLE: Self = Self {
|
||||
top_left: "╔",
|
||||
top_right: "╗",
|
||||
bottom_left: "╚",
|
||||
bottom_right: "╝",
|
||||
top: "═",
|
||||
bottom: "═",
|
||||
left: "║",
|
||||
right: "║",
|
||||
};
|
||||
}
|
||||
|
||||
impl Default for BorderLook {
|
||||
fn default() -> Self {
|
||||
Self::LINE
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Border<I> {
|
||||
pub inner: I,
|
||||
pub look: BorderLook,
|
||||
pub style: Style,
|
||||
}
|
||||
|
||||
impl<I> Border<I> {
|
||||
pub fn new(inner: I) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
look: BorderLook::default(),
|
||||
style: Style::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_look(mut self, look: BorderLook) -> Self {
|
||||
self.look = look;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_style(mut self, style: Style) -> Self {
|
||||
self.style = style;
|
||||
self
|
||||
}
|
||||
|
||||
fn draw_border(&self, frame: &mut Frame) {
|
||||
let size = frame.size();
|
||||
let right = size.width.saturating_sub(1).into();
|
||||
let bottom = size.height.saturating_sub(1).into();
|
||||
|
||||
for y in 1..bottom {
|
||||
frame.write(Pos::new(right, y), (self.look.right, self.style));
|
||||
frame.write(Pos::new(0, y), (self.look.left, self.style));
|
||||
}
|
||||
|
||||
for x in 1..right {
|
||||
frame.write(Pos::new(x, bottom), (self.look.bottom, self.style));
|
||||
frame.write(Pos::new(x, 0), (self.look.top, self.style));
|
||||
}
|
||||
|
||||
frame.write(
|
||||
Pos::new(right, bottom),
|
||||
(self.look.bottom_right, self.style),
|
||||
);
|
||||
frame.write(Pos::new(0, bottom), (self.look.bottom_left, self.style));
|
||||
frame.write(Pos::new(right, 0), (self.look.top_right, self.style));
|
||||
frame.write(Pos::new(0, 0), (self.look.top_left, self.style));
|
||||
}
|
||||
|
||||
fn push_inner(&self, frame: &mut Frame) {
|
||||
let mut size = frame.size();
|
||||
size.width = size.width.saturating_sub(2);
|
||||
size.height = size.height.saturating_sub(2);
|
||||
|
||||
frame.push(Pos::new(1, 1), size);
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, I> Widget<E> for Border<I>
|
||||
where
|
||||
I: Widget<E>,
|
||||
{
|
||||
fn size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
let max_width = max_width.map(|w| w.saturating_sub(2));
|
||||
let max_height = max_height.map(|h| h.saturating_sub(2));
|
||||
let size = self.inner.size(widthdb, max_width, max_height)?;
|
||||
Ok(size + Size::new(2, 2))
|
||||
}
|
||||
|
||||
fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
self.draw_border(frame);
|
||||
|
||||
self.push_inner(frame);
|
||||
self.inner.draw(frame)?;
|
||||
frame.pop();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<E, I> AsyncWidget<E> for Border<I>
|
||||
where
|
||||
I: AsyncWidget<E> + Send + Sync,
|
||||
{
|
||||
async fn size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
let max_width = max_width.map(|w| w.saturating_sub(2));
|
||||
let max_height = max_height.map(|h| h.saturating_sub(2));
|
||||
let size = self.inner.size(widthdb, max_width, max_height).await?;
|
||||
Ok(size + Size::new(2, 2))
|
||||
}
|
||||
|
||||
async fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
self.draw_border(frame);
|
||||
|
||||
self.push_inner(frame);
|
||||
self.inner.draw(frame).await?;
|
||||
frame.pop();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
142
src/widgets/boxed.rs
Normal file
142
src/widgets/boxed.rs
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
use async_trait::async_trait;
|
||||
|
||||
use crate::{AsyncWidget, Frame, Size, Widget, WidthDb};
|
||||
|
||||
pub struct Boxed<'a, E>(Box<dyn WidgetWrapper<E> + 'a>);
|
||||
|
||||
impl<'a, E> Boxed<'a, E> {
|
||||
pub fn new<I>(inner: I) -> Self
|
||||
where
|
||||
I: Widget<E> + 'a,
|
||||
{
|
||||
Self(Box::new(inner))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Widget<E> for Boxed<'_, E> {
|
||||
fn size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
self.0.wrap_size(widthdb, max_width, max_height)
|
||||
}
|
||||
|
||||
fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
self.0.wrap_draw(frame)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BoxedSendSync<'a, E>(Box<dyn WidgetWrapper<E> + Send + Sync + 'a>);
|
||||
|
||||
impl<'a, E> BoxedSendSync<'a, E> {
|
||||
pub fn new<I>(inner: I) -> Self
|
||||
where
|
||||
I: Widget<E> + Send + Sync + 'a,
|
||||
{
|
||||
Self(Box::new(inner))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Widget<E> for BoxedSendSync<'_, E> {
|
||||
fn size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
self.0.wrap_size(widthdb, max_width, max_height)
|
||||
}
|
||||
|
||||
fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
self.0.wrap_draw(frame)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BoxedAsync<'a, E>(Box<dyn AsyncWidgetWrapper<E> + Send + Sync + 'a>);
|
||||
|
||||
impl<'a, E> BoxedAsync<'a, E> {
|
||||
pub fn new<I>(inner: I) -> Self
|
||||
where
|
||||
I: AsyncWidget<E> + Send + Sync + 'a,
|
||||
{
|
||||
Self(Box::new(inner))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<E> AsyncWidget<E> for BoxedAsync<'_, E> {
|
||||
async fn size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
self.0.wrap_size(widthdb, max_width, max_height).await
|
||||
}
|
||||
|
||||
async fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
self.0.wrap_draw(frame).await
|
||||
}
|
||||
}
|
||||
|
||||
trait WidgetWrapper<E> {
|
||||
fn wrap_size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E>;
|
||||
|
||||
fn wrap_draw(self: Box<Self>, frame: &mut Frame) -> Result<(), E>;
|
||||
}
|
||||
|
||||
impl<E, W> WidgetWrapper<E> for W
|
||||
where
|
||||
W: Widget<E>,
|
||||
{
|
||||
fn wrap_size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
self.size(widthdb, max_width, max_height)
|
||||
}
|
||||
|
||||
fn wrap_draw(self: Box<Self>, frame: &mut Frame) -> Result<(), E> {
|
||||
(*self).draw(frame)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
trait AsyncWidgetWrapper<E> {
|
||||
async fn wrap_size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E>;
|
||||
|
||||
async fn wrap_draw(self: Box<Self>, frame: &mut Frame) -> Result<(), E>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<E, W> AsyncWidgetWrapper<E> for W
|
||||
where
|
||||
W: AsyncWidget<E> + Send + Sync,
|
||||
{
|
||||
async fn wrap_size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
self.size(widthdb, max_width, max_height).await
|
||||
}
|
||||
|
||||
async fn wrap_draw(self: Box<Self>, frame: &mut Frame) -> Result<(), E> {
|
||||
(*self).draw(frame).await
|
||||
}
|
||||
}
|
||||
68
src/widgets/cursor.rs
Normal file
68
src/widgets/cursor.rs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
use async_trait::async_trait;
|
||||
|
||||
use crate::{AsyncWidget, Frame, Pos, Size, Widget, WidthDb};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Cursor<I> {
|
||||
pub inner: I,
|
||||
pub position: Pos,
|
||||
}
|
||||
|
||||
impl<I> Cursor<I> {
|
||||
pub fn new(inner: I) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
position: Pos::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_position(mut self, position: Pos) -> Self {
|
||||
self.position = position;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_position_xy(self, x: i32, y: i32) -> Self {
|
||||
self.with_position(Pos::new(x, y))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, I> Widget<E> for Cursor<I>
|
||||
where
|
||||
I: Widget<E>,
|
||||
{
|
||||
fn size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
self.inner.size(widthdb, max_width, max_height)
|
||||
}
|
||||
|
||||
fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
self.inner.draw(frame)?;
|
||||
frame.show_cursor(self.position);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<E, I> AsyncWidget<E> for Cursor<I>
|
||||
where
|
||||
I: AsyncWidget<E> + Send + Sync,
|
||||
{
|
||||
async fn size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
self.inner.size(widthdb, max_width, max_height).await
|
||||
}
|
||||
|
||||
async fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
self.inner.draw(frame).await?;
|
||||
frame.show_cursor(self.position);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
42
src/widgets/desync.rs
Normal file
42
src/widgets/desync.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
use async_trait::async_trait;
|
||||
|
||||
use crate::{AsyncWidget, Widget};
|
||||
|
||||
pub struct Desync<I>(pub I);
|
||||
|
||||
impl<E, I> Widget<E> for Desync<I>
|
||||
where
|
||||
I: Widget<E>,
|
||||
{
|
||||
fn size(
|
||||
&self,
|
||||
widthdb: &mut crate::WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<crate::Size, E> {
|
||||
self.0.size(widthdb, max_width, max_height)
|
||||
}
|
||||
|
||||
fn draw(self, frame: &mut crate::Frame) -> Result<(), E> {
|
||||
self.0.draw(frame)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<E, I> AsyncWidget<E> for Desync<I>
|
||||
where
|
||||
I: Widget<E> + Send + Sync,
|
||||
{
|
||||
async fn size(
|
||||
&self,
|
||||
widthdb: &mut crate::WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<crate::Size, E> {
|
||||
self.0.size(widthdb, max_width, max_height)
|
||||
}
|
||||
|
||||
async fn draw(self, frame: &mut crate::Frame) -> Result<(), E> {
|
||||
self.0.draw(frame)
|
||||
}
|
||||
}
|
||||
485
src/widgets/editor.rs
Normal file
485
src/widgets/editor.rs
Normal file
|
|
@ -0,0 +1,485 @@
|
|||
use std::iter;
|
||||
|
||||
use crossterm::style::Stylize;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use crate::{Frame, Pos, Size, Style, Styled, Widget, WidthDb};
|
||||
|
||||
/// Like [`WidthDb::wrap`] but includes a final break index if the text ends
|
||||
/// with a newline.
|
||||
fn wrap(widthdb: &mut WidthDb, text: &str, width: usize) -> Vec<usize> {
|
||||
let mut breaks = widthdb.wrap(text, width);
|
||||
if text.ends_with('\n') {
|
||||
breaks.push(text.len())
|
||||
}
|
||||
breaks
|
||||
}
|
||||
|
||||
///////////
|
||||
// State //
|
||||
///////////
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EditorState {
|
||||
text: String,
|
||||
|
||||
/// Index of the cursor in the text.
|
||||
///
|
||||
/// Must point to a valid grapheme boundary.
|
||||
cursor_idx: usize,
|
||||
|
||||
/// Column of the cursor on the screen just after it was last moved
|
||||
/// horizontally.
|
||||
cursor_col: usize,
|
||||
|
||||
/// Position of the cursor when the editor was last rendered.
|
||||
last_cursor_pos: Pos,
|
||||
}
|
||||
|
||||
impl EditorState {
|
||||
pub fn new() -> Self {
|
||||
Self::with_initial_text(String::new())
|
||||
}
|
||||
|
||||
pub fn with_initial_text(text: String) -> Self {
|
||||
Self {
|
||||
cursor_idx: text.len(),
|
||||
cursor_col: 0,
|
||||
last_cursor_pos: Pos::ZERO,
|
||||
text,
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
// Grapheme helper functions //
|
||||
///////////////////////////////
|
||||
|
||||
fn grapheme_boundaries(&self) -> Vec<usize> {
|
||||
self.text
|
||||
.grapheme_indices(true)
|
||||
.map(|(i, _)| i)
|
||||
.chain(iter::once(self.text.len()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Ensure the cursor index lies on a grapheme boundary. If it doesn't, it
|
||||
/// is moved to the next grapheme boundary.
|
||||
///
|
||||
/// Can handle arbitrary cursor index.
|
||||
fn move_cursor_to_grapheme_boundary(&mut self) {
|
||||
for i in self.grapheme_boundaries() {
|
||||
#[allow(clippy::comparison_chain)]
|
||||
if i == self.cursor_idx {
|
||||
// We're at a valid grapheme boundary already
|
||||
return;
|
||||
} else if i > self.cursor_idx {
|
||||
// There was no valid grapheme boundary at our cursor index, so
|
||||
// we'll take the next one we can get.
|
||||
self.cursor_idx = i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// The cursor was out of bounds, so move it to the last valid index.
|
||||
self.cursor_idx = self.text.len();
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
// Line/col helper functions //
|
||||
///////////////////////////////
|
||||
|
||||
/// Like [`Self::grapheme_boundaries`] but for lines.
|
||||
///
|
||||
/// Note that the last line can have a length of 0 if the text ends with a
|
||||
/// newline.
|
||||
fn line_boundaries(&self) -> Vec<usize> {
|
||||
let newlines = self
|
||||
.text
|
||||
.char_indices()
|
||||
.filter(|(_, c)| *c == '\n')
|
||||
.map(|(i, _)| i + 1); // utf-8 encodes '\n' as a single byte
|
||||
iter::once(0)
|
||||
.chain(newlines)
|
||||
.chain(iter::once(self.text.len()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Find the cursor's current line.
|
||||
///
|
||||
/// Returns `(line_nr, start_idx, end_idx)`.
|
||||
fn cursor_line(&self, boundaries: &[usize]) -> (usize, usize, usize) {
|
||||
let mut result = (0, 0, 0);
|
||||
for (i, (start, end)) in boundaries.iter().zip(boundaries.iter().skip(1)).enumerate() {
|
||||
if self.cursor_idx >= *start {
|
||||
result = (i, *start, *end);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn cursor_col(&self, widthdb: &mut WidthDb, line_start: usize) -> usize {
|
||||
widthdb.width(&self.text[line_start..self.cursor_idx])
|
||||
}
|
||||
|
||||
fn line(&self, line: usize) -> (usize, usize) {
|
||||
let boundaries = self.line_boundaries();
|
||||
boundaries
|
||||
.iter()
|
||||
.copied()
|
||||
.zip(boundaries.iter().copied().skip(1))
|
||||
.nth(line)
|
||||
.expect("line exists")
|
||||
}
|
||||
|
||||
fn move_cursor_to_line_col(&mut self, widthdb: &mut WidthDb, line: usize, col: usize) {
|
||||
let (start, end) = self.line(line);
|
||||
let line = &self.text[start..end];
|
||||
|
||||
let mut width = 0;
|
||||
for (gi, g) in line.grapheme_indices(true) {
|
||||
self.cursor_idx = start + gi;
|
||||
if col > width {
|
||||
width += widthdb.grapheme_width(g, width) as usize;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if !line.ends_with('\n') {
|
||||
self.cursor_idx = end;
|
||||
}
|
||||
}
|
||||
|
||||
fn record_cursor_col(&mut self, widthdb: &mut WidthDb) {
|
||||
let boundaries = self.line_boundaries();
|
||||
let (_, start, _) = self.cursor_line(&boundaries);
|
||||
self.cursor_col = self.cursor_col(widthdb, start);
|
||||
}
|
||||
|
||||
/////////////
|
||||
// Editing //
|
||||
/////////////
|
||||
|
||||
pub fn text(&self) -> &str {
|
||||
&self.text
|
||||
}
|
||||
|
||||
pub fn set_text(&mut self, widthdb: &mut WidthDb, text: String) {
|
||||
self.text = text;
|
||||
self.move_cursor_to_grapheme_boundary();
|
||||
self.record_cursor_col(widthdb);
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.text = String::new();
|
||||
self.cursor_idx = 0;
|
||||
self.cursor_col = 0;
|
||||
}
|
||||
|
||||
/// Insert a character at the current cursor position and move the cursor
|
||||
/// accordingly.
|
||||
pub fn insert_char(&mut self, widthdb: &mut WidthDb, ch: char) {
|
||||
self.text.insert(self.cursor_idx, ch);
|
||||
self.cursor_idx += ch.len_utf8();
|
||||
self.record_cursor_col(widthdb);
|
||||
}
|
||||
|
||||
/// Insert a string at the current cursor position and move the cursor
|
||||
/// accordingly.
|
||||
pub fn insert_str(&mut self, widthdb: &mut WidthDb, str: &str) {
|
||||
self.text.insert_str(self.cursor_idx, str);
|
||||
self.cursor_idx += str.len();
|
||||
self.record_cursor_col(widthdb);
|
||||
}
|
||||
|
||||
/// Delete the grapheme before the cursor position.
|
||||
pub fn backspace(&mut self, widthdb: &mut WidthDb) {
|
||||
let boundaries = self.grapheme_boundaries();
|
||||
for (start, end) in boundaries.iter().zip(boundaries.iter().skip(1)) {
|
||||
if *end == self.cursor_idx {
|
||||
self.text.replace_range(start..end, "");
|
||||
self.cursor_idx = *start;
|
||||
self.record_cursor_col(widthdb);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete the grapheme after the cursor position.
|
||||
pub fn delete(&mut self) {
|
||||
let boundaries = self.grapheme_boundaries();
|
||||
for (start, end) in boundaries.iter().zip(boundaries.iter().skip(1)) {
|
||||
if *start == self.cursor_idx {
|
||||
self.text.replace_range(start..end, "");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
// Cursor movement //
|
||||
/////////////////////
|
||||
|
||||
pub fn move_cursor_left(&mut self, widthdb: &mut WidthDb) {
|
||||
let boundaries = self.grapheme_boundaries();
|
||||
for (start, end) in boundaries.iter().zip(boundaries.iter().skip(1)) {
|
||||
if *end == self.cursor_idx {
|
||||
self.cursor_idx = *start;
|
||||
self.record_cursor_col(widthdb);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_cursor_right(&mut self, widthdb: &mut WidthDb) {
|
||||
let boundaries = self.grapheme_boundaries();
|
||||
for (start, end) in boundaries.iter().zip(boundaries.iter().skip(1)) {
|
||||
if *start == self.cursor_idx {
|
||||
self.cursor_idx = *end;
|
||||
self.record_cursor_col(widthdb);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_cursor_left_a_word(&mut self, widthdb: &mut WidthDb) {
|
||||
let boundaries = self.grapheme_boundaries();
|
||||
let mut encountered_word = false;
|
||||
for (start, end) in boundaries.iter().zip(boundaries.iter().skip(1)).rev() {
|
||||
if *end == self.cursor_idx {
|
||||
let g = &self.text[*start..*end];
|
||||
let whitespace = g.chars().all(|c| c.is_whitespace());
|
||||
if encountered_word && whitespace {
|
||||
break;
|
||||
} else if !whitespace {
|
||||
encountered_word = true;
|
||||
}
|
||||
self.cursor_idx = *start;
|
||||
}
|
||||
}
|
||||
self.record_cursor_col(widthdb);
|
||||
}
|
||||
|
||||
pub fn move_cursor_right_a_word(&mut self, widthdb: &mut WidthDb) {
|
||||
let boundaries = self.grapheme_boundaries();
|
||||
let mut encountered_word = false;
|
||||
for (start, end) in boundaries.iter().zip(boundaries.iter().skip(1)) {
|
||||
if *start == self.cursor_idx {
|
||||
let g = &self.text[*start..*end];
|
||||
let whitespace = g.chars().all(|c| c.is_whitespace());
|
||||
if encountered_word && whitespace {
|
||||
break;
|
||||
} else if !whitespace {
|
||||
encountered_word = true;
|
||||
}
|
||||
self.cursor_idx = *end;
|
||||
}
|
||||
}
|
||||
self.record_cursor_col(widthdb);
|
||||
}
|
||||
|
||||
pub fn move_cursor_to_start_of_line(&mut self, widthdb: &mut WidthDb) {
|
||||
let boundaries = self.line_boundaries();
|
||||
let (line, _, _) = self.cursor_line(&boundaries);
|
||||
self.move_cursor_to_line_col(widthdb, line, 0);
|
||||
self.record_cursor_col(widthdb);
|
||||
}
|
||||
|
||||
pub fn move_cursor_to_end_of_line(&mut self, widthdb: &mut WidthDb) {
|
||||
let boundaries = self.line_boundaries();
|
||||
let (line, _, _) = self.cursor_line(&boundaries);
|
||||
self.move_cursor_to_line_col(widthdb, line, usize::MAX);
|
||||
self.record_cursor_col(widthdb);
|
||||
}
|
||||
|
||||
pub fn move_cursor_up(&mut self, widthdb: &mut WidthDb) {
|
||||
let boundaries = self.line_boundaries();
|
||||
let (line, _, _) = self.cursor_line(&boundaries);
|
||||
if line > 0 {
|
||||
self.move_cursor_to_line_col(widthdb, line - 1, self.cursor_col);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_cursor_down(&mut self, widthdb: &mut WidthDb) {
|
||||
let boundaries = self.line_boundaries();
|
||||
|
||||
// There's always at least one line, and always at least two line
|
||||
// boundaries at 0 and self.text.len().
|
||||
let amount_of_lines = boundaries.len() - 1;
|
||||
|
||||
let (line, _, _) = self.cursor_line(&boundaries);
|
||||
if line + 1 < amount_of_lines {
|
||||
self.move_cursor_to_line_col(widthdb, line + 1, self.cursor_col);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn last_cursor_pos(&self) -> Pos {
|
||||
self.last_cursor_pos
|
||||
}
|
||||
|
||||
pub fn widget(&mut self) -> Editor<'_> {
|
||||
Editor {
|
||||
highlighted: Styled::new_plain(&self.text),
|
||||
hidden: None,
|
||||
focus: true,
|
||||
state: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EditorState {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
////////////
|
||||
// Widget //
|
||||
////////////
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Editor<'a> {
|
||||
state: &'a mut EditorState,
|
||||
highlighted: Styled,
|
||||
pub hidden: Option<Styled>,
|
||||
pub focus: bool,
|
||||
}
|
||||
|
||||
impl Editor<'_> {
|
||||
pub fn state(&mut self) -> &mut EditorState {
|
||||
self.state
|
||||
}
|
||||
|
||||
pub fn text(&self) -> &Styled {
|
||||
&self.highlighted
|
||||
}
|
||||
|
||||
pub fn highlight<F>(&mut self, highlight: F)
|
||||
where
|
||||
F: FnOnce(&str) -> Styled,
|
||||
{
|
||||
self.highlighted = highlight(&self.state.text);
|
||||
assert_eq!(self.state.text, self.highlighted.text());
|
||||
}
|
||||
|
||||
pub fn with_highlight<F>(mut self, highlight: F) -> Self
|
||||
where
|
||||
F: FnOnce(&str) -> Styled,
|
||||
{
|
||||
self.highlight(highlight);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_visible(mut self) -> Self {
|
||||
self.hidden = None;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_hidden<S: Into<Styled>>(mut self, placeholder: S) -> Self {
|
||||
self.hidden = Some(placeholder.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_hidden_default_placeholder(self) -> Self {
|
||||
self.with_hidden(("<hidden>", Style::new().grey().italic()))
|
||||
}
|
||||
|
||||
pub fn with_focus(mut self, active: bool) -> Self {
|
||||
self.focus = active;
|
||||
self
|
||||
}
|
||||
|
||||
fn wrapped_cursor(cursor_idx: usize, break_indices: &[usize]) -> (usize, usize) {
|
||||
let mut row = 0;
|
||||
let mut line_idx = cursor_idx;
|
||||
|
||||
for break_idx in break_indices {
|
||||
if cursor_idx < *break_idx {
|
||||
break;
|
||||
} else {
|
||||
row += 1;
|
||||
line_idx = cursor_idx - break_idx;
|
||||
}
|
||||
}
|
||||
|
||||
(row, line_idx)
|
||||
}
|
||||
|
||||
fn indices(&self, widthdb: &mut WidthDb, max_width: Option<u16>) -> Vec<usize> {
|
||||
let max_width = max_width
|
||||
// One extra column for cursor
|
||||
.map(|w| w.saturating_sub(1) as usize)
|
||||
.unwrap_or(usize::MAX);
|
||||
let text = self.hidden.as_ref().unwrap_or(&self.highlighted);
|
||||
wrap(widthdb, text.text(), max_width)
|
||||
}
|
||||
|
||||
fn rows(&self, indices: &[usize]) -> Vec<Styled> {
|
||||
let text = match self.hidden.as_ref() {
|
||||
Some(hidden) if !self.highlighted.text().is_empty() => hidden,
|
||||
_ => &self.highlighted,
|
||||
};
|
||||
text.clone().split_at_indices(indices)
|
||||
}
|
||||
|
||||
fn cursor(&self, widthdb: &mut WidthDb, width: u16, indices: &[usize], rows: &[Styled]) -> Pos {
|
||||
if self.hidden.is_some() {
|
||||
return Pos::new(0, 0);
|
||||
}
|
||||
|
||||
let (cursor_row, cursor_line_idx) = Self::wrapped_cursor(self.state.cursor_idx, indices);
|
||||
let cursor_col = widthdb.width(rows[cursor_row].text().split_at(cursor_line_idx).0);
|
||||
|
||||
// Ensure the cursor is always visible
|
||||
let cursor_col = cursor_col.min(width.saturating_sub(1).into());
|
||||
|
||||
let cursor_row: i32 = cursor_row.try_into().unwrap_or(i32::MAX);
|
||||
let cursor_col: i32 = cursor_col.try_into().unwrap_or(i32::MAX);
|
||||
Pos::new(cursor_col, cursor_row)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Widget<E> for Editor<'_> {
|
||||
fn size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
_max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
let indices = self.indices(widthdb, max_width);
|
||||
let rows = self.rows(&indices);
|
||||
|
||||
let width = rows
|
||||
.iter()
|
||||
.map(|row| widthdb.width(row.text()))
|
||||
.max()
|
||||
.unwrap_or(0)
|
||||
// One extra column for cursor
|
||||
.saturating_add(1);
|
||||
let height = rows.len();
|
||||
|
||||
let width: u16 = width.try_into().unwrap_or(u16::MAX);
|
||||
let height: u16 = height.try_into().unwrap_or(u16::MAX);
|
||||
Ok(Size::new(width, height))
|
||||
}
|
||||
|
||||
fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
let size = frame.size();
|
||||
let indices = self.indices(frame.widthdb(), Some(size.width));
|
||||
let rows = self.rows(&indices);
|
||||
let cursor = self.cursor(frame.widthdb(), size.width, &indices, &rows);
|
||||
|
||||
for (i, row) in rows.into_iter().enumerate() {
|
||||
frame.write(Pos::new(0, i as i32), row);
|
||||
}
|
||||
|
||||
if self.focus {
|
||||
frame.set_cursor(Some(cursor));
|
||||
}
|
||||
self.state.last_cursor_pos = cursor;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
118
src/widgets/either.rs
Normal file
118
src/widgets/either.rs
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
use async_trait::async_trait;
|
||||
|
||||
use crate::{AsyncWidget, Frame, Size, Widget, WidthDb};
|
||||
|
||||
macro_rules! mk_either {
|
||||
(
|
||||
pub enum $name:ident {
|
||||
$( $constr:ident($ty:ident), )+
|
||||
}
|
||||
) => {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum $name< $( $ty ),+ > {
|
||||
$( $constr($ty), )+
|
||||
}
|
||||
|
||||
impl<E, $( $ty ),+> Widget<E> for $name< $( $ty ),+ >
|
||||
where
|
||||
$( $ty: Widget<E>, )+
|
||||
{
|
||||
fn size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
match self {
|
||||
$( Self::$constr(w) => w.size(widthdb, max_width, max_height), )+
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
match self {
|
||||
$( Self::$constr(w) => w.draw(frame), )+
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<E, $( $ty ),+> AsyncWidget<E> for $name< $( $ty ),+ >
|
||||
where
|
||||
$( $ty: AsyncWidget<E> + Send + Sync, )+
|
||||
{
|
||||
async fn size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
match self {
|
||||
$( Self::$constr(w) => w.size(widthdb, max_width, max_height).await, )+
|
||||
}
|
||||
}
|
||||
|
||||
async fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
match self {
|
||||
$( Self::$constr(w) => w.draw(frame).await, )+
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
mk_either! {
|
||||
pub enum Either2 {
|
||||
First(I1),
|
||||
Second(I2),
|
||||
}
|
||||
}
|
||||
|
||||
mk_either! {
|
||||
pub enum Either3 {
|
||||
First(I1),
|
||||
Second(I2),
|
||||
Third(I3),
|
||||
}
|
||||
}
|
||||
|
||||
mk_either! {
|
||||
pub enum Either4 {
|
||||
First(I1),
|
||||
Second(I2),
|
||||
Third(I3),
|
||||
Fourth(I4),
|
||||
}
|
||||
}
|
||||
|
||||
mk_either! {
|
||||
pub enum Either5 {
|
||||
First(I1),
|
||||
Second(I2),
|
||||
Third(I3),
|
||||
Fourth(I4),
|
||||
Fifth(I5),
|
||||
}
|
||||
}
|
||||
|
||||
mk_either! {
|
||||
pub enum Either6 {
|
||||
First(I1),
|
||||
Second(I2),
|
||||
Third(I3),
|
||||
Fourth(I4),
|
||||
Fifth(I5),
|
||||
Sixth(I6),
|
||||
}
|
||||
}
|
||||
|
||||
mk_either! {
|
||||
pub enum Either7 {
|
||||
First(I1),
|
||||
Second(I2),
|
||||
Third(I3),
|
||||
Fourth(I4),
|
||||
Fifth(I5),
|
||||
Sixth(I6),
|
||||
Seventh(I7),
|
||||
}
|
||||
}
|
||||
42
src/widgets/empty.rs
Normal file
42
src/widgets/empty.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
use crate::{Frame, Size, Widget, WidthDb};
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub struct Empty {
|
||||
pub size: Size,
|
||||
}
|
||||
|
||||
impl Empty {
|
||||
pub fn new() -> Self {
|
||||
Self { size: Size::ZERO }
|
||||
}
|
||||
|
||||
pub fn with_width(mut self, width: u16) -> Self {
|
||||
self.size.width = width;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_height(mut self, height: u16) -> Self {
|
||||
self.size.height = height;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_size(mut self, size: Size) -> Self {
|
||||
self.size = size;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Widget<E> for Empty {
|
||||
fn size(
|
||||
&self,
|
||||
_widthdb: &mut WidthDb,
|
||||
_max_width: Option<u16>,
|
||||
_max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
Ok(self.size)
|
||||
}
|
||||
|
||||
fn draw(self, _frame: &mut Frame) -> Result<(), E> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
166
src/widgets/float.rs
Normal file
166
src/widgets/float.rs
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
use async_trait::async_trait;
|
||||
|
||||
use crate::{AsyncWidget, Frame, Pos, Size, Widget, WidthDb};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Float<I> {
|
||||
pub inner: I,
|
||||
horizontal: Option<f32>,
|
||||
vertical: Option<f32>,
|
||||
}
|
||||
|
||||
impl<I> Float<I> {
|
||||
pub fn new(inner: I) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
horizontal: None,
|
||||
vertical: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn horizontal(&self) -> Option<f32> {
|
||||
self.horizontal
|
||||
}
|
||||
|
||||
pub fn set_horizontal(&mut self, position: Option<f32>) {
|
||||
if let Some(position) = position {
|
||||
assert!((0.0..=1.0).contains(&position));
|
||||
}
|
||||
self.horizontal = position;
|
||||
}
|
||||
|
||||
pub fn vertical(&self) -> Option<f32> {
|
||||
self.vertical
|
||||
}
|
||||
|
||||
pub fn set_vertical(&mut self, position: Option<f32>) {
|
||||
if let Some(position) = position {
|
||||
assert!((0.0..=1.0).contains(&position));
|
||||
}
|
||||
self.vertical = position;
|
||||
}
|
||||
|
||||
pub fn with_horizontal(mut self, position: f32) -> Self {
|
||||
self.set_horizontal(Some(position));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_vertical(mut self, position: f32) -> Self {
|
||||
self.set_vertical(Some(position));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_all(self, position: f32) -> Self {
|
||||
self.with_horizontal(position).with_vertical(position)
|
||||
}
|
||||
|
||||
pub fn with_left(self) -> Self {
|
||||
self.with_horizontal(0.0)
|
||||
}
|
||||
|
||||
pub fn with_right(self) -> Self {
|
||||
self.with_horizontal(1.0)
|
||||
}
|
||||
|
||||
pub fn with_top(self) -> Self {
|
||||
self.with_vertical(0.0)
|
||||
}
|
||||
|
||||
pub fn with_bottom(self) -> Self {
|
||||
self.with_vertical(1.0)
|
||||
}
|
||||
|
||||
pub fn with_center_h(self) -> Self {
|
||||
self.with_horizontal(0.5)
|
||||
}
|
||||
|
||||
pub fn with_center_v(self) -> Self {
|
||||
self.with_vertical(0.5)
|
||||
}
|
||||
|
||||
pub fn with_center(self) -> Self {
|
||||
self.with_all(0.5)
|
||||
}
|
||||
|
||||
fn push_inner(&self, frame: &mut Frame, size: Size, mut inner_size: Size) {
|
||||
let mut inner_pos = Pos::ZERO;
|
||||
|
||||
if let Some(horizontal) = self.horizontal {
|
||||
let available = size.width.saturating_sub(inner_size.width) as f32;
|
||||
// Biased towards the left if horizontal lands exactly on the
|
||||
// boundary between two cells
|
||||
inner_pos.x = (horizontal * available).floor().min(available) as i32;
|
||||
inner_size.width = inner_size.width.min(size.width);
|
||||
} else {
|
||||
inner_size.width = size.width;
|
||||
}
|
||||
|
||||
if let Some(vertical) = self.vertical {
|
||||
let available = size.height.saturating_sub(inner_size.height) as f32;
|
||||
// Biased towards the top if vertical lands exactly on the boundary
|
||||
// between two cells
|
||||
inner_pos.y = (vertical * available).floor().min(available) as i32;
|
||||
inner_size.height = inner_size.height.min(size.height);
|
||||
} else {
|
||||
inner_size.height = size.height;
|
||||
}
|
||||
|
||||
frame.push(inner_pos, inner_size);
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, I> Widget<E> for Float<I>
|
||||
where
|
||||
I: Widget<E>,
|
||||
{
|
||||
fn size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
self.inner.size(widthdb, max_width, max_height)
|
||||
}
|
||||
|
||||
fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
let size = frame.size();
|
||||
let inner_size = self
|
||||
.inner
|
||||
.size(frame.widthdb(), Some(size.width), Some(size.height))?;
|
||||
|
||||
self.push_inner(frame, size, inner_size);
|
||||
self.inner.draw(frame)?;
|
||||
frame.pop();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<E, I> AsyncWidget<E> for Float<I>
|
||||
where
|
||||
I: AsyncWidget<E> + Send + Sync,
|
||||
{
|
||||
async fn size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
self.inner.size(widthdb, max_width, max_height).await
|
||||
}
|
||||
|
||||
async fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
let size = frame.size();
|
||||
let inner_size = self
|
||||
.inner
|
||||
.size(frame.widthdb(), Some(size.width), Some(size.height))
|
||||
.await?;
|
||||
|
||||
self.push_inner(frame, size, inner_size);
|
||||
self.inner.draw(frame).await?;
|
||||
frame.pop();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
721
src/widgets/join.rs
Normal file
721
src/widgets/join.rs
Normal file
|
|
@ -0,0 +1,721 @@
|
|||
use std::cmp::Ordering;
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::{AsyncWidget, Frame, Pos, Size, Widget, WidthDb};
|
||||
|
||||
// The following algorithm has three goals, listed in order of importance:
|
||||
//
|
||||
// 1. Use the available space
|
||||
// 2. Avoid shrinking segments where possible
|
||||
// 3. Match the given weights as closely as possible
|
||||
//
|
||||
// Its input is a list of weighted segments where each segment wants to use a
|
||||
// certain amount of space. The weights signify how the available space would be
|
||||
// assigned if goal 2 was irrelevant.
|
||||
//
|
||||
// First, the algorithm must decide whether it must grow or shrink segments.
|
||||
// Because goal 2 has a higher priority than goal 3, it never makes sense to
|
||||
// shrink a segment in order to make another larger. In both cases, a segment's
|
||||
// actual size is compared to its allotment, i. e. what size it should be based
|
||||
// on its weight.
|
||||
//
|
||||
// Growth
|
||||
// ======
|
||||
//
|
||||
// If segments must be grown, an important observation can be made: If all
|
||||
// segments are smaller than their allotment, then each segment can be assigned
|
||||
// its allotment without violating goal 2, thereby fulfilling goal 3.
|
||||
//
|
||||
// Another important observation can be made: If a segment is at least as large
|
||||
// as its allotment, it must never be grown as that would violate goal 3.
|
||||
//
|
||||
// Based on these two observations, the growth algorithm first repeatedly
|
||||
// removes all segments that are at least as large as their allotment. It then
|
||||
// resizes the remaining segments to their allotments.
|
||||
//
|
||||
// Shrinkage
|
||||
// =========
|
||||
//
|
||||
// If segments must be shrunk, an important observation can be made: If all
|
||||
// segments are larger than their allotment, then each segment can be assigned
|
||||
// its allotment, thereby fulfilling goal 3. Since goal 1 is more important than
|
||||
// goal 2, we know that some elements must be shrunk.
|
||||
//
|
||||
// Another important observation can be made: If a segment is at least as small
|
||||
// as its allotment, it must never be shrunk as that would violate goal 3.
|
||||
//
|
||||
// Based on these two observations, the shrinkage algorithm first repeatedly
|
||||
// removes all segments that are at least as small as their allotment. It then
|
||||
// resizes the remaining segments to their allotments.
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Segment {
|
||||
major: u16,
|
||||
minor: u16,
|
||||
weight: f32,
|
||||
growing: bool,
|
||||
shrinking: bool,
|
||||
}
|
||||
|
||||
impl Segment {
|
||||
fn new<I>(major_minor: (u16, u16), segment: &JoinSegment<I>) -> Self {
|
||||
Self {
|
||||
major: major_minor.0,
|
||||
minor: major_minor.1,
|
||||
weight: segment.weight,
|
||||
growing: segment.growing,
|
||||
shrinking: segment.shrinking,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn total_size(segments: &[&mut Segment]) -> u16 {
|
||||
let mut total = 0_u16;
|
||||
for segment in segments {
|
||||
total = total.saturating_add(segment.major);
|
||||
}
|
||||
total
|
||||
}
|
||||
|
||||
fn total_weight(segments: &[&mut Segment]) -> f32 {
|
||||
segments.iter().map(|s| s.weight).sum()
|
||||
}
|
||||
|
||||
fn balance(segments: &mut [Segment], available: u16) {
|
||||
let segments = segments.iter_mut().collect::<Vec<_>>();
|
||||
match total_size(&segments).cmp(&available) {
|
||||
Ordering::Less => grow(segments, available),
|
||||
Ordering::Greater => shrink(segments, available),
|
||||
Ordering::Equal => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn grow(mut segments: Vec<&mut Segment>, mut available: u16) {
|
||||
assert!(available >= total_size(&segments));
|
||||
|
||||
// Only grow segments that can be grown.
|
||||
segments.retain(|s| {
|
||||
if s.growing {
|
||||
return true;
|
||||
}
|
||||
available = available.saturating_sub(s.major);
|
||||
false
|
||||
});
|
||||
|
||||
// Repeatedly remove all segments that do not need to grow, i. e. that are
|
||||
// at least as large as their allotment.
|
||||
loop {
|
||||
let mut total_weight = total_weight(&segments);
|
||||
|
||||
// If there are no segments with a weight > 0, space is distributed
|
||||
// evenly among all remaining segments.
|
||||
if total_weight <= 0.0 {
|
||||
for segment in &mut segments {
|
||||
segment.weight = 1.0;
|
||||
}
|
||||
total_weight = segments.len() as f32;
|
||||
}
|
||||
|
||||
let mut removed = 0;
|
||||
segments.retain(|s| {
|
||||
let allotment = s.weight / total_weight * available as f32;
|
||||
if (s.major as f32) < allotment {
|
||||
return true; // May need to grow
|
||||
}
|
||||
removed += s.major;
|
||||
false
|
||||
});
|
||||
available -= removed;
|
||||
|
||||
if removed == 0 {
|
||||
break; // All remaining segments are smaller than their allotments
|
||||
}
|
||||
}
|
||||
|
||||
let total_weight = segments.iter().map(|s| s.weight).sum::<f32>();
|
||||
if total_weight <= 0.0 {
|
||||
return; // No more segments left
|
||||
}
|
||||
|
||||
// Size each remaining segment according to its allotment.
|
||||
let mut used = 0;
|
||||
for segment in &mut segments {
|
||||
let allotment = segment.weight / total_weight * available as f32;
|
||||
segment.major = allotment.floor() as u16;
|
||||
used += segment.major;
|
||||
}
|
||||
|
||||
// Distribute remaining unused space from left to right.
|
||||
//
|
||||
// The rounding error on each segment is at most 1, so we only need to loop
|
||||
// over the segments once.
|
||||
let remaining = available - used;
|
||||
assert!(remaining as usize <= segments.len());
|
||||
for segment in segments.into_iter().take(remaining.into()) {
|
||||
segment.major += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn shrink(mut segments: Vec<&mut Segment>, mut available: u16) {
|
||||
assert!(available <= total_size(&segments));
|
||||
|
||||
// Only shrink segments that can be shrunk.
|
||||
segments.retain(|s| {
|
||||
if s.shrinking {
|
||||
return true;
|
||||
}
|
||||
available = available.saturating_sub(s.major);
|
||||
false
|
||||
});
|
||||
|
||||
// Repeatedly remove all segments that do not need to shrink, i. e. that are
|
||||
// at least as small as their allotment.
|
||||
loop {
|
||||
let mut total_weight = total_weight(&segments);
|
||||
|
||||
// If there are no segments with a weight > 0, space is distributed
|
||||
// evenly among all remaining segments.
|
||||
if total_weight <= 0.0 {
|
||||
for segment in &mut segments {
|
||||
segment.weight = 1.0;
|
||||
}
|
||||
total_weight = segments.len() as f32;
|
||||
}
|
||||
|
||||
let mut removed = 0;
|
||||
segments.retain(|s| {
|
||||
let allotment = s.weight / total_weight * available as f32;
|
||||
if (s.major as f32) > allotment {
|
||||
return true; // May need to shrink
|
||||
}
|
||||
|
||||
// The segment size subtracted from `available` is always smaller
|
||||
// than or equal to its allotment. Since `available` is the sum of
|
||||
// all allotments, it can never go below 0.
|
||||
assert!(s.major <= available);
|
||||
|
||||
removed += s.major;
|
||||
false
|
||||
});
|
||||
available -= removed;
|
||||
|
||||
if removed == 0 {
|
||||
break; // All segments want more than their weight allows.
|
||||
}
|
||||
}
|
||||
|
||||
let total_weight = segments.iter().map(|s| s.weight).sum::<f32>();
|
||||
if total_weight <= 0.0 {
|
||||
return; // No more segments left
|
||||
}
|
||||
|
||||
// Size each remaining segment according to its allotment.
|
||||
let mut used = 0;
|
||||
for segment in &mut segments {
|
||||
let allotment = segment.weight / total_weight * available as f32;
|
||||
segment.major = allotment.floor() as u16;
|
||||
used += segment.major;
|
||||
}
|
||||
|
||||
// Distribute remaining unused space from left to right.
|
||||
//
|
||||
// The rounding error on each segment is at most 1, so we only need to loop
|
||||
// over the segments once.
|
||||
let remaining = available - used;
|
||||
assert!(remaining as usize <= segments.len());
|
||||
for segment in segments.into_iter().take(remaining.into()) {
|
||||
segment.major += 1;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct JoinSegment<I> {
|
||||
pub inner: I,
|
||||
weight: f32,
|
||||
pub growing: bool,
|
||||
pub shrinking: bool,
|
||||
}
|
||||
|
||||
impl<I> JoinSegment<I> {
|
||||
pub fn new(inner: I) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
weight: 1.0,
|
||||
growing: true,
|
||||
shrinking: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn weight(&self) -> f32 {
|
||||
self.weight
|
||||
}
|
||||
|
||||
pub fn set_weight(&mut self, weight: f32) {
|
||||
assert!(weight >= 0.0);
|
||||
self.weight = weight;
|
||||
}
|
||||
|
||||
pub fn with_weight(mut self, weight: f32) -> Self {
|
||||
self.set_weight(weight);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_growing(mut self, enabled: bool) -> Self {
|
||||
self.growing = enabled;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_shrinking(mut self, enabled: bool) -> Self {
|
||||
self.shrinking = enabled;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_fixed(self, fixed: bool) -> Self {
|
||||
self.with_growing(!fixed).with_shrinking(!fixed)
|
||||
}
|
||||
}
|
||||
|
||||
fn to_mm<T>(horizontal: bool, w: T, h: T) -> (T, T) {
|
||||
if horizontal {
|
||||
(w, h)
|
||||
} else {
|
||||
(h, w)
|
||||
}
|
||||
}
|
||||
|
||||
fn from_mm<T>(horizontal: bool, major: T, minor: T) -> (T, T) {
|
||||
if horizontal {
|
||||
(major, minor)
|
||||
} else {
|
||||
(minor, major)
|
||||
}
|
||||
}
|
||||
|
||||
fn size<E, I: Widget<E>>(
|
||||
horizontal: bool,
|
||||
widthdb: &mut WidthDb,
|
||||
segment: &JoinSegment<I>,
|
||||
major: Option<u16>,
|
||||
minor: Option<u16>,
|
||||
) -> Result<(u16, u16), E> {
|
||||
if horizontal {
|
||||
let size = segment.inner.size(widthdb, major, minor)?;
|
||||
Ok((size.width, size.height))
|
||||
} else {
|
||||
let size = segment.inner.size(widthdb, minor, major)?;
|
||||
Ok((size.height, size.width))
|
||||
}
|
||||
}
|
||||
|
||||
fn size_with_balanced<E, I: Widget<E>>(
|
||||
horizontal: bool,
|
||||
widthdb: &mut WidthDb,
|
||||
segment: &JoinSegment<I>,
|
||||
balanced: &Segment,
|
||||
minor: Option<u16>,
|
||||
) -> Result<(u16, u16), E> {
|
||||
size(horizontal, widthdb, segment, Some(balanced.major), minor)
|
||||
}
|
||||
|
||||
async fn size_async<E, I: AsyncWidget<E>>(
|
||||
horizontal: bool,
|
||||
widthdb: &mut WidthDb,
|
||||
segment: &JoinSegment<I>,
|
||||
major: Option<u16>,
|
||||
minor: Option<u16>,
|
||||
) -> Result<(u16, u16), E> {
|
||||
if horizontal {
|
||||
let size = segment.inner.size(widthdb, major, minor).await?;
|
||||
Ok((size.width, size.height))
|
||||
} else {
|
||||
let size = segment.inner.size(widthdb, minor, major).await?;
|
||||
Ok((size.height, size.width))
|
||||
}
|
||||
}
|
||||
|
||||
async fn size_async_with_balanced<E, I: AsyncWidget<E>>(
|
||||
horizontal: bool,
|
||||
widthdb: &mut WidthDb,
|
||||
segment: &JoinSegment<I>,
|
||||
balanced: &Segment,
|
||||
minor: Option<u16>,
|
||||
) -> Result<(u16, u16), E> {
|
||||
size_async(horizontal, widthdb, segment, Some(balanced.major), minor).await
|
||||
}
|
||||
|
||||
fn sum_major_max_minor(segments: &[Segment]) -> (u16, u16) {
|
||||
let mut major = 0_u16;
|
||||
let mut minor = 0_u16;
|
||||
for segment in segments {
|
||||
major = major.saturating_add(segment.major);
|
||||
minor = minor.max(segment.minor);
|
||||
}
|
||||
(major, minor)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Join<I> {
|
||||
horizontal: bool,
|
||||
segments: Vec<JoinSegment<I>>,
|
||||
}
|
||||
|
||||
impl<I> Join<I> {
|
||||
pub fn horizontal(segments: Vec<JoinSegment<I>>) -> Self {
|
||||
Self {
|
||||
horizontal: true,
|
||||
segments,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vertical(segments: Vec<JoinSegment<I>>) -> Self {
|
||||
Self {
|
||||
horizontal: false,
|
||||
segments,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, I> Widget<E> for Join<I>
|
||||
where
|
||||
I: Widget<E>,
|
||||
{
|
||||
fn size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
let (max_major, max_minor) = to_mm(self.horizontal, max_width, max_height);
|
||||
|
||||
let mut segments = Vec::with_capacity(self.segments.len());
|
||||
for segment in &self.segments {
|
||||
let major_minor = size(self.horizontal, widthdb, segment, None, max_minor)?;
|
||||
segments.push(Segment::new(major_minor, segment));
|
||||
}
|
||||
|
||||
if let Some(available) = max_major {
|
||||
balance(&mut segments, available);
|
||||
|
||||
let mut new_segments = Vec::with_capacity(self.segments.len());
|
||||
for (segment, balanced) in self.segments.iter().zip(segments.into_iter()) {
|
||||
let major_minor =
|
||||
size_with_balanced(self.horizontal, widthdb, segment, &balanced, max_minor)?;
|
||||
new_segments.push(Segment::new(major_minor, segment));
|
||||
}
|
||||
segments = new_segments;
|
||||
}
|
||||
|
||||
let (major, minor) = sum_major_max_minor(&segments);
|
||||
let (width, height) = from_mm(self.horizontal, major, minor);
|
||||
Ok(Size::new(width, height))
|
||||
}
|
||||
|
||||
fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
let frame_size = frame.size();
|
||||
let (max_major, max_minor) = to_mm(self.horizontal, frame_size.width, frame_size.height);
|
||||
|
||||
let widthdb = frame.widthdb();
|
||||
let mut segments = Vec::with_capacity(self.segments.len());
|
||||
for segment in &self.segments {
|
||||
let major_minor = size(self.horizontal, widthdb, segment, None, Some(max_minor))?;
|
||||
segments.push(Segment::new(major_minor, segment));
|
||||
}
|
||||
balance(&mut segments, max_major);
|
||||
|
||||
let mut major = 0_i32;
|
||||
for (segment, balanced) in self.segments.into_iter().zip(segments.into_iter()) {
|
||||
let (x, y) = from_mm(self.horizontal, major, 0);
|
||||
let (w, h) = from_mm(self.horizontal, balanced.major, max_minor);
|
||||
frame.push(Pos::new(x, y), Size::new(w, h));
|
||||
segment.inner.draw(frame)?;
|
||||
frame.pop();
|
||||
major += balanced.major as i32;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<E, I> AsyncWidget<E> for Join<I>
|
||||
where
|
||||
I: AsyncWidget<E> + Send + Sync,
|
||||
{
|
||||
async fn size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
let (max_major, max_minor) = to_mm(self.horizontal, max_width, max_height);
|
||||
|
||||
let mut segments = Vec::with_capacity(self.segments.len());
|
||||
for segment in &self.segments {
|
||||
let major_minor =
|
||||
size_async(self.horizontal, widthdb, segment, None, max_minor).await?;
|
||||
segments.push(Segment::new(major_minor, segment));
|
||||
}
|
||||
|
||||
if let Some(available) = max_major {
|
||||
balance(&mut segments, available);
|
||||
|
||||
let mut new_segments = Vec::with_capacity(self.segments.len());
|
||||
for (segment, balanced) in self.segments.iter().zip(segments.into_iter()) {
|
||||
let major_minor = size_async_with_balanced(
|
||||
self.horizontal,
|
||||
widthdb,
|
||||
segment,
|
||||
&balanced,
|
||||
max_minor,
|
||||
)
|
||||
.await?;
|
||||
new_segments.push(Segment::new(major_minor, segment));
|
||||
}
|
||||
segments = new_segments;
|
||||
}
|
||||
|
||||
let (major, minor) = sum_major_max_minor(&segments);
|
||||
let (width, height) = from_mm(self.horizontal, major, minor);
|
||||
Ok(Size::new(width, height))
|
||||
}
|
||||
|
||||
async fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
let frame_size = frame.size();
|
||||
let (max_major, max_minor) = to_mm(self.horizontal, frame_size.width, frame_size.height);
|
||||
|
||||
let widthdb = frame.widthdb();
|
||||
let mut segments = Vec::with_capacity(self.segments.len());
|
||||
for segment in &self.segments {
|
||||
let major_minor =
|
||||
size_async(self.horizontal, widthdb, segment, None, Some(max_minor)).await?;
|
||||
segments.push(Segment::new(major_minor, segment));
|
||||
}
|
||||
balance(&mut segments, max_major);
|
||||
|
||||
let mut major = 0_i32;
|
||||
for (segment, balanced) in self.segments.into_iter().zip(segments.into_iter()) {
|
||||
let (x, y) = from_mm(self.horizontal, major, 0);
|
||||
let (w, h) = from_mm(self.horizontal, balanced.major, max_minor);
|
||||
frame.push(Pos::new(x, y), Size::new(w, h));
|
||||
segment.inner.draw(frame).await?;
|
||||
frame.pop();
|
||||
major += balanced.major as i32;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! mk_join {
|
||||
(
|
||||
pub struct $name:ident {
|
||||
$( pub $arg:ident: $type:ident [$n:expr], )+
|
||||
}
|
||||
) => {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct $name< $($type),+ >{
|
||||
horizontal: bool,
|
||||
$( pub $arg: JoinSegment<$type>, )+
|
||||
}
|
||||
|
||||
impl< $($type),+ > $name< $($type),+ >{
|
||||
pub fn horizontal( $($arg: JoinSegment<$type>),+ ) -> Self {
|
||||
Self { horizontal: true, $( $arg, )+ }
|
||||
}
|
||||
|
||||
pub fn vertical( $($arg: JoinSegment<$type>),+ ) -> Self {
|
||||
Self { horizontal: false, $( $arg, )+ }
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, $($type),+ > Widget<E> for $name< $($type),+ >
|
||||
where
|
||||
$( $type: Widget<E>, )+
|
||||
{
|
||||
fn size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
let (max_major, max_minor) = to_mm(self.horizontal, max_width, max_height);
|
||||
|
||||
let mut segments = [ $(
|
||||
Segment::new(
|
||||
size(self.horizontal, widthdb, &self.$arg, None, max_minor)?,
|
||||
&self.$arg,
|
||||
),
|
||||
)+ ];
|
||||
|
||||
if let Some(available) = max_major {
|
||||
balance(&mut segments, available);
|
||||
|
||||
let new_segments = [ $(
|
||||
Segment::new(
|
||||
size_with_balanced(self.horizontal, widthdb, &self.$arg, &segments[$n], max_minor)?,
|
||||
&self.$arg,
|
||||
),
|
||||
)+ ];
|
||||
segments = new_segments;
|
||||
}
|
||||
|
||||
let (major, minor) = sum_major_max_minor(&segments);
|
||||
let (width, height) = from_mm(self.horizontal, major, minor);
|
||||
Ok(Size::new(width, height))
|
||||
}
|
||||
|
||||
#[allow(unused_assignments)]
|
||||
fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
let frame_size = frame.size();
|
||||
let (max_major, max_minor) = to_mm(self.horizontal, frame_size.width, frame_size.height);
|
||||
|
||||
let widthdb = frame.widthdb();
|
||||
let mut segments = [ $(
|
||||
Segment::new(
|
||||
size(self.horizontal, widthdb, &self.$arg, None, Some(max_minor))?,
|
||||
&self.$arg,
|
||||
),
|
||||
)+ ];
|
||||
balance(&mut segments, max_major);
|
||||
|
||||
let mut major = 0_i32;
|
||||
$( {
|
||||
let balanced = &segments[$n];
|
||||
let (x, y) = from_mm(self.horizontal, major, 0);
|
||||
let (w, h) = from_mm(self.horizontal, balanced.major, max_minor);
|
||||
frame.push(Pos::new(x, y), Size::new(w, h));
|
||||
self.$arg.inner.draw(frame)?;
|
||||
frame.pop();
|
||||
major += balanced.major as i32;
|
||||
} )*
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<E, $($type),+ > AsyncWidget<E> for $name< $($type),+ >
|
||||
where
|
||||
E: Send,
|
||||
$( $type: AsyncWidget<E> + Send + Sync, )+
|
||||
{
|
||||
async fn size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
let (max_major, max_minor) = to_mm(self.horizontal, max_width, max_height);
|
||||
|
||||
let mut segments = [ $(
|
||||
Segment::new(
|
||||
size_async(self.horizontal, widthdb, &self.$arg, None, max_minor).await?,
|
||||
&self.$arg,
|
||||
),
|
||||
)+ ];
|
||||
|
||||
if let Some(available) = max_major {
|
||||
balance(&mut segments, available);
|
||||
|
||||
let new_segments = [ $(
|
||||
Segment::new(
|
||||
size_async_with_balanced(self.horizontal, widthdb, &self.$arg, &segments[$n], max_minor).await?,
|
||||
&self.$arg,
|
||||
),
|
||||
)+ ];
|
||||
segments = new_segments;
|
||||
}
|
||||
|
||||
let (major, minor) = sum_major_max_minor(&segments);
|
||||
let (width, height) = from_mm(self.horizontal, major, minor);
|
||||
Ok(Size::new(width, height))
|
||||
}
|
||||
|
||||
#[allow(unused_assignments)]
|
||||
async fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
let frame_size = frame.size();
|
||||
let (max_major, max_minor) = to_mm(self.horizontal, frame_size.width, frame_size.height);
|
||||
|
||||
let widthdb = frame.widthdb();
|
||||
let mut segments = [ $(
|
||||
Segment::new(
|
||||
size_async(self.horizontal, widthdb, &self.$arg, None, Some(max_minor)).await?,
|
||||
&self.$arg,
|
||||
),
|
||||
)+ ];
|
||||
balance(&mut segments, max_major);
|
||||
|
||||
let mut major = 0_i32;
|
||||
$( {
|
||||
let balanced = &segments[$n];
|
||||
let (x, y) = from_mm(self.horizontal, major, 0);
|
||||
let (w, h) = from_mm(self.horizontal, balanced.major, max_minor);
|
||||
frame.push(Pos::new(x, y), Size::new(w, h));
|
||||
self.$arg.inner.draw(frame).await?;
|
||||
frame.pop();
|
||||
major += balanced.major as i32;
|
||||
} )*
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
mk_join! {
|
||||
pub struct Join2 {
|
||||
pub first: I1 [0],
|
||||
pub second: I2 [1],
|
||||
}
|
||||
}
|
||||
|
||||
mk_join! {
|
||||
pub struct Join3 {
|
||||
pub first: I1 [0],
|
||||
pub second: I2 [1],
|
||||
pub third: I3 [2],
|
||||
}
|
||||
}
|
||||
|
||||
mk_join! {
|
||||
pub struct Join4 {
|
||||
pub first: I1 [0],
|
||||
pub second: I2 [1],
|
||||
pub third: I3 [2],
|
||||
pub fourth: I4 [3],
|
||||
}
|
||||
}
|
||||
|
||||
mk_join! {
|
||||
pub struct Join5 {
|
||||
pub first: I1 [0],
|
||||
pub second: I2 [1],
|
||||
pub third: I3 [2],
|
||||
pub fourth: I4 [3],
|
||||
pub fifth: I5 [4],
|
||||
}
|
||||
}
|
||||
|
||||
mk_join! {
|
||||
pub struct Join6 {
|
||||
pub first: I1 [0],
|
||||
pub second: I2 [1],
|
||||
pub third: I3 [2],
|
||||
pub fourth: I4 [3],
|
||||
pub fifth: I5 [4],
|
||||
pub sixth: I6 [5],
|
||||
}
|
||||
}
|
||||
|
||||
mk_join! {
|
||||
pub struct Join7 {
|
||||
pub first: I1 [0],
|
||||
pub second: I2 [1],
|
||||
pub third: I3 [2],
|
||||
pub fourth: I4 [3],
|
||||
pub fifth: I5 [4],
|
||||
pub sixth: I6 [5],
|
||||
pub seventh: I7 [6],
|
||||
}
|
||||
}
|
||||
201
src/widgets/layer.rs
Normal file
201
src/widgets/layer.rs
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
use async_trait::async_trait;
|
||||
|
||||
use crate::{AsyncWidget, Frame, Size, Widget, WidthDb};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Layer<I> {
|
||||
layers: Vec<I>,
|
||||
}
|
||||
|
||||
impl<I> Layer<I> {
|
||||
pub fn new(layers: Vec<I>) -> Self {
|
||||
Self { layers }
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, I> Widget<E> for Layer<I>
|
||||
where
|
||||
I: Widget<E>,
|
||||
{
|
||||
fn size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
let mut size = Size::ZERO;
|
||||
for layer in &self.layers {
|
||||
let lsize = layer.size(widthdb, max_width, max_height)?;
|
||||
size.width = size.width.max(lsize.width);
|
||||
size.height = size.height.max(lsize.height);
|
||||
}
|
||||
Ok(size)
|
||||
}
|
||||
|
||||
fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
for layer in self.layers {
|
||||
layer.draw(frame)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<E, I> AsyncWidget<E> for Layer<I>
|
||||
where
|
||||
I: AsyncWidget<E> + Send + Sync,
|
||||
{
|
||||
async fn size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
let mut size = Size::ZERO;
|
||||
for layer in &self.layers {
|
||||
let lsize = layer.size(widthdb, max_width, max_height).await?;
|
||||
size.width = size.width.max(lsize.width);
|
||||
size.height = size.height.max(lsize.height);
|
||||
}
|
||||
Ok(size)
|
||||
}
|
||||
|
||||
async fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
for layer in self.layers {
|
||||
layer.draw(frame).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! mk_layer {
|
||||
(
|
||||
pub struct $name:ident {
|
||||
$( pub $arg:ident: $type:ident, )+
|
||||
}
|
||||
) => {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct $name< $($type),+ >{
|
||||
$( pub $arg: $type, )+
|
||||
}
|
||||
|
||||
impl< $($type),+ > $name< $($type),+ >{
|
||||
pub fn new( $($arg: $type),+ ) -> Self {
|
||||
Self { $( $arg, )+ }
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, $($type),+ > Widget<E> for $name< $($type),+ >
|
||||
where
|
||||
$( $type: Widget<E>, )+
|
||||
{
|
||||
fn size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
let mut size = Size::ZERO;
|
||||
|
||||
$({
|
||||
let lsize = self.$arg.size(widthdb, max_width, max_height)?;
|
||||
size.width = size.width.max(lsize.width);
|
||||
size.height = size.height.max(lsize.height);
|
||||
})+
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
|
||||
fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
$( self.$arg.draw(frame)?; )+
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<E, $($type),+ > AsyncWidget<E> for $name< $($type),+ >
|
||||
where
|
||||
E: Send,
|
||||
$( $type: AsyncWidget<E> + Send + Sync, )+
|
||||
{
|
||||
async fn size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
let mut size = Size::ZERO;
|
||||
|
||||
$({
|
||||
let lsize = self.$arg.size(widthdb, max_width, max_height).await?;
|
||||
size.width = size.width.max(lsize.width);
|
||||
size.height = size.height.max(lsize.height);
|
||||
})+
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
|
||||
async fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
$( self.$arg.draw(frame).await?; )+
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
mk_layer!(
|
||||
pub struct Layer2 {
|
||||
pub first: I1,
|
||||
pub second: I2,
|
||||
}
|
||||
);
|
||||
|
||||
mk_layer!(
|
||||
pub struct Layer3 {
|
||||
pub first: I1,
|
||||
pub second: I2,
|
||||
pub third: I3,
|
||||
}
|
||||
);
|
||||
|
||||
mk_layer!(
|
||||
pub struct Layer4 {
|
||||
pub first: I1,
|
||||
pub second: I2,
|
||||
pub third: I3,
|
||||
pub fourth: I4,
|
||||
}
|
||||
);
|
||||
|
||||
mk_layer!(
|
||||
pub struct Layer5 {
|
||||
pub first: I1,
|
||||
pub second: I2,
|
||||
pub third: I3,
|
||||
pub fourth: I4,
|
||||
pub fifth: I5,
|
||||
}
|
||||
);
|
||||
|
||||
mk_layer!(
|
||||
pub struct Layer6 {
|
||||
pub first: I1,
|
||||
pub second: I2,
|
||||
pub third: I3,
|
||||
pub fourth: I4,
|
||||
pub fifth: I5,
|
||||
pub sixth: I6,
|
||||
}
|
||||
);
|
||||
|
||||
mk_layer!(
|
||||
pub struct Layer7 {
|
||||
pub first: I1,
|
||||
pub second: I2,
|
||||
pub third: I3,
|
||||
pub fourth: I4,
|
||||
pub fifth: I5,
|
||||
pub sixth: I6,
|
||||
pub seventh: I7,
|
||||
}
|
||||
);
|
||||
133
src/widgets/padding.rs
Normal file
133
src/widgets/padding.rs
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
use async_trait::async_trait;
|
||||
|
||||
use crate::{AsyncWidget, Frame, Pos, Size, Widget, WidthDb};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Padding<I> {
|
||||
pub inner: I,
|
||||
pub left: u16,
|
||||
pub right: u16,
|
||||
pub top: u16,
|
||||
pub bottom: u16,
|
||||
pub stretch: bool,
|
||||
}
|
||||
|
||||
impl<I> Padding<I> {
|
||||
pub fn new(inner: I) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
stretch: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_left(mut self, amount: u16) -> Self {
|
||||
self.left = amount;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_right(mut self, amount: u16) -> Self {
|
||||
self.right = amount;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_top(mut self, amount: u16) -> Self {
|
||||
self.top = amount;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_bottom(mut self, amount: u16) -> Self {
|
||||
self.bottom = amount;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_horizontal(self, amount: u16) -> Self {
|
||||
self.with_left(amount).with_right(amount)
|
||||
}
|
||||
|
||||
pub fn with_vertical(self, amount: u16) -> Self {
|
||||
self.with_top(amount).with_bottom(amount)
|
||||
}
|
||||
|
||||
pub fn with_all(self, amount: u16) -> Self {
|
||||
self.with_horizontal(amount).with_vertical(amount)
|
||||
}
|
||||
|
||||
pub fn with_stretch(mut self, stretch: bool) -> Self {
|
||||
self.stretch = stretch;
|
||||
self
|
||||
}
|
||||
|
||||
fn pad_size(&self) -> Size {
|
||||
Size::new(self.left + self.right, self.top + self.bottom)
|
||||
}
|
||||
|
||||
fn push_inner(&self, frame: &mut Frame) {
|
||||
let size = frame.size();
|
||||
let pad_size = self.pad_size();
|
||||
let inner_size = size.saturating_sub(pad_size);
|
||||
frame.push(Pos::new(self.left.into(), self.top.into()), inner_size);
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, I> Widget<E> for Padding<I>
|
||||
where
|
||||
I: Widget<E>,
|
||||
{
|
||||
fn size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
let pad_size = self.pad_size();
|
||||
let max_width = max_width.map(|w| w.saturating_sub(pad_size.width));
|
||||
let max_height = max_height.map(|h| h.saturating_sub(pad_size.height));
|
||||
let size = self.inner.size(widthdb, max_width, max_height)?;
|
||||
Ok(size + pad_size)
|
||||
}
|
||||
|
||||
fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
if self.stretch {
|
||||
self.inner.draw(frame)?;
|
||||
} else {
|
||||
self.push_inner(frame);
|
||||
self.inner.draw(frame)?;
|
||||
frame.pop();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<E, I> AsyncWidget<E> for Padding<I>
|
||||
where
|
||||
I: AsyncWidget<E> + Send + Sync,
|
||||
{
|
||||
async fn size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
let pad_size = self.pad_size();
|
||||
let max_width = max_width.map(|w| w.saturating_sub(pad_size.width));
|
||||
let max_height = max_height.map(|h| h.saturating_sub(pad_size.height));
|
||||
let size = self.inner.size(widthdb, max_width, max_height).await?;
|
||||
Ok(size + pad_size)
|
||||
}
|
||||
|
||||
async fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
if self.stretch {
|
||||
self.inner.draw(frame).await?;
|
||||
} else {
|
||||
self.push_inner(frame);
|
||||
self.inner.draw(frame).await?;
|
||||
frame.pop();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
74
src/widgets/predrawn.rs
Normal file
74
src/widgets/predrawn.rs
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
use std::mem;
|
||||
|
||||
use crate::buffer::Buffer;
|
||||
use crate::{AsyncWidget, Frame, Pos, Size, Style, Styled, Widget, WidthDb};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Predrawn {
|
||||
buffer: Buffer,
|
||||
}
|
||||
|
||||
impl Predrawn {
|
||||
pub fn new<E, W: Widget<E>>(inner: W, widthdb: &mut WidthDb) -> Result<Self, E> {
|
||||
let mut tmp_frame = Frame::default();
|
||||
|
||||
let size = inner.size(widthdb, None, None)?;
|
||||
tmp_frame.buffer.resize(size);
|
||||
|
||||
mem::swap(widthdb, &mut tmp_frame.widthdb);
|
||||
inner.draw(&mut tmp_frame)?;
|
||||
mem::swap(widthdb, &mut tmp_frame.widthdb);
|
||||
|
||||
let buffer = tmp_frame.buffer;
|
||||
Ok(Self { buffer })
|
||||
}
|
||||
|
||||
pub async fn new_async<E, W: AsyncWidget<E>>(
|
||||
inner: W,
|
||||
widthdb: &mut WidthDb,
|
||||
) -> Result<Self, E> {
|
||||
let mut tmp_frame = Frame::default();
|
||||
|
||||
let size = inner.size(widthdb, None, None).await?;
|
||||
tmp_frame.buffer.resize(size);
|
||||
|
||||
mem::swap(widthdb, &mut tmp_frame.widthdb);
|
||||
inner.draw(&mut tmp_frame).await?;
|
||||
mem::swap(widthdb, &mut tmp_frame.widthdb);
|
||||
|
||||
let buffer = tmp_frame.buffer;
|
||||
Ok(Self { buffer })
|
||||
}
|
||||
|
||||
pub fn size(&self) -> Size {
|
||||
self.buffer.size()
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Widget<E> for Predrawn {
|
||||
fn size(
|
||||
&self,
|
||||
_widthdb: &mut WidthDb,
|
||||
_max_width: Option<u16>,
|
||||
_max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
Ok(self.buffer.size())
|
||||
}
|
||||
|
||||
fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
for (x, y, cell) in self.buffer.cells() {
|
||||
let pos = Pos::new(x.into(), y.into());
|
||||
let style = Style {
|
||||
content_style: cell.style,
|
||||
opaque: true,
|
||||
};
|
||||
frame.write(pos, Styled::new(&cell.content, style));
|
||||
}
|
||||
|
||||
if let Some(cursor) = self.buffer.cursor() {
|
||||
frame.set_cursor(Some(cursor));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
120
src/widgets/resize.rs
Normal file
120
src/widgets/resize.rs
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
use async_trait::async_trait;
|
||||
|
||||
use crate::{AsyncWidget, Frame, Size, Widget, WidthDb};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Resize<I> {
|
||||
pub inner: I,
|
||||
pub min_width: Option<u16>,
|
||||
pub min_height: Option<u16>,
|
||||
pub max_width: Option<u16>,
|
||||
pub max_height: Option<u16>,
|
||||
}
|
||||
|
||||
impl<I> Resize<I> {
|
||||
pub fn new(inner: I) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
min_width: None,
|
||||
min_height: None,
|
||||
max_width: None,
|
||||
max_height: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_min_width(mut self, width: u16) -> Self {
|
||||
self.min_width = Some(width);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_min_height(mut self, height: u16) -> Self {
|
||||
self.min_height = Some(height);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_max_width(mut self, width: u16) -> Self {
|
||||
self.max_width = Some(width);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_max_height(mut self, height: u16) -> Self {
|
||||
self.max_height = Some(height);
|
||||
self
|
||||
}
|
||||
|
||||
fn presize(
|
||||
&self,
|
||||
mut width: Option<u16>,
|
||||
mut height: Option<u16>,
|
||||
) -> (Option<u16>, Option<u16>) {
|
||||
if let Some(mw) = self.max_width {
|
||||
width = Some(width.unwrap_or(mw).min(mw));
|
||||
}
|
||||
if let Some(mh) = self.max_height {
|
||||
height = Some(height.unwrap_or(mh).max(mh));
|
||||
}
|
||||
(width, height)
|
||||
}
|
||||
|
||||
fn resize(&self, size: Size) -> Size {
|
||||
let mut width = size.width;
|
||||
let mut height = size.height;
|
||||
|
||||
if let Some(min_width) = self.min_width {
|
||||
width = width.max(min_width);
|
||||
}
|
||||
if let Some(min_height) = self.min_height {
|
||||
height = height.max(min_height);
|
||||
}
|
||||
|
||||
if let Some(max_width) = self.max_width {
|
||||
width = width.min(max_width);
|
||||
}
|
||||
if let Some(max_height) = self.max_height {
|
||||
height = height.min(max_height);
|
||||
}
|
||||
|
||||
Size::new(width, height)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, I> Widget<E> for Resize<I>
|
||||
where
|
||||
I: Widget<E>,
|
||||
{
|
||||
fn size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
let (max_width, max_height) = self.presize(max_width, max_height);
|
||||
let size = self.inner.size(widthdb, max_width, max_height)?;
|
||||
Ok(self.resize(size))
|
||||
}
|
||||
|
||||
fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
self.inner.draw(frame)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<E, I> AsyncWidget<E> for Resize<I>
|
||||
where
|
||||
I: AsyncWidget<E> + Send + Sync,
|
||||
{
|
||||
async fn size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
let (max_width, max_height) = self.presize(max_width, max_height);
|
||||
let size = self.inner.size(widthdb, max_width, max_height).await?;
|
||||
Ok(self.resize(size))
|
||||
}
|
||||
|
||||
async fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
self.inner.draw(frame).await
|
||||
}
|
||||
}
|
||||
68
src/widgets/text.rs
Normal file
68
src/widgets/text.rs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
use crate::{Frame, Pos, Size, Styled, Widget, WidthDb};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Text {
|
||||
pub styled: Styled,
|
||||
pub wrap: bool,
|
||||
}
|
||||
|
||||
impl Text {
|
||||
pub fn new<S: Into<Styled>>(styled: S) -> Self {
|
||||
Self {
|
||||
styled: styled.into(),
|
||||
wrap: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_wrap(mut self, active: bool) -> Self {
|
||||
self.wrap = active;
|
||||
self
|
||||
}
|
||||
|
||||
fn wrapped(&self, widthdb: &mut WidthDb, max_width: Option<u16>) -> Vec<Styled> {
|
||||
let max_width = max_width
|
||||
.filter(|_| self.wrap)
|
||||
.map(|w| w as usize)
|
||||
.unwrap_or(usize::MAX);
|
||||
|
||||
let indices = widthdb.wrap(self.styled.text(), max_width);
|
||||
self.styled.clone().split_at_indices(&indices)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Widget<E> for Text {
|
||||
fn size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
_max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
let lines = self.wrapped(widthdb, max_width);
|
||||
|
||||
let min_width = lines
|
||||
.iter()
|
||||
.map(|l| widthdb.width(l.text().trim_end()))
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
let min_height = lines.len();
|
||||
|
||||
let min_width: u16 = min_width.try_into().unwrap_or(u16::MAX);
|
||||
let min_height: u16 = min_height.try_into().unwrap_or(u16::MAX);
|
||||
Ok(Size::new(min_width, min_height))
|
||||
}
|
||||
|
||||
fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
let size = frame.size();
|
||||
|
||||
for (i, line) in self
|
||||
.wrapped(frame.widthdb(), Some(size.width))
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
let i: i32 = i.try_into().unwrap_or(i32::MAX);
|
||||
frame.write(Pos::new(0, i), line);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
59
src/widgets/title.rs
Normal file
59
src/widgets/title.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
use async_trait::async_trait;
|
||||
|
||||
use crate::{AsyncWidget, Frame, Size, Widget, WidthDb};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Title<I> {
|
||||
pub inner: I,
|
||||
pub title: String,
|
||||
}
|
||||
|
||||
impl<I> Title<I> {
|
||||
pub fn new<S: ToString>(inner: I, title: S) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
title: title.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, I> Widget<E> for Title<I>
|
||||
where
|
||||
I: Widget<E>,
|
||||
{
|
||||
fn size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
self.inner.size(widthdb, max_width, max_height)
|
||||
}
|
||||
|
||||
fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
self.inner.draw(frame)?;
|
||||
frame.set_title(Some(self.title));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<E, I> AsyncWidget<E> for Title<I>
|
||||
where
|
||||
I: AsyncWidget<E> + Send + Sync,
|
||||
{
|
||||
async fn size(
|
||||
&self,
|
||||
widthdb: &mut WidthDb,
|
||||
max_width: Option<u16>,
|
||||
max_height: Option<u16>,
|
||||
) -> Result<Size, E> {
|
||||
self.inner.size(widthdb, max_width, max_height).await
|
||||
}
|
||||
|
||||
async fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
self.inner.draw(frame).await?;
|
||||
frame.set_title(Some(self.title));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
132
src/widthdb.rs
132
src/widthdb.rs
|
|
@ -6,51 +6,129 @@ use crossterm::style::Print;
|
|||
use crossterm::terminal::{Clear, ClearType};
|
||||
use crossterm::QueueableCommand;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
||||
|
||||
use crate::wrap;
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum WidthEstimationMethod {
|
||||
/// Estimate the width of a grapheme using legacy methods.
|
||||
///
|
||||
/// Different terminal emulators all use different approaches to determine
|
||||
/// grapheme widths, so this method will never be able to give a fully
|
||||
/// correct solution. For that, the only possible approach is measuring the
|
||||
/// actual grapheme width.
|
||||
#[default]
|
||||
Legacy,
|
||||
|
||||
/// Estimate the width of a grapheme using the unicode standard in a
|
||||
/// best-effort manner.
|
||||
Unicode,
|
||||
}
|
||||
|
||||
/// Measures and stores the with (in terminal coordinates) of graphemes.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct WidthDB {
|
||||
#[derive(Debug)]
|
||||
pub struct WidthDb {
|
||||
pub(crate) estimate: WidthEstimationMethod,
|
||||
pub(crate) measure: bool,
|
||||
pub(crate) tab_width: u8,
|
||||
known: HashMap<String, u8>,
|
||||
requested: HashSet<String>,
|
||||
}
|
||||
|
||||
impl WidthDB {
|
||||
impl Default for WidthDb {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
estimate: WidthEstimationMethod::default(),
|
||||
measure: false,
|
||||
tab_width: 8,
|
||||
known: Default::default(),
|
||||
requested: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WidthDb {
|
||||
/// Determine the width of a tab character starting at the specified column.
|
||||
fn tab_width_at_column(&self, col: usize) -> u8 {
|
||||
self.tab_width - (col % self.tab_width as usize) as u8
|
||||
}
|
||||
|
||||
/// Determine the width of a grapheme.
|
||||
///
|
||||
/// If the width has not been measured yet, it is estimated using the
|
||||
/// Unicode Standard Annex #11.
|
||||
pub fn grapheme_width(&mut self, grapheme: &str) -> u8 {
|
||||
/// If the grapheme is a tab, the column is used to determine its width.
|
||||
///
|
||||
/// If the width has not been measured yet or measurements are turned off,
|
||||
/// it is estimated using the Unicode Standard Annex #11.
|
||||
pub fn grapheme_width(&mut self, grapheme: &str, col: usize) -> u8 {
|
||||
assert_eq!(Some(grapheme), grapheme.graphemes(true).next());
|
||||
if grapheme == "\t" {
|
||||
return self.tab_width_at_column(col);
|
||||
}
|
||||
|
||||
if self.measure {
|
||||
if let Some(width) = self.known.get(grapheme) {
|
||||
*width
|
||||
} else {
|
||||
return *width;
|
||||
}
|
||||
self.requested.insert(grapheme.to_string());
|
||||
grapheme.width() as u8
|
||||
}
|
||||
|
||||
match self.estimate {
|
||||
// A character-wise width calculation is a simple and obvious
|
||||
// approach to compute character widths. The idea is that dumb
|
||||
// terminal emulators tend to do something roughly like this, and
|
||||
// smart terminal emulators try to emulate dumb ones for
|
||||
// compatibility. In practice, this approach seems to be fairly
|
||||
// robust.
|
||||
WidthEstimationMethod::Legacy => grapheme
|
||||
.chars()
|
||||
.filter(|c| !c.is_ascii_control())
|
||||
.flat_map(|c| c.width())
|
||||
.sum::<usize>()
|
||||
.try_into()
|
||||
.unwrap_or(u8::MAX),
|
||||
|
||||
// The unicode width crate considers control chars to have a width
|
||||
// of 1 even though they usually have a width of 0 when displayed.
|
||||
WidthEstimationMethod::Unicode => grapheme
|
||||
.split(|c: char| c.is_ascii_control())
|
||||
.map(|s| s.width())
|
||||
.sum::<usize>()
|
||||
.try_into()
|
||||
.unwrap_or(u8::MAX),
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine the width of a string based on its graphemes.
|
||||
///
|
||||
/// If the width of a grapheme has not been measured yet, it is estimated
|
||||
/// using the Unicode Standard Annex #11.
|
||||
/// If a grapheme is a tab, its column is used to determine its width.
|
||||
///
|
||||
/// If the width of a grapheme has not been measured yet or measurements are
|
||||
/// turned off, it is estimated using the Unicode Standard Annex #11.
|
||||
pub fn width(&mut self, s: &str) -> usize {
|
||||
let mut total: usize = 0;
|
||||
for grapheme in s.graphemes(true) {
|
||||
total += if let Some(width) = self.known.get(grapheme) {
|
||||
(*width).into()
|
||||
} else {
|
||||
self.requested.insert(grapheme.to_string());
|
||||
grapheme.width()
|
||||
};
|
||||
total += self.grapheme_width(grapheme, total) as usize;
|
||||
}
|
||||
total
|
||||
}
|
||||
|
||||
/// Perform primitive word wrapping with the specified maximum width.
|
||||
///
|
||||
/// Returns the byte offsets at which the string should be split into lines.
|
||||
/// An offset of 1 would mean the first line contains only a single byte.
|
||||
/// These offsets lie on grapheme boundaries.
|
||||
///
|
||||
/// This function does not support bidirectional script. It assumes the
|
||||
/// entire text has the same direction.
|
||||
pub fn wrap(&mut self, text: &str, width: usize) -> Vec<usize> {
|
||||
wrap::wrap(self, text, width)
|
||||
}
|
||||
|
||||
/// Whether any new graphemes have been seen since the last time
|
||||
/// [`Self::measure_widths`] was called.
|
||||
pub fn measuring_required(&self) -> bool {
|
||||
!self.requested.is_empty()
|
||||
pub(crate) fn measuring_required(&self) -> bool {
|
||||
self.measure && !self.requested.is_empty()
|
||||
}
|
||||
|
||||
/// Measure the width of all new graphemes that have been seen since the
|
||||
|
|
@ -59,8 +137,20 @@ impl WidthDB {
|
|||
/// This function measures the actual width of graphemes by writing them to
|
||||
/// the terminal. After it finishes, the terminal's contents should be
|
||||
/// assumed to be garbage and a full redraw should be performed.
|
||||
pub fn measure_widths(&mut self, out: &mut impl Write) -> io::Result<()> {
|
||||
pub(crate) fn measure_widths(&mut self, out: &mut impl Write) -> io::Result<()> {
|
||||
if !self.measure {
|
||||
return Ok(());
|
||||
}
|
||||
for grapheme in self.requested.drain() {
|
||||
if grapheme.chars().any(|c| c.is_ascii_control()) {
|
||||
// ASCII control characters like the escape character or the
|
||||
// bell character tend to be interpreted specially by terminals.
|
||||
// This may break width measurements. To avoid this, we just
|
||||
// assign each control character a with of 0.
|
||||
self.known.insert(grapheme, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
out.queue(Clear(ClearType::All))?
|
||||
.queue(MoveTo(0, 0))?
|
||||
.queue(Print(&grapheme))?;
|
||||
|
|
|
|||
84
src/wrap.rs
84
src/wrap.rs
|
|
@ -3,21 +3,21 @@
|
|||
use unicode_linebreak::BreakOpportunity;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use crate::widthdb::WidthDB;
|
||||
use crate::WidthDb;
|
||||
|
||||
// TODO Handle tabs separately?
|
||||
// TODO Convert into an iterator?
|
||||
pub fn wrap(text: &str, width: usize, widthdb: &mut WidthDB) -> Vec<usize> {
|
||||
pub fn wrap(widthdb: &mut WidthDb, text: &str, width: usize) -> Vec<usize> {
|
||||
let mut breaks = vec![];
|
||||
|
||||
let mut break_options = unicode_linebreak::linebreaks(text).peekable();
|
||||
|
||||
// The last valid break point encountered and its width
|
||||
let mut valid_break = None;
|
||||
let mut valid_break_width = 0;
|
||||
|
||||
// Width of the line at the current grapheme
|
||||
// Starting index and width of the line at the current grapheme (with and
|
||||
// without trailing whitespace)
|
||||
let mut current_start = 0;
|
||||
let mut current_width = 0;
|
||||
let mut current_width_trimmed = 0;
|
||||
|
||||
for (gi, g) in text.grapheme_indices(true) {
|
||||
// Advance break options
|
||||
|
|
@ -36,60 +36,56 @@ pub fn wrap(text: &str, width: usize, widthdb: &mut WidthDB) -> Vec<usize> {
|
|||
BreakOpportunity::Mandatory => {
|
||||
breaks.push(bi);
|
||||
valid_break = None;
|
||||
valid_break_width = 0;
|
||||
current_start = bi;
|
||||
current_width = 0;
|
||||
current_width_trimmed = 0;
|
||||
}
|
||||
BreakOpportunity::Allowed => {
|
||||
valid_break = Some(bi);
|
||||
valid_break_width = current_width;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let grapheme_width: usize = widthdb.grapheme_width(g).into();
|
||||
if current_width + grapheme_width > width {
|
||||
if current_width == 0 {
|
||||
// The grapheme is wider than the maximum width, so we'll allow
|
||||
// it, thereby forcing the following grapheme to break no matter
|
||||
// what (either because of a mandatory or allowed break, or via
|
||||
// a forced break).
|
||||
} else if let Some(bi) = valid_break {
|
||||
// We can't fit the grapheme onto the current line, so we'll
|
||||
// just break at the last valid break point.
|
||||
// Calculate widths after current grapheme
|
||||
let g_is_whitespace = g.chars().all(|c| c.is_whitespace());
|
||||
let g_width = widthdb.grapheme_width(g, current_width) as usize;
|
||||
current_width += g_width;
|
||||
if !g_is_whitespace {
|
||||
current_width_trimmed = current_width;
|
||||
}
|
||||
|
||||
// Wrap at last break point if necessary
|
||||
if current_width_trimmed > width {
|
||||
if let Some(bi) = valid_break {
|
||||
let new_line = &text[bi..gi + g.len()];
|
||||
|
||||
breaks.push(bi);
|
||||
current_width -= valid_break_width;
|
||||
valid_break = None;
|
||||
valid_break_width = 0;
|
||||
current_start = bi;
|
||||
current_width = widthdb.width(new_line);
|
||||
current_width_trimmed = widthdb.width(new_line.trim_end());
|
||||
}
|
||||
}
|
||||
|
||||
// Perform a forced break if still necessary
|
||||
if current_width_trimmed > width {
|
||||
if current_start == gi {
|
||||
// The grapheme is the only thing on the current line and it is
|
||||
// wider than the maximum width, so we'll allow it, thereby
|
||||
// forcing the following grapheme to break no matter what
|
||||
// (either because of a mandatory or allowed break, or via a
|
||||
// forced break).
|
||||
} else {
|
||||
// Forced break in the midde of a normally non-breakable chunk
|
||||
// because there have been no valid break points yet.
|
||||
// Forced break in the middle of a normally non-breakable chunk
|
||||
// because there are no valid break points.
|
||||
breaks.push(gi);
|
||||
valid_break = None;
|
||||
valid_break_width = 0;
|
||||
current_width = 0;
|
||||
current_start = gi;
|
||||
current_width = widthdb.grapheme_width(g, 0).into();
|
||||
current_width_trimmed = if g_is_whitespace { 0 } else { current_width };
|
||||
}
|
||||
}
|
||||
|
||||
current_width += grapheme_width;
|
||||
}
|
||||
|
||||
breaks
|
||||
}
|
||||
|
||||
pub fn split_at_indices<'a>(s: &'a str, indices: &[usize]) -> Vec<&'a str> {
|
||||
let mut slices = vec![];
|
||||
|
||||
let mut rest = s;
|
||||
let mut offset = 0;
|
||||
|
||||
for i in indices {
|
||||
let (left, right) = rest.split_at(i - offset);
|
||||
slices.push(left);
|
||||
rest = right;
|
||||
offset = *i;
|
||||
}
|
||||
|
||||
slices.push(rest);
|
||||
|
||||
slices
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
Finished release [optimized] target(s) in 0.02s
|
||||
Running `target/release/examples/measure_widths`
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,82 +0,0 @@
|
|||
Finished release [optimized] target(s) in 0.02s
|
||||
Running `target/release/examples/measure_widths`
|
||||
55216: actual 0, expected 1
|
||||
55217: actual 0, expected 1
|
||||
55218: actual 0, expected 1
|
||||
55219: actual 0, expected 1
|
||||
55220: actual 0, expected 1
|
||||
55221: actual 0, expected 1
|
||||
55222: actual 0, expected 1
|
||||
55223: actual 0, expected 1
|
||||
55224: actual 0, expected 1
|
||||
55225: actual 0, expected 1
|
||||
55226: actual 0, expected 1
|
||||
55227: actual 0, expected 1
|
||||
55228: actual 0, expected 1
|
||||
55229: actual 0, expected 1
|
||||
55230: actual 0, expected 1
|
||||
55231: actual 0, expected 1
|
||||
55232: actual 0, expected 1
|
||||
55233: actual 0, expected 1
|
||||
55234: actual 0, expected 1
|
||||
55235: actual 0, expected 1
|
||||
55236: actual 0, expected 1
|
||||
55237: actual 0, expected 1
|
||||
55238: actual 0, expected 1
|
||||
55239: actual 0, expected 1
|
||||
55240: actual 0, expected 1
|
||||
55241: actual 0, expected 1
|
||||
55242: actual 0, expected 1
|
||||
55243: actual 0, expected 1
|
||||
55244: actual 0, expected 1
|
||||
55245: actual 0, expected 1
|
||||
55246: actual 0, expected 1
|
||||
55247: actual 0, expected 1
|
||||
55248: actual 0, expected 1
|
||||
55249: actual 0, expected 1
|
||||
55250: actual 0, expected 1
|
||||
55251: actual 0, expected 1
|
||||
55252: actual 0, expected 1
|
||||
55253: actual 0, expected 1
|
||||
55254: actual 0, expected 1
|
||||
55255: actual 0, expected 1
|
||||
55256: actual 0, expected 1
|
||||
55257: actual 0, expected 1
|
||||
55258: actual 0, expected 1
|
||||
55259: actual 0, expected 1
|
||||
55260: actual 0, expected 1
|
||||
55261: actual 0, expected 1
|
||||
55262: actual 0, expected 1
|
||||
55263: actual 0, expected 1
|
||||
55264: actual 0, expected 1
|
||||
55265: actual 0, expected 1
|
||||
55266: actual 0, expected 1
|
||||
55267: actual 0, expected 1
|
||||
55268: actual 0, expected 1
|
||||
55269: actual 0, expected 1
|
||||
55270: actual 0, expected 1
|
||||
55271: actual 0, expected 1
|
||||
55272: actual 0, expected 1
|
||||
55273: actual 0, expected 1
|
||||
55274: actual 0, expected 1
|
||||
55275: actual 0, expected 1
|
||||
55276: actual 0, expected 1
|
||||
55277: actual 0, expected 1
|
||||
55278: actual 0, expected 1
|
||||
55279: actual 0, expected 1
|
||||
55280: actual 0, expected 1
|
||||
55281: actual 0, expected 1
|
||||
55282: actual 0, expected 1
|
||||
55283: actual 0, expected 1
|
||||
55284: actual 0, expected 1
|
||||
55285: actual 0, expected 1
|
||||
55286: actual 0, expected 1
|
||||
55287: actual 0, expected 1
|
||||
55288: actual 0, expected 1
|
||||
55289: actual 0, expected 1
|
||||
55290: actual 0, expected 1
|
||||
55291: actual 0, expected 1
|
||||
55292: actual 0, expected 1
|
||||
55293: actual 0, expected 1
|
||||
55294: actual 0, expected 1
|
||||
55295: actual 0, expected 1
|
||||
2896
widths-hyper
2896
widths-hyper
File diff suppressed because it is too large
Load diff
726
widths-kitty
726
widths-kitty
|
|
@ -1,726 +0,0 @@
|
|||
Finished release [optimized] target(s) in 0.02s
|
||||
Running `target/release/examples/measure_widths`
|
||||
71456: actual 0, expected 1
|
||||
71457: actual 0, expected 1
|
||||
71462: actual 0, expected 1
|
||||
64976: actual 0, expected 1
|
||||
64977: actual 0, expected 1
|
||||
64978: actual 0, expected 1
|
||||
64979: actual 0, expected 1
|
||||
64980: actual 0, expected 1
|
||||
64981: actual 0, expected 1
|
||||
64982: actual 0, expected 1
|
||||
64983: actual 0, expected 1
|
||||
64984: actual 0, expected 1
|
||||
64985: actual 0, expected 1
|
||||
64986: actual 0, expected 1
|
||||
64987: actual 0, expected 1
|
||||
64988: actual 0, expected 1
|
||||
64989: actual 0, expected 1
|
||||
64990: actual 0, expected 1
|
||||
64991: actual 0, expected 1
|
||||
64992: actual 0, expected 1
|
||||
64993: actual 0, expected 1
|
||||
64994: actual 0, expected 1
|
||||
64995: actual 0, expected 1
|
||||
64996: actual 0, expected 1
|
||||
64997: actual 0, expected 1
|
||||
64998: actual 0, expected 1
|
||||
64999: actual 0, expected 1
|
||||
65000: actual 0, expected 1
|
||||
65001: actual 0, expected 1
|
||||
65002: actual 0, expected 1
|
||||
65003: actual 0, expected 1
|
||||
65004: actual 0, expected 1
|
||||
65005: actual 0, expected 1
|
||||
65006: actual 0, expected 1
|
||||
65007: actual 0, expected 1
|
||||
6916: actual 0, expected 1
|
||||
6965: actual 0, expected 1
|
||||
6971: actual 0, expected 1
|
||||
6973: actual 0, expected 1
|
||||
6974: actual 0, expected 1
|
||||
6975: actual 0, expected 1
|
||||
6976: actual 0, expected 1
|
||||
6977: actual 0, expected 1
|
||||
6979: actual 0, expected 1
|
||||
6980: actual 0, expected 1
|
||||
7143: actual 0, expected 1
|
||||
7146: actual 0, expected 1
|
||||
7147: actual 0, expected 1
|
||||
7148: actual 0, expected 1
|
||||
7150: actual 0, expected 1
|
||||
7154: actual 0, expected 1
|
||||
7155: actual 0, expected 1
|
||||
2434: actual 0, expected 1
|
||||
2435: actual 0, expected 1
|
||||
2494: actual 0, expected 1
|
||||
2495: actual 0, expected 1
|
||||
2496: actual 0, expected 1
|
||||
2503: actual 0, expected 1
|
||||
2504: actual 0, expected 1
|
||||
2507: actual 0, expected 1
|
||||
2508: actual 0, expected 1
|
||||
2519: actual 0, expected 1
|
||||
72751: actual 0, expected 1
|
||||
72766: actual 0, expected 1
|
||||
69632: actual 0, expected 1
|
||||
69634: actual 0, expected 1
|
||||
6681: actual 0, expected 1
|
||||
6682: actual 0, expected 1
|
||||
69932: actual 0, expected 1
|
||||
69957: actual 0, expected 1
|
||||
69958: actual 0, expected 1
|
||||
43567: actual 0, expected 1
|
||||
43568: actual 0, expected 1
|
||||
43571: actual 0, expected 1
|
||||
43572: actual 0, expected 1
|
||||
43597: actual 0, expected 1
|
||||
12334: actual 0, expected 2
|
||||
12335: actual 0, expected 2
|
||||
12336: actual 1, expected 2
|
||||
12349: actual 1, expected 2
|
||||
2307: actual 0, expected 1
|
||||
2363: actual 0, expected 1
|
||||
2366: actual 0, expected 1
|
||||
2367: actual 0, expected 1
|
||||
2368: actual 0, expected 1
|
||||
2377: actual 0, expected 1
|
||||
2378: actual 0, expected 1
|
||||
2379: actual 0, expected 1
|
||||
2380: actual 0, expected 1
|
||||
2382: actual 0, expected 1
|
||||
2383: actual 0, expected 1
|
||||
71984: actual 0, expected 1
|
||||
71985: actual 0, expected 1
|
||||
71986: actual 0, expected 1
|
||||
71987: actual 0, expected 1
|
||||
71988: actual 0, expected 1
|
||||
71989: actual 0, expected 1
|
||||
71991: actual 0, expected 1
|
||||
71992: actual 0, expected 1
|
||||
71997: actual 0, expected 1
|
||||
72000: actual 0, expected 1
|
||||
72002: actual 0, expected 1
|
||||
71724: actual 0, expected 1
|
||||
71725: actual 0, expected 1
|
||||
71726: actual 0, expected 1
|
||||
71736: actual 0, expected 1
|
||||
127462: actual 2, expected 1
|
||||
127463: actual 2, expected 1
|
||||
127464: actual 2, expected 1
|
||||
127465: actual 2, expected 1
|
||||
127466: actual 2, expected 1
|
||||
127467: actual 2, expected 1
|
||||
127468: actual 2, expected 1
|
||||
127469: actual 2, expected 1
|
||||
127470: actual 2, expected 1
|
||||
127471: actual 2, expected 1
|
||||
127472: actual 2, expected 1
|
||||
127473: actual 2, expected 1
|
||||
127474: actual 2, expected 1
|
||||
127475: actual 2, expected 1
|
||||
127476: actual 2, expected 1
|
||||
127477: actual 2, expected 1
|
||||
127478: actual 2, expected 1
|
||||
127479: actual 2, expected 1
|
||||
127480: actual 2, expected 1
|
||||
127481: actual 2, expected 1
|
||||
127482: actual 2, expected 1
|
||||
127483: actual 2, expected 1
|
||||
127484: actual 2, expected 1
|
||||
127485: actual 2, expected 1
|
||||
127486: actual 2, expected 1
|
||||
127487: actual 2, expected 1
|
||||
12951: actual 1, expected 2
|
||||
12953: actual 1, expected 2
|
||||
127490: actual 1, expected 2
|
||||
127543: actual 1, expected 2
|
||||
8293: actual 0, expected 1
|
||||
70402: actual 0, expected 1
|
||||
70403: actual 0, expected 1
|
||||
70462: actual 0, expected 1
|
||||
70463: actual 0, expected 1
|
||||
70465: actual 0, expected 1
|
||||
70466: actual 0, expected 1
|
||||
70467: actual 0, expected 1
|
||||
70468: actual 0, expected 1
|
||||
70471: actual 0, expected 1
|
||||
70472: actual 0, expected 1
|
||||
70475: actual 0, expected 1
|
||||
70476: actual 0, expected 1
|
||||
70477: actual 0, expected 1
|
||||
70487: actual 0, expected 1
|
||||
70498: actual 0, expected 1
|
||||
70499: actual 0, expected 1
|
||||
2691: actual 0, expected 1
|
||||
2750: actual 0, expected 1
|
||||
2751: actual 0, expected 1
|
||||
2752: actual 0, expected 1
|
||||
2761: actual 0, expected 1
|
||||
2763: actual 0, expected 1
|
||||
2764: actual 0, expected 1
|
||||
73098: actual 0, expected 1
|
||||
73099: actual 0, expected 1
|
||||
73100: actual 0, expected 1
|
||||
73101: actual 0, expected 1
|
||||
73102: actual 0, expected 1
|
||||
73107: actual 0, expected 1
|
||||
73108: actual 0, expected 1
|
||||
73110: actual 0, expected 1
|
||||
2563: actual 0, expected 1
|
||||
2622: actual 0, expected 1
|
||||
2623: actual 0, expected 1
|
||||
2624: actual 0, expected 1
|
||||
65440: actual 0, expected 1
|
||||
12644: actual 0, expected 2
|
||||
4447: actual 0, expected 2
|
||||
4449: actual 1, expected 0
|
||||
4450: actual 1, expected 0
|
||||
4451: actual 1, expected 0
|
||||
4452: actual 1, expected 0
|
||||
4453: actual 1, expected 0
|
||||
4454: actual 1, expected 0
|
||||
4455: actual 1, expected 0
|
||||
4456: actual 1, expected 0
|
||||
4457: actual 1, expected 0
|
||||
4458: actual 1, expected 0
|
||||
4459: actual 1, expected 0
|
||||
4460: actual 1, expected 0
|
||||
4461: actual 1, expected 0
|
||||
4462: actual 1, expected 0
|
||||
4463: actual 1, expected 0
|
||||
4464: actual 1, expected 0
|
||||
4465: actual 1, expected 0
|
||||
4466: actual 1, expected 0
|
||||
4467: actual 1, expected 0
|
||||
4468: actual 1, expected 0
|
||||
4469: actual 1, expected 0
|
||||
4470: actual 1, expected 0
|
||||
4471: actual 1, expected 0
|
||||
4472: actual 1, expected 0
|
||||
4473: actual 1, expected 0
|
||||
4474: actual 1, expected 0
|
||||
4475: actual 1, expected 0
|
||||
4476: actual 1, expected 0
|
||||
4477: actual 1, expected 0
|
||||
4478: actual 1, expected 0
|
||||
4479: actual 1, expected 0
|
||||
4480: actual 1, expected 0
|
||||
4481: actual 1, expected 0
|
||||
4482: actual 1, expected 0
|
||||
4483: actual 1, expected 0
|
||||
4484: actual 1, expected 0
|
||||
4485: actual 1, expected 0
|
||||
4486: actual 1, expected 0
|
||||
4487: actual 1, expected 0
|
||||
4488: actual 1, expected 0
|
||||
4489: actual 1, expected 0
|
||||
4490: actual 1, expected 0
|
||||
4491: actual 1, expected 0
|
||||
4492: actual 1, expected 0
|
||||
4493: actual 1, expected 0
|
||||
4494: actual 1, expected 0
|
||||
4495: actual 1, expected 0
|
||||
4496: actual 1, expected 0
|
||||
4497: actual 1, expected 0
|
||||
4498: actual 1, expected 0
|
||||
4499: actual 1, expected 0
|
||||
4500: actual 1, expected 0
|
||||
4501: actual 1, expected 0
|
||||
4502: actual 1, expected 0
|
||||
4503: actual 1, expected 0
|
||||
4504: actual 1, expected 0
|
||||
4505: actual 1, expected 0
|
||||
4506: actual 1, expected 0
|
||||
4507: actual 1, expected 0
|
||||
4508: actual 1, expected 0
|
||||
4509: actual 1, expected 0
|
||||
4510: actual 1, expected 0
|
||||
4511: actual 1, expected 0
|
||||
4512: actual 1, expected 0
|
||||
4513: actual 1, expected 0
|
||||
4514: actual 1, expected 0
|
||||
4515: actual 1, expected 0
|
||||
4516: actual 1, expected 0
|
||||
4517: actual 1, expected 0
|
||||
4518: actual 1, expected 0
|
||||
4519: actual 1, expected 0
|
||||
4520: actual 1, expected 0
|
||||
4521: actual 1, expected 0
|
||||
4522: actual 1, expected 0
|
||||
4523: actual 1, expected 0
|
||||
4524: actual 1, expected 0
|
||||
4525: actual 1, expected 0
|
||||
4526: actual 1, expected 0
|
||||
4527: actual 1, expected 0
|
||||
4528: actual 1, expected 0
|
||||
4529: actual 1, expected 0
|
||||
4530: actual 1, expected 0
|
||||
4531: actual 1, expected 0
|
||||
4532: actual 1, expected 0
|
||||
4533: actual 1, expected 0
|
||||
4534: actual 1, expected 0
|
||||
4535: actual 1, expected 0
|
||||
4536: actual 1, expected 0
|
||||
4537: actual 1, expected 0
|
||||
4538: actual 1, expected 0
|
||||
4539: actual 1, expected 0
|
||||
4540: actual 1, expected 0
|
||||
4541: actual 1, expected 0
|
||||
4542: actual 1, expected 0
|
||||
4543: actual 1, expected 0
|
||||
4544: actual 1, expected 0
|
||||
4545: actual 1, expected 0
|
||||
4546: actual 1, expected 0
|
||||
4547: actual 1, expected 0
|
||||
4548: actual 1, expected 0
|
||||
4549: actual 1, expected 0
|
||||
4550: actual 1, expected 0
|
||||
4551: actual 1, expected 0
|
||||
4552: actual 1, expected 0
|
||||
4553: actual 1, expected 0
|
||||
4554: actual 1, expected 0
|
||||
4555: actual 1, expected 0
|
||||
4556: actual 1, expected 0
|
||||
4557: actual 1, expected 0
|
||||
4558: actual 1, expected 0
|
||||
4559: actual 1, expected 0
|
||||
4560: actual 1, expected 0
|
||||
4561: actual 1, expected 0
|
||||
4562: actual 1, expected 0
|
||||
4563: actual 1, expected 0
|
||||
4564: actual 1, expected 0
|
||||
4565: actual 1, expected 0
|
||||
4566: actual 1, expected 0
|
||||
4567: actual 1, expected 0
|
||||
4568: actual 1, expected 0
|
||||
4569: actual 1, expected 0
|
||||
4570: actual 1, expected 0
|
||||
4571: actual 1, expected 0
|
||||
4572: actual 1, expected 0
|
||||
4573: actual 1, expected 0
|
||||
4574: actual 1, expected 0
|
||||
4575: actual 1, expected 0
|
||||
4576: actual 1, expected 0
|
||||
4577: actual 1, expected 0
|
||||
4578: actual 1, expected 0
|
||||
4579: actual 1, expected 0
|
||||
4580: actual 1, expected 0
|
||||
4581: actual 1, expected 0
|
||||
4582: actual 1, expected 0
|
||||
4583: actual 1, expected 0
|
||||
4584: actual 1, expected 0
|
||||
4585: actual 1, expected 0
|
||||
4586: actual 1, expected 0
|
||||
4587: actual 1, expected 0
|
||||
4588: actual 1, expected 0
|
||||
4589: actual 1, expected 0
|
||||
4590: actual 1, expected 0
|
||||
4591: actual 1, expected 0
|
||||
4592: actual 1, expected 0
|
||||
4593: actual 1, expected 0
|
||||
4594: actual 1, expected 0
|
||||
4595: actual 1, expected 0
|
||||
4596: actual 1, expected 0
|
||||
4597: actual 1, expected 0
|
||||
4598: actual 1, expected 0
|
||||
4599: actual 1, expected 0
|
||||
4600: actual 1, expected 0
|
||||
4601: actual 1, expected 0
|
||||
4602: actual 1, expected 0
|
||||
4603: actual 1, expected 0
|
||||
4604: actual 1, expected 0
|
||||
4605: actual 1, expected 0
|
||||
4606: actual 1, expected 0
|
||||
4607: actual 1, expected 0
|
||||
5940: actual 0, expected 1
|
||||
94192: actual 0, expected 2
|
||||
94193: actual 0, expected 2
|
||||
43395: actual 0, expected 1
|
||||
43444: actual 0, expected 1
|
||||
43445: actual 0, expected 1
|
||||
43450: actual 0, expected 1
|
||||
43451: actual 0, expected 1
|
||||
43454: actual 0, expected 1
|
||||
43455: actual 0, expected 1
|
||||
43456: actual 0, expected 1
|
||||
69762: actual 0, expected 1
|
||||
69808: actual 0, expected 1
|
||||
69809: actual 0, expected 1
|
||||
69810: actual 0, expected 1
|
||||
69815: actual 0, expected 1
|
||||
69816: actual 0, expected 1
|
||||
3202: actual 0, expected 1
|
||||
3203: actual 0, expected 1
|
||||
3262: actual 0, expected 1
|
||||
3264: actual 0, expected 1
|
||||
3265: actual 0, expected 1
|
||||
3266: actual 0, expected 1
|
||||
3267: actual 0, expected 1
|
||||
3268: actual 0, expected 1
|
||||
3271: actual 0, expected 1
|
||||
3272: actual 0, expected 1
|
||||
3274: actual 0, expected 1
|
||||
3275: actual 0, expected 1
|
||||
3285: actual 0, expected 1
|
||||
3286: actual 0, expected 1
|
||||
6070: actual 0, expected 1
|
||||
6078: actual 0, expected 1
|
||||
6079: actual 0, expected 1
|
||||
6080: actual 0, expected 1
|
||||
6081: actual 0, expected 1
|
||||
6082: actual 0, expected 1
|
||||
6083: actual 0, expected 1
|
||||
6084: actual 0, expected 1
|
||||
6085: actual 0, expected 1
|
||||
6087: actual 0, expected 1
|
||||
6088: actual 0, expected 1
|
||||
70188: actual 0, expected 1
|
||||
70189: actual 0, expected 1
|
||||
70190: actual 0, expected 1
|
||||
70194: actual 0, expected 1
|
||||
70195: actual 0, expected 1
|
||||
70197: actual 0, expected 1
|
||||
70368: actual 0, expected 1
|
||||
70369: actual 0, expected 1
|
||||
70370: actual 0, expected 1
|
||||
173: actual 0, expected 1
|
||||
7204: actual 0, expected 1
|
||||
7205: actual 0, expected 1
|
||||
7206: actual 0, expected 1
|
||||
7207: actual 0, expected 1
|
||||
7208: actual 0, expected 1
|
||||
7209: actual 0, expected 1
|
||||
7210: actual 0, expected 1
|
||||
7211: actual 0, expected 1
|
||||
7220: actual 0, expected 1
|
||||
7221: actual 0, expected 1
|
||||
6435: actual 0, expected 1
|
||||
6436: actual 0, expected 1
|
||||
6437: actual 0, expected 1
|
||||
6438: actual 0, expected 1
|
||||
6441: actual 0, expected 1
|
||||
6442: actual 0, expected 1
|
||||
6443: actual 0, expected 1
|
||||
6448: actual 0, expected 1
|
||||
6449: actual 0, expected 1
|
||||
6451: actual 0, expected 1
|
||||
6452: actual 0, expected 1
|
||||
6453: actual 0, expected 1
|
||||
6454: actual 0, expected 1
|
||||
6455: actual 0, expected 1
|
||||
6456: actual 0, expected 1
|
||||
73461: actual 0, expected 1
|
||||
73462: actual 0, expected 1
|
||||
3330: actual 0, expected 1
|
||||
3331: actual 0, expected 1
|
||||
3390: actual 0, expected 1
|
||||
3391: actual 0, expected 1
|
||||
3392: actual 0, expected 1
|
||||
3398: actual 0, expected 1
|
||||
3399: actual 0, expected 1
|
||||
3400: actual 0, expected 1
|
||||
3402: actual 0, expected 1
|
||||
3403: actual 0, expected 1
|
||||
3404: actual 0, expected 1
|
||||
3415: actual 0, expected 1
|
||||
72873: actual 0, expected 1
|
||||
72881: actual 0, expected 1
|
||||
72884: actual 0, expected 1
|
||||
44003: actual 0, expected 1
|
||||
44004: actual 0, expected 1
|
||||
44006: actual 0, expected 1
|
||||
44007: actual 0, expected 1
|
||||
44009: actual 0, expected 1
|
||||
44010: actual 0, expected 1
|
||||
44012: actual 0, expected 1
|
||||
43755: actual 0, expected 1
|
||||
43758: actual 0, expected 1
|
||||
43759: actual 0, expected 1
|
||||
43765: actual 0, expected 1
|
||||
94033: actual 0, expected 1
|
||||
94034: actual 0, expected 1
|
||||
94035: actual 0, expected 1
|
||||
94036: actual 0, expected 1
|
||||
94037: actual 0, expected 1
|
||||
94038: actual 0, expected 1
|
||||
94039: actual 0, expected 1
|
||||
94040: actual 0, expected 1
|
||||
94041: actual 0, expected 1
|
||||
94042: actual 0, expected 1
|
||||
94043: actual 0, expected 1
|
||||
94044: actual 0, expected 1
|
||||
94045: actual 0, expected 1
|
||||
94046: actual 0, expected 1
|
||||
94047: actual 0, expected 1
|
||||
94048: actual 0, expected 1
|
||||
94049: actual 0, expected 1
|
||||
94050: actual 0, expected 1
|
||||
94051: actual 0, expected 1
|
||||
94052: actual 0, expected 1
|
||||
94053: actual 0, expected 1
|
||||
94054: actual 0, expected 1
|
||||
94055: actual 0, expected 1
|
||||
94056: actual 0, expected 1
|
||||
94057: actual 0, expected 1
|
||||
94058: actual 0, expected 1
|
||||
94059: actual 0, expected 1
|
||||
94060: actual 0, expected 1
|
||||
94061: actual 0, expected 1
|
||||
94062: actual 0, expected 1
|
||||
94063: actual 0, expected 1
|
||||
94064: actual 0, expected 1
|
||||
94065: actual 0, expected 1
|
||||
94066: actual 0, expected 1
|
||||
94067: actual 0, expected 1
|
||||
94068: actual 0, expected 1
|
||||
94069: actual 0, expected 1
|
||||
94070: actual 0, expected 1
|
||||
94071: actual 0, expected 1
|
||||
94072: actual 0, expected 1
|
||||
94073: actual 0, expected 1
|
||||
94074: actual 0, expected 1
|
||||
94075: actual 0, expected 1
|
||||
94076: actual 0, expected 1
|
||||
94077: actual 0, expected 1
|
||||
94078: actual 0, expected 1
|
||||
94079: actual 0, expected 1
|
||||
94080: actual 0, expected 1
|
||||
94081: actual 0, expected 1
|
||||
94082: actual 0, expected 1
|
||||
94083: actual 0, expected 1
|
||||
94084: actual 0, expected 1
|
||||
94085: actual 0, expected 1
|
||||
94086: actual 0, expected 1
|
||||
94087: actual 0, expected 1
|
||||
127995: actual 0, expected 2
|
||||
127996: actual 0, expected 2
|
||||
127997: actual 0, expected 2
|
||||
127998: actual 0, expected 2
|
||||
127999: actual 0, expected 2
|
||||
71216: actual 0, expected 1
|
||||
71217: actual 0, expected 1
|
||||
71218: actual 0, expected 1
|
||||
71227: actual 0, expected 1
|
||||
71228: actual 0, expected 1
|
||||
71230: actual 0, expected 1
|
||||
119141: actual 0, expected 1
|
||||
119142: actual 0, expected 1
|
||||
119149: actual 0, expected 1
|
||||
119150: actual 0, expected 1
|
||||
119151: actual 0, expected 1
|
||||
119152: actual 0, expected 1
|
||||
119153: actual 0, expected 1
|
||||
119154: actual 0, expected 1
|
||||
4139: actual 0, expected 1
|
||||
4140: actual 0, expected 1
|
||||
4145: actual 0, expected 1
|
||||
4152: actual 0, expected 1
|
||||
4155: actual 0, expected 1
|
||||
4156: actual 0, expected 1
|
||||
4182: actual 0, expected 1
|
||||
4183: actual 0, expected 1
|
||||
4194: actual 0, expected 1
|
||||
4195: actual 0, expected 1
|
||||
4196: actual 0, expected 1
|
||||
4199: actual 0, expected 1
|
||||
4200: actual 0, expected 1
|
||||
4201: actual 0, expected 1
|
||||
4202: actual 0, expected 1
|
||||
4203: actual 0, expected 1
|
||||
4204: actual 0, expected 1
|
||||
4205: actual 0, expected 1
|
||||
4227: actual 0, expected 1
|
||||
4228: actual 0, expected 1
|
||||
4231: actual 0, expected 1
|
||||
4232: actual 0, expected 1
|
||||
4233: actual 0, expected 1
|
||||
4234: actual 0, expected 1
|
||||
4235: actual 0, expected 1
|
||||
4236: actual 0, expected 1
|
||||
4239: actual 0, expected 1
|
||||
4250: actual 0, expected 1
|
||||
4251: actual 0, expected 1
|
||||
4252: actual 0, expected 1
|
||||
43643: actual 0, expected 1
|
||||
43645: actual 0, expected 1
|
||||
72145: actual 0, expected 1
|
||||
72146: actual 0, expected 1
|
||||
72147: actual 0, expected 1
|
||||
72156: actual 0, expected 1
|
||||
72157: actual 0, expected 1
|
||||
72158: actual 0, expected 1
|
||||
72159: actual 0, expected 1
|
||||
72164: actual 0, expected 1
|
||||
70709: actual 0, expected 1
|
||||
70710: actual 0, expected 1
|
||||
70711: actual 0, expected 1
|
||||
70720: actual 0, expected 1
|
||||
70721: actual 0, expected 1
|
||||
70725: actual 0, expected 1
|
||||
2818: actual 0, expected 1
|
||||
2819: actual 0, expected 1
|
||||
2878: actual 0, expected 1
|
||||
2880: actual 0, expected 1
|
||||
2887: actual 0, expected 1
|
||||
2888: actual 0, expected 1
|
||||
2891: actual 0, expected 1
|
||||
2892: actual 0, expected 1
|
||||
2903: actual 0, expected 1
|
||||
43346: actual 0, expected 1
|
||||
43347: actual 0, expected 1
|
||||
43136: actual 0, expected 1
|
||||
43137: actual 0, expected 1
|
||||
43188: actual 0, expected 1
|
||||
43189: actual 0, expected 1
|
||||
43190: actual 0, expected 1
|
||||
43191: actual 0, expected 1
|
||||
43192: actual 0, expected 1
|
||||
43193: actual 0, expected 1
|
||||
43194: actual 0, expected 1
|
||||
43195: actual 0, expected 1
|
||||
43196: actual 0, expected 1
|
||||
43197: actual 0, expected 1
|
||||
43198: actual 0, expected 1
|
||||
43199: actual 0, expected 1
|
||||
43200: actual 0, expected 1
|
||||
43201: actual 0, expected 1
|
||||
43202: actual 0, expected 1
|
||||
43203: actual 0, expected 1
|
||||
70018: actual 0, expected 1
|
||||
70067: actual 0, expected 1
|
||||
70068: actual 0, expected 1
|
||||
70069: actual 0, expected 1
|
||||
70079: actual 0, expected 1
|
||||
70080: actual 0, expected 1
|
||||
70094: actual 0, expected 1
|
||||
71087: actual 0, expected 1
|
||||
71088: actual 0, expected 1
|
||||
71089: actual 0, expected 1
|
||||
71096: actual 0, expected 1
|
||||
71097: actual 0, expected 1
|
||||
71098: actual 0, expected 1
|
||||
71099: actual 0, expected 1
|
||||
71102: actual 0, expected 1
|
||||
3458: actual 0, expected 1
|
||||
3459: actual 0, expected 1
|
||||
3535: actual 0, expected 1
|
||||
3536: actual 0, expected 1
|
||||
3537: actual 0, expected 1
|
||||
3544: actual 0, expected 1
|
||||
3545: actual 0, expected 1
|
||||
3546: actual 0, expected 1
|
||||
3547: actual 0, expected 1
|
||||
3548: actual 0, expected 1
|
||||
3549: actual 0, expected 1
|
||||
3550: actual 0, expected 1
|
||||
3551: actual 0, expected 1
|
||||
3570: actual 0, expected 1
|
||||
3571: actual 0, expected 1
|
||||
72279: actual 0, expected 1
|
||||
72280: actual 0, expected 1
|
||||
72343: actual 0, expected 1
|
||||
65520: actual 0, expected 1
|
||||
65521: actual 0, expected 1
|
||||
65522: actual 0, expected 1
|
||||
65523: actual 0, expected 1
|
||||
65524: actual 0, expected 1
|
||||
65525: actual 0, expected 1
|
||||
65526: actual 0, expected 1
|
||||
65527: actual 0, expected 1
|
||||
65528: actual 0, expected 1
|
||||
65534: actual 0, expected 1
|
||||
65535: actual 0, expected 1
|
||||
7042: actual 0, expected 1
|
||||
7073: actual 0, expected 1
|
||||
7078: actual 0, expected 1
|
||||
7079: actual 0, expected 1
|
||||
7082: actual 0, expected 1
|
||||
1048574: actual 0, expected 1
|
||||
1048575: actual 0, expected 1
|
||||
1114110: actual 0, expected 1
|
||||
1114111: actual 0, expected 1
|
||||
43043: actual 0, expected 1
|
||||
43044: actual 0, expected 1
|
||||
43047: actual 0, expected 1
|
||||
5909: actual 0, expected 1
|
||||
917504: actual 0, expected 1
|
||||
917506: actual 0, expected 1
|
||||
917507: actual 0, expected 1
|
||||
917508: actual 0, expected 1
|
||||
917509: actual 0, expected 1
|
||||
917510: actual 0, expected 1
|
||||
917511: actual 0, expected 1
|
||||
917512: actual 0, expected 1
|
||||
917513: actual 0, expected 1
|
||||
917514: actual 0, expected 1
|
||||
917515: actual 0, expected 1
|
||||
917516: actual 0, expected 1
|
||||
917517: actual 0, expected 1
|
||||
917518: actual 0, expected 1
|
||||
917519: actual 0, expected 1
|
||||
917520: actual 0, expected 1
|
||||
917521: actual 0, expected 1
|
||||
917522: actual 0, expected 1
|
||||
917523: actual 0, expected 1
|
||||
917524: actual 0, expected 1
|
||||
917525: actual 0, expected 1
|
||||
917526: actual 0, expected 1
|
||||
917527: actual 0, expected 1
|
||||
917528: actual 0, expected 1
|
||||
917529: actual 0, expected 1
|
||||
917530: actual 0, expected 1
|
||||
917531: actual 0, expected 1
|
||||
917532: actual 0, expected 1
|
||||
917533: actual 0, expected 1
|
||||
917534: actual 0, expected 1
|
||||
917535: actual 0, expected 1
|
||||
6741: actual 0, expected 1
|
||||
6743: actual 0, expected 1
|
||||
6753: actual 0, expected 1
|
||||
6755: actual 0, expected 1
|
||||
6756: actual 0, expected 1
|
||||
6765: actual 0, expected 1
|
||||
6766: actual 0, expected 1
|
||||
6767: actual 0, expected 1
|
||||
6768: actual 0, expected 1
|
||||
6769: actual 0, expected 1
|
||||
6770: actual 0, expected 1
|
||||
71340: actual 0, expected 1
|
||||
71342: actual 0, expected 1
|
||||
71343: actual 0, expected 1
|
||||
71350: actual 0, expected 1
|
||||
3006: actual 0, expected 1
|
||||
3007: actual 0, expected 1
|
||||
3009: actual 0, expected 1
|
||||
3010: actual 0, expected 1
|
||||
3014: actual 0, expected 1
|
||||
3015: actual 0, expected 1
|
||||
3016: actual 0, expected 1
|
||||
3018: actual 0, expected 1
|
||||
3019: actual 0, expected 1
|
||||
3020: actual 0, expected 1
|
||||
3031: actual 0, expected 1
|
||||
3073: actual 0, expected 1
|
||||
3074: actual 0, expected 1
|
||||
3075: actual 0, expected 1
|
||||
3137: actual 0, expected 1
|
||||
3138: actual 0, expected 1
|
||||
3139: actual 0, expected 1
|
||||
3140: actual 0, expected 1
|
||||
3902: actual 0, expected 1
|
||||
3903: actual 0, expected 1
|
||||
3967: actual 0, expected 1
|
||||
70832: actual 0, expected 1
|
||||
70833: actual 0, expected 1
|
||||
70834: actual 0, expected 1
|
||||
70841: actual 0, expected 1
|
||||
70843: actual 0, expected 1
|
||||
70844: actual 0, expected 1
|
||||
70845: actual 0, expected 1
|
||||
70846: actual 0, expected 1
|
||||
70849: actual 0, expected 1
|
||||
7393: actual 0, expected 1
|
||||
7415: actual 0, expected 1
|
||||
72249: actual 0, expected 1
|
||||
260
widths-konsole
260
widths-konsole
|
|
@ -1,260 +0,0 @@
|
|||
Finished release [optimized] target(s) in 0.02s
|
||||
Running `target/release/examples/measure_widths`
|
||||
127462: actual 2, expected 1
|
||||
127463: actual 2, expected 1
|
||||
127464: actual 2, expected 1
|
||||
127465: actual 2, expected 1
|
||||
127466: actual 2, expected 1
|
||||
127467: actual 2, expected 1
|
||||
127468: actual 2, expected 1
|
||||
127469: actual 2, expected 1
|
||||
127470: actual 2, expected 1
|
||||
127471: actual 2, expected 1
|
||||
127472: actual 2, expected 1
|
||||
127473: actual 2, expected 1
|
||||
127474: actual 2, expected 1
|
||||
127475: actual 2, expected 1
|
||||
127476: actual 2, expected 1
|
||||
127477: actual 2, expected 1
|
||||
127478: actual 2, expected 1
|
||||
127479: actual 2, expected 1
|
||||
127480: actual 2, expected 1
|
||||
127481: actual 2, expected 1
|
||||
127482: actual 2, expected 1
|
||||
127483: actual 2, expected 1
|
||||
127484: actual 2, expected 1
|
||||
127485: actual 2, expected 1
|
||||
127486: actual 2, expected 1
|
||||
127487: actual 2, expected 1
|
||||
4448: actual 2, expected 0
|
||||
4449: actual 2, expected 0
|
||||
4450: actual 2, expected 0
|
||||
4451: actual 2, expected 0
|
||||
4452: actual 2, expected 0
|
||||
4453: actual 2, expected 0
|
||||
4454: actual 2, expected 0
|
||||
4455: actual 2, expected 0
|
||||
4456: actual 2, expected 0
|
||||
4457: actual 2, expected 0
|
||||
4458: actual 2, expected 0
|
||||
4459: actual 2, expected 0
|
||||
4460: actual 2, expected 0
|
||||
4461: actual 2, expected 0
|
||||
4462: actual 2, expected 0
|
||||
4463: actual 2, expected 0
|
||||
4464: actual 2, expected 0
|
||||
4465: actual 2, expected 0
|
||||
4466: actual 2, expected 0
|
||||
4467: actual 2, expected 0
|
||||
4468: actual 2, expected 0
|
||||
4469: actual 2, expected 0
|
||||
4470: actual 2, expected 0
|
||||
4471: actual 2, expected 0
|
||||
4472: actual 2, expected 0
|
||||
4473: actual 2, expected 0
|
||||
4474: actual 2, expected 0
|
||||
4475: actual 2, expected 0
|
||||
4476: actual 2, expected 0
|
||||
4477: actual 2, expected 0
|
||||
4478: actual 2, expected 0
|
||||
4479: actual 2, expected 0
|
||||
4480: actual 2, expected 0
|
||||
4481: actual 2, expected 0
|
||||
4482: actual 2, expected 0
|
||||
4483: actual 2, expected 0
|
||||
4484: actual 2, expected 0
|
||||
4485: actual 2, expected 0
|
||||
4486: actual 2, expected 0
|
||||
4487: actual 2, expected 0
|
||||
4488: actual 2, expected 0
|
||||
4489: actual 2, expected 0
|
||||
4490: actual 2, expected 0
|
||||
4491: actual 2, expected 0
|
||||
4492: actual 2, expected 0
|
||||
4493: actual 2, expected 0
|
||||
4494: actual 2, expected 0
|
||||
4495: actual 2, expected 0
|
||||
4496: actual 2, expected 0
|
||||
4497: actual 2, expected 0
|
||||
4498: actual 2, expected 0
|
||||
4499: actual 2, expected 0
|
||||
4500: actual 2, expected 0
|
||||
4501: actual 2, expected 0
|
||||
4502: actual 2, expected 0
|
||||
4503: actual 2, expected 0
|
||||
4504: actual 2, expected 0
|
||||
4505: actual 2, expected 0
|
||||
4506: actual 2, expected 0
|
||||
4507: actual 2, expected 0
|
||||
4508: actual 2, expected 0
|
||||
4509: actual 2, expected 0
|
||||
4510: actual 2, expected 0
|
||||
4511: actual 2, expected 0
|
||||
4512: actual 2, expected 0
|
||||
4513: actual 2, expected 0
|
||||
4514: actual 2, expected 0
|
||||
4515: actual 2, expected 0
|
||||
4516: actual 2, expected 0
|
||||
4517: actual 2, expected 0
|
||||
4518: actual 2, expected 0
|
||||
4519: actual 2, expected 0
|
||||
4520: actual 2, expected 0
|
||||
4521: actual 2, expected 0
|
||||
4522: actual 2, expected 0
|
||||
4523: actual 2, expected 0
|
||||
4524: actual 2, expected 0
|
||||
4525: actual 2, expected 0
|
||||
4526: actual 2, expected 0
|
||||
4527: actual 2, expected 0
|
||||
4528: actual 2, expected 0
|
||||
4529: actual 2, expected 0
|
||||
4530: actual 2, expected 0
|
||||
4531: actual 2, expected 0
|
||||
4532: actual 2, expected 0
|
||||
4533: actual 2, expected 0
|
||||
4534: actual 2, expected 0
|
||||
4535: actual 2, expected 0
|
||||
4536: actual 2, expected 0
|
||||
4537: actual 2, expected 0
|
||||
4538: actual 2, expected 0
|
||||
4539: actual 2, expected 0
|
||||
4540: actual 2, expected 0
|
||||
4541: actual 2, expected 0
|
||||
4542: actual 2, expected 0
|
||||
4543: actual 2, expected 0
|
||||
4544: actual 2, expected 0
|
||||
4545: actual 2, expected 0
|
||||
4546: actual 2, expected 0
|
||||
4547: actual 2, expected 0
|
||||
4548: actual 2, expected 0
|
||||
4549: actual 2, expected 0
|
||||
4550: actual 2, expected 0
|
||||
4551: actual 2, expected 0
|
||||
4552: actual 2, expected 0
|
||||
4553: actual 2, expected 0
|
||||
4554: actual 2, expected 0
|
||||
4555: actual 2, expected 0
|
||||
4556: actual 2, expected 0
|
||||
4557: actual 2, expected 0
|
||||
4558: actual 2, expected 0
|
||||
4559: actual 2, expected 0
|
||||
4560: actual 2, expected 0
|
||||
4561: actual 2, expected 0
|
||||
4562: actual 2, expected 0
|
||||
4563: actual 2, expected 0
|
||||
4564: actual 2, expected 0
|
||||
4565: actual 2, expected 0
|
||||
4566: actual 2, expected 0
|
||||
4567: actual 2, expected 0
|
||||
4568: actual 2, expected 0
|
||||
4569: actual 2, expected 0
|
||||
4570: actual 2, expected 0
|
||||
4571: actual 2, expected 0
|
||||
4572: actual 2, expected 0
|
||||
4573: actual 2, expected 0
|
||||
4574: actual 2, expected 0
|
||||
4575: actual 2, expected 0
|
||||
4576: actual 2, expected 0
|
||||
4577: actual 2, expected 0
|
||||
4578: actual 2, expected 0
|
||||
4579: actual 2, expected 0
|
||||
4580: actual 2, expected 0
|
||||
4581: actual 2, expected 0
|
||||
4582: actual 2, expected 0
|
||||
4583: actual 2, expected 0
|
||||
4584: actual 2, expected 0
|
||||
4585: actual 2, expected 0
|
||||
4586: actual 2, expected 0
|
||||
4587: actual 2, expected 0
|
||||
4588: actual 2, expected 0
|
||||
4589: actual 2, expected 0
|
||||
4590: actual 2, expected 0
|
||||
4591: actual 2, expected 0
|
||||
4592: actual 2, expected 0
|
||||
4593: actual 2, expected 0
|
||||
4594: actual 2, expected 0
|
||||
4595: actual 2, expected 0
|
||||
4596: actual 2, expected 0
|
||||
4597: actual 2, expected 0
|
||||
4598: actual 2, expected 0
|
||||
4599: actual 2, expected 0
|
||||
4600: actual 2, expected 0
|
||||
4601: actual 2, expected 0
|
||||
4602: actual 2, expected 0
|
||||
4603: actual 2, expected 0
|
||||
4604: actual 2, expected 0
|
||||
4605: actual 2, expected 0
|
||||
4606: actual 2, expected 0
|
||||
4607: actual 2, expected 0
|
||||
55216: actual 2, expected 1
|
||||
55217: actual 2, expected 1
|
||||
55218: actual 2, expected 1
|
||||
55219: actual 2, expected 1
|
||||
55220: actual 2, expected 1
|
||||
55221: actual 2, expected 1
|
||||
55222: actual 2, expected 1
|
||||
55223: actual 2, expected 1
|
||||
55224: actual 2, expected 1
|
||||
55225: actual 2, expected 1
|
||||
55226: actual 2, expected 1
|
||||
55227: actual 2, expected 1
|
||||
55228: actual 2, expected 1
|
||||
55229: actual 2, expected 1
|
||||
55230: actual 2, expected 1
|
||||
55231: actual 2, expected 1
|
||||
55232: actual 2, expected 1
|
||||
55233: actual 2, expected 1
|
||||
55234: actual 2, expected 1
|
||||
55235: actual 2, expected 1
|
||||
55236: actual 2, expected 1
|
||||
55237: actual 2, expected 1
|
||||
55238: actual 2, expected 1
|
||||
55243: actual 2, expected 1
|
||||
55244: actual 2, expected 1
|
||||
55245: actual 2, expected 1
|
||||
55246: actual 2, expected 1
|
||||
55247: actual 2, expected 1
|
||||
55248: actual 2, expected 1
|
||||
55249: actual 2, expected 1
|
||||
55250: actual 2, expected 1
|
||||
55251: actual 2, expected 1
|
||||
55252: actual 2, expected 1
|
||||
55253: actual 2, expected 1
|
||||
55254: actual 2, expected 1
|
||||
55255: actual 2, expected 1
|
||||
55256: actual 2, expected 1
|
||||
55257: actual 2, expected 1
|
||||
55258: actual 2, expected 1
|
||||
55259: actual 2, expected 1
|
||||
55260: actual 2, expected 1
|
||||
55261: actual 2, expected 1
|
||||
55262: actual 2, expected 1
|
||||
55263: actual 2, expected 1
|
||||
55264: actual 2, expected 1
|
||||
55265: actual 2, expected 1
|
||||
55266: actual 2, expected 1
|
||||
55267: actual 2, expected 1
|
||||
55268: actual 2, expected 1
|
||||
55269: actual 2, expected 1
|
||||
55270: actual 2, expected 1
|
||||
55271: actual 2, expected 1
|
||||
55272: actual 2, expected 1
|
||||
55273: actual 2, expected 1
|
||||
55274: actual 2, expected 1
|
||||
55275: actual 2, expected 1
|
||||
55276: actual 2, expected 1
|
||||
55277: actual 2, expected 1
|
||||
55278: actual 2, expected 1
|
||||
55279: actual 2, expected 1
|
||||
55280: actual 2, expected 1
|
||||
55281: actual 2, expected 1
|
||||
55282: actual 2, expected 1
|
||||
55283: actual 2, expected 1
|
||||
55284: actual 2, expected 1
|
||||
55285: actual 2, expected 1
|
||||
55286: actual 2, expected 1
|
||||
55287: actual 2, expected 1
|
||||
55288: actual 2, expected 1
|
||||
55289: actual 2, expected 1
|
||||
55290: actual 2, expected 1
|
||||
55291: actual 2, expected 1
|
||||
420
widths-st
420
widths-st
|
|
@ -1,420 +0,0 @@
|
|||
Finished release [optimized] target(s) in 0.02s
|
||||
Running `target/release/examples/measure_widths`
|
||||
1536: actual 1, expected 0
|
||||
1537: actual 1, expected 0
|
||||
1538: actual 1, expected 0
|
||||
1539: actual 1, expected 0
|
||||
1540: actual 1, expected 0
|
||||
1541: actual 1, expected 0
|
||||
1757: actual 1, expected 0
|
||||
2250: actual 1, expected 0
|
||||
2251: actual 1, expected 0
|
||||
2252: actual 1, expected 0
|
||||
2253: actual 1, expected 0
|
||||
2254: actual 1, expected 0
|
||||
2255: actual 1, expected 0
|
||||
2256: actual 1, expected 0
|
||||
2257: actual 1, expected 0
|
||||
2258: actual 1, expected 0
|
||||
2274: actual 1, expected 0
|
||||
2192: actual 1, expected 0
|
||||
2193: actual 1, expected 0
|
||||
2200: actual 1, expected 0
|
||||
2201: actual 1, expected 0
|
||||
2202: actual 1, expected 0
|
||||
2203: actual 1, expected 0
|
||||
2204: actual 1, expected 0
|
||||
2205: actual 1, expected 0
|
||||
2206: actual 1, expected 0
|
||||
2207: actual 1, expected 0
|
||||
69744: actual 1, expected 0
|
||||
69747: actual 1, expected 0
|
||||
69748: actual 1, expected 0
|
||||
64110: actual 1, expected 2
|
||||
64111: actual 1, expected 2
|
||||
64218: actual 1, expected 2
|
||||
64219: actual 1, expected 2
|
||||
64220: actual 1, expected 2
|
||||
64221: actual 1, expected 2
|
||||
64222: actual 1, expected 2
|
||||
64223: actual 1, expected 2
|
||||
64224: actual 1, expected 2
|
||||
64225: actual 1, expected 2
|
||||
64226: actual 1, expected 2
|
||||
64227: actual 1, expected 2
|
||||
64228: actual 1, expected 2
|
||||
64229: actual 1, expected 2
|
||||
64230: actual 1, expected 2
|
||||
64231: actual 1, expected 2
|
||||
64232: actual 1, expected 2
|
||||
64233: actual 1, expected 2
|
||||
64234: actual 1, expected 2
|
||||
64235: actual 1, expected 2
|
||||
64236: actual 1, expected 2
|
||||
64237: actual 1, expected 2
|
||||
64238: actual 1, expected 2
|
||||
64239: actual 1, expected 2
|
||||
64240: actual 1, expected 2
|
||||
64241: actual 1, expected 2
|
||||
64242: actual 1, expected 2
|
||||
64243: actual 1, expected 2
|
||||
64244: actual 1, expected 2
|
||||
64245: actual 1, expected 2
|
||||
64246: actual 1, expected 2
|
||||
64247: actual 1, expected 2
|
||||
64248: actual 1, expected 2
|
||||
64249: actual 1, expected 2
|
||||
64250: actual 1, expected 2
|
||||
64251: actual 1, expected 2
|
||||
64252: actual 1, expected 2
|
||||
64253: actual 1, expected 2
|
||||
64254: actual 1, expected 2
|
||||
64255: actual 1, expected 2
|
||||
195102: actual 1, expected 2
|
||||
195103: actual 1, expected 2
|
||||
40957: actual 1, expected 2
|
||||
40958: actual 1, expected 2
|
||||
40959: actual 1, expected 2
|
||||
173790: actual 1, expected 2
|
||||
173791: actual 1, expected 2
|
||||
177973: actual 1, expected 2
|
||||
177974: actual 1, expected 2
|
||||
177975: actual 1, expected 2
|
||||
177976: actual 1, expected 2
|
||||
177977: actual 1, expected 2
|
||||
177978: actual 1, expected 2
|
||||
177979: actual 1, expected 2
|
||||
177980: actual 1, expected 2
|
||||
177981: actual 1, expected 2
|
||||
177982: actual 1, expected 2
|
||||
177983: actual 1, expected 2
|
||||
178206: actual 1, expected 2
|
||||
178207: actual 1, expected 2
|
||||
183970: actual 1, expected 2
|
||||
183971: actual 1, expected 2
|
||||
183972: actual 1, expected 2
|
||||
183973: actual 1, expected 2
|
||||
183974: actual 1, expected 2
|
||||
183975: actual 1, expected 2
|
||||
183976: actual 1, expected 2
|
||||
183977: actual 1, expected 2
|
||||
183978: actual 1, expected 2
|
||||
183979: actual 1, expected 2
|
||||
183980: actual 1, expected 2
|
||||
183981: actual 1, expected 2
|
||||
183982: actual 1, expected 2
|
||||
183983: actual 1, expected 2
|
||||
191457: actual 1, expected 2
|
||||
191458: actual 1, expected 2
|
||||
191459: actual 1, expected 2
|
||||
191460: actual 1, expected 2
|
||||
191461: actual 1, expected 2
|
||||
191462: actual 1, expected 2
|
||||
191463: actual 1, expected 2
|
||||
191464: actual 1, expected 2
|
||||
191465: actual 1, expected 2
|
||||
191466: actual 1, expected 2
|
||||
191467: actual 1, expected 2
|
||||
191468: actual 1, expected 2
|
||||
191469: actual 1, expected 2
|
||||
191470: actual 1, expected 2
|
||||
191471: actual 1, expected 2
|
||||
201547: actual 1, expected 2
|
||||
201548: actual 1, expected 2
|
||||
201549: actual 1, expected 2
|
||||
201550: actual 1, expected 2
|
||||
201551: actual 1, expected 2
|
||||
6849: actual 1, expected 0
|
||||
6850: actual 1, expected 0
|
||||
6851: actual 1, expected 0
|
||||
6852: actual 1, expected 0
|
||||
6853: actual 1, expected 0
|
||||
6854: actual 1, expected 0
|
||||
6855: actual 1, expected 0
|
||||
6856: actual 1, expected 0
|
||||
6857: actual 1, expected 0
|
||||
6858: actual 1, expected 0
|
||||
6859: actual 1, expected 0
|
||||
6860: actual 1, expected 0
|
||||
6861: actual 1, expected 0
|
||||
6862: actual 1, expected 0
|
||||
7674: actual 1, expected 0
|
||||
12872: actual 2, expected 1
|
||||
12873: actual 2, expected 1
|
||||
12874: actual 2, expected 1
|
||||
12875: actual 2, expected 1
|
||||
12876: actual 2, expected 1
|
||||
12877: actual 2, expected 1
|
||||
12878: actual 2, expected 1
|
||||
12879: actual 2, expected 1
|
||||
129008: actual 1, expected 2
|
||||
55216: actual 0, expected 1
|
||||
55217: actual 0, expected 1
|
||||
55218: actual 0, expected 1
|
||||
55219: actual 0, expected 1
|
||||
55220: actual 0, expected 1
|
||||
55221: actual 0, expected 1
|
||||
55222: actual 0, expected 1
|
||||
55223: actual 0, expected 1
|
||||
55224: actual 0, expected 1
|
||||
55225: actual 0, expected 1
|
||||
55226: actual 0, expected 1
|
||||
55227: actual 0, expected 1
|
||||
55228: actual 0, expected 1
|
||||
55229: actual 0, expected 1
|
||||
55230: actual 0, expected 1
|
||||
55231: actual 0, expected 1
|
||||
55232: actual 0, expected 1
|
||||
55233: actual 0, expected 1
|
||||
55234: actual 0, expected 1
|
||||
55235: actual 0, expected 1
|
||||
55236: actual 0, expected 1
|
||||
55237: actual 0, expected 1
|
||||
55238: actual 0, expected 1
|
||||
55243: actual 0, expected 1
|
||||
55244: actual 0, expected 1
|
||||
55245: actual 0, expected 1
|
||||
55246: actual 0, expected 1
|
||||
55247: actual 0, expected 1
|
||||
55248: actual 0, expected 1
|
||||
55249: actual 0, expected 1
|
||||
55250: actual 0, expected 1
|
||||
55251: actual 0, expected 1
|
||||
55252: actual 0, expected 1
|
||||
55253: actual 0, expected 1
|
||||
55254: actual 0, expected 1
|
||||
55255: actual 0, expected 1
|
||||
55256: actual 0, expected 1
|
||||
55257: actual 0, expected 1
|
||||
55258: actual 0, expected 1
|
||||
55259: actual 0, expected 1
|
||||
55260: actual 0, expected 1
|
||||
55261: actual 0, expected 1
|
||||
55262: actual 0, expected 1
|
||||
55263: actual 0, expected 1
|
||||
55264: actual 0, expected 1
|
||||
55265: actual 0, expected 1
|
||||
55266: actual 0, expected 1
|
||||
55267: actual 0, expected 1
|
||||
55268: actual 0, expected 1
|
||||
55269: actual 0, expected 1
|
||||
55270: actual 0, expected 1
|
||||
55271: actual 0, expected 1
|
||||
55272: actual 0, expected 1
|
||||
55273: actual 0, expected 1
|
||||
55274: actual 0, expected 1
|
||||
55275: actual 0, expected 1
|
||||
55276: actual 0, expected 1
|
||||
55277: actual 0, expected 1
|
||||
55278: actual 0, expected 1
|
||||
55279: actual 0, expected 1
|
||||
55280: actual 0, expected 1
|
||||
55281: actual 0, expected 1
|
||||
55282: actual 0, expected 1
|
||||
55283: actual 0, expected 1
|
||||
55284: actual 0, expected 1
|
||||
55285: actual 0, expected 1
|
||||
55286: actual 0, expected 1
|
||||
55287: actual 0, expected 1
|
||||
55288: actual 0, expected 1
|
||||
55289: actual 0, expected 1
|
||||
55290: actual 0, expected 1
|
||||
55291: actual 0, expected 1
|
||||
5940: actual 0, expected 1
|
||||
69821: actual 1, expected 0
|
||||
69826: actual 1, expected 0
|
||||
69837: actual 1, expected 0
|
||||
110879: actual 1, expected 2
|
||||
110880: actual 1, expected 2
|
||||
110881: actual 1, expected 2
|
||||
110882: actual 1, expected 2
|
||||
110576: actual 1, expected 2
|
||||
110577: actual 1, expected 2
|
||||
110578: actual 1, expected 2
|
||||
110579: actual 1, expected 2
|
||||
110581: actual 1, expected 2
|
||||
110582: actual 1, expected 2
|
||||
110583: actual 1, expected 2
|
||||
110584: actual 1, expected 2
|
||||
110585: actual 1, expected 2
|
||||
110586: actual 1, expected 2
|
||||
110587: actual 1, expected 2
|
||||
110589: actual 1, expected 2
|
||||
110590: actual 1, expected 2
|
||||
6159: actual 1, expected 0
|
||||
69506: actual 1, expected 0
|
||||
69507: actual 1, expected 0
|
||||
69508: actual 1, expected 0
|
||||
69509: actual 1, expected 0
|
||||
129401: actual 1, expected 2
|
||||
129484: actual 1, expected 2
|
||||
129659: actual 1, expected 2
|
||||
129660: actual 1, expected 2
|
||||
129705: actual 1, expected 2
|
||||
129706: actual 1, expected 2
|
||||
129707: actual 1, expected 2
|
||||
129708: actual 1, expected 2
|
||||
129719: actual 1, expected 2
|
||||
129720: actual 1, expected 2
|
||||
129721: actual 1, expected 2
|
||||
129722: actual 1, expected 2
|
||||
129731: actual 1, expected 2
|
||||
129732: actual 1, expected 2
|
||||
129733: actual 1, expected 2
|
||||
129751: actual 1, expected 2
|
||||
129752: actual 1, expected 2
|
||||
129753: actual 1, expected 2
|
||||
129760: actual 1, expected 2
|
||||
129761: actual 1, expected 2
|
||||
129762: actual 1, expected 2
|
||||
129763: actual 1, expected 2
|
||||
129764: actual 1, expected 2
|
||||
129765: actual 1, expected 2
|
||||
129766: actual 1, expected 2
|
||||
129767: actual 1, expected 2
|
||||
129776: actual 1, expected 2
|
||||
129777: actual 1, expected 2
|
||||
129778: actual 1, expected 2
|
||||
129779: actual 1, expected 2
|
||||
129780: actual 1, expected 2
|
||||
129781: actual 1, expected 2
|
||||
129782: actual 1, expected 2
|
||||
1807: actual 1, expected 0
|
||||
3132: actual 1, expected 0
|
||||
123566: actual 1, expected 0
|
||||
128733: actual 1, expected 2
|
||||
128734: actual 1, expected 2
|
||||
128735: actual 1, expected 2
|
||||
19904: actual 2, expected 1
|
||||
19905: actual 2, expected 1
|
||||
19906: actual 2, expected 1
|
||||
19907: actual 2, expected 1
|
||||
19908: actual 2, expected 1
|
||||
19909: actual 2, expected 1
|
||||
19910: actual 2, expected 1
|
||||
19911: actual 2, expected 1
|
||||
19912: actual 2, expected 1
|
||||
19913: actual 2, expected 1
|
||||
19914: actual 2, expected 1
|
||||
19915: actual 2, expected 1
|
||||
19916: actual 2, expected 1
|
||||
19917: actual 2, expected 1
|
||||
19918: actual 2, expected 1
|
||||
19919: actual 2, expected 1
|
||||
19920: actual 2, expected 1
|
||||
19921: actual 2, expected 1
|
||||
19922: actual 2, expected 1
|
||||
19923: actual 2, expected 1
|
||||
19924: actual 2, expected 1
|
||||
19925: actual 2, expected 1
|
||||
19926: actual 2, expected 1
|
||||
19927: actual 2, expected 1
|
||||
19928: actual 2, expected 1
|
||||
19929: actual 2, expected 1
|
||||
19930: actual 2, expected 1
|
||||
19931: actual 2, expected 1
|
||||
19932: actual 2, expected 1
|
||||
19933: actual 2, expected 1
|
||||
19934: actual 2, expected 1
|
||||
19935: actual 2, expected 1
|
||||
19936: actual 2, expected 1
|
||||
19937: actual 2, expected 1
|
||||
19938: actual 2, expected 1
|
||||
19939: actual 2, expected 1
|
||||
19940: actual 2, expected 1
|
||||
19941: actual 2, expected 1
|
||||
19942: actual 2, expected 1
|
||||
19943: actual 2, expected 1
|
||||
19944: actual 2, expected 1
|
||||
19945: actual 2, expected 1
|
||||
19946: actual 2, expected 1
|
||||
19947: actual 2, expected 1
|
||||
19948: actual 2, expected 1
|
||||
19949: actual 2, expected 1
|
||||
19950: actual 2, expected 1
|
||||
19951: actual 2, expected 1
|
||||
19952: actual 2, expected 1
|
||||
19953: actual 2, expected 1
|
||||
19954: actual 2, expected 1
|
||||
19955: actual 2, expected 1
|
||||
19956: actual 2, expected 1
|
||||
19957: actual 2, expected 1
|
||||
19958: actual 2, expected 1
|
||||
19959: actual 2, expected 1
|
||||
19960: actual 2, expected 1
|
||||
19961: actual 2, expected 1
|
||||
19962: actual 2, expected 1
|
||||
19963: actual 2, expected 1
|
||||
19964: actual 2, expected 1
|
||||
19965: actual 2, expected 1
|
||||
19966: actual 2, expected 1
|
||||
19967: actual 2, expected 1
|
||||
118528: actual 1, expected 0
|
||||
118529: actual 1, expected 0
|
||||
118530: actual 1, expected 0
|
||||
118531: actual 1, expected 0
|
||||
118532: actual 1, expected 0
|
||||
118533: actual 1, expected 0
|
||||
118534: actual 1, expected 0
|
||||
118535: actual 1, expected 0
|
||||
118536: actual 1, expected 0
|
||||
118537: actual 1, expected 0
|
||||
118538: actual 1, expected 0
|
||||
118539: actual 1, expected 0
|
||||
118540: actual 1, expected 0
|
||||
118541: actual 1, expected 0
|
||||
118542: actual 1, expected 0
|
||||
118543: actual 1, expected 0
|
||||
118544: actual 1, expected 0
|
||||
118545: actual 1, expected 0
|
||||
118546: actual 1, expected 0
|
||||
118547: actual 1, expected 0
|
||||
118548: actual 1, expected 0
|
||||
118549: actual 1, expected 0
|
||||
118550: actual 1, expected 0
|
||||
118551: actual 1, expected 0
|
||||
118552: actual 1, expected 0
|
||||
118553: actual 1, expected 0
|
||||
118554: actual 1, expected 0
|
||||
118555: actual 1, expected 0
|
||||
118556: actual 1, expected 0
|
||||
118557: actual 1, expected 0
|
||||
118558: actual 1, expected 0
|
||||
118559: actual 1, expected 0
|
||||
118560: actual 1, expected 0
|
||||
118561: actual 1, expected 0
|
||||
118562: actual 1, expected 0
|
||||
118563: actual 1, expected 0
|
||||
118564: actual 1, expected 0
|
||||
118565: actual 1, expected 0
|
||||
118566: actual 1, expected 0
|
||||
118567: actual 1, expected 0
|
||||
118568: actual 1, expected 0
|
||||
118569: actual 1, expected 0
|
||||
118570: actual 1, expected 0
|
||||
118571: actual 1, expected 0
|
||||
118572: actual 1, expected 0
|
||||
118573: actual 1, expected 0
|
||||
118576: actual 1, expected 0
|
||||
118577: actual 1, expected 0
|
||||
118578: actual 1, expected 0
|
||||
118579: actual 1, expected 0
|
||||
118580: actual 1, expected 0
|
||||
118581: actual 1, expected 0
|
||||
118582: actual 1, expected 0
|
||||
118583: actual 1, expected 0
|
||||
118584: actual 1, expected 0
|
||||
118585: actual 1, expected 0
|
||||
118586: actual 1, expected 0
|
||||
118587: actual 1, expected 0
|
||||
118588: actual 1, expected 0
|
||||
118589: actual 1, expected 0
|
||||
118590: actual 1, expected 0
|
||||
118591: actual 1, expected 0
|
||||
118592: actual 1, expected 0
|
||||
118593: actual 1, expected 0
|
||||
118594: actual 1, expected 0
|
||||
118595: actual 1, expected 0
|
||||
118596: actual 1, expected 0
|
||||
118597: actual 1, expected 0
|
||||
118598: actual 1, expected 0
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
Finished release [optimized] target(s) in 0.02s
|
||||
Running `target/release/examples/measure_widths`
|
||||
55216: actual 0, expected 1
|
||||
55217: actual 0, expected 1
|
||||
55218: actual 0, expected 1
|
||||
55219: actual 0, expected 1
|
||||
55220: actual 0, expected 1
|
||||
55221: actual 0, expected 1
|
||||
55222: actual 0, expected 1
|
||||
55223: actual 0, expected 1
|
||||
55224: actual 0, expected 1
|
||||
55225: actual 0, expected 1
|
||||
55226: actual 0, expected 1
|
||||
55227: actual 0, expected 1
|
||||
55228: actual 0, expected 1
|
||||
55229: actual 0, expected 1
|
||||
55230: actual 0, expected 1
|
||||
55231: actual 0, expected 1
|
||||
55232: actual 0, expected 1
|
||||
55233: actual 0, expected 1
|
||||
55234: actual 0, expected 1
|
||||
55235: actual 0, expected 1
|
||||
55236: actual 0, expected 1
|
||||
55237: actual 0, expected 1
|
||||
55238: actual 0, expected 1
|
||||
55239: actual 0, expected 1
|
||||
55240: actual 0, expected 1
|
||||
55241: actual 0, expected 1
|
||||
55242: actual 0, expected 1
|
||||
55243: actual 0, expected 1
|
||||
55244: actual 0, expected 1
|
||||
55245: actual 0, expected 1
|
||||
55246: actual 0, expected 1
|
||||
55247: actual 0, expected 1
|
||||
55248: actual 0, expected 1
|
||||
55249: actual 0, expected 1
|
||||
55250: actual 0, expected 1
|
||||
55251: actual 0, expected 1
|
||||
55252: actual 0, expected 1
|
||||
55253: actual 0, expected 1
|
||||
55254: actual 0, expected 1
|
||||
55255: actual 0, expected 1
|
||||
55256: actual 0, expected 1
|
||||
55257: actual 0, expected 1
|
||||
55258: actual 0, expected 1
|
||||
55259: actual 0, expected 1
|
||||
55260: actual 0, expected 1
|
||||
55261: actual 0, expected 1
|
||||
55262: actual 0, expected 1
|
||||
55263: actual 0, expected 1
|
||||
55264: actual 0, expected 1
|
||||
55265: actual 0, expected 1
|
||||
55266: actual 0, expected 1
|
||||
55267: actual 0, expected 1
|
||||
55268: actual 0, expected 1
|
||||
55269: actual 0, expected 1
|
||||
55270: actual 0, expected 1
|
||||
55271: actual 0, expected 1
|
||||
55272: actual 0, expected 1
|
||||
55273: actual 0, expected 1
|
||||
55274: actual 0, expected 1
|
||||
55275: actual 0, expected 1
|
||||
55276: actual 0, expected 1
|
||||
55277: actual 0, expected 1
|
||||
55278: actual 0, expected 1
|
||||
55279: actual 0, expected 1
|
||||
55280: actual 0, expected 1
|
||||
55281: actual 0, expected 1
|
||||
55282: actual 0, expected 1
|
||||
55283: actual 0, expected 1
|
||||
55284: actual 0, expected 1
|
||||
55285: actual 0, expected 1
|
||||
55286: actual 0, expected 1
|
||||
55287: actual 0, expected 1
|
||||
55288: actual 0, expected 1
|
||||
55289: actual 0, expected 1
|
||||
55290: actual 0, expected 1
|
||||
55291: actual 0, expected 1
|
||||
55292: actual 0, expected 1
|
||||
55293: actual 0, expected 1
|
||||
55294: actual 0, expected 1
|
||||
55295: actual 0, expected 1
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
Finished release [optimized] target(s) in 0.02s
|
||||
Running `target/release/examples/measure_widths`
|
||||
55216: actual 0, expected 1
|
||||
55217: actual 0, expected 1
|
||||
55218: actual 0, expected 1
|
||||
55219: actual 0, expected 1
|
||||
55220: actual 0, expected 1
|
||||
55221: actual 0, expected 1
|
||||
55222: actual 0, expected 1
|
||||
55223: actual 0, expected 1
|
||||
55224: actual 0, expected 1
|
||||
55225: actual 0, expected 1
|
||||
55226: actual 0, expected 1
|
||||
55227: actual 0, expected 1
|
||||
55228: actual 0, expected 1
|
||||
55229: actual 0, expected 1
|
||||
55230: actual 0, expected 1
|
||||
55231: actual 0, expected 1
|
||||
55232: actual 0, expected 1
|
||||
55233: actual 0, expected 1
|
||||
55234: actual 0, expected 1
|
||||
55235: actual 0, expected 1
|
||||
55236: actual 0, expected 1
|
||||
55237: actual 0, expected 1
|
||||
55238: actual 0, expected 1
|
||||
55239: actual 0, expected 1
|
||||
55240: actual 0, expected 1
|
||||
55241: actual 0, expected 1
|
||||
55242: actual 0, expected 1
|
||||
55243: actual 0, expected 1
|
||||
55244: actual 0, expected 1
|
||||
55245: actual 0, expected 1
|
||||
55246: actual 0, expected 1
|
||||
55247: actual 0, expected 1
|
||||
55248: actual 0, expected 1
|
||||
55249: actual 0, expected 1
|
||||
55250: actual 0, expected 1
|
||||
55251: actual 0, expected 1
|
||||
55252: actual 0, expected 1
|
||||
55253: actual 0, expected 1
|
||||
55254: actual 0, expected 1
|
||||
55255: actual 0, expected 1
|
||||
55256: actual 0, expected 1
|
||||
55257: actual 0, expected 1
|
||||
55258: actual 0, expected 1
|
||||
55259: actual 0, expected 1
|
||||
55260: actual 0, expected 1
|
||||
55261: actual 0, expected 1
|
||||
55262: actual 0, expected 1
|
||||
55263: actual 0, expected 1
|
||||
55264: actual 0, expected 1
|
||||
55265: actual 0, expected 1
|
||||
55266: actual 0, expected 1
|
||||
55267: actual 0, expected 1
|
||||
55268: actual 0, expected 1
|
||||
55269: actual 0, expected 1
|
||||
55270: actual 0, expected 1
|
||||
55271: actual 0, expected 1
|
||||
55272: actual 0, expected 1
|
||||
55273: actual 0, expected 1
|
||||
55274: actual 0, expected 1
|
||||
55275: actual 0, expected 1
|
||||
55276: actual 0, expected 1
|
||||
55277: actual 0, expected 1
|
||||
55278: actual 0, expected 1
|
||||
55279: actual 0, expected 1
|
||||
55280: actual 0, expected 1
|
||||
55281: actual 0, expected 1
|
||||
55282: actual 0, expected 1
|
||||
55283: actual 0, expected 1
|
||||
55284: actual 0, expected 1
|
||||
55285: actual 0, expected 1
|
||||
55286: actual 0, expected 1
|
||||
55287: actual 0, expected 1
|
||||
55288: actual 0, expected 1
|
||||
55289: actual 0, expected 1
|
||||
55290: actual 0, expected 1
|
||||
55291: actual 0, expected 1
|
||||
55292: actual 0, expected 1
|
||||
55293: actual 0, expected 1
|
||||
55294: actual 0, expected 1
|
||||
55295: actual 0, expected 1
|
||||
427
widths-xterm
427
widths-xterm
|
|
@ -1,427 +0,0 @@
|
|||
Finished release [optimized] target(s) in 0.02s
|
||||
Running `target/release/examples/measure_widths`
|
||||
1536: actual 1, expected 0
|
||||
1537: actual 1, expected 0
|
||||
1538: actual 1, expected 0
|
||||
1539: actual 1, expected 0
|
||||
1540: actual 1, expected 0
|
||||
1541: actual 1, expected 0
|
||||
1757: actual 1, expected 0
|
||||
2250: actual 1, expected 0
|
||||
2251: actual 1, expected 0
|
||||
2252: actual 1, expected 0
|
||||
2253: actual 1, expected 0
|
||||
2254: actual 1, expected 0
|
||||
2255: actual 1, expected 0
|
||||
2256: actual 1, expected 0
|
||||
2257: actual 1, expected 0
|
||||
2258: actual 1, expected 0
|
||||
2274: actual 1, expected 0
|
||||
2192: actual 1, expected 0
|
||||
2193: actual 1, expected 0
|
||||
2200: actual 1, expected 0
|
||||
2201: actual 1, expected 0
|
||||
2202: actual 1, expected 0
|
||||
2203: actual 1, expected 0
|
||||
2204: actual 1, expected 0
|
||||
2205: actual 1, expected 0
|
||||
2206: actual 1, expected 0
|
||||
2207: actual 1, expected 0
|
||||
69744: actual 1, expected 0
|
||||
69747: actual 1, expected 0
|
||||
69748: actual 1, expected 0
|
||||
64110: actual 1, expected 2
|
||||
64111: actual 1, expected 2
|
||||
64218: actual 1, expected 2
|
||||
64219: actual 1, expected 2
|
||||
64220: actual 1, expected 2
|
||||
64221: actual 1, expected 2
|
||||
64222: actual 1, expected 2
|
||||
64223: actual 1, expected 2
|
||||
64224: actual 1, expected 2
|
||||
64225: actual 1, expected 2
|
||||
64226: actual 1, expected 2
|
||||
64227: actual 1, expected 2
|
||||
64228: actual 1, expected 2
|
||||
64229: actual 1, expected 2
|
||||
64230: actual 1, expected 2
|
||||
64231: actual 1, expected 2
|
||||
64232: actual 1, expected 2
|
||||
64233: actual 1, expected 2
|
||||
64234: actual 1, expected 2
|
||||
64235: actual 1, expected 2
|
||||
64236: actual 1, expected 2
|
||||
64237: actual 1, expected 2
|
||||
64238: actual 1, expected 2
|
||||
64239: actual 1, expected 2
|
||||
64240: actual 1, expected 2
|
||||
64241: actual 1, expected 2
|
||||
64242: actual 1, expected 2
|
||||
64243: actual 1, expected 2
|
||||
64244: actual 1, expected 2
|
||||
64245: actual 1, expected 2
|
||||
64246: actual 1, expected 2
|
||||
64247: actual 1, expected 2
|
||||
64248: actual 1, expected 2
|
||||
64249: actual 1, expected 2
|
||||
64250: actual 1, expected 2
|
||||
64251: actual 1, expected 2
|
||||
64252: actual 1, expected 2
|
||||
64253: actual 1, expected 2
|
||||
64254: actual 1, expected 2
|
||||
64255: actual 1, expected 2
|
||||
195102: actual 1, expected 2
|
||||
195103: actual 1, expected 2
|
||||
40957: actual 1, expected 2
|
||||
40958: actual 1, expected 2
|
||||
40959: actual 1, expected 2
|
||||
173790: actual 1, expected 2
|
||||
173791: actual 1, expected 2
|
||||
177973: actual 1, expected 2
|
||||
177974: actual 1, expected 2
|
||||
177975: actual 1, expected 2
|
||||
177976: actual 1, expected 2
|
||||
177977: actual 1, expected 2
|
||||
177978: actual 1, expected 2
|
||||
177979: actual 1, expected 2
|
||||
177980: actual 1, expected 2
|
||||
177981: actual 1, expected 2
|
||||
177982: actual 1, expected 2
|
||||
177983: actual 1, expected 2
|
||||
178206: actual 1, expected 2
|
||||
178207: actual 1, expected 2
|
||||
183970: actual 1, expected 2
|
||||
183971: actual 1, expected 2
|
||||
183972: actual 1, expected 2
|
||||
183973: actual 1, expected 2
|
||||
183974: actual 1, expected 2
|
||||
183975: actual 1, expected 2
|
||||
183976: actual 1, expected 2
|
||||
183977: actual 1, expected 2
|
||||
183978: actual 1, expected 2
|
||||
183979: actual 1, expected 2
|
||||
183980: actual 1, expected 2
|
||||
183981: actual 1, expected 2
|
||||
183982: actual 1, expected 2
|
||||
183983: actual 1, expected 2
|
||||
191457: actual 1, expected 2
|
||||
191458: actual 1, expected 2
|
||||
191459: actual 1, expected 2
|
||||
191460: actual 1, expected 2
|
||||
191461: actual 1, expected 2
|
||||
191462: actual 1, expected 2
|
||||
191463: actual 1, expected 2
|
||||
191464: actual 1, expected 2
|
||||
191465: actual 1, expected 2
|
||||
191466: actual 1, expected 2
|
||||
191467: actual 1, expected 2
|
||||
191468: actual 1, expected 2
|
||||
191469: actual 1, expected 2
|
||||
191470: actual 1, expected 2
|
||||
191471: actual 1, expected 2
|
||||
201547: actual 1, expected 2
|
||||
201548: actual 1, expected 2
|
||||
201549: actual 1, expected 2
|
||||
201550: actual 1, expected 2
|
||||
201551: actual 1, expected 2
|
||||
6849: actual 1, expected 0
|
||||
6850: actual 1, expected 0
|
||||
6851: actual 1, expected 0
|
||||
6852: actual 1, expected 0
|
||||
6853: actual 1, expected 0
|
||||
6854: actual 1, expected 0
|
||||
6855: actual 1, expected 0
|
||||
6856: actual 1, expected 0
|
||||
6857: actual 1, expected 0
|
||||
6858: actual 1, expected 0
|
||||
6859: actual 1, expected 0
|
||||
6860: actual 1, expected 0
|
||||
6861: actual 1, expected 0
|
||||
6862: actual 1, expected 0
|
||||
7674: actual 1, expected 0
|
||||
12872: actual 2, expected 1
|
||||
12873: actual 2, expected 1
|
||||
12874: actual 2, expected 1
|
||||
12875: actual 2, expected 1
|
||||
12876: actual 2, expected 1
|
||||
12877: actual 2, expected 1
|
||||
12878: actual 2, expected 1
|
||||
12879: actual 2, expected 1
|
||||
8206: actual 1, expected 0
|
||||
8207: actual 1, expected 0
|
||||
8234: actual 1, expected 0
|
||||
8235: actual 1, expected 0
|
||||
8236: actual 1, expected 0
|
||||
8237: actual 1, expected 0
|
||||
8238: actual 1, expected 0
|
||||
129008: actual 1, expected 2
|
||||
55216: actual 0, expected 1
|
||||
55217: actual 0, expected 1
|
||||
55218: actual 0, expected 1
|
||||
55219: actual 0, expected 1
|
||||
55220: actual 0, expected 1
|
||||
55221: actual 0, expected 1
|
||||
55222: actual 0, expected 1
|
||||
55223: actual 0, expected 1
|
||||
55224: actual 0, expected 1
|
||||
55225: actual 0, expected 1
|
||||
55226: actual 0, expected 1
|
||||
55227: actual 0, expected 1
|
||||
55228: actual 0, expected 1
|
||||
55229: actual 0, expected 1
|
||||
55230: actual 0, expected 1
|
||||
55231: actual 0, expected 1
|
||||
55232: actual 0, expected 1
|
||||
55233: actual 0, expected 1
|
||||
55234: actual 0, expected 1
|
||||
55235: actual 0, expected 1
|
||||
55236: actual 0, expected 1
|
||||
55237: actual 0, expected 1
|
||||
55238: actual 0, expected 1
|
||||
55243: actual 0, expected 1
|
||||
55244: actual 0, expected 1
|
||||
55245: actual 0, expected 1
|
||||
55246: actual 0, expected 1
|
||||
55247: actual 0, expected 1
|
||||
55248: actual 0, expected 1
|
||||
55249: actual 0, expected 1
|
||||
55250: actual 0, expected 1
|
||||
55251: actual 0, expected 1
|
||||
55252: actual 0, expected 1
|
||||
55253: actual 0, expected 1
|
||||
55254: actual 0, expected 1
|
||||
55255: actual 0, expected 1
|
||||
55256: actual 0, expected 1
|
||||
55257: actual 0, expected 1
|
||||
55258: actual 0, expected 1
|
||||
55259: actual 0, expected 1
|
||||
55260: actual 0, expected 1
|
||||
55261: actual 0, expected 1
|
||||
55262: actual 0, expected 1
|
||||
55263: actual 0, expected 1
|
||||
55264: actual 0, expected 1
|
||||
55265: actual 0, expected 1
|
||||
55266: actual 0, expected 1
|
||||
55267: actual 0, expected 1
|
||||
55268: actual 0, expected 1
|
||||
55269: actual 0, expected 1
|
||||
55270: actual 0, expected 1
|
||||
55271: actual 0, expected 1
|
||||
55272: actual 0, expected 1
|
||||
55273: actual 0, expected 1
|
||||
55274: actual 0, expected 1
|
||||
55275: actual 0, expected 1
|
||||
55276: actual 0, expected 1
|
||||
55277: actual 0, expected 1
|
||||
55278: actual 0, expected 1
|
||||
55279: actual 0, expected 1
|
||||
55280: actual 0, expected 1
|
||||
55281: actual 0, expected 1
|
||||
55282: actual 0, expected 1
|
||||
55283: actual 0, expected 1
|
||||
55284: actual 0, expected 1
|
||||
55285: actual 0, expected 1
|
||||
55286: actual 0, expected 1
|
||||
55287: actual 0, expected 1
|
||||
55288: actual 0, expected 1
|
||||
55289: actual 0, expected 1
|
||||
55290: actual 0, expected 1
|
||||
55291: actual 0, expected 1
|
||||
5940: actual 0, expected 1
|
||||
69821: actual 1, expected 0
|
||||
69826: actual 1, expected 0
|
||||
69837: actual 1, expected 0
|
||||
110879: actual 1, expected 2
|
||||
110880: actual 1, expected 2
|
||||
110881: actual 1, expected 2
|
||||
110882: actual 1, expected 2
|
||||
110576: actual 1, expected 2
|
||||
110577: actual 1, expected 2
|
||||
110578: actual 1, expected 2
|
||||
110579: actual 1, expected 2
|
||||
110581: actual 1, expected 2
|
||||
110582: actual 1, expected 2
|
||||
110583: actual 1, expected 2
|
||||
110584: actual 1, expected 2
|
||||
110585: actual 1, expected 2
|
||||
110586: actual 1, expected 2
|
||||
110587: actual 1, expected 2
|
||||
110589: actual 1, expected 2
|
||||
110590: actual 1, expected 2
|
||||
6159: actual 1, expected 0
|
||||
69506: actual 1, expected 0
|
||||
69507: actual 1, expected 0
|
||||
69508: actual 1, expected 0
|
||||
69509: actual 1, expected 0
|
||||
129401: actual 1, expected 2
|
||||
129484: actual 1, expected 2
|
||||
129659: actual 1, expected 2
|
||||
129660: actual 1, expected 2
|
||||
129705: actual 1, expected 2
|
||||
129706: actual 1, expected 2
|
||||
129707: actual 1, expected 2
|
||||
129708: actual 1, expected 2
|
||||
129719: actual 1, expected 2
|
||||
129720: actual 1, expected 2
|
||||
129721: actual 1, expected 2
|
||||
129722: actual 1, expected 2
|
||||
129731: actual 1, expected 2
|
||||
129732: actual 1, expected 2
|
||||
129733: actual 1, expected 2
|
||||
129751: actual 1, expected 2
|
||||
129752: actual 1, expected 2
|
||||
129753: actual 1, expected 2
|
||||
129760: actual 1, expected 2
|
||||
129761: actual 1, expected 2
|
||||
129762: actual 1, expected 2
|
||||
129763: actual 1, expected 2
|
||||
129764: actual 1, expected 2
|
||||
129765: actual 1, expected 2
|
||||
129766: actual 1, expected 2
|
||||
129767: actual 1, expected 2
|
||||
129776: actual 1, expected 2
|
||||
129777: actual 1, expected 2
|
||||
129778: actual 1, expected 2
|
||||
129779: actual 1, expected 2
|
||||
129780: actual 1, expected 2
|
||||
129781: actual 1, expected 2
|
||||
129782: actual 1, expected 2
|
||||
1807: actual 1, expected 0
|
||||
3132: actual 1, expected 0
|
||||
123566: actual 1, expected 0
|
||||
128733: actual 1, expected 2
|
||||
128734: actual 1, expected 2
|
||||
128735: actual 1, expected 2
|
||||
19904: actual 2, expected 1
|
||||
19905: actual 2, expected 1
|
||||
19906: actual 2, expected 1
|
||||
19907: actual 2, expected 1
|
||||
19908: actual 2, expected 1
|
||||
19909: actual 2, expected 1
|
||||
19910: actual 2, expected 1
|
||||
19911: actual 2, expected 1
|
||||
19912: actual 2, expected 1
|
||||
19913: actual 2, expected 1
|
||||
19914: actual 2, expected 1
|
||||
19915: actual 2, expected 1
|
||||
19916: actual 2, expected 1
|
||||
19917: actual 2, expected 1
|
||||
19918: actual 2, expected 1
|
||||
19919: actual 2, expected 1
|
||||
19920: actual 2, expected 1
|
||||
19921: actual 2, expected 1
|
||||
19922: actual 2, expected 1
|
||||
19923: actual 2, expected 1
|
||||
19924: actual 2, expected 1
|
||||
19925: actual 2, expected 1
|
||||
19926: actual 2, expected 1
|
||||
19927: actual 2, expected 1
|
||||
19928: actual 2, expected 1
|
||||
19929: actual 2, expected 1
|
||||
19930: actual 2, expected 1
|
||||
19931: actual 2, expected 1
|
||||
19932: actual 2, expected 1
|
||||
19933: actual 2, expected 1
|
||||
19934: actual 2, expected 1
|
||||
19935: actual 2, expected 1
|
||||
19936: actual 2, expected 1
|
||||
19937: actual 2, expected 1
|
||||
19938: actual 2, expected 1
|
||||
19939: actual 2, expected 1
|
||||
19940: actual 2, expected 1
|
||||
19941: actual 2, expected 1
|
||||
19942: actual 2, expected 1
|
||||
19943: actual 2, expected 1
|
||||
19944: actual 2, expected 1
|
||||
19945: actual 2, expected 1
|
||||
19946: actual 2, expected 1
|
||||
19947: actual 2, expected 1
|
||||
19948: actual 2, expected 1
|
||||
19949: actual 2, expected 1
|
||||
19950: actual 2, expected 1
|
||||
19951: actual 2, expected 1
|
||||
19952: actual 2, expected 1
|
||||
19953: actual 2, expected 1
|
||||
19954: actual 2, expected 1
|
||||
19955: actual 2, expected 1
|
||||
19956: actual 2, expected 1
|
||||
19957: actual 2, expected 1
|
||||
19958: actual 2, expected 1
|
||||
19959: actual 2, expected 1
|
||||
19960: actual 2, expected 1
|
||||
19961: actual 2, expected 1
|
||||
19962: actual 2, expected 1
|
||||
19963: actual 2, expected 1
|
||||
19964: actual 2, expected 1
|
||||
19965: actual 2, expected 1
|
||||
19966: actual 2, expected 1
|
||||
19967: actual 2, expected 1
|
||||
118528: actual 1, expected 0
|
||||
118529: actual 1, expected 0
|
||||
118530: actual 1, expected 0
|
||||
118531: actual 1, expected 0
|
||||
118532: actual 1, expected 0
|
||||
118533: actual 1, expected 0
|
||||
118534: actual 1, expected 0
|
||||
118535: actual 1, expected 0
|
||||
118536: actual 1, expected 0
|
||||
118537: actual 1, expected 0
|
||||
118538: actual 1, expected 0
|
||||
118539: actual 1, expected 0
|
||||
118540: actual 1, expected 0
|
||||
118541: actual 1, expected 0
|
||||
118542: actual 1, expected 0
|
||||
118543: actual 1, expected 0
|
||||
118544: actual 1, expected 0
|
||||
118545: actual 1, expected 0
|
||||
118546: actual 1, expected 0
|
||||
118547: actual 1, expected 0
|
||||
118548: actual 1, expected 0
|
||||
118549: actual 1, expected 0
|
||||
118550: actual 1, expected 0
|
||||
118551: actual 1, expected 0
|
||||
118552: actual 1, expected 0
|
||||
118553: actual 1, expected 0
|
||||
118554: actual 1, expected 0
|
||||
118555: actual 1, expected 0
|
||||
118556: actual 1, expected 0
|
||||
118557: actual 1, expected 0
|
||||
118558: actual 1, expected 0
|
||||
118559: actual 1, expected 0
|
||||
118560: actual 1, expected 0
|
||||
118561: actual 1, expected 0
|
||||
118562: actual 1, expected 0
|
||||
118563: actual 1, expected 0
|
||||
118564: actual 1, expected 0
|
||||
118565: actual 1, expected 0
|
||||
118566: actual 1, expected 0
|
||||
118567: actual 1, expected 0
|
||||
118568: actual 1, expected 0
|
||||
118569: actual 1, expected 0
|
||||
118570: actual 1, expected 0
|
||||
118571: actual 1, expected 0
|
||||
118572: actual 1, expected 0
|
||||
118573: actual 1, expected 0
|
||||
118576: actual 1, expected 0
|
||||
118577: actual 1, expected 0
|
||||
118578: actual 1, expected 0
|
||||
118579: actual 1, expected 0
|
||||
118580: actual 1, expected 0
|
||||
118581: actual 1, expected 0
|
||||
118582: actual 1, expected 0
|
||||
118583: actual 1, expected 0
|
||||
118584: actual 1, expected 0
|
||||
118585: actual 1, expected 0
|
||||
118586: actual 1, expected 0
|
||||
118587: actual 1, expected 0
|
||||
118588: actual 1, expected 0
|
||||
118589: actual 1, expected 0
|
||||
118590: actual 1, expected 0
|
||||
118591: actual 1, expected 0
|
||||
118592: actual 1, expected 0
|
||||
118593: actual 1, expected 0
|
||||
118594: actual 1, expected 0
|
||||
118595: actual 1, expected 0
|
||||
118596: actual 1, expected 0
|
||||
118597: actual 1, expected 0
|
||||
118598: actual 1, expected 0
|
||||
Loading…
Add table
Add a link
Reference in a new issue