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#[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 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 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 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 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 #[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 #[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 pub fn focus(&self) -> bool {
154 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
155 wh::get_focus(handle)
156 }
157
158 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 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 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 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 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 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 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 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 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 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 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 pub fn class_name(&self) -> &'static str {
236 winapi::um::commctrl::WC_TABCONTROL
237 }
238
239 pub fn flags(&self) -> u32 {
241 ::winapi::um::winuser::WS_VISIBLE
242 }
243
244 pub fn forced_flags(&self) -> u32 {
246 use winapi::um::winuser::{WS_CHILD, WS_CLIPCHILDREN};
247 WS_CHILD | WS_CLIPCHILDREN }
251
252 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 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 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 #[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#[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 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 #[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 #[cfg(feature = "image-list")]
570 pub fn image_index(&self) -> Option<i32> {
571 None
572 }
573
574 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 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 pub fn class_name(&self) -> &'static str {
599 "NWG_TAB"
600 }
601
602 pub fn flags(&self) -> u32 {
604 0
605 }
606
607 pub fn forced_flags(&self) -> u32 {
609 use winapi::um::winuser::{WS_CHILD, WS_CLIPCHILDREN};
611
612 WS_CHILD | WS_CLIPCHILDREN
613 }
614
615 unsafe fn init(current_handle: HWND, tab_view_handle: HWND, index: usize) {
617 use winapi::um::winuser::GWL_USERDATA;
618
619 wh::set_window_long(current_handle, GWL_USERDATA, index);
621
622 let (w, h) = wh::get_window_size(tab_view_handle);
624 let width = w - 11;
625 let height = h - 33;
626
627 wh::set_window_size(current_handle, width, height, false);
629
630 wh::set_window_position(current_handle, 5, 25);
632
633 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 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 #[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
807extern "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}