History

editor: Make `TextAlign::Center` and `TextAlign::Right` work (#45417)

Closes https://github.com/zed-industries/zed/issues/43208 This PR essentially unblocks the editable number field. The function that shapes editor lines was hard-coding text alignment to the left, meaning that whatever different alignment we'd pass through `EditorStyles`would be ignored. To solve this, I just added a text align and align width fields to the line paint function and updated all call sites keeping the default configuration. Had to also add an `alignment_offset()` helper to make sure the cursor positioning, the selection background element, and the click-to-focus functionality were kept in-sync with the non-left aligned editor. Then... the big star of the show here is being able to add the `mode` method to the number field, which uses `TextAlign::Center`, thus making it work as we designed it to work. https://github.com/user-attachments/assets/3539c976-d7bf-4d94-8188-a14328f94fbf Next up, is turning the number filed to edit mode where applicable. Release Notes: - Fixed a bug where different text alignment configurations (i.e., center and right-aligned) wouldn't take effect in editors.

Danilo Leal committed 10 months ago

Commit 0facdfa1 parent 58461375 files · +164 −50Browse files

Unified · Split

5 files
  1. crates/editor/src/element.rs +86 −20
  2. crates/gpui/examples/input.rs +9 −2
  3. crates/gpui/src/text_system/line.rs +8 −4
  4. crates/terminal_view/src/terminal_element.rs +16 −3
  5. crates/ui_input/src/number_field.rs +45 −21
crates/editor/src/element.rs+86−20
@@ -46,9 +46,9 @@ use gpui::{ · 45 unmodified lines
4646 KeybindingKeystroke, Length, Modifiers, ModifiersChangedEvent, MouseButton, MouseClickEvent,
4747 MouseDownEvent, MouseMoveEvent, MousePressureEvent, MouseUpEvent, PaintQuad, ParentElement,
4848 Pixels, PressureStage, ScrollDelta, ScrollHandle, ScrollWheelEvent, ShapedLine, SharedString,
49− Size, StatefulInteractiveElement, Style, Styled, TextRun, TextStyleRefinement, WeakEntity,
50− Window, anchored, deferred, div, fill, linear_color_stop, linear_gradient, outline, point, px,
51− quad, relative, size, solid_background, transparent_black,
49+ Size, StatefulInteractiveElement, Style, Styled, TextAlign, TextRun, TextStyleRefinement,
50+ WeakEntity, Window, anchored, deferred, div, fill, linear_color_stop, linear_gradient, outline,
51+ point, px, quad, relative, size, solid_background, transparent_black,
5252 };
5353 use itertools::Itertools;
5454 use language::{IndentGuideSettings, language_settings::ShowWhitespaceSetting};
@@ -1695,9 +1695,13 @@ impl EditorElement { · 1640 unmodified lines
16951695 [cursor_position.row().minus(visible_display_row_range.start) as usize];
16961696 let cursor_column = cursor_position.column() as usize;
16971697
1698− let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
1699− let mut block_width =
1700− cursor_row_layout.x_for_index(cursor_column + 1) - cursor_character_x;
1698+ let cursor_character_x = cursor_row_layout.x_for_index(cursor_column)
1699+ + cursor_row_layout
1700+ .alignment_offset(self.style.text.text_align, text_hitbox.size.width);
1701+ let cursor_next_x = cursor_row_layout.x_for_index(cursor_column + 1)
1702+ + cursor_row_layout
1703+ .alignment_offset(self.style.text.text_align, text_hitbox.size.width);
1704+ let mut block_width = cursor_next_x - cursor_character_x;
17011705 if block_width == Pixels::ZERO {
17021706 block_width = em_advance;
17031707 }
@@ -6160,10 +6164,25 @@ impl EditorElement { · 4456 unmodified lines
61606164 let color = cx.theme().colors().editor_hover_line_number;
61616165
61626166 let line = self.shape_line_number(shaped_line.text.clone(), color, window);
6163− line.paint(hitbox.origin, line_height, window, cx).log_err()
6167+ line.paint(
6168+ hitbox.origin,
6169+ line_height,
6170+ TextAlign::Left,
6171+ None,
6172+ window,
6173+ cx,
6174+ )
6175+ .log_err()
61646176 } else {
61656177 shaped_line
6166− .paint(hitbox.origin, line_height, window, cx)
6178+ .paint(
6179+ hitbox.origin,
6180+ line_height,
6181+ TextAlign::Left,
6182+ None,
6183+ window,
6184+ cx,
6185+ )
61676186 .log_err()
61686187 }) else {
61696188 continue;
@@ -7252,23 +7271,27 @@ impl EditorElement { · 1082 unmodified lines
72527271 .map(|row| {
72537272 let line_layout =
72547273 &layout.position_map.line_layouts[row.minus(start_row) as usize];
7274+ let alignment_offset =
7275+ line_layout.alignment_offset(layout.text_align, layout.content_width);
72557276 HighlightedRangeLine {
72567277 start_x: if row == range.start.row() {
72577278 layout.content_origin.x
72587279 + Pixels::from(
72597280 ScrollPixelOffset::from(
7260− line_layout.x_for_index(range.start.column() as usize),
7281+ line_layout.x_for_index(range.start.column() as usize)
7282+ + alignment_offset,
72617283 ) - layout.position_map.scroll_pixel_position.x,
72627284 )
72637285 } else {
7264− layout.content_origin.x
7286+ layout.content_origin.x + alignment_offset
72657287 - Pixels::from(layout.position_map.scroll_pixel_position.x)
72667288 },
72677289 end_x: if row == range.end.row() {
72687290 layout.content_origin.x
72697291 + Pixels::from(
72707292 ScrollPixelOffset::from(
7271− line_layout.x_for_index(range.end.column() as usize),
7293+ line_layout.x_for_index(range.end.column() as usize)
7294+ + alignment_offset,
72727295 ) - layout.position_map.scroll_pixel_position.x,
72737296 )
72747297 } else {
@@ -7276,6 +7299,7 @@ impl EditorElement { · 1 unmodified line
72767299 ScrollPixelOffset::from(
72777300 layout.content_origin.x
72787301 + line_layout.width
7302+ + alignment_offset
72797303 + line_end_overshoot,
72807304 ) - layout.position_map.scroll_pixel_position.x,
72817305 )
@@ -8516,8 +8540,15 @@ impl LineWithInvisibles { · 1234 unmodified lines
85168540 for fragment in &self.fragments {
85178541 match fragment {
85188542 LineFragment::Text(line) => {
8519− line.paint(fragment_origin, line_height, window, cx)
8520− .log_err();
8543+ line.paint(
8544+ fragment_origin,
8545+ line_height,
8546+ layout.text_align,
8547+ Some(layout.content_width),
8548+ window,
8549+ cx,
8550+ )
8551+ .log_err();
85218552 fragment_origin.x += line.width;
85228553 }
85238554 LineFragment::Element { size, .. } => {
@@ -8559,8 +8590,15 @@ impl LineWithInvisibles { · 35 unmodified lines
85598590 for fragment in &self.fragments {
85608591 match fragment {
85618592 LineFragment::Text(line) => {
8562− line.paint_background(fragment_origin, line_height, window, cx)
8563− .log_err();
8593+ line.paint_background(
8594+ fragment_origin,
8595+ line_height,
8596+ layout.text_align,
8597+ Some(layout.content_width),
8598+ window,
8599+ cx,
8600+ )
8601+ .log_err();
85648602 fragment_origin.x += line.width;
85658603 }
85668604 LineFragment::Element { size, .. } => {
@@ -8609,7 +8647,7 @@ impl LineWithInvisibles { · 42 unmodified lines
86098647 [token_offset, token_end_offset],
86108648 Box::new(move |window: &mut Window, cx: &mut App| {
86118649 invisible_symbol
8612− .paint(origin, line_height, window, cx)
8650+ .paint(origin, line_height, TextAlign::Left, None, window, cx)
86138651 .log_err();
86148652 }),
86158653 )
@@ -8770,6 +8808,15 @@ impl LineWithInvisibles { · 154 unmodified lines
87708808
87718809 None
87728810 }
8811+
8812+ pub fn alignment_offset(&self, text_align: TextAlign, content_width: Pixels) -> Pixels {
8813+ let line_width = self.width;
8814+ match text_align {
8815+ TextAlign::Left => px(0.0),
8816+ TextAlign::Center => (content_width - line_width) / 2.0,
8817+ TextAlign::Right => content_width - line_width,
8818+ }
8819+ }
87738820 }
87748821
87758822 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -10172,6 +10219,8 @@ impl Element for EditorElement { · 1396 unmodified lines
1017210219 em_width,
1017310220 em_advance,
1017410221 snapshot,
10222+ text_align: self.style.text.text_align,
10223+ content_width: text_hitbox.size.width,
1017510224 gutter_hitbox: gutter_hitbox.clone(),
1017610225 text_hitbox: text_hitbox.clone(),
1017710226 inline_blame_bounds: inline_blame_layout
@@ -10225,6 +10274,8 @@ impl Element for EditorElement { · 47 unmodified lines
1022510274 sticky_buffer_header,
1022610275 sticky_headers,
1022710276 expand_toggles,
10277+ text_align: self.style.text.text_align,
10278+ content_width: text_hitbox.size.width,
1022810279 }
1022910280 })
1023010281 })
@@ -10405,6 +10456,8 @@ pub struct EditorLayout { · 174 unmodified lines
1040510456 sticky_buffer_header: Option<AnyElement>,
1040610457 sticky_headers: Option<StickyHeaders>,
1040710458 document_colors: Option<(DocumentColorsRenderMode, Vec<(Range<DisplayPoint>, Hsla)>)>,
10459+ text_align: TextAlign,
10460+ content_width: Pixels,
1040810461 }
1040910462
1041010463 struct StickyHeaders {
@@ -10572,7 +10625,9 @@ impl StickyHeaderLine { · 161 unmodified lines
1057210625 gutter_origin.x + gutter_width - gutter_right_padding - line_number.width,
1057310626 gutter_origin.y,
1057410627 );
10575− line_number.paint(origin, line_height, window, cx).log_err();
10628+ line_number
10629+ .paint(origin, line_height, TextAlign::Left, None, window, cx)
10630+ .log_err();
1057610631 }
1057710632 }
1057810633 }
@@ -11011,6 +11066,8 @@ pub(crate) struct PositionMap { · 432 unmodified lines
1101111066 pub visible_row_range: Range<DisplayRow>,
1101211067 pub line_layouts: Vec<LineWithInvisibles>,
1101311068 pub snapshot: EditorSnapshot,
11069+ pub text_align: TextAlign,
11070+ pub content_width: Pixels,
1101411071 pub text_hitbox: Hitbox,
1101511072 pub gutter_hitbox: Hitbox,
1101611073 pub inline_blame_bounds: Option<(Bounds<Pixels>, BufferId, BlameEntry)>,
@@ -11076,10 +11133,12 @@ impl PositionMap { · 59 unmodified lines
1107611133 .line_layouts
1107711134 .get(row as usize - scroll_position.y as usize)
1107811135 {
11079− if let Some(ix) = line.index_for_x(x) {
11136+ let alignment_offset = line.alignment_offset(self.text_align, self.content_width);
11137+ let x_relative_to_text = x - alignment_offset;
11138+ if let Some(ix) = line.index_for_x(x_relative_to_text) {
1108011139 (ix as u32, px(0.))
1108111140 } else {
11082− (line.len as u32, px(0.).max(x - line.width))
11141+ (line.len as u32, px(0.).max(x_relative_to_text - line.width))
1108311142 }
1108411143 } else {
1108511144 (0, x)
@@ -11268,7 +11327,14 @@ impl CursorLayout { · 182 unmodified lines
1126811327
1126911328 if let Some(block_text) = &self.block_text {
1127011329 block_text
11271− .paint(self.origin + origin, self.line_height, window, cx)
11330+ .paint(
11331+ self.origin + origin,
11332+ self.line_height,
11333+ TextAlign::Left,
11334+ None,
11335+ window,
11336+ cx,
11337+ )
1127211338 .log_err();
1127311339 }
1127411340 }
crates/gpui/examples/input.rs+9−2
@@ -546,8 +546,15 @@ impl Element for TextElement { · 545 unmodified lines
546546 window.paint_quad(selection)
547547 }
548548 let line = prepaint.line.take().unwrap();
549− line.paint(bounds.origin, window.line_height(), window, cx)
550− .unwrap();
549+ line.paint(
550+ bounds.origin,
551+ window.line_height(),
552+ gpui::TextAlign::Left,
553+ None,
554+ window,
555+ cx,
556+ )
557+ .unwrap();
551558
552559 if focus_handle.is_focused(window)
553560 && let Some(cursor) = prepaint.cursor.take()
crates/gpui/src/text_system/line.rs+8−4
@@ -64,6 +64,8 @@ impl ShapedLine { · 63 unmodified lines
6464 &self,
6565 origin: Point<Pixels>,
6666 line_height: Pixels,
67+ align: TextAlign,
68+ align_width: Option<Pixels>,
6769 window: &mut Window,
6870 cx: &mut App,
6971 ) -> Result<()> {
@@ -71,8 +73,8 @@ impl ShapedLine { · 1 unmodified line
7173 origin,
7274 &self.layout,
7375 line_height,
74− TextAlign::default(),
75− None,
76+ align,
77+ align_width,
7678 &self.decoration_runs,
7779 &[],
7880 window,
@@ -87,6 +89,8 @@ impl ShapedLine { · 8 unmodified lines
8789 &self,
8890 origin: Point<Pixels>,
8991 line_height: Pixels,
92+ align: TextAlign,
93+ align_width: Option<Pixels>,
9094 window: &mut Window,
9195 cx: &mut App,
9296 ) -> Result<()> {
@@ -94,8 +98,8 @@ impl ShapedLine { · 1 unmodified line
9498 origin,
9599 &self.layout,
96100 line_height,
97− TextAlign::default(),
98− None,
101+ align,
102+ align_width,
99103 &self.decoration_runs,
100104 &[],
101105 window,
crates/terminal_view/src/terminal_element.rs+16−3
@@ -151,7 +151,14 @@ impl BatchedTextRun { · 150 unmodified lines
151151 std::slice::from_ref(&self.style),
152152 Some(dimensions.cell_width),
153153 )
154− .paint(pos, dimensions.line_height, window, cx);
154+ .paint(
155+ pos,
156+ dimensions.line_height,
157+ gpui::TextAlign::Left,
158+ None,
159+ window,
160+ cx,
161+ );
155162 }
156163 }
157164
@@ -1326,8 +1333,14 @@ impl Element for TerminalElement { · 1168 unmodified lines
13261333 }],
13271334 None
13281335 );
1329− shaped_line
1330− .paint(ime_position, layout.dimensions.line_height, window, cx)
1336+ shaped_line.paint(
1337+ ime_position,
1338+ layout.dimensions.line_height,
1339+ gpui::TextAlign::Left,
1340+ None,
1341+ window,
1342+ cx,
1343+ )
13311344 .log_err();
13321345 }
13331346
crates/ui_input/src/number_field.rs+45−21
@@ -5,8 +5,11 @@ use std::{ · 4 unmodified lines
55 str::FromStr,
66 };
77
8−use editor::{Editor, EditorStyle};
9−use gpui::{ClickEvent, Entity, FocusHandle, Focusable, FontWeight, Modifiers};
8+use editor::Editor;
9+use gpui::{
10+ ClickEvent, Entity, FocusHandle, Focusable, FontWeight, Modifiers, TextAlign,
11+ TextStyleRefinement,
12+};
1013
1114 use settings::{CenteredPaddingSettings, CodeFade, DelayMs, InactiveOpacity, MinimumContrast};
1215 use ui::prelude::*;
@@ -309,6 +312,11 @@ impl<T: NumberFieldType> NumberField<T> { · 296 unmodified lines
309312 self
310313 }
311314
315+ pub fn mode(self, mode: NumberFieldMode, cx: &mut App) -> Self {
316+ self.mode.write(cx, mode);
317+ self
318+ }
319+
312320 pub fn on_reset(
313321 mut self,
314322 on_reset: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
@@ -451,9 +459,11 @@ impl<T: NumberFieldType> RenderOnce for NumberField<T> { · 136 unmodified lines
451459 |window, cx| {
452460 let previous_focus_handle = window.focused(cx);
453461 let mut editor = Editor::single_line(window, cx);
454− let mut style = EditorStyle::default();
455− style.text.text_align = gpui::TextAlign::Right;
456− editor.set_style(style, window, cx);
462+
463+ editor.set_text_style_refinement(TextStyleRefinement {
464+ text_align: Some(TextAlign::Center),
465+ ..Default::default()
466+ });
457467
458468 editor.set_text(format!("{}", self.value), window, cx);
459469 cx.on_focus_out(&editor.focus_handle(cx), window, {
@@ -555,22 +565,36 @@ impl Component for NumberField<usize> { · 95 unmodified lines
555565 Some(
556566 v_flex()
557567 .gap_6()
558− .children(vec![single_example(
559− "Default Numeric Stepper",
560− NumberField::new(
561− "numeric-stepper-component-preview",
562− *stepper_example.read(cx),
563− window,
564− cx,
565− )
566− .on_change({
567− let stepper_example = stepper_example.clone();
568− move |value, _, cx| stepper_example.write(cx, *value)
569− })
570− .min(1.0)
571− .max(100.0)
572− .into_any_element(),
573− )])
568+ .children(vec![
569+ single_example(
570+ "Default Number Field",
571+ NumberField::new("number-field", *stepper_example.read(cx), window, cx)
572+ .on_change({
573+ let stepper_example = stepper_example.clone();
574+ move |value, _, cx| stepper_example.write(cx, *value)
575+ })
576+ .min(1.0)
577+ .max(100.0)
578+ .into_any_element(),
579+ ),
580+ single_example(
581+ "Read-Only Number Field",
582+ NumberField::new(
583+ "editable-number-field",
584+ *stepper_example.read(cx),
585+ window,
586+ cx,
587+ )
588+ .on_change({
589+ let stepper_example = stepper_example.clone();
590+ move |value, _, cx| stepper_example.write(cx, *value)
591+ })
592+ .min(1.0)
593+ .max(100.0)
594+ .mode(NumberFieldMode::Edit, cx)
595+ .into_any_element(),
596+ ),
597+ ])
574598 .into_any_element(),
575599 )
576600 }