gpui: Add line_clamp to truncate text after a specified number of lines (#23058)

Release Notes:

- N/A

Add this feature for some case we need keep 2 or 3 lines, but truncate.
For example the blog post summary.

- Added `line_clamp` method.
    Ref: https://tailwindcss.com/docs/line-clamp


## Break changes:

- Renamed `gpui::Truncate` to `gpui::TextOverflow` to match
[CSS](https://developer.mozilla.org/en-US/docs/Web/CSS/text-overflow).
- Update `truncate` style method to match [Tailwind
CSS](https://tailwindcss.com/docs/text-overflow) behavior:

    ```css
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    ```
<img width="538" alt="image"
src="https://github.com/user-attachments/assets/c69c4213-eac9-4087-9daa-ce7afe18c758"
/>


## Show case

<img width="816" alt="image"
src="https://github.com/user-attachments/assets/e0660290-8042-4954-b93c-c729d609484a"
/>

![CleanShot 2025-01-13 at 17 22
05](https://github.com/user-attachments/assets/38644892-79fe-4254-af9e-88c1349561bd)

## Describe changes

The [second
commit](6b41c2772f)
for make sure text layout to match with the line clamp. Before this
change, they may wrap many lines in sometimes. And I also make
line_clamp default to 1 if we used `truncate` to ensure no wrap.

> TODO: There is still a tiny detail that is not easy to fix. This
problem only occurs in the case of certain long words. I will think
about how to improve it later. At present, this has some flaws but does
not affect the use.
This commit is contained in:
Jason Lee 2025-01-30 04:14:24 +08:00 committed by GitHub
parent baac01cea4
commit 706f7be5e7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 118 additions and 66 deletions

View file

@ -129,9 +129,9 @@ impl LineLayout {
&self,
text: &str,
wrap_width: Pixels,
max_lines: Option<usize>,
) -> SmallVec<[WrapBoundary; 1]> {
let mut boundaries = SmallVec::new();
let mut first_non_whitespace_ix = None;
let mut last_candidate_ix = None;
let mut last_candidate_x = px(0.);
@ -182,7 +182,15 @@ impl LineLayout {
let next_x = glyphs.peek().map_or(self.width, |(_, _, x)| *x);
let width = next_x - last_boundary_x;
if width > wrap_width && boundary > last_boundary {
// When used line_clamp, we should limit the number of lines.
if let Some(max_lines) = max_lines {
if boundaries.len() >= max_lines - 1 {
break;
}
}
if let Some(last_candidate_ix) = last_candidate_ix.take() {
last_boundary = last_candidate_ix;
last_boundary_x = last_candidate_x;
@ -190,7 +198,6 @@ impl LineLayout {
last_boundary = boundary;
last_boundary_x = x;
}
boundaries.push(last_boundary);
}
prev_ch = ch;
@ -434,6 +441,7 @@ impl LineLayoutCache {
font_size: Pixels,
runs: &[FontRun],
wrap_width: Option<Pixels>,
max_lines: Option<usize>,
) -> Arc<WrappedLineLayout>
where
Text: AsRef<str>,
@ -464,7 +472,7 @@ impl LineLayoutCache {
let text = SharedString::from(text);
let unwrapped_layout = self.layout_line::<&SharedString>(&text, font_size, runs);
let wrap_boundaries = if let Some(wrap_width) = wrap_width {
unwrapped_layout.compute_wrap_boundaries(text.as_ref(), wrap_width)
unwrapped_layout.compute_wrap_boundaries(text.as_ref(), wrap_width, max_lines)
} else {
SmallVec::new()
};