rmux_core/screen/
selection.rs1use crate::grid::{GridCell, GridCellFlags};
2use crate::input::{GridAttr, COLOUR_DEFAULT, COLOUR_TERMINAL};
3use crate::style::{style_parse, Style, StyleCell};
4
5use super::Screen;
6
7impl Screen {
8 #[must_use]
10 pub fn has_selected_cells(&self) -> bool {
11 self.has_selected_cells
12 }
13
14 pub fn mark_selected_row_range(&mut self, row: u32, start_x: u32, end_x: u32) {
16 let line_width = self.grid.sx();
17 if row >= self.grid.sy() || line_width == 0 {
18 return;
19 }
20
21 let Some(line) = self.grid.visible_line_mut(row) else {
22 return;
23 };
24 let start_x = start_x.min(line_width.saturating_sub(1));
25 let end_x = end_x.min(line_width.saturating_sub(1));
26 if start_x > end_x {
27 return;
28 }
29
30 let start_x = line.owning_cell_x(start_x).unwrap_or(start_x);
31 let end_x = line.owning_cell_x(end_x).unwrap_or(end_x);
32 let mut touched = false;
33 let mut x = start_x;
34 while x <= end_x {
35 let owner_x = line.owning_cell_x(x).unwrap_or(x);
36 let width = line
37 .cell(owner_x)
38 .map(|cell| u32::from(cell.width().max(1)))
39 .unwrap_or(1);
40
41 for offset in 0..width {
42 let cell_x = owner_x.saturating_add(offset);
43 if let Some(cell) = line.cell_mut(cell_x) {
44 let mut flags = cell.flags();
45 flags.insert(GridCellFlags::SELECTED);
46 cell.set_flags(flags);
47 self.has_selected_cells = true;
48 touched = true;
49 }
50 }
51
52 x = owner_x.saturating_add(width.max(1));
53 }
54 if touched {
55 line.touch();
56 }
57 }
58
59 pub fn clear_selected_cells(&mut self) {
61 if !self.has_selected_cells {
62 return;
63 }
64
65 let width = self.grid.sx();
66 for row in 0..self.grid.sy() {
67 let Some(line) = self.grid.visible_line_mut(row) else {
68 continue;
69 };
70 let mut touched = false;
71 for x in 0..width {
72 let Some(cell) = line.cell_mut(x) else {
73 continue;
74 };
75 if !cell.flags().contains(GridCellFlags::SELECTED) {
76 continue;
77 }
78 let mut flags = cell.flags();
79 flags.remove(GridCellFlags::SELECTED);
80 cell.set_flags(flags);
81 touched = true;
82 }
83 if touched {
84 line.touch();
85 }
86 }
87 self.has_selected_cells = false;
88 }
89
90 pub fn overlay_style_on_selected(&mut self, style_input: &str) {
92 if style_input.is_empty() {
93 self.clear_selected_cells();
94 return;
95 }
96 let Some(style) = parse_cell_overlay_style(style_input) else {
97 return;
98 };
99
100 let width = self.grid.sx();
101 for row in 0..self.grid.sy() {
102 let Some(line) = self.grid.visible_line_mut(row) else {
103 continue;
104 };
105 let mut touched = false;
106 for x in 0..width {
107 let Some(cell) = line.cell_mut(x) else {
108 continue;
109 };
110 if !cell.flags().contains(GridCellFlags::SELECTED) || cell.is_padding() {
111 continue;
112 }
113 apply_cell_overlay_style(cell, &style);
114 touched = true;
115 }
116 if touched {
117 line.touch();
118 }
119 }
120 self.clear_selected_cells();
121 }
122
123 pub fn overlay_style_on_row_range(
125 &mut self,
126 row: u32,
127 start_x: u32,
128 end_x: u32,
129 style: &Style,
130 ) {
131 if row >= self.grid.sy() || self.grid.sx() == 0 {
132 return;
133 }
134 let line_width = self.grid.sx();
135 let Some(line) = self.grid.visible_line_mut(row) else {
136 return;
137 };
138 let start_x = start_x.min(line_width.saturating_sub(1));
139 let end_x = end_x.min(line_width.saturating_sub(1));
140 if start_x > end_x {
141 return;
142 }
143
144 let start_x = line.owning_cell_x(start_x).unwrap_or(start_x);
145 let end_x = line.owning_cell_x(end_x).unwrap_or(end_x);
146 let mut touched = false;
147 let mut x = start_x;
148 while x <= end_x {
149 let owner_x = line.owning_cell_x(x).unwrap_or(x);
150 let width = line
151 .cell(owner_x)
152 .map(|cell| u32::from(cell.width().max(1)))
153 .unwrap_or(1);
154 if let Some(cell) = line.cell_mut(owner_x) {
155 if !cell.is_padding() {
156 apply_cell_overlay_style(cell, style);
157 touched = true;
158 }
159 }
160 let next = owner_x.saturating_add(width.max(1));
161 if next <= x {
162 break;
163 }
164 x = next;
165 }
166 if touched {
167 line.touch();
168 }
169 }
170}
171
172fn parse_cell_overlay_style(style_input: &str) -> Option<Style> {
173 let base = StyleCell::default();
174 let mut style = Style::with_cell(base);
175 style_parse(&mut style, &base, style_input).ok()?;
176 Some(style)
177}
178
179fn apply_cell_overlay_style(cell: &mut GridCell, style: &Style) {
180 let fg_set = !matches!(style.cell.fg, COLOUR_DEFAULT | COLOUR_TERMINAL);
186 let bg_set = !matches!(style.cell.bg, COLOUR_DEFAULT | COLOUR_TERMINAL);
187 let style_attr = style.cell.attr & !GridAttr::NOATTR;
188 let attr = if style.cell.attr & GridAttr::NOATTR != 0 || (fg_set && bg_set) {
189 style_attr | (cell.attr() & GridAttr::CHARSET)
190 } else {
191 style_attr | cell.attr()
192 };
193 cell.set_attr(attr);
194 if fg_set {
195 cell.set_fg(style.cell.fg);
196 }
197 if bg_set {
198 cell.set_bg(style.cell.bg);
199 }
200 cell.set_us(style.cell.us);
201}