native_windows_gui2/controls/
tabs.rs

1use super::{ControlBase, ControlHandle};
2use crate::win32::{
3    base_helper::{check_hwnd, to_utf16},
4    window_helper as wh,
5};
6use crate::{Font, NwgError, RawEventHandler, unbind_raw_event_handler};
7use std::{cell::RefCell, mem};
8use winapi::shared::minwindef::{BOOL, LPARAM, WPARAM};
9use winapi::shared::windef::HWND;
10use winapi::um::winnt::LPWSTR;
11use winapi::um::winuser::{EnumChildWindows, WS_DISABLED, WS_EX_CONTROLPARENT, WS_VISIBLE};
12
13#[cfg(feature = "image-list")]
14use crate::ImageList;
15
16#[cfg(feature = "image-list")]
17use std::ptr;
18
19const NOT_BOUND: &'static str = "TabsContainer/Tab is not yet bound to a winapi object";
20const BAD_HANDLE: &'static str = "INTERNAL ERROR: TabsContainer/Tab handle is not HWND!";
21
22bitflags! {
23    pub struct TabsContainerFlags: u32 {
24        const VISIBLE = WS_VISIBLE;
25        const DISABLED = WS_DISABLED;
26    }
27}
28
29/**
30A tabs container is a frame-like control that can contain `Tab` control.
31Tabs are added by specifying the `TabsContainer` as parent in the `Tab` builder.
32
33Do not add other control type as children to the TabsContainer
34
35Requires the `tabs` feature
36
37**Builder parameters:**
38  * `parent`:     **Required.** The button parent container.
39  * `position`:   The tab container position.
40  * `font`:       The font used for the tabs title
41  * `flags`:      A combination of the `TabsContainerFlags` values.
42  * `ex_flags`: A combination of win32 window extended flags. Unlike `flags`, ex_flags must be used straight from winapi
43  * `image_list`: The image list specifying the tabs icons
44
45
46**Control events:**
47  * `TabsContainerChanged`: The select tab of a TabsContainer changed
48  * `TabsContainerChanging`: The selected tab of a TabsContainer is about to be changed
49  * `MousePress(_)`: Generic mouse press events on the button
50  * `OnMouseMove`: Generic mouse mouse event
51  * `OnMouseWheel`: Generic mouse wheel event
52
53*/
54#[derive(Default)]
55pub struct TabsContainer {
56    pub handle: ControlHandle,
57    handler0: RefCell<Option<RawEventHandler>>,
58    handler1: RefCell<Option<RawEventHandler>>,
59}
60
61impl TabsContainer {
62    pub fn builder<'a>() -> TabsContainerBuilder<'a> {
63        TabsContainerBuilder {
64            size: (300, 300),
65            position: (0, 0),
66            parent: None,
67            font: None,
68            flags: None,
69            ex_flags: 0,
70
71            #[cfg(feature = "image-list")]
72            image_list: None,
73        }
74    }
75
76    /// Return the index of the currently selected tab
77    /// May return `usize::max_value()` if no tab is selected
78    pub fn selected_tab(&self) -> usize {
79        use winapi::um::commctrl::TCM_GETCURSEL;
80
81        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
82        wh::send_message(handle, TCM_GETCURSEL, 0, 0) as usize
83    }
84
85    /// Set the currently selected tab by index
86    pub fn set_selected_tab(&self, index: usize) {
87        use winapi::um::commctrl::TCM_SETCURSEL;
88
89        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
90        wh::send_message(handle, TCM_SETCURSEL, index as WPARAM, 0);
91
92        // Update the visible state of the tabs (this is not done automatically)
93        let data: (HWND, i32) = (handle, index as i32);
94        let data_ptr = &data as *const (HWND, i32);
95
96        unsafe {
97            EnumChildWindows(handle, Some(toggle_children_tabs), data_ptr as LPARAM);
98        }
99    }
100
101    /// Return the number of tabs in the view
102    pub fn tab_count(&self) -> usize {
103        use winapi::um::commctrl::TCM_GETITEMCOUNT;
104
105        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
106        wh::send_message(handle, TCM_GETITEMCOUNT, 0, 0) as usize
107    }
108
109    /**
110        Sets the image list of the tab container. Pass None to remove the image list.
111
112        This is only available is the feature "image-list" is enabled.
113    */
114    #[cfg(feature = "image-list")]
115    pub fn set_image_list(&self, list: Option<&ImageList>) {
116        use winapi::um::commctrl::TCM_SETIMAGELIST;
117
118        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
119        let list_handle = match list {
120            None => 0,
121            Some(list) => list.handle as _,
122        };
123
124        wh::send_message(handle, TCM_SETIMAGELIST, 0, list_handle);
125    }
126
127    /**
128        Returns a reference to the current image list in the tab container. The image list
129        is not owned and dropping it won't free the resources.
130
131        This is only available is the feature "image-list" is enabled.
132    */
133    #[cfg(feature = "image-list")]
134    pub fn image_list(&self) -> Option<ImageList> {
135        use winapi::um::commctrl::TCM_GETIMAGELIST;
136
137        let control_handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
138        let handle = wh::send_message(control_handle, TCM_GETIMAGELIST, 0, 0);
139        match handle == 0 {
140            true => None,
141            false => Some(ImageList {
142                handle: handle as _,
143                owned: false,
144            }),
145        }
146    }
147
148    //
149    // Default methods
150    //
151
152    /// Return true if the control currently has the keyboard focus
153    pub fn focus(&self) -> bool {
154        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
155        wh::get_focus(handle)
156    }
157
158    /// Set the keyboard focus on the button.
159    pub fn set_focus(&self) {
160        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
161
162        wh::set_focus(handle);
163    }
164
165    /// Return true if the control user can interact with the control, return false otherwise
166    pub fn enabled(&self) -> bool {
167        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
168        wh::get_window_enabled(handle)
169    }
170
171    /// Enable or disable the control
172    pub fn set_enabled(&self, v: bool) {
173        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
174        wh::set_window_enabled(handle, v)
175    }
176
177    /// Return true if the control is visible to the user. Will return true even if the
178    /// control is outside of the parent client view (ex: at the position (10000, 10000))
179    pub fn visible(&self) -> bool {
180        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
181        wh::get_window_visibility(handle)
182    }
183
184    /// Show or hide the control to the user
185    pub fn set_visible(&self, v: bool) {
186        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
187        wh::set_window_visibility(handle, v)
188    }
189
190    /// Return the size of the tabs container in the parent window
191    pub fn size(&self) -> (u32, u32) {
192        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
193        wh::get_window_size(handle)
194    }
195
196    /// Set the size of the tabs container in the parent window
197    pub fn set_size(&self, x: u32, y: u32) {
198        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
199        wh::set_window_size(handle, x, y, false)
200    }
201
202    /// Return the position of the tabs container in the parent window
203    pub fn position(&self) -> (i32, i32) {
204        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
205        wh::get_window_position(handle)
206    }
207
208    /// Set the position of the tabs container in the parent window
209    pub fn set_position(&self, x: i32, y: i32) {
210        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
211        wh::set_window_position(handle, x, y)
212    }
213
214    /// Return the font of the control
215    pub fn font(&self) -> Option<Font> {
216        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
217        let font_handle = wh::get_window_font(handle);
218        if font_handle.is_null() {
219            None
220        } else {
221            Some(Font {
222                handle: font_handle,
223            })
224        }
225    }
226
227    /// Set the font of the control
228    pub fn set_font(&self, font: Option<&Font>) {
229        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
230
231        wh::set_window_font(handle, font.map(|f| f.handle), true);
232    }
233
234    /// Winapi class name used during control creation
235    pub fn class_name(&self) -> &'static str {
236        winapi::um::commctrl::WC_TABCONTROL
237    }
238
239    /// Winapi base flags used during window creation
240    pub fn flags(&self) -> u32 {
241        ::winapi::um::winuser::WS_VISIBLE
242    }
243
244    /// Winapi flags required by the control
245    pub fn forced_flags(&self) -> u32 {
246        use winapi::um::winuser::{WS_CHILD, WS_CLIPCHILDREN};
247        //use winapi::um::commctrl::TCS_OWNERDRAWFIXED;
248
249        WS_CHILD | WS_CLIPCHILDREN //| TCS_OWNERDRAWFIXED
250    }
251
252    //
253    // Private
254    //
255
256    /// The tab widget lacks basic functionalities on it's own. This fix it.
257    fn hook_tabs(&self) {
258        use crate::bind_raw_event_handler_inner;
259        use winapi::shared::minwindef::{HIWORD, LOWORD};
260        use winapi::um::commctrl::{TCM_GETCURSEL, TCN_SELCHANGE};
261        use winapi::um::winuser::SendMessageW;
262        use winapi::um::winuser::{NMHDR, WM_NOTIFY, WM_SIZE};
263
264        if self.handle.blank() {
265            panic!("{}", NOT_BOUND);
266        }
267        let handle = self.handle.hwnd().expect(BAD_HANDLE);
268
269        let parent_handle_raw = wh::get_window_parent(handle);
270        let parent_handle = ControlHandle::Hwnd(parent_handle_raw);
271
272        let handler0 = bind_raw_event_handler_inner(
273            &parent_handle,
274            handle as usize,
275            move |_hwnd, msg, _w, l| unsafe {
276                match msg {
277                    WM_NOTIFY => {
278                        let nmhdr = &*(l as *const NMHDR);
279                        if nmhdr.code == TCN_SELCHANGE {
280                            let index = SendMessageW(handle, TCM_GETCURSEL, 0, 0) as i32;
281                            let data: (HWND, i32) = (handle, index);
282                            let data_ptr = &data as *const (HWND, i32);
283                            EnumChildWindows(
284                                handle,
285                                Some(toggle_children_tabs),
286                                data_ptr as LPARAM,
287                            );
288                        }
289                    }
290                    _ => {}
291                }
292
293                None
294            },
295        );
296
297        let handler1 =
298            bind_raw_event_handler_inner(&self.handle, handle as usize, move |hwnd, msg, _w, l| {
299                unsafe {
300                    match msg {
301                        WM_SIZE => {
302                            use winapi::shared::windef::{HGDIOBJ, RECT};
303                            use winapi::um::wingdi::SelectObject;
304                            use winapi::um::winuser::{
305                                DT_CALCRECT, DT_LEFT, DrawTextW, GetDC, ReleaseDC,
306                            };
307
308                            let size = l as u32;
309                            let width = LOWORD(size) as i32;
310                            let height = HIWORD(size) as i32;
311                            let (w, h) = crate::win32::high_dpi::physical_to_logical(width, height);
312
313                            let mut data = ResizeDirectChildrenParams {
314                                parent: hwnd,
315                                width: w as u32,
316                                height: h as u32,
317                                tab_offset_y: 0,
318                            };
319
320                            // Get the height of the tabs
321                            let font_handle = wh::get_window_font(hwnd);
322                            let mut r: RECT = mem::zeroed();
323                            let dc = GetDC(hwnd);
324                            let old = SelectObject(dc, font_handle as HGDIOBJ);
325                            let calc: [u16; 2] = [75, 121];
326                            DrawTextW(dc, calc.as_ptr(), 2, &mut r, DT_CALCRECT | DT_LEFT);
327                            SelectObject(dc, old);
328                            ReleaseDC(hwnd, dc);
329
330                            // Fix the width/height of the tabs
331                            const BORDER_SIZE: u32 = 11;
332                            let tab_height = r.bottom as u32 + BORDER_SIZE;
333                            if data.width > BORDER_SIZE {
334                                data.width -= BORDER_SIZE;
335                            }
336                            if data.height > tab_height {
337                                data.height -= (tab_height + BORDER_SIZE).min(data.height);
338                            }
339                            data.tab_offset_y = tab_height;
340
341                            let data_ptr = &data as *const ResizeDirectChildrenParams;
342                            EnumChildWindows(
343                                hwnd,
344                                Some(resize_direct_children),
345                                data_ptr as LPARAM,
346                            );
347                        }
348                        _ => {}
349                    }
350
351                    None
352                }
353            });
354
355        *self.handler0.borrow_mut() = Some(handler0.unwrap());
356        *self.handler1.borrow_mut() = Some(handler1.unwrap());
357    }
358}
359
360impl Drop for TabsContainer {
361    fn drop(&mut self) {
362        let handler = self.handler0.borrow();
363        if let Some(h) = handler.as_ref() {
364            drop(unbind_raw_event_handler(h));
365        }
366
367        let handler = self.handler1.borrow();
368        if let Some(h) = handler.as_ref() {
369            drop(unbind_raw_event_handler(h));
370        }
371
372        self.handle.destroy();
373    }
374}
375
376impl PartialEq for TabsContainer {
377    fn eq(&self, other: &Self) -> bool {
378        self.handle == other.handle
379    }
380}
381
382pub struct TabsContainerBuilder<'a> {
383    size: (i32, i32),
384    position: (i32, i32),
385    parent: Option<ControlHandle>,
386    font: Option<&'a Font>,
387    flags: Option<TabsContainerFlags>,
388    ex_flags: u32,
389
390    #[cfg(feature = "image-list")]
391    image_list: Option<&'a ImageList>,
392}
393
394impl<'a> TabsContainerBuilder<'a> {
395    pub fn flags(mut self, flags: TabsContainerFlags) -> TabsContainerBuilder<'a> {
396        self.flags = Some(flags);
397        self
398    }
399
400    pub fn ex_flags(mut self, flags: u32) -> TabsContainerBuilder<'a> {
401        self.ex_flags = flags;
402        self
403    }
404
405    pub fn size(mut self, size: (i32, i32)) -> TabsContainerBuilder<'a> {
406        self.size = size;
407        self
408    }
409
410    pub fn position(mut self, pos: (i32, i32)) -> TabsContainerBuilder<'a> {
411        self.position = pos;
412        self
413    }
414
415    pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> TabsContainerBuilder<'a> {
416        self.parent = Some(p.into());
417        self
418    }
419
420    pub fn font(mut self, font: Option<&'a Font>) -> TabsContainerBuilder<'a> {
421        self.font = font;
422        self
423    }
424
425    #[cfg(feature = "image-list")]
426    pub fn image_list(mut self, list: Option<&'a ImageList>) -> TabsContainerBuilder<'a> {
427        self.image_list = list;
428        self
429    }
430
431    pub fn build(self, out: &mut TabsContainer) -> Result<(), NwgError> {
432        let flags = self.flags.map(|f| f.bits()).unwrap_or(out.flags());
433
434        let parent = match self.parent {
435            Some(p) => Ok(p),
436            None => Err(NwgError::no_parent("TabsContainer")),
437        }?;
438
439        *out = Default::default();
440
441        out.handle = ControlBase::build_hwnd()
442            .class_name(out.class_name())
443            .forced_flags(out.forced_flags())
444            .flags(flags)
445            .ex_flags(WS_EX_CONTROLPARENT | self.ex_flags)
446            .size(self.size)
447            .position(self.position)
448            .parent(Some(parent))
449            .build()?;
450
451        out.hook_tabs();
452
453        if self.font.is_some() {
454            out.set_font(self.font);
455        } else {
456            out.set_font(Font::global_default().as_ref());
457        }
458
459        // Image list
460        #[cfg(feature = "image-list")]
461        fn set_image_list(b: &TabsContainerBuilder, out: &mut TabsContainer) {
462            if b.image_list.is_some() {
463                out.set_image_list(b.image_list);
464            }
465        }
466
467        #[cfg(not(feature = "image-list"))]
468        fn set_image_list(_b: &TabsContainerBuilder, _out: &mut TabsContainer) {}
469
470        set_image_list(&self, out);
471
472        Ok(())
473    }
474}
475
476/**
477A subwindow in a `TabContainer` widget. A Tab control can only be added as a child of a `TabContainer`.
478
479A Tab controls doesn't do much on its own. See `TabContainer` for the tab specific events.
480
481**Builder parameters:**
482  * `parent`:      **Required.** The Tab parent container.
483  * `text`:        The tab text
484  * `image_index`: The tab icon index in the tab container image list
485*/
486#[derive(Default, Debug, PartialEq, Eq)]
487pub struct Tab {
488    pub handle: ControlHandle,
489}
490
491impl Tab {
492    pub fn builder<'a>() -> TabBuilder<'a> {
493        TabBuilder {
494            text: "Tab",
495            parent: None,
496
497            #[cfg(feature = "image-list")]
498            image_index: None,
499        }
500    }
501
502    /// Sets the title of the tab
503    pub fn set_text<'a>(&self, text: &'a str) {
504        use winapi::um::commctrl::{TCIF_TEXT, TCITEMW, TCM_SETITEMW};
505        use winapi::um::winuser::GWL_USERDATA;
506
507        if self.handle.blank() {
508            panic!("{}", NOT_BOUND);
509        }
510        let handle = self.handle.hwnd().expect(BAD_HANDLE);
511
512        let tab_index = (wh::get_window_long(handle, GWL_USERDATA) - 1) as WPARAM;
513
514        let tab_view_handle = wh::get_window_parent(handle);
515
516        let text = to_utf16(text);
517        let item = TCITEMW {
518            mask: TCIF_TEXT,
519            dwState: 0,
520            dwStateMask: 0,
521            pszText: text.as_ptr() as LPWSTR,
522            cchTextMax: 0,
523            iImage: -1,
524            lParam: 0,
525        };
526
527        let item_ptr = &item as *const TCITEMW;
528        wh::send_message(tab_view_handle, TCM_SETITEMW, tab_index, item_ptr as LPARAM);
529    }
530
531    /**
532        Sets the image of the tab. index is the index of the image in the tab container image list.
533
534        This is only available if the "image-list" feature is enabled
535    */
536    #[cfg(feature = "image-list")]
537    pub fn set_image_index(&self, index: Option<i32>) {
538        use winapi::um::commctrl::{TCIF_IMAGE, TCITEMW, TCM_SETITEMW};
539        use winapi::um::winuser::GWL_USERDATA;
540
541        if self.handle.blank() {
542            panic!("{}", NOT_BOUND);
543        }
544        let handle = self.handle.hwnd().expect(BAD_HANDLE);
545
546        let tab_index = (wh::get_window_long(handle, GWL_USERDATA) - 1) as WPARAM;
547        let tab_view_handle = wh::get_window_parent(handle);
548
549        let item = TCITEMW {
550            mask: TCIF_IMAGE,
551            dwState: 0,
552            dwStateMask: 0,
553            pszText: ptr::null_mut(),
554            cchTextMax: 0,
555            iImage: index.unwrap_or(-1),
556            lParam: 0,
557        };
558
559        let item_ptr = &item as *const TCITEMW;
560        wh::send_message(tab_view_handle, TCM_SETITEMW, tab_index, item_ptr as LPARAM);
561    }
562
563    /**
564        Returns the index of image of the tab.
565        The index maps to the image list of the tab container.
566
567        This is only available if the "image-list" feature is enabled
568    */
569    #[cfg(feature = "image-list")]
570    pub fn image_index(&self) -> Option<i32> {
571        None
572    }
573
574    /// Returns true if the control is visible to the user. Will return true even if the
575    /// control is outside of the parent client view (ex: at the position (10000, 10000))
576    pub fn visible(&self) -> bool {
577        if self.handle.blank() {
578            panic!("{}", NOT_BOUND);
579        }
580        let handle = self.handle.hwnd().expect(BAD_HANDLE);
581        wh::get_window_visibility(handle)
582    }
583
584    /// Show or hide the control to the user
585    pub fn set_visible(&self, v: bool) {
586        if self.handle.blank() {
587            panic!("{}", NOT_BOUND);
588        }
589        let handle = self.handle.hwnd().expect(BAD_HANDLE);
590        wh::set_window_visibility(handle, v)
591    }
592
593    //
594    // Other methods
595    //
596
597    /// Winapi class name used during control creation
598    pub fn class_name(&self) -> &'static str {
599        "NWG_TAB"
600    }
601
602    /// Winapi base flags used during window creation
603    pub fn flags(&self) -> u32 {
604        0
605    }
606
607    /// Winapi flags required by the control
608    pub fn forced_flags(&self) -> u32 {
609        //use winapi::um::commctrl::{TCS_SINGLELINE};
610        use winapi::um::winuser::{WS_CHILD, WS_CLIPCHILDREN};
611
612        WS_CHILD | WS_CLIPCHILDREN
613    }
614
615    /// Set and initialize a tab as active
616    unsafe fn init(current_handle: HWND, tab_view_handle: HWND, index: usize) {
617        use winapi::um::winuser::GWL_USERDATA;
618
619        // Save the index of the tab in the window data
620        wh::set_window_long(current_handle, GWL_USERDATA, index);
621
622        // Resize the tabs so that they match the tab view size and hide all children tabs
623        let (w, h) = wh::get_window_size(tab_view_handle);
624        let width = w - 11;
625        let height = h - 33;
626
627        // Resize the tab to match the tab view
628        wh::set_window_size(current_handle, width, height, false);
629
630        // Move the tab under the headers
631        wh::set_window_position(current_handle, 5, 25);
632
633        // Make the current tab visible
634        if index == 1 {
635            wh::set_window_visibility(current_handle, true);
636        }
637    }
638
639    fn next_index(tab_view_handle: HWND) -> usize {
640        let mut count = 0;
641        let count_ptr = &mut count as *mut usize;
642
643        unsafe {
644            EnumChildWindows(tab_view_handle, Some(count_children), count_ptr as LPARAM);
645        }
646
647        count
648    }
649
650    /// Bind the tab to a tab view
651    fn bind_container<'a>(&self, text: &'a str) {
652        use winapi::um::commctrl::{TCIF_TEXT, TCITEMW, TCM_INSERTITEMW};
653
654        if self.handle.blank() {
655            panic!("{}", NOT_BOUND);
656        }
657        let handle = self.handle.hwnd().expect(BAD_HANDLE);
658
659        let tab_view_handle = wh::get_window_parent(handle);
660        let next_index = Tab::next_index(tab_view_handle);
661
662        unsafe {
663            Tab::init(handle, tab_view_handle, next_index);
664        }
665
666        let text = to_utf16(&text);
667        let tab_info = TCITEMW {
668            mask: TCIF_TEXT,
669            dwState: 0,
670            dwStateMask: 0,
671            pszText: text.as_ptr() as LPWSTR,
672            cchTextMax: 0,
673            iImage: -1,
674            lParam: 0,
675        };
676
677        let tab_info_ptr = &tab_info as *const TCITEMW;
678        wh::send_message(
679            tab_view_handle,
680            TCM_INSERTITEMW,
681            next_index as WPARAM,
682            tab_info_ptr as LPARAM,
683        );
684    }
685}
686
687impl Drop for Tab {
688    fn drop(&mut self) {
689        self.handle.destroy();
690    }
691}
692
693pub struct TabBuilder<'a> {
694    text: &'a str,
695    parent: Option<ControlHandle>,
696
697    #[cfg(feature = "image-list")]
698    image_index: Option<i32>,
699}
700
701impl<'a> TabBuilder<'a> {
702    pub fn text(mut self, text: &'a str) -> TabBuilder<'a> {
703        self.text = text;
704        self
705    }
706
707    pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> TabBuilder<'a> {
708        self.parent = Some(p.into());
709        self
710    }
711
712    #[cfg(feature = "image-list")]
713    pub fn image_index(mut self, index: Option<i32>) -> TabBuilder<'a> {
714        self.image_index = index;
715        self
716    }
717
718    pub fn build(self, out: &mut Tab) -> Result<(), NwgError> {
719        use winapi::um::commctrl::WC_TABCONTROL;
720
721        let parent = match self.parent {
722            Some(p) => Ok(p),
723            None => Err(NwgError::no_parent("Tab")),
724        }?;
725
726        *out = Default::default();
727
728        match &parent {
729            &ControlHandle::Hwnd(h) => {
730                let class_name = wh::get_window_class_name(h);
731                if &class_name != WC_TABCONTROL {
732                    Err(NwgError::control_create(
733                        "Tab requires a TabsContainer parent.",
734                    ))
735                } else {
736                    Ok(())
737                }
738            }
739            _ => Err(NwgError::control_create(
740                "Tab requires a TabsContainer parent.",
741            )),
742        }?;
743
744        out.handle = ControlBase::build_hwnd()
745            .class_name(out.class_name())
746            .forced_flags(out.forced_flags())
747            .ex_flags(WS_EX_CONTROLPARENT)
748            .flags(out.flags())
749            .text(self.text)
750            .parent(Some(parent))
751            .build()?;
752
753        out.bind_container(self.text);
754
755        // Image index
756
757        #[cfg(feature = "image-list")]
758        fn set_image_index<'a>(b: &TabBuilder<'a>, out: &mut Tab) {
759            if b.image_index.is_some() {
760                out.set_image_index(b.image_index);
761            }
762        }
763
764        #[cfg(not(feature = "image-list"))]
765        fn set_image_index<'a>(_b: &TabBuilder<'a>, _out: &mut Tab) {}
766
767        set_image_index(&self, out);
768
769        Ok(())
770    }
771}
772
773struct ResizeDirectChildrenParams {
774    parent: HWND,
775    width: u32,
776    height: u32,
777    tab_offset_y: u32,
778}
779
780extern "system" fn resize_direct_children(handle: HWND, params: LPARAM) -> BOOL {
781    let params: &ResizeDirectChildrenParams =
782        unsafe { &*(params as *const ResizeDirectChildrenParams) };
783    if wh::get_window_parent(handle) == params.parent {
784        wh::set_window_size(handle, params.width, params.height, false);
785
786        let (x, _y) = wh::get_window_position(handle);
787        wh::set_window_position(handle, x, params.tab_offset_y as i32);
788    }
789
790    1
791}
792
793extern "system" fn count_children(handle: HWND, params: LPARAM) -> BOOL {
794    use winapi::um::winuser::GWL_USERDATA;
795
796    if &wh::get_window_class_name(handle) == "NWG_TAB" {
797        let tab_index = (wh::get_window_long(handle, GWL_USERDATA)) as WPARAM;
798        let count = params as *mut usize;
799        unsafe {
800            *count = usize::max(tab_index + 1, *count);
801        }
802    }
803
804    1
805}
806
807/// Toggle the visibility of the active and inactive tab.
808extern "system" fn toggle_children_tabs(handle: HWND, params: LPARAM) -> BOOL {
809    use winapi::um::winuser::GWL_USERDATA;
810
811    let (parent, index) = unsafe { *(params as *const (HWND, i32)) };
812    if wh::get_window_parent(handle) == parent {
813        let tab_index = wh::get_window_long(handle, GWL_USERDATA) as i32;
814        let visible = tab_index == index + 1;
815        wh::set_window_visibility(handle, visible);
816    }
817
818    1
819}