native_windows_gui/layouts/
grid_layout.rs

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