1use self::layout::Dimension;
2
3use super::ResizeBehavior;
4use super::SplitterPanel;
5use crate::prelude::*;
6use crate::ui::layout::Coordinate;
7
8#[derive(Eq, PartialEq, Copy, Clone)]
9enum State {
10 None,
11 OverSeparator,
12 OverTopButton,
13 OverBottomButton,
14 ClickedOnTopButton,
15 ClickedOnBottomButton,
16 Dragging,
17}
18
19#[CustomControl(overwrite=OnPaint + OnKeyPressed + OnMouseEvent + OnResize, internal = true)]
20pub struct HSplitter {
21 top: Handle<SplitterPanel>,
22 bottom: Handle<SplitterPanel>,
23 min_left: Dimension,
24 min_right: Dimension,
25 pos: Coordinate,
26 preserve_pos: i32,
27 resize_behavior: ResizeBehavior,
28 state: State,
29}
30impl HSplitter {
31 pub fn new<T>(pos: T, layout: Layout, resize_behavior: ResizeBehavior) -> Self
47 where
48 Coordinate: From<T>,
49 {
50 let mut obj = Self {
51 base: ControlBase::with_status_flags(layout, StatusFlags::Visible | StatusFlags::Enabled | StatusFlags::AcceptInput),
52 top: Handle::None,
53 bottom: Handle::None,
54 pos: pos.into(),
55 min_left: Dimension::Absolute(0),
56 min_right: Dimension::Absolute(0),
57 state: State::None,
58 resize_behavior,
59 preserve_pos: 0,
60 };
61 obj.set_size_bounds(1, 3, u16::MAX, u16::MAX);
62 obj.top = obj.add_child(SplitterPanel::new());
63 obj.bottom = obj.add_child(SplitterPanel::new());
64 obj
65 }
66
67 #[inline(always)]
78 pub fn add<T>(&mut self, panel: hsplitter::Panel, control: T) -> Handle<T>
79 where
80 T: Control + NotWindow + NotDesktop + 'static,
81 {
82 let h = if panel == hsplitter::Panel::Top { self.top } else { self.bottom };
83 let cm = RuntimeManager::get().get_controls_mut();
84 if let Some(panel) = cm.get_mut(h.cast()) {
85 panel.base_mut().add_child(control)
86 } else {
87 Handle::None
88 }
89 }
90
91 pub fn set_min_height<T>(&mut self, panel: hsplitter::Panel, min_size: T)
107 where
108 Dimension: From<T>,
109 {
110 match panel {
111 hsplitter::Panel::Top => self.min_left = min_size.into(),
112 hsplitter::Panel::Bottom => self.min_right = min_size.into(),
113 }
114 }
115
116 #[inline(always)]
118 pub fn position(&self) -> i32 {
119 self.pos.absolute(self.size().height.saturating_sub(1) as u16)
120 }
121
122 pub fn set_position<T>(&mut self, pos: T)
124 where
125 Coordinate: From<T>,
126 {
127 self.pos = pos.into();
129 self.update_position(self.pos, true);
131 }
132 fn update_position(&mut self, pos: Coordinate, upadate_preserve_position: bool) {
133 let bottom_most = self.size().height.saturating_sub(1) as u16;
134 let mut abs_value = pos.absolute(bottom_most);
135 let min_top_margin = self.min_left.absolute(bottom_most);
136 let min_bottom_margin = self.min_right.absolute(bottom_most);
137 if abs_value > (bottom_most as i32 - min_bottom_margin as i32) {
138 abs_value = bottom_most as i32 - min_bottom_margin as i32;
139 }
140 abs_value = abs_value.max(min_top_margin as i32);
141 match self.resize_behavior {
142 ResizeBehavior::PreserveAspectRatio => {
143 self.pos.update_with_absolute_value(abs_value as i16, bottom_most);
144 }
145 ResizeBehavior::PreserveTopPanelSize | ResizeBehavior::PreserveBottomPanelSize => {
146 self.pos = Coordinate::Absolute(abs_value);
148 }
149 };
150 self.update_panel_sizes(self.size());
151 if upadate_preserve_position {
152 match self.resize_behavior {
153 ResizeBehavior::PreserveTopPanelSize => {
154 self.preserve_pos = self.pos.absolute(bottom_most);
155 }
156 ResizeBehavior::PreserveBottomPanelSize => {
157 self.preserve_pos = bottom_most as i32 - self.pos.absolute(bottom_most);
158 }
159 _ => {}
160 }
161 }
162 }
163 fn update_panel_sizes(&mut self, new_size: Size) {
164 let splitter_pos = self.pos.absolute(new_size.height.saturating_sub(1) as u16).max(0) as u16;
165 let w = new_size.width as u16;
166 let h1 = self.top;
167 let h2 = self.bottom;
168 let rm = RuntimeManager::get();
169 if let Some(p1) = rm.get_control_mut(h1) {
170 p1.set_position(0, 0);
171 if splitter_pos > 0 {
172 p1.set_size(w, splitter_pos);
173 p1.set_visible(true);
174 } else {
175 p1.set_size(w, 0);
176 p1.set_visible(false);
177 }
178 }
179 if let Some(p2) = rm.get_control_mut(h2) {
180 p2.set_position(0, splitter_pos as i32 + 1);
181 if (splitter_pos as i32) + 1 < (new_size.height as i32) {
182 p2.set_size(w, new_size.height as u16 - splitter_pos - 1);
183 p2.set_visible(true);
184 } else {
185 p2.set_size(w, 0);
186 p2.set_visible(false);
187 }
188 }
189 }
190 fn mouse_to_state(&self, x: i32, y: i32, clicked: bool) -> State {
191 let sz = self.size();
192 let pos = self.pos.absolute(sz.height.saturating_sub(1) as u16);
193 if y != pos {
194 State::None
195 } else if clicked {
196 match x {
197 1 => State::ClickedOnTopButton,
198 2 => State::ClickedOnBottomButton,
199 _ => State::Dragging,
200 }
201 } else {
202 match x {
203 1 => State::OverTopButton,
204 2 => State::OverBottomButton,
205 _ => State::OverSeparator,
206 }
207 }
208 }
209}
210impl OnPaint for HSplitter {
211 fn on_paint(&self, surface: &mut Surface, theme: &Theme) {
212 let (col_line, col_b1, col_b2) = if !self.is_enabled() {
213 (theme.lines.inactive, theme.symbol.inactive, theme.symbol.inactive)
214 } else {
215 match self.state {
216 State::OverSeparator => (theme.lines.hovered, theme.symbol.arrows, theme.symbol.arrows),
217 State::OverTopButton => (theme.lines.normal, theme.symbol.hovered, theme.symbol.arrows),
218 State::OverBottomButton => (theme.lines.normal, theme.symbol.arrows, theme.symbol.hovered),
219 State::ClickedOnTopButton => (theme.lines.normal, theme.symbol.pressed, theme.symbol.arrows),
220 State::ClickedOnBottomButton => (theme.lines.normal, theme.symbol.arrows, theme.symbol.pressed),
221 State::Dragging => (theme.lines.pressed_or_selected, theme.symbol.arrows, theme.symbol.arrows),
222 State::None => (theme.lines.normal, theme.symbol.arrows, theme.symbol.arrows),
223 }
224 };
225 let sz = self.size();
226 let y = self.pos.absolute(sz.height.saturating_sub(1) as u16);
227 surface.draw_horizontal_line_with_size(0, y, sz.width, LineType::Single, col_line);
228 surface.write_char(1, y, Character::with_attributes(SpecialChar::TriangleUp, col_b1));
229 surface.write_char(2, y, Character::with_attributes(SpecialChar::TriangleDown, col_b2));
230 }
231}
232impl OnKeyPressed for HSplitter {
233 fn on_key_pressed(&mut self, key: Key, _character: char) -> EventProcessStatus {
234 match key.value() {
235 key!("Ctrl+Alt+UP") => {
236 let sz = self.size();
237 if sz.height > 0 {
238 self.update_position(Coordinate::Absolute(self.pos.absolute(sz.height.saturating_sub(1) as u16) - 1), true);
239 }
240 EventProcessStatus::Processed
241 }
242 key!("Ctrl+Alt+Down") => {
243 let sz = self.size();
244 if sz.height > 0 {
245 self.update_position(Coordinate::Absolute(self.pos.absolute(sz.height.saturating_sub(1) as u16) + 1), true);
246 }
247 EventProcessStatus::Processed
248 }
249 key!("Ctrl+Alt+Shift+Up") => {
250 self.update_position(Coordinate::Absolute(0), true);
251 EventProcessStatus::Processed
252 }
253 key!("Ctrl+Alt+Shift+Down") => {
254 self.update_position(Coordinate::Absolute(self.size().height.saturating_sub(1) as i32), true);
255 EventProcessStatus::Processed
256 }
257 _ => EventProcessStatus::Ignored,
258 }
259 }
260}
261impl OnMouseEvent for HSplitter {
262 fn on_mouse_event(&mut self, event: &MouseEvent) -> EventProcessStatus {
263 match event {
264 MouseEvent::Enter | MouseEvent::Leave => {
265 self.state = State::None;
266 EventProcessStatus::Processed
267 }
268 MouseEvent::Over(point) => {
269 let new_state = self.mouse_to_state(point.x, point.y, false);
270 if new_state != self.state {
271 self.state = new_state;
272 EventProcessStatus::Processed
273 } else {
274 EventProcessStatus::Ignored
275 }
276 }
277 MouseEvent::Pressed(evn) => {
278 let new_state = self.mouse_to_state(evn.x, evn.y, true);
279 if new_state != self.state {
280 self.state = new_state;
281 EventProcessStatus::Processed
282 } else {
283 EventProcessStatus::Ignored
284 }
285 }
286 MouseEvent::Released(evn) => {
287 let processed = match self.state {
288 State::ClickedOnTopButton => {
289 self.update_position(Coordinate::Absolute(0), true);
290 true
291 }
292 State::ClickedOnBottomButton => {
293 self.update_position(Coordinate::Absolute(self.size().height.saturating_sub(1) as i32), true);
294 true
295 }
296 _ => false,
297 };
298 let new_state = self.mouse_to_state(evn.x, evn.y, false);
299 if (new_state != self.state) || processed {
300 self.state = new_state;
301 EventProcessStatus::Processed
302 } else {
303 EventProcessStatus::Ignored
304 }
305 }
306 MouseEvent::Drag(evn) => {
307 if self.state == State::Dragging {
308 self.update_position(Coordinate::Absolute(evn.y), true);
309 EventProcessStatus::Processed
310 } else {
311 EventProcessStatus::Ignored
312 }
313 }
314 MouseEvent::DoubleClick(_) => EventProcessStatus::Ignored,
315 MouseEvent::Wheel(_) => EventProcessStatus::Ignored,
316 }
317 }
318}
319impl OnResize for HSplitter {
320 fn on_resize(&mut self, old_size: Size, new_size: Size) {
321 let previous_width = old_size.height as i32;
322 match self.resize_behavior {
324 ResizeBehavior::PreserveAspectRatio => {
325 if (previous_width > 0) && (self.pos.is_absolute()) {
326 let ratio = self.pos.absolute(old_size.height.saturating_sub(1) as u16) as f32 / previous_width as f32;
327 let new_pos = (new_size.height as f32 * ratio) as i32;
328 self.update_position(Coordinate::Absolute(new_pos), false);
329 } else {
330 self.update_panel_sizes(new_size);
332 }
333 }
334 ResizeBehavior::PreserveTopPanelSize => {
335 if previous_width == 0 {
336 self.preserve_pos = self.pos.absolute(new_size.height.saturating_sub(1) as u16);
338 self.set_position(self.preserve_pos);
339 } else {
340 self.update_position(Coordinate::Absolute(self.preserve_pos), false);
341 }
342 }
343 ResizeBehavior::PreserveBottomPanelSize => {
344 if previous_width == 0 {
345 self.preserve_pos =
347 (new_size.height.saturating_sub(1) as i32 - self.pos.absolute(new_size.height.saturating_sub(1) as u16)).max(0);
348 let new_pos = (new_size.height.saturating_sub(1) as i32 - self.preserve_pos).max(0);
349 self.set_position(new_pos);
350 } else {
351 let new_pos = (new_size.height.saturating_sub(1) as i32 - self.preserve_pos).max(0);
352 self.update_position(Coordinate::Absolute(new_pos), false);
353 }
354 }
355 }
356 }
357}