native_windows_gui2/layouts/
dyn_layout.rs

1use crate::NwgError;
2use crate::controls::ControlHandle;
3use crate::win32::window::bind_raw_event_handler_inner;
4use crate::win32::window_helper as wh;
5use std::cell::RefCell;
6use std::ptr;
7use std::rc::Rc;
8use winapi::shared::windef::HWND;
9
10/// A control item in a DynLayout
11#[derive(Debug)]
12pub struct DynLayoutItem {
13    /// The handle to the control in the item
14    control: HWND,
15    pos_init: (i32, i32),
16    size_init: (i32, i32),
17    mv: (i32, i32),
18    sz: (i32, i32),
19}
20
21impl DynLayoutItem {
22    /// Initialize a new layout item
23    pub fn new<W: Into<ControlHandle>>(c: W, mv: (i32, i32), sz: (i32, i32)) -> DynLayoutItem {
24        let control = c
25            .into()
26            .hwnd()
27            .expect("Child must be a window-like control (HWND handle)");
28        let pos_init = (0, 0);
29        let size_init = (0, 0);
30
31        DynLayoutItem {
32            control,
33            pos_init,
34            size_init,
35            mv,
36            sz,
37        }
38    }
39}
40
41/// A layout that lays out widgets
42/// This is the inner data shared between the callback and the application
43pub struct DynLayoutInner {
44    /// The control that holds the layout
45    base: HWND,
46
47    /// The children of the control that fit in the layout
48    children: Vec<DynLayoutItem>,
49}
50
51#[derive(Clone)]
52pub struct DynLayout {
53    inner: Rc<RefCell<DynLayoutInner>>,
54}
55
56impl DynLayout {
57    pub fn builder() -> DynLayoutBuilder {
58        let layout = DynLayoutInner {
59            base: ptr::null_mut(),
60            children: Vec::new(),
61        };
62
63        DynLayoutBuilder { layout }
64    }
65
66    /// Set the layout parent. The handle must be a window object otherwise the function will panic
67    pub fn parent<W: Into<ControlHandle>>(&self, p: W) {
68        let mut inner = self.inner.borrow_mut();
69        inner.base = p.into().hwnd().expect("Parent must be HWND");
70    }
71
72    /**
73        Add a children control to the layout.
74        This is a simplified interface over `add_child_item`
75
76        Panic:
77        - If the layout is not initialized
78        - If the control is not window-like (HWND handle)
79    */
80    pub fn add_child<W: Into<ControlHandle>>(&self, m: (i32, i32), s: (i32, i32), c: W) {
81        let hwnd = c
82            .into()
83            .hwnd()
84            .expect("Child must be a window-like control (HWND handle)");
85        let pos = wh::get_window_position(hwnd);
86        let size = wh::get_window_size(hwnd);
87
88        let (whost, hhost) = wh::get_window_size(self.inner.borrow_mut().base);
89
90        let xdelta = 0.01 * whost as f32;
91        let ydelta = 0.01 * hhost as f32;
92
93        let mut xpos = pos.0;
94        if m.0 > 0 {
95            xpos -= (xdelta * m.0 as f32) as i32;
96        }
97
98        let mut ypos = pos.1;
99        if m.1 > 0 {
100            ypos -= (ydelta * m.1 as f32) as i32;
101        }
102
103        let mut xsize = size.0 as i32;
104        if s.0 > 0 {
105            xsize -= (xdelta * s.0 as f32) as i32;
106        }
107
108        let mut ysize = size.1 as i32;
109        if s.1 > 0 {
110            ysize -= (ydelta * s.1 as f32) as i32;
111        }
112
113        let item = DynLayoutItem {
114            control: hwnd,
115            pos_init: (xpos, ypos),
116            size_init: (xsize, ysize),
117            mv: m,
118            sz: s,
119        };
120
121        self.add_child_item(item);
122    }
123
124    /**
125    Add a children control to the layout.
126
127    Panic:
128        - If the layout is not initialized
129        - If the control is not window-like (HWND handle)
130    */
131    pub fn add_child_item(&self, i: DynLayoutItem) {
132        let base = {
133            let mut inner = self.inner.borrow_mut();
134            if inner.base.is_null() {
135                panic!("DynLayout is not initialized");
136            }
137
138            // No need to check the layout item control because it's checked in `DynLayoutItem::new`
139
140            inner.children.push(i);
141            inner.base
142        };
143
144        let (w, h) = wh::get_window_size(base);
145        self.update_layout(w as u32, h as u32);
146    }
147
148    /**
149        Remove the children control in the layout. See also `remove_child_by_pos`.
150        Note that the child control won't be hidden after being removed from the control.
151
152        This method won't do anything if there is no control at the specified position.
153
154        Panic:
155        - If the layout is not initialized
156    */
157    pub fn remove_child<W: Into<ControlHandle>>(&self, c: W) {
158        let base = {
159            let mut inner = self.inner.borrow_mut();
160            if inner.base.is_null() {
161                panic!("DynLayout is not initialized");
162            }
163
164            let handle = c
165                .into()
166                .hwnd()
167                .expect("Control must be window-like (HWND handle)");
168            let index = inner
169                .children
170                .iter()
171                .position(|item| item.control == handle);
172            match index {
173                Some(i) => {
174                    inner.children.remove(i);
175                }
176                None => {
177                    return;
178                }
179            }
180
181            inner.base
182        };
183
184        let (w, h) = wh::get_window_size(base);
185        self.update_layout(w as u32, h as u32);
186    }
187
188    /**
189        Check if a window control is a children of the layout
190
191        Panic:
192        - If the layout is not initialized
193        - If the child is not a window-like control
194    */
195    pub fn has_child<W: Into<ControlHandle>>(&self, c: W) -> bool {
196        let inner = self.inner.borrow();
197        if inner.base.is_null() {
198            panic!("DynLayout is not initialized");
199        }
200
201        let handle = c
202            .into()
203            .hwnd()
204            .expect("Children is not a window-like control (HWND handle)");
205        inner.children.iter().any(|c| c.control == handle)
206    }
207
208    /// Resize the layout as if the parent window had the specified size.
209    ///
210    /// Arguments:
211    ///   w: New width of the layout
212    ///   h: New height of the layout
213    ///
214    ///  Panic:
215    ///   - The layout must have been successfully built otherwise this function will panic.
216    pub fn resize(&self, w: u32, h: u32) {
217        let inner = self.inner.borrow();
218        if inner.base.is_null() {
219            panic!("Layout is not bound to a parent control.")
220        }
221        self.update_layout(w, h);
222    }
223
224    /// Resize the layout to fit the parent window size
225    ///
226    /// Panic:
227    ///   - The layout must have been successfully built otherwise this function will panic.
228    pub fn fit(&self) {
229        let inner = self.inner.borrow();
230        if inner.base.is_null() {
231            panic!("Layout is not bound to a parent control.")
232        }
233
234        let (w, h) = wh::get_window_size(inner.base);
235        self.update_layout(w, h);
236    }
237
238    fn update_layout(&self, width: u32, height: u32) -> () {
239        use winapi::ctypes::c_int;
240        use winapi::um::winuser::{BeginDeferWindowPos, DeferWindowPos, EndDeferWindowPos};
241        use winapi::um::winuser::{
242            HWND_TOP, SWP_NOACTIVATE, SWP_NOCOPYBITS, SWP_NOREPOSITION, SWP_NOZORDER,
243        };
244
245        let inner = self.inner.borrow();
246        if inner.base.is_null() || inner.children.len() == 0 {
247            return;
248        }
249
250        let xdelta = 0.01 * width as f32;
251        let ydelta = 0.01 * height as f32;
252
253        unsafe {
254            let hdwp = BeginDeferWindowPos(inner.children.len() as c_int);
255
256            let mut last_handle = None;
257            for item in inner.children.iter() {
258                let mut x = item.pos_init.0;
259                if item.mv.0 > 0 {
260                    x += (xdelta * item.mv.0 as f32) as i32;
261                }
262
263                let mut y = item.pos_init.1;
264                if item.mv.1 > 0 {
265                    y += (ydelta * item.mv.1 as f32) as i32;
266                }
267
268                let mut w = item.size_init.0;
269                if item.sz.0 > 0 {
270                    w += (xdelta * item.sz.0 as f32) as i32;
271                }
272
273                let mut h = item.size_init.1;
274                if item.sz.1 > 0 {
275                    h += (ydelta * item.sz.1 as f32) as i32;
276                }
277
278                DeferWindowPos(
279                    hdwp,
280                    item.control,
281                    HWND_TOP,
282                    x,
283                    y,
284                    w,
285                    h,
286                    SWP_NOZORDER | SWP_NOREPOSITION | SWP_NOACTIVATE | SWP_NOCOPYBITS,
287                );
288
289                wh::set_window_after(item.control, last_handle);
290                last_handle = Some(item.control);
291            }
292
293            EndDeferWindowPos(hdwp);
294        }
295    }
296}
297
298impl Default for DynLayout {
299    fn default() -> DynLayout {
300        let inner = DynLayoutInner {
301            base: ptr::null_mut(),
302            children: Vec::new(),
303        };
304
305        DynLayout {
306            inner: Rc::new(RefCell::new(inner)),
307        }
308    }
309}
310
311/// Builder for a `DynLayout` struct
312pub struct DynLayoutBuilder {
313    layout: DynLayoutInner,
314}
315
316impl DynLayoutBuilder {
317    /// Set the layout parent. The handle must be a window object otherwise the function will panic
318    pub fn parent<W: Into<ControlHandle>>(mut self, p: W) -> DynLayoutBuilder {
319        self.layout.base = p.into().hwnd().expect("Parent must be HWND");
320        self
321    }
322
323    /// Add a children to the layout at the position `col` and `row`.
324    /// This is a shortcut over `child_item` for item with default span.
325    /// The handle must be a window object otherwise the function will panic
326    pub fn child<W: Into<ControlHandle>>(
327        mut self,
328        m: (i32, i32),
329        s: (i32, i32),
330        c: W,
331    ) -> DynLayoutBuilder {
332        let hwnd = c.into().hwnd().expect("Child must be HWND");
333        let pos = wh::get_window_position(hwnd);
334        let size = wh::get_window_size(hwnd);
335
336        self.layout.children.push(DynLayoutItem {
337            control: hwnd,
338            pos_init: pos,
339            size_init: (size.0 as i32, size.1 as i32),
340            mv: m,
341            sz: s,
342        });
343
344        self
345    }
346
347    /// Add a children to the layout
348    /// The handle must be a window object otherwise the function will panic
349    pub fn child_item(mut self, item: DynLayoutItem) -> DynLayoutBuilder {
350        self.layout.children.push(item);
351        self
352    }
353
354    /// Build the layout object and bind the callback.
355    /// Children must only contains window object otherwise this method will panic.
356    pub fn build(self, layout: &DynLayout) -> Result<(), NwgError> {
357        use winapi::shared::minwindef::{HIWORD, LOWORD};
358        use winapi::um::winuser::WM_SIZE;
359
360        if self.layout.base.is_null() {
361            return Err(NwgError::layout_create("DynLayout does not have a parent."));
362        }
363
364        let (w, h) = wh::get_window_size(self.layout.base);
365        let base_handle = ControlHandle::Hwnd(self.layout.base);
366
367        // Saves the new layout. TODO: should free the old one too (if any)
368        {
369            let mut layout_inner = layout.inner.borrow_mut();
370            *layout_inner = self.layout;
371        }
372
373        // Initial layout update
374        layout.update_layout(w, h);
375
376        // Bind the event handler
377        let event_layout = layout.clone();
378        let cb = move |_h, msg, _w, l| {
379            if msg == WM_SIZE {
380                let size = l as u32;
381                let width = LOWORD(size) as i32;
382                let height = HIWORD(size) as i32;
383                let (w, h) = crate::win32::high_dpi::physical_to_logical(width, height);
384                DynLayout::update_layout(&event_layout, w as u32, h as u32);
385            }
386            None
387        };
388
389        /// Keep generating ids so that multiple layouts can be applied to the same parent
390        use std::sync::atomic::{AtomicUsize, Ordering};
391        static BOX_LAYOUT_ID: AtomicUsize = AtomicUsize::new(0x8FFF);
392        bind_raw_event_handler_inner(
393            &base_handle,
394            BOX_LAYOUT_ID.fetch_add(1, Ordering::SeqCst),
395            cb,
396        )
397        .unwrap();
398
399        Ok(())
400    }
401}