diff --git a/docs/examples/viz_tab.py b/docs/examples/viz_tab.py index adcfcbdc9..6cabf95b9 100644 --- a/docs/examples/viz_tab.py +++ b/docs/examples/viz_tab.py @@ -27,6 +27,12 @@ tab_ui = ui.TabUI(position=(49, 94), size=(300, 300), nb_tabs=3, draggable=True) +############################################################################### +# We can also define the position of the Tab Bar. +# By default the Tab Bar is positioned at top + +tab_ui.tab_bar_pos = 'bottom' + ############################################################################### # Slider Controls for a Cube for Tab Index 0 # ========================================== diff --git a/fury/data/files/test_ui_tab_ui_top_position.json b/fury/data/files/test_ui_tab_ui_top_position.json new file mode 100644 index 000000000..9934fd112 --- /dev/null +++ b/fury/data/files/test_ui_tab_ui_top_position.json @@ -0,0 +1 @@ +{"CharEvent": 0, "MouseMoveEvent": 1014, "KeyPressEvent": 0, "KeyReleaseEvent": 0, "LeftButtonPressEvent": 58, "LeftButtonReleaseEvent": 58, "RightButtonPressEvent": 10, "RightButtonReleaseEvent": 10, "MiddleButtonPressEvent": 0, "MiddleButtonReleaseEvent": 0} \ No newline at end of file diff --git a/fury/data/files/test_ui_tab_ui_top_position.log.gz b/fury/data/files/test_ui_tab_ui_top_position.log.gz new file mode 100644 index 000000000..e7aebcdd7 Binary files /dev/null and b/fury/data/files/test_ui_tab_ui_top_position.log.gz differ diff --git a/fury/ui/containers.py b/fury/ui/containers.py index f88a00634..e28399cd9 100644 --- a/fury/ui/containers.py +++ b/fury/ui/containers.py @@ -18,7 +18,7 @@ ) from fury.ui.core import UI, Rectangle2D, TextBlock2D from fury.utils import rotate, set_input - +from warnings import warn class Panel2D(UI): """A 2D UI Panel. @@ -640,6 +640,7 @@ def __init__( inactive_color=(0.5, 0.5, 0.5), draggable=False, startup_tab_id=None, + tab_bar_pos="top", ): """Init class instance. @@ -661,7 +662,8 @@ def __init__( startup_tab_id : int, optional Tab to be activated and uncollapsed on startup. by default None is activated/ all collapsed. - + tab_bar_pos : str, optional + Position of the Tab Bar in the panel """ self.tabs = [] self.nb_tabs = nb_tabs @@ -672,6 +674,7 @@ def __init__( self.inactive_color = inactive_color self.active_tab_idx = startup_tab_id self.collapsed = True + self.tab_bar_pos = tab_bar_pos super(TabUI, self).__init__() self.position = position @@ -737,8 +740,15 @@ def _get_size(self): def update_tabs(self): """Update position, size and callbacks for tab panels.""" self.tab_panel_size = (self.size[0] // self.nb_tabs, int(0.1 * self.size[1])) + if self.tab_bar_pos.lower() not in ['top', 'bottom']: + warn("tab_bar_pos can only have value top/bottom") + self.tab_bar_pos = "top" + + if self.tab_bar_pos.lower() == "top": + tab_panel_pos = [0.0, 0.9] + elif self.tab_bar_pos.lower() == "bottom": + tab_panel_pos = [0.0, 0.0] - tab_panel_pos = [0.0, 0.9] for tab_panel in self.tabs: tab_panel.resize(self.tab_panel_size) tab_panel.content_panel.position = self.position @@ -776,7 +786,12 @@ def update_tabs(self): tab_panel.content_panel.resize(self.content_size) self.parent_panel.add_element(tab_panel, tab_panel_pos) - self.parent_panel.add_element(tab_panel.content_panel, (0.0, 0.0)) + if self.tab_bar_pos.lower() == "top": + self.parent_panel.add_element(tab_panel.content_panel, + (0.0, 0.0)) + elif self.tab_bar_pos.lower() == "bottom": + self.parent_panel.add_element(tab_panel.content_panel, + (0.0, 0.1)) tab_panel_pos[0] += 1 / self.nb_tabs def select_tab_callback(self, iren, _obj, _tab_comp): diff --git a/fury/ui/tests/test_containers.py b/fury/ui/tests/test_containers.py index 3fa76594c..79edcd4a0 100644 --- a/fury/ui/tests/test_containers.py +++ b/fury/ui/tests/test_containers.py @@ -309,7 +309,8 @@ def test_ui_tab_ui(interactive=False): npt.assert_equal(tab_ui.tabs[1].title_italic, True) npt.assert_equal(tab_ui.tabs[2].title_italic, False) - tab_ui.add_element(0, ui.Checkbox(['Option 1', 'Option 2']), (0.5, 0.5)) + tab_ui.add_element(0, ui.Checkbox(['Option 1', 'Option 2']), + (0.5, 0.5)) tab_ui.add_element(1, ui.LineSlider2D(), (0.0, 0.5)) tab_ui.add_element(2, ui.TextBlock2D(), (0.5, 0.5)) @@ -345,7 +346,8 @@ def tab_change(tab_ui): event_counter.monitor(tab_ui) current_size = (800, 800) - show_manager = window.ShowManager(size=current_size, title='Tab UI Test') + show_manager = window.ShowManager(size=current_size, + title='Tab UI Test') show_manager.scene.add(tab_ui) if interactive: @@ -360,3 +362,111 @@ def tab_change(tab_ui): npt.assert_equal(0, tab_ui.active_tab_idx) npt.assert_equal(11, next(changes)) npt.assert_equal(5, next(collapses)) + + +def test_ui_tab_ui_position(interactive=False): + filename = 'test_ui_tab_ui_top_position' + recording_filename = pjoin(DATA_DIR, filename + '.log.gz') + expected_events_counts_filename = pjoin(DATA_DIR, filename + '.json') + + tab_ui_top = ui.TabUI( + position=(50, 50), size=(300, 300), nb_tabs=3, draggable=True, + tab_bar_pos='top') + + tab_ui_top.tabs[0].title = 'Tab 1' + tab_ui_top.tabs[1].title = 'Tab 2' + tab_ui_top.tabs[2].title = 'Tab 3' + + tab_ui_top.add_element(0, ui.Checkbox(['Option 1', 'Option 2']), + (0.5, 0.5)) + tab_ui_top.add_element(1, ui.LineSlider2D(), (0.0, 0.5)) + tab_ui_top.add_element(2, ui.TextBlock2D(), (0.5, 0.5)) + + npt.assert_equal('Tab 1', tab_ui_top.tabs[0].title) + npt.assert_equal('Tab 2', tab_ui_top.tabs[1].title) + npt.assert_equal('Tab 3', tab_ui_top.tabs[2].title) + + npt.assert_equal(3, tab_ui_top.nb_tabs) + + npt.assert_equal((50, 50), tab_ui_top.position) + npt.assert_equal((300, 300), tab_ui_top.size) + + with npt.assert_raises(IndexError): + tab_ui_top.add_element(3, ui.TextBlock2D(), (0.5, 0.5, 0.5)) + + with npt.assert_raises(IndexError): + tab_ui_top.remove_element(3, ui.TextBlock2D()) + + with npt.assert_raises(IndexError): + tab_ui_top.update_element(3, ui.TextBlock2D(), (0.5, 0.5, 0.5)) + + tab_ui_bottom = ui.TabUI( + position=(350, 50), size=(300, 300), nb_tabs=3, draggable=True, + tab_bar_pos='bottom') + + tab_ui_bottom.tabs[0].title = 'Tab 1' + tab_ui_bottom.tabs[1].title = 'Tab 2' + tab_ui_bottom.tabs[2].title = 'Tab 3' + + tab_ui_bottom.add_element(0, ui.Checkbox(['Option 1', 'Option 2']), + (0.5, 0.5)) + tab_ui_bottom.add_element(1, ui.LineSlider2D(), (0.0, 0.5)) + tab_ui_bottom.add_element(2, ui.TextBlock2D(), (0.5, 0.5)) + + npt.assert_equal('Tab 1', tab_ui_bottom.tabs[0].title) + npt.assert_equal('Tab 2', tab_ui_bottom.tabs[1].title) + npt.assert_equal('Tab 3', tab_ui_bottom.tabs[2].title) + + npt.assert_equal(3, tab_ui_bottom.nb_tabs) + + npt.assert_equal((350, 50), tab_ui_bottom.position) + npt.assert_equal((300, 300), tab_ui_bottom.size) + + with npt.assert_raises(IndexError): + tab_ui_bottom.add_element(3, ui.TextBlock2D(), (0.5, 0.5, 0.5)) + + with npt.assert_raises(IndexError): + tab_ui_bottom.remove_element(3, ui.TextBlock2D()) + + with npt.assert_raises(IndexError): + tab_ui_bottom.update_element(3, ui.TextBlock2D(), (0.5, 0.5, 0.5)) + + collapses = itertools.count() + changes = itertools.count() + + def collapse(tab_ui_top): + if tab_ui_top.collapsed or tab_ui_bottom.collapsed: + next(collapses) + + def tab_change(tab_ui_top): + next(changes) + + tab_ui_top.on_change = tab_change + tab_ui_top.on_collapse = collapse + + tab_ui_bottom.on_change = tab_change + tab_ui_bottom.on_collapse = collapse + + event_counter = EventCounter() + event_counter.monitor(tab_ui_top) + event_counter.monitor(tab_ui_bottom) + + current_size = (800, 800) + show_manager = window.ShowManager(size=current_size, + title='Tab UI Test') + show_manager.scene.add(tab_ui_top) + show_manager.scene.add(tab_ui_bottom) + + if interactive: + show_manager.record_events_to_file(recording_filename) + print(list(event_counter.events_counts.items())) + event_counter.save(expected_events_counts_filename) + else: + show_manager.play_events_from_file(recording_filename) + expected = EventCounter.load(expected_events_counts_filename) + event_counter.check_counts(expected) + + npt.assert_equal(0, tab_ui_top.active_tab_idx) + npt.assert_equal(0, tab_ui_bottom.active_tab_idx) + npt.assert_equal(14, next(changes)) + npt.assert_equal(5, next(collapses))