1use winapi::shared::minwindef::{WPARAM, LPARAM, BOOL};
2use winapi::shared::windef::HWND;
3use winapi::um::winnt::LPWSTR;
4use winapi::um::winuser::{EnumChildWindows, WS_VISIBLE, WS_DISABLED, WS_EX_CONTROLPARENT};
5use crate::win32::{base_helper::{to_utf16, check_hwnd}, window_helper as wh};
6use crate::{NwgError, Font, RawEventHandler, unbind_raw_event_handler};
7use super::{ControlBase, ControlHandle};
8use std::{mem, cell::RefCell};
9
10#[cfg(feature="image-list")]
11use crate::ImageList;
12
13#[cfg(feature="image-list")]
14use std::ptr;
15
16const NOT_BOUND: &'static str = "TabsContainer/Tab is not yet bound to a winapi object";
17const BAD_HANDLE: &'static str = "INTERNAL ERROR: TabsContainer/Tab handle is not HWND!";
18
19
20bitflags! {
21 pub struct TabsContainerFlags: u32 {
22 const VISIBLE = WS_VISIBLE;
23 const DISABLED = WS_DISABLED;
24 }
25}
26
27#[derive(Default)]
53pub struct TabsContainer {
54 pub handle: ControlHandle,
55 handler0: RefCell<Option<RawEventHandler>>,
56 handler1: RefCell<Option<RawEventHandler>>,
57}
58
59impl TabsContainer {
60
61 pub fn builder<'a>() -> TabsContainerBuilder<'a> {
62 TabsContainerBuilder {
63 size: (300, 300),
64 position: (0, 0),
65 parent: None,
66 font: None,
67 flags: None,
68 ex_flags: 0,
69
70 #[cfg(feature = "image-list")]
71 image_list: None
72 }
73 }
74
75 pub fn selected_tab(&self) -> usize {
78 use winapi::um::commctrl::{TCM_GETCURSEL};
79
80 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
81 wh::send_message(handle, TCM_GETCURSEL, 0, 0) as usize
82 }
83
84 pub fn set_selected_tab(&self, index: usize) {
86 use winapi::um::commctrl::TCM_SETCURSEL;
87
88 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
89 wh::send_message(handle, TCM_SETCURSEL, index as WPARAM, 0);
90
91 let data: (HWND, i32) = (handle, index as i32);
93 let data_ptr = &data as *const (HWND, i32);
94
95 unsafe {
96 EnumChildWindows(handle, Some(toggle_children_tabs), data_ptr as LPARAM);
97 }
98 }
99
100 pub fn tab_count(&self) -> usize {
102 use winapi::um::commctrl::TCM_GETITEMCOUNT;
103
104 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
105 wh::send_message(handle, TCM_GETITEMCOUNT, 0, 0) as usize
106 }
107
108 #[cfg(feature = "image-list")]
114 pub fn set_image_list(&self, list: Option<&ImageList>) {
115 use winapi::um::commctrl::TCM_SETIMAGELIST;
116
117 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
118 let list_handle = match list {
119 None => 0,
120 Some(list) => list.handle as _
121 };
122
123 wh::send_message(handle, TCM_SETIMAGELIST, 0, list_handle);
124 }
125
126 #[cfg(feature = "image-list")]
133 pub fn image_list(&self) -> Option<ImageList> {
134 use winapi::um::commctrl::TCM_GETIMAGELIST;
135
136 let control_handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
137 let handle = wh::send_message(control_handle, TCM_GETIMAGELIST, 0, 0);
138 match handle == 0 {
139 true => None,
140 false => Some(ImageList {
141 handle: handle as _,
142 owned: false,
143 })
144 }
145 }
146
147 pub fn focus(&self) -> bool {
153 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
154 unsafe { wh::get_focus(handle) }
155 }
156
157 pub fn set_focus(&self) {
159 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
160 unsafe { wh::set_focus(handle); }
161 }
162
163 pub fn enabled(&self) -> bool {
165 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
166 unsafe { wh::get_window_enabled(handle) }
167 }
168
169 pub fn set_enabled(&self, v: bool) {
171 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
172 unsafe { wh::set_window_enabled(handle, v) }
173 }
174
175 pub fn visible(&self) -> bool {
178 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
179 unsafe { wh::get_window_visibility(handle) }
180 }
181
182 pub fn set_visible(&self, v: bool) {
184 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
185 unsafe { wh::set_window_visibility(handle, v) }
186 }
187
188 pub fn size(&self) -> (u32, u32) {
190 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
191 unsafe { wh::get_window_size(handle) }
192 }
193
194 pub fn set_size(&self, x: u32, y: u32) {
196 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
197 unsafe { wh::set_window_size(handle, x, y, false) }
198 }
199
200 pub fn position(&self) -> (i32, i32) {
202 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
203 unsafe { wh::get_window_position(handle) }
204 }
205
206 pub fn set_position(&self, x: i32, y: i32) {
208 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
209 unsafe { wh::set_window_position(handle, x, y) }
210 }
211
212 pub fn font(&self) -> Option<Font> {
214 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
215 let font_handle = wh::get_window_font(handle);
216 if font_handle.is_null() {
217 None
218 } else {
219 Some(Font { handle: font_handle })
220 }
221 }
222
223 pub fn set_font(&self, font: Option<&Font>) {
225 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
226 unsafe { wh::set_window_font(handle, font.map(|f| f.handle), true); }
227 }
228
229 pub fn class_name(&self) -> &'static str {
231 winapi::um::commctrl::WC_TABCONTROL
232 }
233
234 pub fn flags(&self) -> u32 {
236 ::winapi::um::winuser::WS_VISIBLE
237 }
238
239 pub fn forced_flags(&self) -> u32 {
241 use winapi::um::winuser::{WS_CHILD, WS_CLIPCHILDREN};
242 WS_CHILD | WS_CLIPCHILDREN }
246
247 fn hook_tabs(&self) {
253 use crate::bind_raw_event_handler_inner;
254 use winapi::shared::minwindef::{HIWORD, LOWORD};
255 use winapi::um::winuser::{NMHDR, WM_SIZE, WM_NOTIFY};
256 use winapi::um::commctrl::{TCM_GETCURSEL, TCN_SELCHANGE};
257 use winapi::um::winuser::SendMessageW;
258
259 if self.handle.blank() { panic!("{}", NOT_BOUND); }
260 let handle = self.handle.hwnd().expect(BAD_HANDLE);
261
262 let parent_handle_raw = wh::get_window_parent(handle);
263 let parent_handle = ControlHandle::Hwnd(parent_handle_raw);
264
265 let handler0 = bind_raw_event_handler_inner(&parent_handle, handle as usize, move |_hwnd, msg, _w, l| { unsafe {
266 match msg {
267 WM_NOTIFY => {
268 let nmhdr: &NMHDR = mem::transmute(l);
269 if nmhdr.code == TCN_SELCHANGE {
270 let index = SendMessageW(handle, TCM_GETCURSEL, 0, 0) as i32;
271 let data: (HWND, i32) = (handle, index);
272 let data_ptr = &data as *const (HWND, i32);
273 EnumChildWindows(handle, Some(toggle_children_tabs), data_ptr as LPARAM);
274 }
275 },
276 _ => {}
277 }
278
279 None
280 } });
281
282 let handler1 = bind_raw_event_handler_inner(&self.handle, handle as usize, move |hwnd, msg, _w, l| { unsafe {
283 match msg {
284 WM_SIZE => {
285 use winapi::shared::windef::{RECT, HGDIOBJ};
286 use winapi::um::winuser::{GetDC, DrawTextW, ReleaseDC, DT_CALCRECT, DT_LEFT};
287 use winapi::um::wingdi::SelectObject;
288
289 let size = l as u32;
290 let width = LOWORD(size) as i32;
291 let height = HIWORD(size) as i32;
292 let (w, h) = crate::win32::high_dpi::physical_to_logical(width, height);
293
294 let mut data = ResizeDirectChildrenParams {
295 parent: hwnd,
296 width: w as u32,
297 height: h as u32,
298 tab_offset_y: 0
299 };
300
301 let font_handle = wh::get_window_font(hwnd);
303 let mut r: RECT = mem::zeroed();
304 let dc = GetDC(hwnd);
305 let old = SelectObject(dc, font_handle as HGDIOBJ);
306 let calc: [u16;2] = [75, 121];
307 DrawTextW(dc, calc.as_ptr(), 2, &mut r, DT_CALCRECT | DT_LEFT);
308 SelectObject(dc, old);
309 ReleaseDC(hwnd, dc);
310
311 const BORDER_SIZE: u32 = 11;
313 let tab_height = r.bottom as u32 + BORDER_SIZE;
314 if data.width > BORDER_SIZE { data.width -= BORDER_SIZE; }
315 if data.height > tab_height {
316 data.height -= (tab_height + BORDER_SIZE).min(data.height);
317 }
318 data.tab_offset_y = tab_height;
319
320 let data_ptr = &data as *const ResizeDirectChildrenParams;
321 EnumChildWindows(hwnd, Some(resize_direct_children), mem::transmute(data_ptr));
322 },
323 _ => {}
324 }
325
326 None
327 } } );
328
329 *self.handler0.borrow_mut() = Some(handler0.unwrap());
330 *self.handler1.borrow_mut() = Some(handler1.unwrap());
331 }
332}
333
334impl Drop for TabsContainer {
335 fn drop(&mut self) {
336 let handler = self.handler0.borrow();
337 if let Some(h) = handler.as_ref() {
338 drop(unbind_raw_event_handler(h));
339 }
340
341 let handler = self.handler1.borrow();
342 if let Some(h) = handler.as_ref() {
343 drop(unbind_raw_event_handler(h));
344 }
345
346 self.handle.destroy();
347 }
348}
349
350impl PartialEq for TabsContainer {
351 fn eq(&self, other: &Self) -> bool {
352 self.handle == other.handle
353 }
354}
355
356
357pub struct TabsContainerBuilder<'a> {
358 size: (i32, i32),
359 position: (i32, i32),
360 parent: Option<ControlHandle>,
361 font: Option<&'a Font>,
362 flags: Option<TabsContainerFlags>,
363 ex_flags: u32,
364
365 #[cfg(feature = "image-list")]
366 image_list: Option<&'a ImageList>
367}
368
369impl<'a> TabsContainerBuilder<'a> {
370
371 pub fn flags(mut self, flags: TabsContainerFlags) -> TabsContainerBuilder<'a> {
372 self.flags = Some(flags);
373 self
374 }
375
376 pub fn ex_flags(mut self, flags: u32) -> TabsContainerBuilder<'a> {
377 self.ex_flags = flags;
378 self
379 }
380
381 pub fn size(mut self, size: (i32, i32)) -> TabsContainerBuilder<'a> {
382 self.size = size;
383 self
384 }
385
386 pub fn position(mut self, pos: (i32, i32)) -> TabsContainerBuilder<'a> {
387 self.position = pos;
388 self
389 }
390
391 pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> TabsContainerBuilder<'a> {
392 self.parent = Some(p.into());
393 self
394 }
395
396 pub fn font(mut self, font: Option<&'a Font>) -> TabsContainerBuilder<'a> {
397 self.font = font;
398 self
399 }
400
401 #[cfg(feature = "image-list")]
402 pub fn image_list(mut self, list: Option<&'a ImageList>) -> TabsContainerBuilder<'a> {
403 self.image_list = list;
404 self
405 }
406
407 pub fn build(self, out: &mut TabsContainer) -> Result<(), NwgError> {
408 let flags = self.flags.map(|f| f.bits()).unwrap_or(out.flags());
409
410 let parent = match self.parent {
411 Some(p) => Ok(p),
412 None => Err(NwgError::no_parent("TabsContainer"))
413 }?;
414
415 *out = Default::default();
416
417 out.handle = ControlBase::build_hwnd()
418 .class_name(out.class_name())
419 .forced_flags(out.forced_flags())
420 .flags(flags)
421 .ex_flags(WS_EX_CONTROLPARENT | self.ex_flags)
422 .size(self.size)
423 .position(self.position)
424 .parent(Some(parent))
425 .build()?;
426
427 out.hook_tabs();
428
429 if self.font.is_some() {
430 out.set_font(self.font);
431 } else {
432 out.set_font(Font::global_default().as_ref());
433 }
434
435 #[cfg(feature = "image-list")]
437 fn set_image_list(b: &TabsContainerBuilder, out: &mut TabsContainer) {
438 if b.image_list.is_some() {
439 out.set_image_list(b.image_list);
440 }
441 }
442
443 #[cfg(not(feature = "image-list"))]
444 fn set_image_list(_b: &TabsContainerBuilder, _out: &mut TabsContainer) {}
445
446 set_image_list(&self, out);
447
448 Ok(())
449 }
450}
451
452
453#[derive(Default, Debug, PartialEq, Eq)]
464pub struct Tab {
465 pub handle: ControlHandle
466}
467
468impl Tab {
469
470 pub fn builder<'a>() -> TabBuilder<'a> {
471 TabBuilder {
472 text: "Tab",
473 parent: None,
474
475 #[cfg(feature = "image-list")]
476 image_index: None,
477 }
478 }
479
480 pub fn set_text<'a>(&self, text: &'a str) {
482 use winapi::um::commctrl::{TCM_SETITEMW, TCIF_TEXT, TCITEMW};
483 use winapi::um::winuser::GWL_USERDATA;
484
485 if self.handle.blank() { panic!("{}", NOT_BOUND); }
486 let handle = self.handle.hwnd().expect(BAD_HANDLE);
487
488 let tab_index = (wh::get_window_long(handle, GWL_USERDATA) - 1) as WPARAM;
489
490 let tab_view_handle = wh::get_window_parent(handle);
491
492 let text = to_utf16(text);
493 let item = TCITEMW {
494 mask: TCIF_TEXT,
495 dwState: 0,
496 dwStateMask: 0,
497 pszText: text.as_ptr() as LPWSTR,
498 cchTextMax: 0,
499 iImage: -1,
500 lParam: 0
501 };
502
503 let item_ptr = &item as *const TCITEMW;
504 wh::send_message(tab_view_handle, TCM_SETITEMW, tab_index, item_ptr as LPARAM);
505 }
506
507 #[cfg(feature = "image-list")]
513 pub fn set_image_index(&self, index: Option<i32>) {
514 use winapi::um::commctrl::{TCM_SETITEMW, TCIF_IMAGE, TCITEMW};
515 use winapi::um::winuser::GWL_USERDATA;
516
517 if self.handle.blank() { panic!("{}", NOT_BOUND); }
518 let handle = self.handle.hwnd().expect(BAD_HANDLE);
519
520 let tab_index = (wh::get_window_long(handle, GWL_USERDATA) - 1) as WPARAM;
521 let tab_view_handle = wh::get_window_parent(handle);
522
523 let item = TCITEMW {
524 mask: TCIF_IMAGE,
525 dwState: 0,
526 dwStateMask: 0,
527 pszText: ptr::null_mut(),
528 cchTextMax: 0,
529 iImage: index.unwrap_or(-1),
530 lParam: 0
531 };
532
533 let item_ptr = &item as *const TCITEMW;
534 wh::send_message(tab_view_handle, TCM_SETITEMW, tab_index, item_ptr as LPARAM);
535 }
536
537 #[cfg(feature = "image-list")]
544 pub fn image_index(&self) -> Option<i32> {
545 None
546 }
547
548 pub fn visible(&self) -> bool {
551 if self.handle.blank() { panic!("{}", NOT_BOUND); }
552 let handle = self.handle.hwnd().expect(BAD_HANDLE);
553 unsafe { wh::get_window_visibility(handle) }
554 }
555
556 pub fn set_visible(&self, v: bool) {
558 if self.handle.blank() { panic!("{}", NOT_BOUND); }
559 let handle = self.handle.hwnd().expect(BAD_HANDLE);
560 unsafe { wh::set_window_visibility(handle, v) }
561 }
562
563
564 pub fn class_name(&self) -> &'static str {
570 "NWG_TAB"
571 }
572
573 pub fn flags(&self) -> u32 {
575 0
576 }
577
578 pub fn forced_flags(&self) -> u32 {
580 use winapi::um::winuser::{WS_CHILD, WS_CLIPCHILDREN};
582
583 WS_CHILD | WS_CLIPCHILDREN
584 }
585
586 unsafe fn init(current_handle: HWND, tab_view_handle: HWND, index: usize) {
588 use winapi::um::winuser::GWL_USERDATA;
589
590 wh::set_window_long(current_handle, GWL_USERDATA, index);
592
593 let (w, h) = wh::get_window_size(tab_view_handle);
595 let width = w - 11;
596 let height = h - 33;
597
598 wh::set_window_size(current_handle, width, height, false);
600
601 wh::set_window_position(current_handle, 5, 25);
603
604 if index == 1 {
606 wh::set_window_visibility(current_handle, true);
607 }
608 }
609
610 fn next_index(tab_view_handle: HWND) -> usize {
611 let mut count = 0;
612 let count_ptr = &mut count as *mut usize;
613
614 unsafe {
615 EnumChildWindows(tab_view_handle, Some(count_children), mem::transmute(count_ptr));
616 }
617
618 count
619 }
620
621 fn bind_container<'a>(&self, text: &'a str) {
623 use winapi::um::commctrl::{TCITEMW, TCM_INSERTITEMW, TCIF_TEXT};
624
625 if self.handle.blank() { panic!("{}", NOT_BOUND); }
626 let handle = self.handle.hwnd().expect(BAD_HANDLE);
627
628 let tab_view_handle = wh::get_window_parent(handle);
629 let next_index = Tab::next_index(tab_view_handle);
630
631 unsafe {
632 Tab::init(handle, tab_view_handle, next_index);
633 }
634
635 let text = to_utf16(&text);
636 let tab_info = TCITEMW {
637 mask: TCIF_TEXT,
638 dwState: 0,
639 dwStateMask: 0,
640 pszText: text.as_ptr() as LPWSTR,
641 cchTextMax: 0,
642 iImage: -1,
643 lParam: 0
644 };
645
646 let tab_info_ptr = &tab_info as *const TCITEMW;
647 wh::send_message(tab_view_handle, TCM_INSERTITEMW, next_index as WPARAM, tab_info_ptr as LPARAM);
648 }
649
650}
651
652impl Drop for Tab {
653 fn drop(&mut self) {
654 self.handle.destroy();
655 }
656}
657
658pub struct TabBuilder<'a> {
659 text: &'a str,
660 parent: Option<ControlHandle>,
661
662 #[cfg(feature = "image-list")]
663 image_index: Option<i32>,
664}
665
666impl<'a> TabBuilder<'a> {
667
668 pub fn text(mut self, text: &'a str) -> TabBuilder<'a> {
669 self.text = text;
670 self
671 }
672
673 pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> TabBuilder<'a> {
674 self.parent = Some(p.into());
675 self
676 }
677
678 #[cfg(feature = "image-list")]
679 pub fn image_index(mut self, index: Option<i32>) -> TabBuilder<'a> {
680 self.image_index = index;
681 self
682 }
683
684 pub fn build(self, out: &mut Tab) -> Result<(), NwgError> {
685 use winapi::um::commctrl::WC_TABCONTROL;
686
687 let parent = match self.parent {
688 Some(p) => Ok(p),
689 None => Err(NwgError::no_parent("Tab"))
690 }?;
691
692 *out = Default::default();
693
694 match &parent {
695 &ControlHandle::Hwnd(h) => {
696 let class_name = unsafe { wh::get_window_class_name(h) };
697 if &class_name != WC_TABCONTROL {
698 Err(NwgError::control_create("Tab requires a TabsContainer parent."))
699 } else {
700 Ok(())
701 }
702 },
703 _ => Err(NwgError::control_create("Tab requires a TabsContainer parent."))
704 }?;
705
706 out.handle = ControlBase::build_hwnd()
707 .class_name(out.class_name())
708 .forced_flags(out.forced_flags())
709 .ex_flags(WS_EX_CONTROLPARENT)
710 .flags(out.flags())
711 .text(self.text)
712 .parent(Some(parent))
713 .build()?;
714
715 out.bind_container(self.text);
716
717 #[cfg(feature = "image-list")]
720 fn set_image_index<'a>(b: &TabBuilder<'a>, out: &mut Tab) {
721 if b.image_index.is_some() {
722 out.set_image_index(b.image_index);
723 }
724 }
725
726 #[cfg(not(feature = "image-list"))]
727 fn set_image_index<'a>(_b: &TabBuilder<'a>, _out: &mut Tab) {}
728
729 set_image_index(&self, out);
730
731 Ok(())
732 }
733}
734
735
736struct ResizeDirectChildrenParams {
737 parent: HWND,
738 width: u32,
739 height: u32,
740 tab_offset_y: u32
741}
742
743unsafe extern "system" fn resize_direct_children(handle: HWND, params: LPARAM) -> BOOL {
744 let params: &ResizeDirectChildrenParams = &*(params as *const ResizeDirectChildrenParams);
745 if wh::get_window_parent(handle) == params.parent {
746 wh::set_window_size(handle, params.width, params.height, false);
747
748 let (x, _y) = wh::get_window_position(handle);
749 wh::set_window_position(handle, x, params.tab_offset_y as i32);
750 }
751
752 1
753}
754
755unsafe extern "system" fn count_children(handle: HWND, params: LPARAM) -> BOOL {
756 use winapi::um::winuser::GWL_USERDATA;
757
758 if &wh::get_window_class_name(handle) == "NWG_TAB" {
759 let tab_index = (wh::get_window_long(handle, GWL_USERDATA)) as WPARAM;
760 let count: &mut usize = ::std::mem::transmute(params);
761 *count = usize::max(tab_index+1, *count);
762 }
763
764 1
765}
766
767unsafe extern "system" fn toggle_children_tabs(handle: HWND, params: LPARAM) -> BOOL {
769 use winapi::um::winuser::GWL_USERDATA;
770
771 let &(parent, index): &(HWND, i32) = mem::transmute(params);
772 if wh::get_window_parent(handle) == parent {
773 let tab_index = wh::get_window_long(handle, GWL_USERDATA) as i32;
774 let visible = tab_index == index + 1;
775 wh::set_window_visibility(handle, visible);
776 }
777
778 1
779}