Skip to main content

iced_resizable_split/
split_vertical.rs

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        // TODO: Implement touch events
132        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}