tpaint/tools/
rectangle.rs

1use crossterm::event::{KeyEvent, MouseEventKind};
2
3use std::{convert::TryFrom, iter::once};
4
5use crate::{box_drawing::BoxFlags, buffer::Buffer, state::State, tools::Tool};
6
7#[derive(Default)]
8pub struct Rectangle {
9	started: bool,
10	start: (usize, usize),
11	end: (usize, usize),
12	complete: bool,
13}
14
15impl Tool for Rectangle {
16	fn mouse_event(&mut self, x: isize, y: isize, kind: MouseEventKind) -> fn(state: &mut State) {
17		match kind {
18			MouseEventKind::Down(_) => {
19				if let (Ok(x), Ok(y)) = (usize::try_from(x), usize::try_from(y)) {
20					if !self.started {
21						self.start = (x, y);
22						self.end = (x, y);
23						self.started = true;
24						|_| ()
25					}
26					else {
27						// Edge case - dragged off edge then released mouse
28						self.end = (x, y);
29						self.complete = true;
30						|_| ()
31					}
32				}
33				else {
34					|_| ()
35				}
36			}
37			MouseEventKind::Drag(_) => {
38				if let (Ok(x), Ok(y)) = (usize::try_from(x), usize::try_from(y)) {
39					self.end = (x, y);
40					self.complete = true;
41				}
42				|_| ()
43			}
44			MouseEventKind::Up(_) => |state| state.reset_current_mouse_element(),
45
46			_ => |_| (),
47		}
48	}
49
50	fn key_event(&mut self, _: KeyEvent) -> fn(state: &mut State) { |_| () }
51
52	fn bounding_box(&self) -> Option<(usize, usize, usize, usize)> {
53		if self.started {
54			let (start_x, start_y) = self.start;
55			let (end_x, end_y) = self.end;
56			Some((
57				start_x.min(end_x),
58				start_x.max(end_x),
59				start_y.min(end_y),
60				start_y.max(end_y),
61			))
62		}
63		else {
64			None
65		}
66	}
67
68	fn render(&self, buffer: &mut Buffer, ascii_mode: bool) {
69		if !self.started {
70			return;
71		}
72
73		let rect_min_x = self.start.0.min(self.end.0);
74		let rect_max_x = self.start.0.max(self.end.0);
75		let rect_min_y = self.start.1.min(self.end.1);
76		let rect_max_y = self.start.1.max(self.end.1);
77
78		let top = (rect_min_x + 1..rect_max_x).map(|x| (x, rect_min_y));
79		let bottom = (rect_min_x + 1..rect_max_x).map(|x| (x, rect_max_y));
80		let left = (rect_min_y + 1..rect_max_y).map(|y| (rect_min_x, y));
81		let right = (rect_min_y + 1..rect_max_y).map(|y| (rect_max_x, y));
82
83		let top_left = (rect_min_x, rect_min_y, BoxFlags::DOWN | BoxFlags::RIGHT);
84		let top_right = (rect_max_x, rect_min_y, BoxFlags::DOWN | BoxFlags::LEFT);
85		let bottom_left = (rect_min_x, rect_max_y, BoxFlags::UP | BoxFlags::RIGHT);
86		let bottom_right = (rect_max_x, rect_max_y, BoxFlags::UP | BoxFlags::LEFT);
87
88		top.map(|(x, y)| (x, y, BoxFlags::LEFT | BoxFlags::RIGHT))
89			.chain(bottom.map(|(x, y)| (x, y, BoxFlags::LEFT | BoxFlags::RIGHT)))
90			.chain(left.map(|(x, y)| (x, y, BoxFlags::UP | BoxFlags::DOWN)))
91			.chain(right.map(|(x, y)| (x, y, BoxFlags::UP | BoxFlags::DOWN)))
92			.chain(once(top_left))
93			.chain(once(top_right))
94			.chain(once(bottom_left))
95			.chain(once(bottom_right))
96			.for_each(|(x, y, box_dir)| {
97				let current_box = BoxFlags::from_char(buffer.get_point(x, y), ascii_mode);
98				let final_box = box_dir | current_box;
99				buffer.render_point(x, y, final_box.to_char(ascii_mode))
100			})
101	}
102
103	fn render_bounded(
104		&self,
105		min_x: usize,
106		max_x: usize,
107		min_y: usize,
108		max_y: usize,
109		buffer: &mut Buffer,
110		ascii_mode: bool,
111	) {
112		if !self.started {
113			return;
114		}
115
116		let rect_min_x = self.start.0.min(self.end.0);
117		let rect_max_x = self.start.0.max(self.end.0);
118		let rect_min_y = self.start.1.min(self.end.1);
119		let rect_max_y = self.start.1.max(self.end.1);
120
121		let top = (rect_min_x + 1..rect_max_x).map(|x| (x, rect_min_y));
122		let bottom = (rect_min_x + 1..rect_max_x).map(|x| (x, rect_max_y));
123		let left = (rect_min_y + 1..rect_max_y).map(|y| (rect_min_x, y));
124		let right = (rect_min_y + 1..rect_max_y).map(|y| (rect_max_x, y));
125
126		let top_left = (rect_min_x, rect_min_y, BoxFlags::DOWN | BoxFlags::RIGHT);
127		let top_right = (rect_max_x, rect_min_y, BoxFlags::DOWN | BoxFlags::LEFT);
128		let bottom_left = (rect_min_x, rect_max_y, BoxFlags::UP | BoxFlags::RIGHT);
129		let bottom_right = (rect_max_x, rect_max_y, BoxFlags::UP | BoxFlags::LEFT);
130
131		top.map(|(x, y)| (x, y, BoxFlags::LEFT | BoxFlags::RIGHT))
132			.chain(bottom.map(|(x, y)| (x, y, BoxFlags::LEFT | BoxFlags::RIGHT)))
133			.chain(left.map(|(x, y)| (x, y, BoxFlags::UP | BoxFlags::DOWN)))
134			.chain(right.map(|(x, y)| (x, y, BoxFlags::UP | BoxFlags::DOWN)))
135			.chain(once(top_left))
136			.chain(once(top_right))
137			.chain(once(bottom_left))
138			.chain(once(bottom_right))
139			.filter(|(x, y, _)| (min_x <= *x && *x < max_x) && (min_y <= *y && *y < max_y))
140			.for_each(|(x, y, box_dir)| {
141				let current_box = BoxFlags::from_char(buffer.get_point(x, y), ascii_mode);
142				let final_box = box_dir | current_box;
143				buffer.render_point(x, y, final_box.to_char(ascii_mode))
144			})
145	}
146
147	fn complete(&self) -> bool { self.complete }
148}