native_windows_gui2/layouts/
grid_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 GridLayout
11#[derive(Debug)]
12pub struct GridLayoutItem {
13    /// The handle to the control in the item
14    control: HWND,
15
16    /// The column position of the control in the layout
17    pub col: u32,
18
19    /// The row position of the control in the layout
20    pub row: u32,
21
22    /// The number column this item should span. Should be 1 for single column item.
23    pub col_span: u32,
24
25    /// The number row this item should span. Should be 1 for single row item.
26    pub row_span: u32,
27}
28
29impl GridLayoutItem {
30    /// Initialize a new grid layout item
31    pub fn new<W: Into<ControlHandle>>(
32        c: W,
33        col: u32,
34        row: u32,
35        col_span: u32,
36        row_span: u32,
37    ) -> GridLayoutItem {
38        let control = c
39            .into()
40            .hwnd()
41            .expect("Child must be a window-like control (HWND handle)");
42
43        GridLayoutItem {
44            control,
45            col,
46            row,
47            col_span,
48            row_span,
49        }
50    }
51}
52
53/// A layout that lays out widgets in a grid
54/// This is the inner data shared between the callback and the application
55pub struct GridLayoutInner {
56    /// The control that holds the layout
57    base: HWND,
58
59    /// The children of the control that fit in the layout
60    children: Vec<GridLayoutItem>,
61
62    /// The top, right, bottom, left space around the layout
63    margins: [u32; 4],
64
65    /// The minimum size of the layout. Used if `base` is smaller than `min_size`.
66    min_size: [u32; 2],
67
68    /// The maximum size of the layout. Used if `base` is bigger than `min_size`.
69    max_size: [u32; 2],
70
71    /// The number of column. If None, compute the value from children.
72    column_count: Option<u32>,
73
74    /// The number of row. If None, compute the value from children.
75    row_count: Option<u32>,
76
77    /// The spacing between controls
78    spacing: u32,
79}
80
81/**
82A layout that lays out widgets in a grid
83NWG layouts use interior mutability to manage their controls.
84
85A GridLayouts has the following properties:
86* margin - The top, right, bottom, left margins of the layout - (default: [5, 5, 5, 5])
87* spacing - The spacing between children controls - (default: 5)
88* min_size - The minimum size of the layout - (default: [0, 0])
89* max_size - The maximum size of the layout - (default: [u32::max_value(), u32::max_value()])
90* max_column - Number of columns - (default: None),
91* max_row - Number of rows - (default: None),
92
93```rust
94    use native_windows_gui2 as nwg;
95    fn layout(layout: &nwg::GridLayout, window: &nwg::Window, item1: &nwg::Button, item2: &nwg::Button) {
96        nwg::GridLayout::builder()
97            .parent(window)
98            .max_row(Some(6))
99            .spacing(5)
100            .margin([0,0,0,0])
101            .child(0, 0, item1)
102            .child_item(nwg::GridLayoutItem::new(item2, 1, 0, 2, 1))
103            .build(&layout);
104    }
105```
106*/
107#[derive(Clone)]
108pub struct GridLayout {
109    inner: Rc<RefCell<GridLayoutInner>>,
110}
111
112impl GridLayout {
113    pub fn builder() -> GridLayoutBuilder {
114        let layout = GridLayoutInner {
115            base: ptr::null_mut(),
116            children: Vec::new(),
117            margins: [5, 5, 5, 5],
118            spacing: 5,
119            min_size: [0, 0],
120            max_size: [u32::max_value(), u32::max_value()],
121            column_count: None,
122            row_count: None,
123        };
124
125        GridLayoutBuilder { layout }
126    }
127
128    /**
129        Add a children control to the grid layout.
130        This is a simplified interface over `add_child_item`
131
132        Panic:
133        - If the layout is not initialized
134        - If the control is not window-like (HWND handle)
135    */
136    pub fn add_child<W: Into<ControlHandle>>(&self, col: u32, row: u32, c: W) {
137        let h = c
138            .into()
139            .hwnd()
140            .expect("Child must be a window-like control (HWND handle)");
141        let item = GridLayoutItem {
142            control: h,
143            col,
144            row,
145            col_span: 1,
146            row_span: 1,
147        };
148
149        self.add_child_item(item);
150    }
151
152    /**
153    Add a children control to the grid layout.
154
155    Panic:
156        - If the layout is not initialized
157        - If the control is not window-like (HWND handle)
158    */
159    pub fn add_child_item(&self, i: GridLayoutItem) {
160        let base = {
161            let mut inner = self.inner.borrow_mut();
162            if inner.base.is_null() {
163                panic!("GridLayout is not initialized");
164            }
165
166            // No need to check the layout item control because it's checked in `GridLayoutItem::new`
167
168            inner.children.push(i);
169            inner.base
170        };
171
172        let (w, h) = wh::get_window_size(base);
173        self.update_layout(w as u32, h as u32);
174    }
175
176    /**
177        Remove the children control in the layout. See also `remove_child_by_pos`.
178        Note that the child control won't be hidden after being removed from the control.
179
180        This method won't do anything if there is no control at the specified position.
181
182        Panic:
183        - If the layout is not initialized
184    */
185    pub fn remove_child<W: Into<ControlHandle>>(&self, c: W) {
186        let base = {
187            let mut inner = self.inner.borrow_mut();
188            if inner.base.is_null() {
189                panic!("GridLayout is not initialized");
190            }
191
192            let handle = c
193                .into()
194                .hwnd()
195                .expect("Control must be window-like (HWND handle)");
196            let index = inner
197                .children
198                .iter()
199                .position(|item| item.control == handle);
200            match index {
201                Some(i) => {
202                    inner.children.remove(i);
203                }
204                None => {
205                    return;
206                }
207            }
208
209            inner.base
210        };
211
212        let (w, h) = wh::get_window_size(base);
213        self.update_layout(w as u32, h as u32);
214    }
215
216    /**
217        Remove the children control in the layout. See also `remove_child_by_pos`.
218        Note that the child control won't be hidden after being removed from the control.
219
220        This method won't do anything if there is no control at the specified position.
221
222        Panic:
223        - If the layout is not initialized
224    */
225    pub fn remove_child_by_pos(&self, col: u32, row: u32) {
226        let base = {
227            let mut inner = self.inner.borrow_mut();
228            if inner.base.is_null() {
229                panic!("GridLayout is not initialized");
230            }
231
232            let index = inner
233                .children
234                .iter()
235                .position(|item| item.col == col && item.row == row);
236            match index {
237                Some(i) => {
238                    inner.children.remove(i);
239                }
240                None => {}
241            }
242
243            inner.base
244        };
245
246        let (w, h) = wh::get_window_size(base);
247        self.update_layout(w as u32, h as u32);
248    }
249
250    /**
251        Move the selected control to a new position in the grid layout. The old position
252        becomes empty (as if `remove_child` was called). However it won't remove the control
253        at the new position if there is one.
254
255        This method won't do anything if there is no control at the specified position.
256
257        Panic:
258        - If the layout is not initialized
259    */
260    pub fn move_child<W: Into<ControlHandle>>(&self, c: W, col: u32, row: u32) {
261        let base = {
262            let mut inner = self.inner.borrow_mut();
263            if inner.base.is_null() {
264                panic!("GridLayout is not initialized");
265            }
266
267            let handle = c
268                .into()
269                .hwnd()
270                .expect("Control must be window-like (HWND handle)");
271            let index = inner
272                .children
273                .iter()
274                .position(|item| item.control == handle);
275            match index {
276                Some(i) => {
277                    let mut child = inner.children.remove(i);
278                    child.col = col;
279                    child.row = row;
280                    inner.children.push(child);
281                }
282                None => {
283                    return;
284                }
285            }
286
287            inner.base
288        };
289
290        let (w, h) = wh::get_window_size(base);
291        self.update_layout(w as u32, h as u32);
292    }
293
294    /**
295        Move the selected control to a new position in the grid layout. The old position
296        becomes empty (as if `remove_child` was called). However it won't remove the control
297        at the new position if there is one.
298
299        This method won't do anything if there is no control at the specified position.
300
301        Panic:
302        - If the layout is not initialized
303    */
304    pub fn move_child_by_pos<W: Into<ControlHandle>>(
305        &self,
306        col: u32,
307        row: u32,
308        new_col: u32,
309        new_row: u32,
310    ) {
311        let base = {
312            let mut inner = self.inner.borrow_mut();
313            if inner.base.is_null() {
314                panic!("GridLayout is not initialized");
315            }
316
317            let index = inner
318                .children
319                .iter()
320                .position(|item| item.col == col && item.row == row);
321            match index {
322                Some(i) => {
323                    let mut child = inner.children.remove(i);
324                    child.col = new_col;
325                    child.row = new_row;
326                    inner.children.push(child);
327                }
328                None => {}
329            }
330
331            inner.base
332        };
333
334        let (w, h) = wh::get_window_size(base);
335        self.update_layout(w as u32, h as u32);
336    }
337
338    /**
339        Check if a window control is a children of the layout
340
341        Panic:
342        - If the layout is not initialized
343        - If the child is not a window-like control
344    */
345    pub fn has_child<W: Into<ControlHandle>>(&self, c: W) -> bool {
346        let inner = self.inner.borrow();
347        if inner.base.is_null() {
348            panic!("GridLayout is not initialized");
349        }
350
351        let handle = c
352            .into()
353            .hwnd()
354            .expect("Children is not a window-like control (HWND handle)");
355        inner.children.iter().any(|c| c.control == handle)
356    }
357
358    /// Resize the layout as if the parent window had the specified size.
359    ///
360    /// Arguments:
361    ///   w: New width of the layout
362    ///   h: New height of the layout
363    ///
364    ///  Panic:
365    ///   - The layout must have been successfully built otherwise this function will panic.
366    pub fn resize(&self, w: u32, h: u32) {
367        let inner = self.inner.borrow();
368        if inner.base.is_null() {
369            panic!("Grid layout is not bound to a parent control.")
370        }
371        self.update_layout(w, h);
372    }
373
374    /// Resize the layout to fit the parent window size
375    ///
376    /// Panic:
377    ///   - The layout must have been successfully built otherwise this function will panic.
378    pub fn fit(&self) {
379        let inner = self.inner.borrow();
380        if inner.base.is_null() {
381            panic!("Grid layout is not bound to a parent control.")
382        }
383
384        let (w, h) = wh::get_window_size(inner.base);
385        self.update_layout(w, h);
386    }
387
388    /// Set the margins of the layout. The four values are in this order: top, right, bottom, left.
389    pub fn margin(&self, m: [u32; 4]) {
390        let mut inner = self.inner.borrow_mut();
391        inner.margins = m;
392    }
393
394    /// Set the size of the space between the children in the layout. Default value is 5.
395    pub fn spacing(&self, sp: u32) {
396        let mut inner = self.inner.borrow_mut();
397        inner.spacing = sp;
398    }
399
400    /// Sets the minimum size of the layout
401    pub fn min_size(&self, sz: [u32; 2]) {
402        let mut inner = self.inner.borrow_mut();
403        inner.min_size = sz;
404    }
405
406    /// Sets the maximum size of the layout
407    pub fn max_size(&self, sz: [u32; 2]) {
408        let mut inner = self.inner.borrow_mut();
409        inner.max_size = sz;
410    }
411
412    /// Set the number of column in the layout
413    pub fn max_column(&self, count: Option<u32>) {
414        let mut inner = self.inner.borrow_mut();
415        inner.column_count = count;
416    }
417
418    /// Set the number of row in the layout
419    pub fn max_row(&self, count: Option<u32>) {
420        let mut inner = self.inner.borrow_mut();
421        inner.row_count = count;
422    }
423
424    fn update_layout(&self, mut width: u32, mut height: u32) -> () {
425        let inner = self.inner.borrow();
426        if inner.base.is_null() || inner.children.len() == 0 {
427            return;
428        }
429
430        let [m_top, m_right, m_bottom, m_left] = inner.margins;
431        let sp = inner.spacing;
432
433        let children = &inner.children;
434
435        let [min_w, min_h] = inner.min_size;
436        if width < min_w {
437            width = min_w;
438        }
439        if height < min_h {
440            height = min_h;
441        }
442
443        let [max_w, max_h] = inner.max_size;
444        if width > max_w {
445            width = max_w;
446        }
447        if height > max_h {
448            height = max_h;
449        }
450
451        let column_count = match inner.column_count {
452            Some(c) => c,
453            None => children
454                .iter()
455                .map(|item| item.col + item.col_span)
456                .max()
457                .unwrap_or(1),
458        };
459
460        let row_count = match inner.row_count {
461            Some(c) => c,
462            None => children
463                .iter()
464                .map(|item| item.row + item.row_span)
465                .max()
466                .unwrap_or(1),
467        };
468
469        if width < (m_right + m_left) + ((sp * 2) * column_count) {
470            return;
471        }
472
473        if height < (m_top + m_bottom) + ((sp * 2) * row_count) {
474            return;
475        }
476
477        // Apply margins
478        width = width - m_right - m_left;
479        height = height - m_top - m_bottom;
480
481        // Apply spacing
482        width = width - ((sp * 2) * column_count);
483        height = height - ((sp * 2) * row_count);
484
485        let item_width = width / column_count;
486        let item_height = height / row_count;
487        let sp2 = sp * 2;
488
489        let mut columns = vec![item_width; column_count as usize];
490        let mut rows = vec![item_height; row_count as usize];
491        let extra_width = width - item_width * column_count;
492        if extra_width > 0 {
493            for x in &mut columns[0..(extra_width as usize)] {
494                *x += 1;
495            }
496        }
497        let extra_height = height - item_height * row_count;
498        if extra_height > 0 {
499            for x in &mut rows[0..(extra_height as usize)] {
500                *x += 1;
501            }
502        }
503
504        let mut last_handle = None;
505        for item in inner.children.iter() {
506            let x: u32 = m_left
507                + (sp + (sp2 * item.col))
508                + columns[0..(item.col as usize)].iter().sum::<u32>();
509            let y: u32 =
510                m_top + (sp + (sp2 * item.row)) + rows[0..(item.row as usize)].iter().sum::<u32>();
511
512            let local_width: u32 = &columns
513                [(item.col as usize)..((item.col + item.col_span) as usize)]
514                .iter()
515                .sum::<u32>()
516                + (sp2 * (item.col_span - 1));
517            let local_height: u32 = &rows
518                [(item.row as usize)..((item.row + item.row_span) as usize)]
519                .iter()
520                .sum::<u32>()
521                + (sp2 * (item.row_span - 1));
522
523            wh::set_window_position(item.control, x as i32, y as i32);
524            wh::set_window_size(item.control, local_width, local_height, false);
525            wh::set_window_after(item.control, last_handle);
526
527            last_handle = Some(item.control);
528        }
529    }
530}
531
532impl Default for GridLayout {
533    fn default() -> GridLayout {
534        let inner = GridLayoutInner {
535            base: ptr::null_mut(),
536            children: Vec::new(),
537            margins: [5, 5, 5, 5],
538            min_size: [0, 0],
539            max_size: [u32::max_value(), u32::max_value()],
540            column_count: None,
541            row_count: None,
542            spacing: 5,
543        };
544
545        GridLayout {
546            inner: Rc::new(RefCell::new(inner)),
547        }
548    }
549}
550
551/// Builder for a `GridLayout` struct
552pub struct GridLayoutBuilder {
553    layout: GridLayoutInner,
554}
555
556impl GridLayoutBuilder {
557    /// Set the layout parent. The handle must be a window object otherwise the function will panic
558    pub fn parent<W: Into<ControlHandle>>(mut self, p: W) -> GridLayoutBuilder {
559        self.layout.base = p.into().hwnd().expect("Parent must be HWND");
560        self
561    }
562
563    /// Add a children to the layout at the position `col` and `row`.
564    /// This is a shortcut over `child_item` for item with default span.
565    /// The handle must be a window object otherwise the function will panic
566    pub fn child<W: Into<ControlHandle>>(mut self, col: u32, row: u32, c: W) -> GridLayoutBuilder {
567        let h = c.into().hwnd().expect("Child must be HWND");
568        self.layout.children.push(GridLayoutItem {
569            control: h,
570            col,
571            row,
572            col_span: 1,
573            row_span: 1,
574        });
575
576        self
577    }
578
579    /// Add a children to the layout
580    /// The handle must be a window object otherwise the function will panic
581    pub fn child_item(mut self, item: GridLayoutItem) -> GridLayoutBuilder {
582        self.layout.children.push(item);
583        self
584    }
585
586    /// Set the margins of the layout. The four values are in this order: top, right, bottom, left.
587    pub fn margin(mut self, m: [u32; 4]) -> GridLayoutBuilder {
588        self.layout.margins = m;
589        self
590    }
591
592    /// Set the size of the space between the children in the layout. Default value is 5.
593    pub fn spacing(mut self, sp: u32) -> GridLayoutBuilder {
594        self.layout.spacing = sp;
595        self
596    }
597
598    /// Sets the minimum size of the layout
599    pub fn min_size(mut self, sz: [u32; 2]) -> GridLayoutBuilder {
600        self.layout.min_size = sz;
601        self
602    }
603
604    /// Sets the maximum size of the layout
605    pub fn max_size(mut self, sz: [u32; 2]) -> GridLayoutBuilder {
606        self.layout.max_size = sz;
607        self
608    }
609
610    /// Set the number of column in the layout
611    pub fn max_column(mut self, count: Option<u32>) -> GridLayoutBuilder {
612        self.layout.column_count = count;
613        self
614    }
615
616    /// Set the number of row in the layout
617    pub fn max_row(mut self, count: Option<u32>) -> GridLayoutBuilder {
618        self.layout.row_count = count;
619        self
620    }
621
622    /// Build the layout object and bind the callback.
623    /// Children must only contains window object otherwise this method will panic.
624    pub fn build(self, layout: &GridLayout) -> Result<(), NwgError> {
625        use winapi::shared::minwindef::{HIWORD, LOWORD};
626        use winapi::um::winuser::WM_SIZE;
627
628        if self.layout.base.is_null() {
629            return Err(NwgError::layout_create(
630                "Gridlayout does not have a parent.",
631            ));
632        }
633
634        // Checks if the layouts cell or row are outside max_column or max_row
635        if let Some(max_row) = self.layout.row_count {
636            if let Some(item) = self.layout.children.iter().find(|c| c.row >= max_row) {
637                return Err(NwgError::layout_create(format!(
638                    "A layout item row is bigger or equal than the max number of row. {} >= {}",
639                    item.row, max_row
640                )));
641            }
642        }
643
644        if let Some(max_column) = self.layout.column_count {
645            if let Some(item) = self.layout.children.iter().find(|c| c.col >= max_column) {
646                return Err(NwgError::layout_create(format!(
647                    "A layout item column is bigger or equal than the max number of column. {} >= {}",
648                    item.col, max_column
649                )));
650            }
651        }
652
653        let (w, h) = wh::get_window_size(self.layout.base);
654        let base_handle = ControlHandle::Hwnd(self.layout.base);
655
656        // Saves the new layout. TODO: should free the old one too (if any)
657        {
658            let mut layout_inner = layout.inner.borrow_mut();
659            *layout_inner = self.layout;
660        }
661
662        // Initial layout update
663        layout.update_layout(w, h);
664
665        // Bind the event handler
666        let event_layout = layout.clone();
667        let cb = move |_h, msg, _w, l| {
668            if msg == WM_SIZE {
669                let size = l as u32;
670                let width = LOWORD(size) as i32;
671                let height = HIWORD(size) as i32;
672                let (w, h) = crate::win32::high_dpi::physical_to_logical(width, height);
673                GridLayout::update_layout(&event_layout, w as u32, h as u32);
674            }
675            None
676        };
677
678        /// Keep generating ids so that multiple layouts can be applied to the same parent
679        use std::sync::atomic::{AtomicUsize, Ordering};
680        static BOX_LAYOUT_ID: AtomicUsize = AtomicUsize::new(0x8FFF);
681        bind_raw_event_handler_inner(
682            &base_handle,
683            BOX_LAYOUT_ID.fetch_add(1, Ordering::SeqCst),
684            cb,
685        )
686        .unwrap();
687
688        Ok(())
689    }
690}