Skip to content

Commit 62b6b85

Browse files
Updated systems to work correctly with multiwidth characters
1 parent 1d0c9ca commit 62b6b85

File tree

6 files changed

+86
-11
lines changed

6 files changed

+86
-11
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ doctest = false
1414
[dependencies]
1515
compact_str = "0.9.0"
1616
crossterm = "0.29.0"
17+
unicode-width = "0.2.1"
1718

1819
[dev-dependencies]
1920
regex = "1.10.3"

examples/multiwidth.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
2+
use ascii_forge::prelude::*;
3+
use std::{io, time::Duration};
4+
5+
fn main() -> io::Result<()> {
6+
// Will init the window for you, handling all required procedures.
7+
let mut window = Window::init()?;
8+
9+
// Ask the system to handle panics for us.
10+
handle_panics();
11+
12+
loop {
13+
// Ask the window to draw, handle events, and fix sizing issues.
14+
// Duration is the time for which to poll events before re-rendering.
15+
window.update(Duration::from_millis(200))?;
16+
17+
// Render elements to the window
18+
render!(
19+
window,
20+
vec2(0, 0) => [ "Normal: Hello World!" ],
21+
vec2(0, 1) => [ "Wide: 👩‍👩‍👧‍👦 and 🚀" ],
22+
vec2(0, 2) => [ "Mixed: a👩‍👩‍👧‍👦b🚀c" ],
23+
vec2(0, 4) => [ "Press `Enter` to exit!".red() ],
24+
);
25+
26+
// Check if the Enter Key was pressed, and exit the app if it was.
27+
if event!(window, Event::Key(e) => e.code == KeyCode::Enter) {
28+
break;
29+
}
30+
}
31+
32+
// Restore the window, enabling the window to function normally again
33+
// If nothing will be run after this, once the window is dropped, this will be run implicitly.
34+
window.restore()
35+
}

src/math.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use std::ops::{Add, AddAssign, Sub, SubAssign};
22

3-
/// A 2d Vector that has no math, is only used as a pretty version of a tuple of u16s
3+
/// A 2d Vector
44
/// Can be made from (u16, u16).
55
/// Using a single u16.into() will create a vec2 where both values are the same.
6+
/// Basic Mathematic Operations are supported
67
#[derive(Default, Debug, Eq, PartialEq, PartialOrd, Ord, Copy, Clone)]
78
pub struct Vec2 {
89
pub x: u16,

src/renderer/buffer.rs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,22 @@ impl Buffer {
5050

5151
/// Sets a cell at the given location to the given cell
5252
pub fn set<C: Into<Cell>>(&mut self, loc: impl Into<Vec2>, cell: C) {
53+
let loc = loc.into();
5354
let idx = self.index_of(loc);
5455

5556
// Ignore if cell is out of bounds
5657
let Some(idx) = idx else {
5758
return;
5859
};
5960

60-
self.cells[idx] = cell.into();
61+
let cell = cell.into();
62+
63+
// Overwrite the next cell if the character is wide
64+
if cell.width() > 1 {
65+
self.set(loc + vec2(1, 0), Cell::default());
66+
}
67+
68+
self.cells[idx] = cell;
6169
}
6270

6371
/// Sets all cells at the given location to the given cell
@@ -101,14 +109,23 @@ impl Buffer {
101109
assert!(self.size == other.size);
102110

103111
let mut res = vec![];
112+
let mut skip = 0;
104113

105114
for x in 0..self.size.x {
106115
for y in 0..self.size.y {
107-
if self.get((x, y)) != other.get((x, y)) {
108-
res.push((
109-
vec2(x, y),
110-
other.get((x, y)).expect("Cell should be in bounds"),
111-
))
116+
if skip > 0 {
117+
skip -= 1;
118+
continue;
119+
}
120+
121+
let old = self.get((x, y));
122+
let new = other.get((x, y));
123+
124+
if old != new {
125+
if let Some(new) = new {
126+
skip = new.width().saturating_sub(1) as usize;
127+
res.push((vec2(x, y), new))
128+
}
112129
}
113130
}
114131
}

src/renderer/cell.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ use std::fmt::Display;
22

33
use crate::prelude::*;
44
use compact_str::{CompactString, ToCompactString};
5+
use unicode_width::UnicodeWidthStr;
56

67
/// A cell that stores a symbol, and the style that will be applied to it.
78
#[derive(Debug, Clone, Eq, PartialEq)]
89
pub struct Cell {
910
text: CompactString,
1011
style: ContentStyle,
12+
width: u16,
1113
}
1214

1315
impl Default for Cell {
@@ -18,33 +20,45 @@ impl Default for Cell {
1820

1921
impl Cell {
2022
pub fn new<S: Into<ContentStyle>>(text: impl Into<CompactString>, style: S) -> Self {
23+
let text = text.into();
2124
Self {
22-
text: text.into(),
25+
width: text.width() as u16,
26+
text,
2327
style: style.into(),
2428
}
2529
}
2630

2731
pub fn string(string: impl AsRef<str>) -> Self {
32+
let text = CompactString::new(string);
2833
Self {
29-
text: CompactString::new(string),
34+
width: text.width() as u16,
35+
text,
3036
style: ContentStyle::default(),
3137
}
3238
}
3339

3440
pub fn chr(chr: char) -> Self {
41+
let text = chr.to_compact_string();
3542
Self {
36-
text: chr.to_compact_string(),
43+
width: text.width() as u16,
44+
text,
3745
style: ContentStyle::default(),
3846
}
3947
}
4048

4149
pub fn styled<D: Display>(content: StyledContent<D>) -> Self {
50+
let text = CompactString::new(format!("{}", content.content()));
4251
Self {
43-
text: CompactString::new(format!("{}", content.content())),
52+
width: text.width() as u16,
53+
text,
4454
style: *content.style(),
4555
}
4656
}
4757

58+
pub fn width(&self) -> u16 {
59+
self.width
60+
}
61+
4862
pub fn is_empty(&self) -> bool {
4963
self.text.trim().is_empty()
5064
}

0 commit comments

Comments
 (0)