1pub struct SplitVertical<'a, Message, Theme, Renderer> {
2 left: iced_core::Element<'a, Message, Theme, Renderer>,
3 right: iced_core::Element<'a, Message, Theme, Renderer>,
4
5 state: super::state::State,
6 on_drag: Box<dyn Fn(super::state::State) -> Message + 'a>,
7
8 drag_area_size: f32,
9
10 style: super::style::StyleFn<'a, Theme>,
11}
12
13impl<'a, Message, Theme, Renderer> SplitVertical<'a, Message, Theme, Renderer>
14where
15 Theme: 'a,
16{
17 pub fn new(
18 left: impl Into<iced_core::Element<'a, Message, Theme, Renderer>>,
19 right: impl Into<iced_core::Element<'a, Message, Theme, Renderer>>,
20 state: super::state::State,
21 message: impl Fn(super::state::State) -> Message + 'a,
22 ) -> Self {
23 Self {
24 left: left.into(),
25 right: right.into(),
26 state,
27 on_drag: Box::new(message),
28 drag_area_size: super::DEFAULT_DRAG_AREA_SIZE,
29 style: Box::new(|_, _| super::style::Style::default()),
30 }
31 }
32
33 #[must_use]
34 pub fn style(
35 mut self,
36 style: impl Fn(&Theme, super::style::State) -> super::style::Style + 'a,
37 ) -> Self {
38 self.style = Box::new(style);
39 self
40 }
41
42 #[must_use]
43 pub const fn drag_area_size(mut self, size: f32) -> Self {
44 self.drag_area_size = size;
45 self
46 }
47}
48
49impl<Message, Theme, Renderer> iced_core::Widget<Message, Theme, Renderer>
50 for SplitVertical<'_, Message, Theme, Renderer>
51where
52 Renderer: iced_core::renderer::Renderer,
53{
54 fn tag(&self) -> iced_core::widget::tree::Tag {
55 iced_core::widget::tree::Tag::of::<super::state::IsDragging>()
56 }
57
58 fn state(&self) -> iced_core::widget::tree::State {
59 iced_core::widget::tree::State::new(false)
60 }
61
62 fn children(&self) -> Vec<iced_core::widget::Tree> {
63 vec![
64 iced_core::widget::Tree::new(&self.left),
65 iced_core::widget::Tree::new(&self.right),
66 ]
67 }
68
69 fn diff(&self, tree: &mut iced_core::widget::Tree) {
70 tree.diff_children(&[&self.left, &self.right]);
71 }
72
73 fn size(&self) -> iced_core::Size<iced_core::Length> {
74 iced_core::Size::new(iced_core::Length::Fill, iced_core::Length::Fill)
75 }
76
77 fn layout(
78 &mut self,
79 tree: &mut iced_core::widget::Tree,
80 renderer: &Renderer,
81 limits: &iced_core::layout::Limits,
82 ) -> iced_core::layout::Node {
83 let limits = limits
84 .width(iced_core::Length::Fill)
85 .height(iced_core::Length::Fill);
86 let size = limits.resolve(
87 iced_core::Length::Fill,
88 iced_core::Length::Fill,
89 iced_core::Size::ZERO,
90 );
91
92 let split_x_pos = size.width * self.state.ratio();
93
94 let left_limits = limits.max_width(split_x_pos);
95 let left_node =
96 self.left
97 .as_widget_mut()
98 .layout(&mut tree.children[0], renderer, &left_limits);
99
100 let right_limits = limits.max_width(size.width - split_x_pos);
101 let right_node = self
102 .right
103 .as_widget_mut()
104 .layout(&mut tree.children[1], renderer, &right_limits)
105 .move_to(iced_core::Point::new(split_x_pos, 0.0));
106
107 iced_core::layout::Node::with_children(size, vec![left_node, right_node])
108 }
109
110 fn update(
111 &mut self,
112 tree: &mut iced_core::widget::Tree,
113 event: &iced_core::Event,
114 layout: iced_core::Layout<'_>,
115 cursor: iced_core::mouse::Cursor,
116 renderer: &Renderer,
117 clipboard: &mut dyn iced_core::Clipboard,
118 shell: &mut iced_core::Shell<'_, Message>,
119 viewport: &iced_core::Rectangle,
120 ) {
121 let is_dragging = tree.state.downcast_mut::<super::state::IsDragging>();
122 let bounds = layout.bounds();
123 let divider_x_pos = bounds.width.mul_add(self.state.ratio(), bounds.x);
124 let drag_rect = iced_core::Rectangle {
125 x: divider_x_pos - self.drag_area_size / 2.0,
126 y: bounds.y,
127 width: self.drag_area_size,
128 height: bounds.height,
129 };
130
131 match event {
133 iced_core::Event::Mouse(iced_core::mouse::Event::ButtonPressed(
134 iced_core::mouse::Button::Left,
135 )) => {
136 if let Some(cursor_pos) = cursor.position()
137 && drag_rect.contains(cursor_pos)
138 {
139 *is_dragging = true;
140 shell.capture_event();
141 return;
142 }
143 }
144
145 iced_core::Event::Mouse(
146 iced_core::mouse::Event::CursorLeft
147 | iced_core::mouse::Event::ButtonReleased(iced_core::mouse::Button::Left),
148 ) => {
149 if *is_dragging {
150 *is_dragging = false;
151 let next_state = self.state;
152 if next_state != self.state {
153 shell.publish((self.on_drag)(next_state));
154 }
155 shell.capture_event();
156 return;
157 }
158 }
159
160 iced_core::Event::Mouse(iced_core::mouse::Event::CursorMoved { position })
161 if *is_dragging =>
162 {
163 let relative_x = position.x - bounds.x;
164 let new_ratio = (relative_x / bounds.width).clamp(0.0, 1.0);
165 let mut next_state = self.state;
166 next_state.set_ratio(new_ratio);
167 if next_state != self.state {
168 shell.publish((self.on_drag)(next_state));
169 }
170 shell.capture_event();
171 return;
172 }
173
174 _ => {}
175 }
176
177 let mut layouts = layout.children();
178 self.left.as_widget_mut().update(
179 &mut tree.children[0],
180 event,
181 layouts.next().unwrap(),
182 cursor,
183 renderer,
184 clipboard,
185 shell,
186 viewport,
187 );
188
189 self.right.as_widget_mut().update(
190 &mut tree.children[1],
191 event,
192 layouts.next().unwrap(),
193 cursor,
194 renderer,
195 clipboard,
196 shell,
197 viewport,
198 );
199 }
200
201 fn draw(
202 &self,
203 tree: &iced_core::widget::Tree,
204 renderer: &mut Renderer,
205 theme: &Theme,
206 style: &iced_core::renderer::Style,
207 layout: iced_core::Layout<'_>,
208 cursor: iced_core::mouse::Cursor,
209 viewport: &iced_core::Rectangle,
210 ) {
211 let mut layouts = layout.children();
212 let left_layout = layouts.next().unwrap();
213 let right_layout = layouts.next().unwrap();
214
215 self.left.as_widget().draw(
216 &tree.children[0],
217 renderer,
218 theme,
219 style,
220 left_layout,
221 cursor,
222 viewport,
223 );
224
225 self.right.as_widget().draw(
226 &tree.children[1],
227 renderer,
228 theme,
229 style,
230 right_layout,
231 cursor,
232 viewport,
233 );
234
235 let bounds = layout.bounds();
236 let divider_x = bounds.width.mul_add(self.state.ratio(), bounds.x);
237
238 let is_hovering = cursor.position().is_some_and(|position| {
239 let hover_rect = iced_core::Rectangle {
240 x: divider_x - (self.drag_area_size / 2.0),
241 y: bounds.y,
242 width: self.drag_area_size,
243 height: bounds.height,
244 };
245 hover_rect.contains(position)
246 });
247
248 let status = if *tree.state.downcast_ref::<super::state::IsDragging>() {
249 super::style::State::Dragging
250 } else if is_hovering {
251 super::style::State::Hovering
252 } else {
253 super::style::State::Idle
254 };
255
256 let style = (self.style)(theme, status);
257
258 renderer.fill_quad(
259 iced_core::renderer::Quad {
260 bounds: iced_core::Rectangle {
261 x: divider_x - (style.divider_width / 2.0),
262 y: bounds.y,
263 width: style.divider_width,
264 height: bounds.height,
265 },
266 ..Default::default()
267 },
268 style.divider_color,
269 );
270 }
271}
272
273impl<'a, Message, Theme, Renderer> From<SplitVertical<'a, Message, Theme, Renderer>>
274 for iced_core::Element<'a, Message, Theme, Renderer>
275where
276 Message: 'a,
277 Theme: 'a,
278 Renderer: iced_core::Renderer + 'a,
279{
280 fn from(value: SplitVertical<'a, Message, Theme, Renderer>) -> Self {
281 Self::new(value)
282 }
283}