on master help sign in
Refactor new Rect construction (#575)
f9375f4Gokul Soumya
helix-term/src/ui/editor.rs+8−16
@@ -108,13 +108,11 @@ impl EditorView {
108
108
109 self.render_diagnostics(doc, view, inner, surface, theme);
109 self.render_diagnostics(doc, view, inner, surface, theme);
110
110
111− let area = Rect::new(
111+ let statusline_area = view
112− view.area.x,
112+ .area
113− view.area.y + view.area.height.saturating_sub(1),
113+ .clip_top(view.area.height.saturating_sub(1))
114− view.area.width,
114+ .clip_bottom(1); // -1 from bottom to remove commandline
115− 1,
116− );
117− self.render_statusline(doc, view, area, surface, theme, is_focused);
115+ self.render_statusline(doc, view, statusline_area, surface, theme, is_focused);
118 }
116 }
119
117
120 /// Get syntax highlights for a document in a view represented by the first line
118 /// Get syntax highlights for a document in a view represented by the first line
@@ -528,12 +526,7 @@ impl EditorView {
528 let width = 80.min(viewport.width);
526 let width = 80.min(viewport.width);
529 let height = 15.min(viewport.height);
527 let height = 15.min(viewport.height);
530 paragraph.render(
528 paragraph.render(
531− Rect::new(
532− viewport.right() - width,
529+ Rect::new(viewport.right() - width, viewport.y + 1, width, height),
533− viewport.y as u16 + 1,
534− width,
535− height,
536− ),
537 surface,
530 surface,
538 );
531 );
539 }
532 }
@@ -572,7 +565,7 @@ impl EditorView {
572 theme.get("ui.statusline.inactive")
565 theme.get("ui.statusline.inactive")
573 };
566 };
574 // statusline
567 // statusline
575− surface.set_style(Rect::new(viewport.x, viewport.y, viewport.width, 1), style);
568+ surface.set_style(viewport.with_height(1), style);
576 if is_focused {
569 if is_focused {
577 surface.set_string(viewport.x + 1, viewport.y, mode, style);
570 surface.set_string(viewport.x + 1, viewport.y, mode, style);
578 }
571 }
@@ -1001,8 +994,7 @@ impl Component for EditorView {
1001 surface.set_style(area, cx.editor.theme.get("ui.background"));
994 surface.set_style(area, cx.editor.theme.get("ui.background"));
1002
995
1003 // if the terminal size suddenly changed, we need to trigger a resize
996 // if the terminal size suddenly changed, we need to trigger a resize
1004− cx.editor
1005− .resize(Rect::new(area.x, area.y, area.width, area.height - 1)); // - 1 to account for commandline
997+ cx.editor.resize(area.clip_bottom(1)); // -1 from bottom for commandline
1006
998
1007 for (view, is_focused) in cx.editor.tree.views() {
999 for (view, is_focused) in cx.editor.tree.views() {
1008 let doc = cx.editor.document(view.doc).unwrap();
1000 let doc = cx.editor.document(view.doc).unwrap();
helix-term/src/ui/markdown.rs+6−3
@@ -13,7 +13,7 @@ use helix_core::{
13 Rope,
13 Rope,
14};
14};
15use helix_view::{
15use helix_view::{
16− graphics::{Color, Rect, Style},
16+ graphics::{Color, Margin, Rect, Style},
17 Theme,
17 Theme,
18};
18};
19
19
@@ -207,8 +207,11 @@ impl Component for Markdown {
207 .wrap(Wrap { trim: false })
207 .wrap(Wrap { trim: false })
208 .scroll((cx.scroll.unwrap_or_default() as u16, 0));
208 .scroll((cx.scroll.unwrap_or_default() as u16, 0));
209
209
210− let area = Rect::new(area.x + 1, area.y + 1, area.width - 2, area.height - 2);
210+ let margin = Margin {
211+ vertical: 1,
212+ horizontal: 1,
213+ };
211− par.render(area, surface);
214+ par.render(area.inner(&margin), surface);
212 }
215 }
213
216
214 fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
217 fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
helix-term/src/ui/menu.rs−8
@@ -304,14 +304,6 @@ impl<T: Item + 'static> Component for Menu<T> {
304 },
304 },
305 );
305 );
306
306
307− // // TODO: set bg for the whole row if selected
308− // if line == self.cursor {
309− // surface.set_style(
310− // Rect::new(area.x, area.y + i as u16, area.width - 1, 1),
311− // selected,
312− // )
313− // }
314−
315 for (i, _) in (scroll..(scroll + win_height).min(len)).enumerate() {
307 for (i, _) in (scroll..(scroll + win_height).min(len)).enumerate() {
316 let is_marked = i >= scroll_line && i < scroll_line + scroll_height;
308 let is_marked = i >= scroll_line && i < scroll_line + scroll_height;
317
309
helix-term/src/ui/picker.rs+18−19
@@ -19,7 +19,7 @@ use helix_core::Position;
19use helix_view::{
19use helix_view::{
20 document::canonicalize_path,
20 document::canonicalize_path,
21 editor::Action,
21 editor::Action,
22− graphics::{Color, CursorKind, Rect, Style},
22+ graphics::{Color, CursorKind, Margin, Rect, Style},
23 Document, Editor,
23 Document, Editor,
24};
24};
25
25
@@ -90,23 +90,26 @@ impl<T: 'static> Component for FilePicker<T> {
90 area.width
90 area.width
91 };
91 };
92
92
93− let picker_area = Rect::new(area.x, area.y, picker_width, area.height);
93+ let picker_area = area.with_width(picker_width);
94 self.picker.render(picker_area, surface, cx);
94 self.picker.render(picker_area, surface, cx);
95
95
96 if !render_preview {
96 if !render_preview {
97 return;
97 return;
98 }
98 }
99
99
100− let preview_area = Rect::new(area.x + picker_width, area.y, area.width / 2, area.height);
100+ let preview_area = area.clip_left(picker_width);
101
101
102 // don't like this but the lifetime sucks
102 // don't like this but the lifetime sucks
103 let block = Block::default().borders(Borders::ALL);
103 let block = Block::default().borders(Borders::ALL);
104
104
105 // calculate the inner area inside the box
105 // calculate the inner area inside the box
106− let mut inner = block.inner(preview_area);
106+ let inner = block.inner(preview_area);
107 // 1 column gap on either side
107 // 1 column gap on either side
108− inner.x += 1;
108+ let margin = Margin {
109− inner.width = inner.width.saturating_sub(2);
109+ vertical: 1,
110+ horizontal: 0,
111+ };
112+ let inner = inner.inner(&margin);
110
113
111 block.render(preview_area, surface);
114 block.render(preview_area, surface);
112
115
@@ -282,15 +285,11 @@ impl<T> Picker<T> {
282// - score all the names in relation to input
285// - score all the names in relation to input
283
286
284fn inner_rect(area: Rect) -> Rect {
287fn inner_rect(area: Rect) -> Rect {
288+ let margin = Margin {
285− let padding_vertical = area.height * 10 / 100;
289+ vertical: area.height * 10 / 100,
286− let padding_horizontal = area.width * 10 / 100;
290+ horizontal: area.width * 10 / 100,
287−
291+ };
288− Rect::new(
292+ area.inner(&margin)
289− area.x + padding_horizontal,
290− area.y + padding_vertical,
291− area.width - padding_horizontal * 2,
292− area.height - padding_vertical * 2,
293− )
294}
293}
295
294
296impl<T: 'static> Component for Picker<T> {
295impl<T: 'static> Component for Picker<T> {
@@ -410,7 +409,7 @@ impl<T: 'static> Component for Picker<T> {
410
409
411 // -- Render the input bar:
410 // -- Render the input bar:
412
411
413− let area = Rect::new(inner.x + 1, inner.y, inner.width - 1, 1);
412+ let area = inner.clip_left(1).with_height(1);
414
413
415 let count = format!("{}/{}", self.matches.len(), self.options.len());
414 let count = format!("{}/{}", self.matches.len(), self.options.len());
416 surface.set_stringn(
415 surface.set_stringn(
@@ -434,8 +433,8 @@ impl<T: 'static> Component for Picker<T> {
434 }
433 }
435
434
436 // -- Render the contents:
435 // -- Render the contents:
437− // subtract the area of the prompt (-2) and current item marker " > " (-3)
436+ // subtract area of prompt from top and current item marker " > " from left
438− let inner = Rect::new(inner.x + 3, inner.y + 2, inner.width - 3, inner.height - 2);
437+ let inner = inner.clip_top(2).clip_left(3);
439
438
440 let selected = cx.editor.theme.get("ui.text.focus");
439 let selected = cx.editor.theme.get("ui.text.focus");
441
440
@@ -474,7 +473,7 @@ impl<T: 'static> Component for Picker<T> {
474 let inner = block.inner(area);
473 let inner = block.inner(area);
475
474
476 // prompt area
475 // prompt area
477− let area = Rect::new(inner.x + 1, inner.y, inner.width - 1, 1);
476+ let area = inner.clip_left(1).with_height(1);
478
477
479 self.prompt.cursor(area, editor)
478 self.prompt.cursor(area, editor)
480 }
479 }
helix-view/src/graphics.rs+86−1
@@ -24,7 +24,7 @@ pub struct Margin {
24}
24}
25
25
26/// A simple rectangle used in the computation of the layout and to give widgets an hint about the
26/// A simple rectangle used in the computation of the layout and to give widgets an hint about the
27−/// area they are supposed to render to.
27+/// area they are supposed to render to. (x, y) = (0, 0) is at the top left corner of the screen.
28#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
28#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
29pub struct Rect {
29pub struct Rect {
30 pub x: u16,
30 pub x: u16,
@@ -92,6 +92,57 @@ impl Rect {
92 self.y.saturating_add(self.height)
92 self.y.saturating_add(self.height)
93 }
93 }
94
94
95+ // Returns a new Rect with width reduced from the left side.
96+ // This changes the `x` coordinate and clamps it to the right
97+ // edge of the original Rect.
98+ pub fn clip_left(self, width: u16) -> Rect {
99+ let width = std::cmp::min(width, self.width);
100+ Rect {
101+ x: self.x.saturating_add(width),
102+ width: self.width.saturating_sub(width),
103+ ..self
104+ }
105+ }
106+
107+ // Returns a new Rect with width reduced from the right side.
108+ // This does _not_ change the `x` coordinate.
109+ pub fn clip_right(self, width: u16) -> Rect {
110+ Rect {
111+ width: self.width.saturating_sub(width),
112+ ..self
113+ }
114+ }
115+
116+ // Returns a new Rect with height reduced from the top.
117+ // This changes the `y` coordinate and clamps it to the bottom
118+ // edge of the original Rect.
119+ pub fn clip_top(self, height: u16) -> Rect {
120+ let height = std::cmp::min(height, self.height);
121+ Rect {
122+ y: self.y.saturating_add(height),
123+ height: self.height.saturating_sub(height),
124+ ..self
125+ }
126+ }
127+
128+ // Returns a new Rect with height reduced from the bottom.
129+ // This does _not_ change the `y` coordinate.
130+ pub fn clip_bottom(self, height: u16) -> Rect {
131+ Rect {
132+ height: self.height.saturating_sub(height),
133+ ..self
134+ }
135+ }
136+
137+ pub fn with_height(self, height: u16) -> Rect {
138+ // new height may make area > u16::max_value, so use new()
139+ Self::new(self.x, self.y, self.width, height)
140+ }
141+
142+ pub fn with_width(self, width: u16) -> Rect {
143+ Self::new(self.x, self.y, width, self.height)
144+ }
145+
95 pub fn inner(self, margin: &Margin) -> Rect {
146 pub fn inner(self, margin: &Margin) -> Rect {
96 if self.width < 2 * margin.horizontal || self.height < 2 * margin.vertical {
147 if self.width < 2 * margin.horizontal || self.height < 2 * margin.vertical {
97 Rect::default()
148 Rect::default()
@@ -495,6 +546,40 @@ mod tests {
495 assert_eq!(rect.height, 100);
546 assert_eq!(rect.height, 100);
496 }
547 }
497
548
549+ #[test]
550+ fn test_rect_chop_from_left() {
551+ let rect = Rect::new(0, 0, 20, 30);
552+ assert_eq!(Rect::new(10, 0, 10, 30), rect.clip_left(10));
553+ assert_eq!(
554+ Rect::new(20, 0, 0, 30),
555+ rect.clip_left(40),
556+ "x should be clamped to original width if new width is bigger"
557+ );
558+ }
559+
560+ #[test]
561+ fn test_rect_chop_from_right() {
562+ let rect = Rect::new(0, 0, 20, 30);
563+ assert_eq!(Rect::new(0, 0, 10, 30), rect.clip_right(10));
564+ }
565+
566+ #[test]
567+ fn test_rect_chop_from_top() {
568+ let rect = Rect::new(0, 0, 20, 30);
569+ assert_eq!(Rect::new(0, 10, 20, 20), rect.clip_top(10));
570+ assert_eq!(
571+ Rect::new(0, 30, 20, 0),
572+ rect.clip_top(50),
573+ "y should be clamped to original height if new height is bigger"
574+ );
575+ }
576+
577+ #[test]
578+ fn test_rect_chop_from_bottom() {
579+ let rect = Rect::new(0, 0, 20, 30);
580+ assert_eq!(Rect::new(0, 0, 20, 20), rect.clip_bottom(10));
581+ }
582+
498 fn styles() -> Vec<Style> {
583 fn styles() -> Vec<Style> {
499 vec![
584 vec![
500 Style::default(),
585 Style::default(),
helix-view/src/view.rs+1−6
@@ -83,12 +83,7 @@ impl View {
83 pub fn inner_area(&self) -> Rect {
83 pub fn inner_area(&self) -> Rect {
84 // TODO: not ideal
84 // TODO: not ideal
85 const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
85 const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
86− Rect::new(
87− self.area.x + OFFSET,
86+ self.area.clip_left(OFFSET).clip_bottom(1) // -1 for statusline
88− self.area.y,
89− self.area.width - OFFSET,
90− self.area.height.saturating_sub(1), // -1 for statusline
91− )
92 }
87 }
93
88
94 pub fn ensure_cursor_in_view(&mut self, doc: &Document, scrolloff: usize) {
89 pub fn ensure_cursor_in_view(&mut self, doc: &Document, scrolloff: usize) {