History

Introduce `InteractiveText` (#3397)

This new element will let us react to click events on arbitrary ranges of some rendered text, e.g.: ```rs InteractiveText::new( "element-id", StyledText::new("Hello world, how is it going?").with_runs(vec![ cx.text_style().to_run(6), TextRun { background_color: Some(green()), ..cx.text_style().to_run(5) }, cx.text_style().to_run(18), ]), ) .on_click(vec![2..4, 1..3, 7..9], |range_ix, cx| { println!("Clicked range {range_ix}"); }) ``` As part of this, I also added the ability to give text runs a background color. Release Notes: - N/A

Antonio Scandurra committed 2 years ago

Commit 510320b2 parents df7b89b 7eeb80711 files · +262 −33Browse files

Unified · Split

11 files
  1. crates/editor2/src/editor.rs +2 −0
  2. crates/editor2/src/element.rs +1 −1
  3. crates/gpui2/src/elements/text.rs +116 −19
  4. crates/gpui2/src/style.rs +13 −1
  5. crates/gpui2/src/styled.rs +7 −0
  6. crates/gpui2/src/text_system.rs +9 −2
  7. crates/gpui2/src/text_system/line.rs +59 −5
  8. crates/gpui2/src/text_system/line_layout.rs +35 −0
  9. crates/gpui2/src/window.rs +1 −2
  10. crates/storybook2/src/stories/text.rs +18 −2
  11. crates/ui2/src/components/label.rs +1 −1
crates/editor2/src/editor.rs+2−0
@@ -9423,6 +9423,7 @@ impl Render for Editor { · 9422 unmodified lines
9423 font_weight: FontWeight::NORMAL,9423 font_weight: FontWeight::NORMAL,
9424 font_style: FontStyle::Normal,9424 font_style: FontStyle::Normal,
9425 line_height: relative(1.).into(),9425 line_height: relative(1.).into(),
9426 background_color: None,
9426 underline: None,9427 underline: None,
9427 white_space: WhiteSpace::Normal,9428 white_space: WhiteSpace::Normal,
9428 },9429 },
@@ -9437,6 +9438,7 @@ impl Render for Editor { · 8 unmodified lines
9437 font_weight: FontWeight::NORMAL,9438 font_weight: FontWeight::NORMAL,
9438 font_style: FontStyle::Normal,9439 font_style: FontStyle::Normal,
9439 line_height: relative(settings.buffer_line_height.value()),9440 line_height: relative(settings.buffer_line_height.value()),
9441 background_color: None,
9440 underline: None,9442 underline: None,
9441 white_space: WhiteSpace::Normal,9443 white_space: WhiteSpace::Normal,
9442 },9444 },
crates/editor2/src/element.rs+1−1
@@ -2452,7 +2452,7 @@ impl LineWithInvisibles { · 2451 unmodified lines
2452 len: line_chunk.len(),2452 len: line_chunk.len(),
2453 font: text_style.font(),2453 font: text_style.font(),
2454 color: text_style.color,2454 color: text_style.color,
2455 background_color: None,2455 background_color: text_style.background_color,
2456 underline: text_style.underline,2456 underline: text_style.underline,
2457 });2457 });
2458 2458
crates/gpui2/src/elements/text.rs+116−19
@@ -1,11 +1,11 @@
1use crate::{1use crate::{
2 Bounds, Element, ElementId, IntoElement, LayoutId, Pixels, SharedString, Size, TextRun,2 Bounds, DispatchPhase, Element, ElementId, IntoElement, LayoutId, MouseDownEvent, MouseUpEvent,
3 WhiteSpace, WindowContext, WrappedLine,3 Pixels, Point, SharedString, Size, TextRun, WhiteSpace, WindowContext, WrappedLine,
4};4};
5use anyhow::anyhow;5use anyhow::anyhow;
6use parking_lot::{Mutex, MutexGuard};6use parking_lot::{Mutex, MutexGuard};
7use smallvec::SmallVec;7use smallvec::SmallVec;
8use std::{cell::Cell, rc::Rc, sync::Arc};8use std::{cell::Cell, ops::Range, rc::Rc, sync::Arc};
9use util::ResultExt;9use util::ResultExt;
10 10
11impl Element for &'static str {11impl Element for &'static str {
@@ -69,23 +69,28 @@ impl IntoElement for SharedString { · 57 unmodified lines
69 }69 }
70}70}
71 71
72/// Renders text with runs of different styles.
73///
74/// Callers are responsible for setting the correct style for each run.
75/// For text with a uniform style, you can usually avoid calling this constructor
76/// and just pass text directly.
72pub struct StyledText {77pub struct StyledText {
73 text: SharedString,78 text: SharedString,
74 runs: Option<Vec<TextRun>>,79 runs: Option<Vec<TextRun>>,
75}80}
76 81
77impl StyledText {82impl StyledText {
78 /// Renders text with runs of different styles.83 pub fn new(text: impl Into<SharedString>) -> Self {
79 ///
80 /// Callers are responsible for setting the correct style for each run.
81 /// For text with a uniform style, you can usually avoid calling this constructor
82 /// and just pass text directly.
83 pub fn new(text: SharedString, runs: Vec<TextRun>) -> Self {
84 StyledText {84 StyledText {
85 text,85 text: text.into(),
86 runs: Some(runs),86 runs: None,
87 }87 }
88 }88 }
89
90 pub fn with_runs(mut self, runs: Vec<TextRun>) -> Self {
91 self.runs = Some(runs);
92 self
93 }
89}94}
90 95
91impl Element for StyledText {96impl Element for StyledText {
@@ -226,16 +231,73 @@ impl TextState { · 134 unmodified lines
226 line_origin.y += line.size(line_height).height;231 line_origin.y += line.size(line_height).height;
227 }232 }
228 }233 }
234
235 fn index_for_position(&self, bounds: Bounds<Pixels>, position: Point<Pixels>) -> Option<usize> {
236 if !bounds.contains_point(&position) {
237 return None;
238 }
239
240 let element_state = self.lock();
241 let element_state = element_state
242 .as_ref()
243 .expect("measurement has not been performed");
244
245 let line_height = element_state.line_height;
246 let mut line_origin = bounds.origin;
247 for line in &element_state.lines {
248 let line_bottom = line_origin.y + line.size(line_height).height;
249 if position.y > line_bottom {
250 line_origin.y = line_bottom;
251 } else {
252 let position_within_line = position - line_origin;
253 return line.index_for_position(position_within_line, line_height);
254 }
255 }
256
257 None
258 }
229}259}
230 260
231struct InteractiveText {261pub struct InteractiveText {
232 element_id: ElementId,262 element_id: ElementId,
233 text: StyledText,263 text: StyledText,
264 click_listener: Option<Box<dyn Fn(InteractiveTextClickEvent, &mut WindowContext<'_>)>>,
234}265}
235 266
236struct InteractiveTextState {267struct InteractiveTextClickEvent {
268 mouse_down_index: usize,
269 mouse_up_index: usize,
270}
271
272pub struct InteractiveTextState {
237 text_state: TextState,273 text_state: TextState,
238 clicked_range_ixs: Rc<Cell<SmallVec<[usize; 1]>>>,274 mouse_down_index: Rc<Cell<Option<usize>>>,
275}
276
277impl InteractiveText {
278 pub fn new(id: impl Into<ElementId>, text: StyledText) -> Self {
279 Self {
280 element_id: id.into(),
281 text,
282 click_listener: None,
283 }
284 }
285
286 pub fn on_click(
287 mut self,
288 ranges: Vec<Range<usize>>,
289 listener: impl Fn(usize, &mut WindowContext<'_>) + 'static,
290 ) -> Self {
291 self.click_listener = Some(Box::new(move |event, cx| {
292 for (range_ix, range) in ranges.iter().enumerate() {
293 if range.contains(&event.mouse_down_index) && range.contains(&event.mouse_up_index)
294 {
295 listener(range_ix, cx);
296 }
297 }
298 }));
299 self
300 }
239}301}
240 302
241impl Element for InteractiveText {303impl Element for InteractiveText {
@@ -247,27 +309,62 @@ impl Element for InteractiveText { · 5 unmodified lines
247 cx: &mut WindowContext,309 cx: &mut WindowContext,
248 ) -> (LayoutId, Self::State) {310 ) -> (LayoutId, Self::State) {
249 if let Some(InteractiveTextState {311 if let Some(InteractiveTextState {
250 text_state,312 mouse_down_index, ..
251 clicked_range_ixs,
252 }) = state313 }) = state
253 {314 {
254 let (layout_id, text_state) = self.text.layout(Some(text_state), cx);315 let (layout_id, text_state) = self.text.layout(None, cx);
255 let element_state = InteractiveTextState {316 let element_state = InteractiveTextState {
256 text_state,317 text_state,
257 clicked_range_ixs,318 mouse_down_index,
258 };319 };
259 (layout_id, element_state)320 (layout_id, element_state)
260 } else {321 } else {
261 let (layout_id, text_state) = self.text.layout(None, cx);322 let (layout_id, text_state) = self.text.layout(None, cx);
262 let element_state = InteractiveTextState {323 let element_state = InteractiveTextState {
263 text_state,324 text_state,
264 clicked_range_ixs: Rc::default(),325 mouse_down_index: Rc::default(),
265 };326 };
266 (layout_id, element_state)327 (layout_id, element_state)
267 }328 }
268 }329 }
269 330
270 fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {331 fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
332 if let Some(click_listener) = self.click_listener {
333 let text_state = state.text_state.clone();
334 let mouse_down = state.mouse_down_index.clone();
335 if let Some(mouse_down_index) = mouse_down.get() {
336 cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
337 if phase == DispatchPhase::Bubble {
338 if let Some(mouse_up_index) =
339 text_state.index_for_position(bounds, event.position)
340 {
341 click_listener(
342 InteractiveTextClickEvent {
343 mouse_down_index,
344 mouse_up_index,
345 },
346 cx,
347 )
348 }
349
350 mouse_down.take();
351 cx.notify();
352 }
353 });
354 } else {
355 cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
356 if phase == DispatchPhase::Bubble {
357 if let Some(mouse_down_index) =
358 text_state.index_for_position(bounds, event.position)
359 {
360 mouse_down.set(Some(mouse_down_index));
361 cx.notify();
362 }
363 }
364 });
365 }
366 }
367
271 self.text.paint(bounds, &mut state.text_state, cx)368 self.text.paint(bounds, &mut state.text_state, cx)
272 }369 }
273}370}
crates/gpui2/src/style.rs+13−1
@@ -145,6 +145,7 @@ pub struct TextStyle { · 144 unmodified lines
145 pub line_height: DefiniteLength,145 pub line_height: DefiniteLength,
146 pub font_weight: FontWeight,146 pub font_weight: FontWeight,
147 pub font_style: FontStyle,147 pub font_style: FontStyle,
148 pub background_color: Option<Hsla>,
148 pub underline: Option<UnderlineStyle>,149 pub underline: Option<UnderlineStyle>,
149 pub white_space: WhiteSpace,150 pub white_space: WhiteSpace,
150}151}
@@ -159,6 +160,7 @@ impl Default for TextStyle { · 8 unmodified lines
159 line_height: phi(),160 line_height: phi(),
160 font_weight: FontWeight::default(),161 font_weight: FontWeight::default(),
161 font_style: FontStyle::default(),162 font_style: FontStyle::default(),
163 background_color: None,
162 underline: None,164 underline: None,
163 white_space: WhiteSpace::Normal,165 white_space: WhiteSpace::Normal,
164 }166 }
@@ -182,6 +184,10 @@ impl TextStyle { · 17 unmodified lines
182 self.color.fade_out(factor);184 self.color.fade_out(factor);
183 }185 }
184 186
187 if let Some(background_color) = style.background_color {
188 self.background_color = Some(background_color);
189 }
190
185 if let Some(underline) = style.underline {191 if let Some(underline) = style.underline {
186 self.underline = Some(underline);192 self.underline = Some(underline);
187 }193 }
@@ -212,7 +218,7 @@ impl TextStyle { · 24 unmodified lines
212 style: self.font_style,218 style: self.font_style,
213 },219 },
214 color: self.color,220 color: self.color,
215 background_color: None,221 background_color: self.background_color,
216 underline: self.underline.clone(),222 underline: self.underline.clone(),
217 }223 }
218 }224 }
@@ -223,6 +229,7 @@ pub struct HighlightStyle { · 4 unmodified lines
223 pub color: Option<Hsla>,229 pub color: Option<Hsla>,
224 pub font_weight: Option<FontWeight>,230 pub font_weight: Option<FontWeight>,
225 pub font_style: Option<FontStyle>,231 pub font_style: Option<FontStyle>,
232 pub background_color: Option<Hsla>,
226 pub underline: Option<UnderlineStyle>,233 pub underline: Option<UnderlineStyle>,
227 pub fade_out: Option<f32>,234 pub fade_out: Option<f32>,
228}235}
@@ -441,6 +448,7 @@ impl From<&TextStyle> for HighlightStyle { · 212 unmodified lines
441 color: Some(other.color),448 color: Some(other.color),
442 font_weight: Some(other.font_weight),449 font_weight: Some(other.font_weight),
443 font_style: Some(other.font_style),450 font_style: Some(other.font_style),
451 background_color: other.background_color,
444 underline: other.underline.clone(),452 underline: other.underline.clone(),
445 fade_out: None,453 fade_out: None,
446 }454 }
@@ -467,6 +475,10 @@ impl HighlightStyle { · 20 unmodified lines
467 self.font_style = other.font_style;475 self.font_style = other.font_style;
468 }476 }
469 477
478 if other.background_color.is_some() {
479 self.background_color = other.background_color;
480 }
481
470 if other.underline.is_some() {482 if other.underline.is_some() {
471 self.underline = other.underline;483 self.underline = other.underline;
472 }484 }
crates/gpui2/src/styled.rs+7−0
@@ -361,6 +361,13 @@ pub trait Styled: Sized { · 360 unmodified lines
361 self361 self
362 }362 }
363 363
364 fn text_bg(mut self, bg: impl Into<Hsla>) -> Self {
365 self.text_style()
366 .get_or_insert_with(Default::default)
367 .background_color = Some(bg.into());
368 self
369 }
370
364 fn text_size(mut self, size: impl Into<AbsoluteLength>) -> Self {371 fn text_size(mut self, size: impl Into<AbsoluteLength>) -> Self {
365 self.text_style()372 self.text_style()
366 .get_or_insert_with(Default::default)373 .get_or_insert_with(Default::default)
crates/gpui2/src/text_system.rs+9−2
@@ -196,7 +196,10 @@ impl TextSystem { · 195 unmodified lines
196 let mut decoration_runs = SmallVec::<[DecorationRun; 32]>::new();196 let mut decoration_runs = SmallVec::<[DecorationRun; 32]>::new();
197 for run in runs {197 for run in runs {
198 if let Some(last_run) = decoration_runs.last_mut() {198 if let Some(last_run) = decoration_runs.last_mut() {
199 if last_run.color == run.color && last_run.underline == run.underline {199 if last_run.color == run.color
200 && last_run.underline == run.underline
201 && last_run.background_color == run.background_color
202 {
200 last_run.len += run.len as u32;203 last_run.len += run.len as u32;
201 continue;204 continue;
202 }205 }
@@ -204,6 +207,7 @@ impl TextSystem { · 1 unmodified line
204 decoration_runs.push(DecorationRun {207 decoration_runs.push(DecorationRun {
205 len: run.len as u32,208 len: run.len as u32,
206 color: run.color,209 color: run.color,
210 background_color: run.background_color,
207 underline: run.underline.clone(),211 underline: run.underline.clone(),
208 });212 });
209 }213 }
@@ -254,13 +258,16 @@ impl TextSystem { · 44 unmodified lines
254 }258 }
255 259
256 if decoration_runs.last().map_or(false, |last_run| {260 if decoration_runs.last().map_or(false, |last_run| {
257 last_run.color == run.color && last_run.underline == run.underline261 last_run.color == run.color
262 && last_run.underline == run.underline
263 && last_run.background_color == run.background_color
258 }) {264 }) {
259 decoration_runs.last_mut().unwrap().len += run_len_within_line as u32;265 decoration_runs.last_mut().unwrap().len += run_len_within_line as u32;
260 } else {266 } else {
261 decoration_runs.push(DecorationRun {267 decoration_runs.push(DecorationRun {
262 len: run_len_within_line as u32,268 len: run_len_within_line as u32,
263 color: run.color,269 color: run.color,
270 background_color: run.background_color,
264 underline: run.underline.clone(),271 underline: run.underline.clone(),
265 });272 });
266 }273 }
crates/gpui2/src/text_system/line.rs+59−5
@@ -1,6 +1,7 @@
1use crate::{1use crate::{
2 black, point, px, BorrowWindow, Bounds, Hsla, LineLayout, Pixels, Point, Result, SharedString,2 black, point, px, size, transparent_black, BorrowWindow, Bounds, Corners, Edges, Hsla,
3 UnderlineStyle, WindowContext, WrapBoundary, WrappedLineLayout,3 LineLayout, Pixels, Point, Result, SharedString, UnderlineStyle, WindowContext, WrapBoundary,
4 WrappedLineLayout,
4};5};
5use derive_more::{Deref, DerefMut};6use derive_more::{Deref, DerefMut};
6use smallvec::SmallVec;7use smallvec::SmallVec;
@@ -10,6 +11,7 @@ use std::sync::Arc; · 3 unmodified lines
10pub struct DecorationRun {11pub struct DecorationRun {
11 pub len: u32,12 pub len: u32,
12 pub color: Hsla,13 pub color: Hsla,
14 pub background_color: Option<Hsla>,
13 pub underline: Option<UnderlineStyle>,15 pub underline: Option<UnderlineStyle>,
14}16}
15 17
@@ -97,6 +99,7 @@ fn paint_line( · 81 unmodified lines
97 let mut run_end = 0;99 let mut run_end = 0;
98 let mut color = black();100 let mut color = black();
99 let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;101 let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
102 let mut current_background: Option<(Point<Pixels>, Hsla)> = None;
100 let text_system = cx.text_system().clone();103 let text_system = cx.text_system().clone();
101 let mut glyph_origin = origin;104 let mut glyph_origin = origin;
102 let mut prev_glyph_position = Point::default();105 let mut prev_glyph_position = Point::default();
@@ -110,12 +113,24 @@ fn paint_line( · 7 unmodified lines
110 113
111 if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {114 if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
112 wraps.next();115 wraps.next();
116 if let Some((background_origin, background_color)) = current_background.take() {
117 cx.paint_quad(
118 Bounds {
119 origin: background_origin,
120 size: size(glyph_origin.x - background_origin.x, line_height),
121 },
122 Corners::default(),
123 background_color,
124 Edges::default(),
125 transparent_black(),
126 );
127 }
113 if let Some((underline_origin, underline_style)) = current_underline.take() {128 if let Some((underline_origin, underline_style)) = current_underline.take() {
114 cx.paint_underline(129 cx.paint_underline(
115 underline_origin,130 underline_origin,
116 glyph_origin.x - underline_origin.x,131 glyph_origin.x - underline_origin.x,
117 &underline_style,132 &underline_style,
118 )?;133 );
119 }134 }
120 135
121 glyph_origin.x = origin.x;136 glyph_origin.x = origin.x;
@@ -123,9 +138,20 @@ fn paint_line( · 1 unmodified line
123 }138 }
124 prev_glyph_position = glyph.position;139 prev_glyph_position = glyph.position;
125 140
141 let mut finished_background: Option<(Point<Pixels>, Hsla)> = None;
126 let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;142 let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
127 if glyph.index >= run_end {143 if glyph.index >= run_end {
128 if let Some(style_run) = decoration_runs.next() {144 if let Some(style_run) = decoration_runs.next() {
145 if let Some((_, background_color)) = &mut current_background {
146 if style_run.background_color.as_ref() != Some(background_color) {
147 finished_background = current_background.take();
148 }
149 }
150 if let Some(run_background) = style_run.background_color {
151 current_background
152 .get_or_insert((point(glyph_origin.x, origin.y), run_background));
153 }
154
129 if let Some((_, underline_style)) = &mut current_underline {155 if let Some((_, underline_style)) = &mut current_underline {
130 if style_run.underline.as_ref() != Some(underline_style) {156 if style_run.underline.as_ref() != Some(underline_style) {
131 finished_underline = current_underline.take();157 finished_underline = current_underline.take();
@@ -149,16 +175,30 @@ fn paint_line( · 17 unmodified lines
149 color = style_run.color;175 color = style_run.color;
150 } else {176 } else {
151 run_end = layout.len;177 run_end = layout.len;
178 finished_background = current_background.take();
152 finished_underline = current_underline.take();179 finished_underline = current_underline.take();
153 }180 }
154 }181 }
155 182
183 if let Some((background_origin, background_color)) = finished_background {
184 cx.paint_quad(
185 Bounds {
186 origin: background_origin,
187 size: size(glyph_origin.x - background_origin.x, line_height),
188 },
189 Corners::default(),
190 background_color,
191 Edges::default(),
192 transparent_black(),
193 );
194 }
195
156 if let Some((underline_origin, underline_style)) = finished_underline {196 if let Some((underline_origin, underline_style)) = finished_underline {
157 cx.paint_underline(197 cx.paint_underline(
158 underline_origin,198 underline_origin,
159 glyph_origin.x - underline_origin.x,199 glyph_origin.x - underline_origin.x,
160 &underline_style,200 &underline_style,
161 )?;201 );
162 }202 }
163 203
164 let max_glyph_bounds = Bounds {204 let max_glyph_bounds = Bounds {
@@ -188,13 +228,27 @@ fn paint_line( · 23 unmodified lines
188 }228 }
189 }229 }
190 230
231 if let Some((background_origin, background_color)) = current_background.take() {
232 let line_end_x = origin.x + wrap_width.unwrap_or(Pixels::MAX).min(layout.width);
233 cx.paint_quad(
234 Bounds {
235 origin: background_origin,
236 size: size(line_end_x - background_origin.x, line_height),
237 },
238 Corners::default(),
239 background_color,
240 Edges::default(),
241 transparent_black(),
242 );
243 }
244
191 if let Some((underline_start, underline_style)) = current_underline.take() {245 if let Some((underline_start, underline_style)) = current_underline.take() {
192 let line_end_x = origin.x + wrap_width.unwrap_or(Pixels::MAX).min(layout.width);246 let line_end_x = origin.x + wrap_width.unwrap_or(Pixels::MAX).min(layout.width);
193 cx.paint_underline(247 cx.paint_underline(
194 underline_start,248 underline_start,
195 line_end_x - underline_start.x,249 line_end_x - underline_start.x,
196 &underline_style,250 &underline_style,
197 )?;251 );
198 }252 }
199 253
200 Ok(())254 Ok(())
crates/gpui2/src/text_system/line_layout.rs+35−0
@@ -198,6 +198,41 @@ impl WrappedLineLayout { · 197 unmodified lines
198 pub fn runs(&self) -> &[ShapedRun] {198 pub fn runs(&self) -> &[ShapedRun] {
199 &self.unwrapped_layout.runs199 &self.unwrapped_layout.runs
200 }200 }
201
202 pub fn index_for_position(
203 &self,
204 position: Point<Pixels>,
205 line_height: Pixels,
206 ) -> Option<usize> {
207 let wrapped_line_ix = (position.y / line_height) as usize;
208
209 let wrapped_line_start_x = if wrapped_line_ix > 0 {
210 let wrap_boundary_ix = wrapped_line_ix - 1;
211 let wrap_boundary = self.wrap_boundaries[wrap_boundary_ix];
212 let run = &self.unwrapped_layout.runs[wrap_boundary.run_ix];
213 run.glyphs[wrap_boundary.glyph_ix].position.x
214 } else {
215 Pixels::ZERO
216 };
217
218 let wrapped_line_end_x = if wrapped_line_ix < self.wrap_boundaries.len() {
219 let next_wrap_boundary_ix = wrapped_line_ix;
220 let next_wrap_boundary = self.wrap_boundaries[next_wrap_boundary_ix];
221 let run = &self.unwrapped_layout.runs[next_wrap_boundary.run_ix];
222 run.glyphs[next_wrap_boundary.glyph_ix].position.x
223 } else {
224 self.unwrapped_layout.width
225 };
226
227 let mut position_in_unwrapped_line = position;
228 position_in_unwrapped_line.x += wrapped_line_start_x;
229 if position_in_unwrapped_line.x > wrapped_line_end_x {
230 None
231 } else {
232 self.unwrapped_layout
233 .index_for_x(position_in_unwrapped_line.x)
234 }
235 }
201}236}
202 237
203pub(crate) struct LineLayoutCache {238pub(crate) struct LineLayoutCache {
crates/gpui2/src/window.rs+1−2
@@ -881,7 +881,7 @@ impl<'a> WindowContext<'a> { · 880 unmodified lines
881 origin: Point<Pixels>,881 origin: Point<Pixels>,
882 width: Pixels,882 width: Pixels,
883 style: &UnderlineStyle,883 style: &UnderlineStyle,
884 ) -> Result<()> {884 ) {
885 let scale_factor = self.scale_factor();885 let scale_factor = self.scale_factor();
886 let height = if style.wavy {886 let height = if style.wavy {
887 style.thickness * 3.887 style.thickness * 3.
@@ -905,7 +905,6 @@ impl<'a> WindowContext<'a> { · 17 unmodified lines
905 wavy: style.wavy,905 wavy: style.wavy,
906 },906 },
907 );907 );
908 Ok(())
909 }908 }
910 909
911 /// Paint a monochrome (non-emoji) glyph into the scene for the current frame at the current z-index.910 /// Paint a monochrome (non-emoji) glyph into the scene for the current frame at the current z-index.
crates/storybook2/src/stories/text.rs+18−2
@@ -1,5 +1,6 @@
1use gpui::{1use gpui::{
2 blue, div, red, white, Div, ParentElement, Render, Styled, View, VisualContext, WindowContext,2 blue, div, green, red, white, Div, InteractiveText, ParentElement, Render, Styled, StyledText,
3 TextRun, View, VisualContext, WindowContext,
3};4};
4use ui::v_stack;5use ui::v_stack;
5 6
@@ -55,6 +56,21 @@ impl Render for TextStory { · 49 unmodified lines
55 "flex-row. width 96. The quick brown fox jumps over the lazy dog. ",56 "flex-row. width 96. The quick brown fox jumps over the lazy dog. ",
56 "Meanwhile, the lazy dog decided it was time for a change. ",57 "Meanwhile, the lazy dog decided it was time for a change. ",
57 "He started daily workout routines, ate healthier and became the fastest dog in town.",58 "He started daily workout routines, ate healthier and became the fastest dog in town.",
58 )))59 ))).child(
60 InteractiveText::new(
61 "interactive",
62 StyledText::new("Hello world, how is it going?").with_runs(vec![
63 cx.text_style().to_run(6),
64 TextRun {
65 background_color: Some(green()),
66 ..cx.text_style().to_run(5)
67 },
68 cx.text_style().to_run(18),
69 ]),
70 )
71 .on_click(vec![2..4, 1..3, 7..9], |range_ix, cx| {
72 println!("Clicked range {range_ix}");
73 })
74 )
59 }75 }
60}76}
crates/ui2/src/components/label.rs+1−1
@@ -150,7 +150,7 @@ impl RenderOnce for HighlightedLabel { · 149 unmodified lines
150 LabelSize::Default => this.text_ui(),150 LabelSize::Default => this.text_ui(),
151 LabelSize::Small => this.text_ui_sm(),151 LabelSize::Small => this.text_ui_sm(),
152 })152 })
153 .child(StyledText::new(self.label, runs))153 .child(StyledText::new(self.label).with_runs(runs))
154 }154 }
155}155}
156 156

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t