on master help sign in
Refactor new Rect construction (#575)

* Refactor new Rect construction Introduces methods that can be chained to construct new Rects out of pre-existing ones * Clamp x and y to edges in Rect chop methods * Rename Rect clipping functions

commitf9375f449c8b7bbf792c88b89903fe38d524f2e5 authorGokul Soumya <gokulps15@gmail.com> dateSat 21 Aug 2021, 10:51 (5 years ago) parentac8bc54
6 files changed+119−53
helix-term/src/ui/editor.rs+8−16 helix-term/src/ui/markdown.rs+6−3 helix-term/src/ui/menu.rs−8 helix-term/src/ui/picker.rs+18−19 helix-view/src/graphics.rs+86−1 helix-view/src/view.rs+1−6
helix-term/src/ui/editor.rs+8−16
@@ -108,13 +108,11 @@ impl EditorView {
108108
109109 self.render_diagnostics(doc, view, inner, surface, theme);
110110
111− let area = Rect::new(
112− view.area.x,
113− view.area.y + view.area.height.saturating_sub(1),
114− view.area.width,
115− 1,
116− );
117− self.render_statusline(doc, view, area, surface, theme, is_focused);
111+ let statusline_area = view
112+ .area
113+ .clip_top(view.area.height.saturating_sub(1))
114+ .clip_bottom(1); // -1 from bottom to remove commandline
115+ self.render_statusline(doc, view, statusline_area, surface, theme, is_focused);
118116 }
119117
120118 /// Get syntax highlights for a document in a view represented by the first line
@@ -528,12 +526,7 @@ impl EditorView {
528526 let width = 80.min(viewport.width);
529527 let height = 15.min(viewport.height);
530528 paragraph.render(
531− Rect::new(
532− viewport.right() - width,
533− viewport.y as u16 + 1,
534− width,
535− height,
536− ),
529+ Rect::new(viewport.right() - width, viewport.y + 1, width, height),
537530 surface,
538531 );
539532 }
@@ -572,7 +565,7 @@ impl EditorView {
572565 theme.get("ui.statusline.inactive")
573566 };
574567 // statusline
575− surface.set_style(Rect::new(viewport.x, viewport.y, viewport.width, 1), style);
568+ surface.set_style(viewport.with_height(1), style);
576569 if is_focused {
577570 surface.set_string(viewport.x + 1, viewport.y, mode, style);
578571 }
@@ -1001,8 +994,7 @@ impl Component for EditorView {
1001994 surface.set_style(area, cx.editor.theme.get("ui.background"));
1002995
1003996 // 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
1006998
1007999 for (view, is_focused) in cx.editor.tree.views() {
10081000 let doc = cx.editor.document(view.doc).unwrap();
helix-term/src/ui/markdown.rs+6−3
@@ -13,7 +13,7 @@ use helix_core::{
1313 Rope,
1414};
1515use helix_view::{
16− graphics::{Color, Rect, Style},
16+ graphics::{Color, Margin, Rect, Style},
1717 Theme,
1818};
1919
@@ -207,8 +207,11 @@ impl Component for Markdown {
207207 .wrap(Wrap { trim: false })
208208 .scroll((cx.scroll.unwrap_or_default() as u16, 0));
209209
210− let area = Rect::new(area.x + 1, area.y + 1, area.width - 2, area.height - 2);
211− par.render(area, surface);
210+ let margin = Margin {
211+ vertical: 1,
212+ horizontal: 1,
213+ };
214+ par.render(area.inner(&margin), surface);
212215 }
213216
214217 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> {
304304 },
305305 );
306306
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−
315307 for (i, _) in (scroll..(scroll + win_height).min(len)).enumerate() {
316308 let is_marked = i >= scroll_line && i < scroll_line + scroll_height;
317309
helix-term/src/ui/picker.rs+18−19
@@ -19,7 +19,7 @@ use helix_core::Position;
1919use helix_view::{
2020 document::canonicalize_path,
2121 editor::Action,
22− graphics::{Color, CursorKind, Rect, Style},
22+ graphics::{Color, CursorKind, Margin, Rect, Style},
2323 Document, Editor,
2424};
2525
@@ -90,23 +90,26 @@ impl<T: 'static> Component for FilePicker<T> {
9090 area.width
9191 };
9292
93− let picker_area = Rect::new(area.x, area.y, picker_width, area.height);
93+ let picker_area = area.with_width(picker_width);
9494 self.picker.render(picker_area, surface, cx);
9595
9696 if !render_preview {
9797 return;
9898 }
9999
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);
101101
102102 // don't like this but the lifetime sucks
103103 let block = Block::default().borders(Borders::ALL);
104104
105105 // calculate the inner area inside the box
106− let mut inner = block.inner(preview_area);
106+ let inner = block.inner(preview_area);
107107 // 1 column gap on either side
108− inner.x += 1;
109− inner.width = inner.width.saturating_sub(2);
108+ let margin = Margin {
109+ vertical: 1,
110+ horizontal: 0,
111+ };
112+ let inner = inner.inner(&margin);
110113
111114 block.render(preview_area, surface);
112115
@@ -282,15 +285,11 @@ impl<T> Picker<T> {
282285// - score all the names in relation to input
283286
284287fn inner_rect(area: Rect) -> Rect {
285− let padding_vertical = area.height * 10 / 100;
286− let padding_horizontal = area.width * 10 / 100;
287−
288− Rect::new(
289− area.x + padding_horizontal,
290− area.y + padding_vertical,
291− area.width - padding_horizontal * 2,
292− area.height - padding_vertical * 2,
293− )
288+ let margin = Margin {
289+ vertical: area.height * 10 / 100,
290+ horizontal: area.width * 10 / 100,
291+ };
292+ area.inner(&margin)
294293}
295294
296295impl<T: 'static> Component for Picker<T> {
@@ -410,7 +409,7 @@ impl<T: 'static> Component for Picker<T> {
410409
411410 // -- Render the input bar:
412411
413− let area = Rect::new(inner.x + 1, inner.y, inner.width - 1, 1);
412+ let area = inner.clip_left(1).with_height(1);
414413
415414 let count = format!("{}/{}", self.matches.len(), self.options.len());
416415 surface.set_stringn(
@@ -434,8 +433,8 @@ impl<T: 'static> Component for Picker<T> {
434433 }
435434
436435 // -- Render the contents:
437− // subtract the area of the prompt (-2) and current item marker " > " (-3)
438− let inner = Rect::new(inner.x + 3, inner.y + 2, inner.width - 3, inner.height - 2);
436+ // subtract area of prompt from top and current item marker " > " from left
437+ let inner = inner.clip_top(2).clip_left(3);
439438
440439 let selected = cx.editor.theme.get("ui.text.focus");
441440
@@ -474,7 +473,7 @@ impl<T: 'static> Component for Picker<T> {
474473 let inner = block.inner(area);
475474
476475 // 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);
478477
479478 self.prompt.cursor(area, editor)
480479 }
helix-view/src/graphics.rs+86−1
@@ -24,7 +24,7 @@ pub struct Margin {
2424}
2525
2626/// 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.
2828#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
2929pub struct Rect {
3030 pub x: u16,
@@ -92,6 +92,57 @@ impl Rect {
9292 self.y.saturating_add(self.height)
9393 }
9494
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+
95146 pub fn inner(self, margin: &Margin) -> Rect {
96147 if self.width < 2 * margin.horizontal || self.height < 2 * margin.vertical {
97148 Rect::default()
@@ -495,6 +546,40 @@ mod tests {
495546 assert_eq!(rect.height, 100);
496547 }
497548
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+
498583 fn styles() -> Vec<Style> {
499584 vec![
500585 Style::default(),
helix-view/src/view.rs+1−6
@@ -83,12 +83,7 @@ impl View {
8383 pub fn inner_area(&self) -> Rect {
8484 // TODO: not ideal
8585 const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
86− Rect::new(
87− self.area.x + OFFSET,
88− self.area.y,
89− self.area.width - OFFSET,
90− self.area.height.saturating_sub(1), // -1 for statusline
91− )
86+ self.area.clip_left(OFFSET).clip_bottom(1) // -1 for statusline
9287 }
9388
9489 pub fn ensure_cursor_in_view(&mut self, doc: &Document, scrolloff: usize) {