Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding SpinBoxUI to the UI module #499

Merged
merged 62 commits into from
Aug 10, 2023
Merged
Show file tree
Hide file tree
Changes from 60 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
ebcdef1
adding spinui
ganimtron-10 Sep 5, 2021
ef29559
repositing elements in panel
ganimtron-10 Sep 9, 2021
e31d66f
adding button callbacks, and some value parameters
ganimtron-10 Sep 12, 2021
584a23a
adding resize method
ganimtron-10 Sep 13, 2021
3644d4a
Merge branch 'fury-gl:master' into spinboxui
ganimtron-10 Sep 13, 2021
4c122ab
adding tests and hook
ganimtron-10 Sep 14, 2021
6a62630
Merge branch 'spinboxui' of https://github.com/ganimtron-10/fury into…
ganimtron-10 Sep 14, 2021
6ba4283
updating tests
ganimtron-10 Sep 14, 2021
fc62100
pep fixs
ganimtron-10 Sep 14, 2021
e1cb2f2
pep fixs
ganimtron-10 Sep 14, 2021
fc74020
updating in/decrement callback functions
ganimtron-10 Sep 14, 2021
1f2b6db
updating tutorial
ganimtron-10 Sep 16, 2021
9bc7ec9
updating docstring and alignment issue
ganimtron-10 Sep 18, 2021
3deee9f
pep fixs
ganimtron-10 Sep 18, 2021
4f04f18
adding on_blur hook in TextBox2D
ganimtron-10 Sep 18, 2021
30b2cad
changing value using textbox
ganimtron-10 Sep 18, 2021
e5b8de5
updating tests
ganimtron-10 Sep 18, 2021
c9eeefd
pep fix
ganimtron-10 Sep 18, 2021
600383d
Merge branch 'textbox_hook' into spinboxui
ganimtron-10 Oct 15, 2021
ee3377a
updating the textboxhook name
ganimtron-10 Oct 15, 2021
8d3dd85
changes from fury master
ganimtron-10 Jan 20, 2022
373529f
updating test file
ganimtron-10 Jan 20, 2022
42430da
merging master
ganimtron-10 Apr 22, 2022
d7af56d
Merge branch 'fury-gl-master' into spinboxui
ganimtron-10 Apr 22, 2022
2915ca2
updating test file
ganimtron-10 Apr 23, 2022
e585ce6
Merge branch 'master' into spinboxui
ganimtron-10 Jul 23, 2022
da87c65
Merge branch 'master' into spinboxui
ganimtron-10 Aug 19, 2022
7036d70
updating pep formatting
ganimtron-10 Aug 19, 2022
e246386
Merge branch 'master' into spinboxui
ganimtron-10 Nov 12, 2022
2c268da
Merge branch 'fury-gl:master' into spinboxui
ganimtron-10 Nov 18, 2022
b9f7489
adding spinui
ganimtron-10 Sep 5, 2021
4b69145
repositing elements in panel
ganimtron-10 Sep 9, 2021
a6fadf7
adding button callbacks, and some value parameters
ganimtron-10 Sep 12, 2021
bd27434
adding resize method
ganimtron-10 Sep 13, 2021
f6e9296
adding tests and hook
ganimtron-10 Sep 14, 2021
ba7089b
updating tests
ganimtron-10 Sep 14, 2021
34ed09d
pep fixs
ganimtron-10 Sep 14, 2021
ffb0c93
updating in/decrement callback functions
ganimtron-10 Sep 14, 2021
b9a0432
updating tutorial
ganimtron-10 Sep 16, 2021
46b8eb2
updating docstring and alignment issue
ganimtron-10 Sep 18, 2021
7755035
pep fixs
ganimtron-10 Sep 18, 2021
0d36286
changing value using textbox
ganimtron-10 Sep 18, 2021
f969c8e
updating tests
ganimtron-10 Sep 18, 2021
739640c
pep fix
ganimtron-10 Sep 18, 2021
dbc9932
updating the textboxhook name
ganimtron-10 Oct 15, 2021
3dc951a
updating test file
ganimtron-10 Jan 20, 2022
1b05168
updating test file
ganimtron-10 Apr 23, 2022
e8de3b4
updating pep formatting
ganimtron-10 Aug 19, 2022
5363291
updating test file
ganimtron-10 Apr 21, 2023
d597fe8
Merge branch 'master' into spinboxui
ganimtron-10 Jun 2, 2023
a30d6f2
aligning the text to the center
ganimtron-10 Jun 4, 2023
061b594
Merge branch 'master' into spinboxui
ganimtron-10 Jul 30, 2023
d891f83
Merge branch 'master' into spinboxui
ganimtron-10 Jul 30, 2023
22ff592
Merge branch 'master' into spinboxui
ganimtron-10 Aug 7, 2023
4130d1e
updating the textbox to automatically scale
ganimtron-10 Aug 7, 2023
9d286a2
skipping new line in the textbox
ganimtron-10 Aug 7, 2023
81be226
updating parameter names
ganimtron-10 Aug 8, 2023
5f7fb62
renaming paramters
ganimtron-10 Aug 9, 2023
ad08db8
adding validation for textbox input
ganimtron-10 Aug 9, 2023
ebf1a25
updating test
ganimtron-10 Aug 9, 2023
315a7ba
adding examples to valid examples file
ganimtron-10 Aug 10, 2023
b3fe482
fix typo before merging
skoudoro Aug 10, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
70 changes: 70 additions & 0 deletions docs/examples/viz_spinbox.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# -*- coding: utf-8 -*-
"""
===========
SpinBox UI
===========

This example shows how to use the UI API. We will demonstrate how to create
a SpinBox UI.

First, some imports.
"""
from fury import actor, ui, utils, window
from fury.data import fetch_viz_icons
import numpy as np

##############################################################################
# First we need to fetch some icons that are included in FURY.

fetch_viz_icons()

###############################################################################
# Let's create a Cone.

cone = actor.cone(centers=np.random.rand(1, 3),
directions=np.random.rand(1, 3),
colors=(1, 1, 1), heights=np.random.rand(1))

###############################################################################
# Creating the SpinBox UI.

spinbox = ui.SpinBox(position=(200, 100), size=(300, 100), min_val=0,
max_val=360, initial_val=180, step=10)

###############################################################################
# Now that all the elements have been initialised, we add them to the show
# manager.

current_size = (800, 800)
show_manager = window.ShowManager(size=current_size,
title="FURY SpinBox Example")

show_manager.scene.add(cone)
show_manager.scene.add(spinbox)

###############################################################################
# Using the on_change hook to rotate the cone.

# Tracking previous value to check in/decrement.
previous_value = spinbox.value


def rotate_cone(spinbox):
global previous_value
change_in_value = spinbox.value - previous_value
utils.rotate(cone, (change_in_value, 1, 0, 0))
previous_value = spinbox.value


spinbox.on_change = rotate_cone

###############################################################################
# Starting the ShowManager.

interactive = False

if interactive:
show_manager.start()

window.record(show_manager.scene, size=current_size,
out_path="viz_spinbox.png")
1 change: 1 addition & 0 deletions fury/data/files/test_ui_spinbox.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"CharEvent": 14, "MouseMoveEvent": 209, "KeyPressEvent": 16, "KeyReleaseEvent": 14, "LeftButtonPressEvent": 41, "LeftButtonReleaseEvent": 41, "RightButtonPressEvent": 0, "RightButtonReleaseEvent": 0, "MiddleButtonPressEvent": 0, "MiddleButtonReleaseEvent": 0}
Binary file added fury/data/files/test_ui_spinbox.log.gz
Binary file not shown.
193 changes: 193 additions & 0 deletions fury/ui/elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
'DrawShape',
'DrawPanel',
'PlaybackPanel',
'Card2D',
'SpinBox'
]

import os
Expand Down Expand Up @@ -4368,3 +4370,194 @@
self.panel.position += change
self._click_position = click_position
i_ren.force_render()


class SpinBox(UI):
"""SpinBox UI.
"""

def __init__(self, position=(350, 400), size=(300, 100), padding=10,
panel_color=(1, 1, 1), min_val=0, max_val=100,
initial_val=50, step=1, max_column=10, max_line=2):
"""Init this UI element.

Parameters
----------
position : (int, int), optional
Absolute coordinates (x, y) of the lower-left corner of this
UI component.
size : (int, int), optional
Width and height in pixels of this UI component.
padding : int, optional
Distance between TextBox and Buttons.
panel_color : (float, float, float), optional
Panel color of SpinBoxUI.
min_val: int, optional
Minimum value of SpinBoxUI.
max_val: int, optional
Maximum value of SpinBoxUI.
initial_val: int, optional
Initial value of SpinBoxUI.
step: int, optional
Step value of SpinBoxUI.
max_column: int, optional
Max number of characters in a line.
max_line: int, optional
Max number of lines in the textbox.
"""
self.panel_size = size
self.padding = padding
self.panel_color = panel_color
self.min_val = min_val
self.max_val = max_val
self.step = step
self.max_column = max_column
self.max_line = max_line

super(SpinBox, self).__init__(position)
self.value = initial_val
self.resize(size)

self.on_change = lambda ui: None

def _setup(self):
"""Setup this UI component.

Create the SpinBoxUI with Background (Panel2D) and InputBox (TextBox2D)
and Increment,Decrement Button (Button2D).
"""
self.panel = Panel2D(size=self.panel_size, color=self.panel_color)

self.textbox = TextBox2D(width=self.max_column,
height=self.max_line)
self.textbox.text.dynamic_bbox = False
self.textbox.text.auto_font_scale = True
self.increment_button = Button2D(
icon_fnames=[("up", read_viz_icons(fname="circle-up.png"))])
self.decrement_button = Button2D(
icon_fnames=[("down", read_viz_icons(fname="circle-down.png"))])

self.panel.add_element(self.textbox, (0, 0))
self.panel.add_element(self.increment_button, (0, 0))
self.panel.add_element(self.decrement_button, (0, 0))

# Adding button click callbacks
self.increment_button.on_left_mouse_button_pressed = \
self.increment_callback
self.decrement_button.on_left_mouse_button_pressed = \
self.decrement_callback
self.textbox.off_focus = self.textbox_update_value

def resize(self, size):
"""Resize SpinBox.

Parameters
----------
size : (float, float)
ganimtron-10 marked this conversation as resolved.
Show resolved Hide resolved
SpinBox size(width, height) in pixels.
"""
self.panel_size = size
self.textbox_size = (int(0.7 * size[0]), int(0.8 * size[1]))
self.button_size = (int(0.2 * size[0]), int(0.3 * size[1]))
self.padding = int(0.03 * self.panel_size[0])

self.panel.resize(size)
self.textbox.text.resize(self.textbox_size)
self.increment_button.resize(self.button_size)
self.decrement_button.resize(self.button_size)

textbox_pos = (self.padding, int((size[1] - self.textbox_size[1])/2))
inc_btn_pos = (size[0] - self.padding - self.button_size[0],
int((1.5*size[1] - self.button_size[1])/2))
dec_btn_pos = (size[0] - self.padding - self.button_size[0],
int((0.5*size[1] - self.button_size[1])/2))

self.panel.update_element(self.textbox, textbox_pos)
self.panel.update_element(self.increment_button, inc_btn_pos)
self.panel.update_element(self.decrement_button, dec_btn_pos)

def _get_actors(self):
"""Get the actors composing this UI component."""
return self.panel.actors

def _add_to_scene(self, scene):
"""Add all subcomponents or VTK props that compose this UI component.

Parameters
----------
scene : Scene

"""
self.panel.add_to_scene(scene)

def _get_size(self):
return self.panel.size

Check warning on line 4494 in fury/ui/elements.py

View check run for this annotation

Codecov / codecov/patch

fury/ui/elements.py#L4494

Added line #L4494 was not covered by tests

def _set_position(self, coords):
"""Set the lower-left corner position of this UI component.

Parameters
----------
coords: (float, float)
Absolute pixel coordinates (x, y).
"""
self.panel.center = coords

def increment_callback(self, i_ren, _obj, _button):
self.increment()
i_ren.force_render()
i_ren.event.abort()

def decrement_callback(self, i_ren, _obj, _button):
self.decrement()
i_ren.force_render()
i_ren.event.abort()

@property
def value(self):
return self._value

@value.setter
def value(self, value):
if value >= self.max_val:
self._value = self.max_val
elif value <= self.min_val:
self._value = self.min_val
else:
self._value = value

self.textbox.set_message(str(self._value))

def validate_value(self, value):
"""Validate and convert the given value into integer.

Parameters
----------
value : str
Input value recived from the textbox.

Check failure on line 4537 in fury/ui/elements.py

View workflow job for this annotation

GitHub Actions / Check for spelling errors

recived ==> received

Returns
-------
int
If valid return converted integer else the previous value.
"""
if value.isnumeric():
return int(value)

return self.value

def increment(self):
ganimtron-10 marked this conversation as resolved.
Show resolved Hide resolved
"""Increment the current value by the step."""
current_val = self.validate_value(self.textbox.message)
self.value = current_val + self.step
self.on_change(self)

def decrement(self):
ganimtron-10 marked this conversation as resolved.
Show resolved Hide resolved
"""Decrement the current value by the step."""
current_val = self.validate_value(self.textbox.message)
self.value = current_val - self.step
self.on_change(self)

def textbox_update_value(self, textbox):
skoudoro marked this conversation as resolved.
Show resolved Hide resolved
self.value = self.validate_value(textbox.message)
self.on_change(self)
51 changes: 50 additions & 1 deletion fury/ui/tests/test_elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -1098,13 +1098,14 @@ def test_ui_combobox_2d(interactive=False):
npt.assert_equal((90, 90), combobox.drop_button_size)
npt.assert_equal((450, 210), combobox.drop_menu_size)


def test_ui_combobox_2d_dropdown_visibility(interactive=False):

values = ['An Item' + str(i) for i in range(0, 5)]

tab_ui = ui.TabUI(position=(49, 94), size=(400, 400), nb_tabs=1 , draggable=True)
combobox = ui.ComboBox2D(items=values, position=(400, 400), size=(300, 200))

tab_ui.add_element(0, combobox, (0.1, 0.3))

# Assign the counter callback to every possible event.
Expand Down Expand Up @@ -1139,6 +1140,7 @@ def test_ui_combobox_2d_dropdown_visibility(interactive=False):
npt.assert_equal(True, combobox.drop_down_button.actors[0].GetVisibility())
npt.assert_equal(True, combobox.selection_box.actors[0].GetVisibility())


@pytest.mark.skipif(
skip_osx,
reason='This test does not work on macOS.'
Expand Down Expand Up @@ -1377,3 +1379,50 @@ def test_card_ui(interactive=False):
show_manager.play_events_from_file(recording_filename)
expected = EventCounter.load(expected_events_counts_filename)
event_counter.check_counts(expected)


def test_ui_spinbox(interactive=False):
filename = "test_ui_spinbox"
recording_filename = pjoin(DATA_DIR, filename + ".log.gz")
expected_events_counts_filename = pjoin(DATA_DIR, filename + ".json")

spinbox = ui.SpinBox(size=(300, 200), min_val=-20, max_val=10, step=2)
npt.assert_equal(spinbox.value, 10)

spinbox.value = 5
npt.assert_equal(spinbox.value, 5)
spinbox.value = 50
npt.assert_equal(spinbox.value, 10)
spinbox.value = -50
npt.assert_equal(spinbox.value, -20)

spinbox.min_val = -100
spinbox.max_val = 100

spinbox.value = 5
npt.assert_equal(spinbox.value, 5)
spinbox.value = 50
npt.assert_equal(spinbox.value, 50)
spinbox.value = -50
npt.assert_equal(spinbox.value, -50)

# Assign the counter callback to every possible event.
event_counter = EventCounter()
event_counter.monitor(spinbox)

current_size = (800, 800)
show_manager = window.ShowManager(size=current_size, title="SpinBox UI Example")
show_manager.scene.add(spinbox)

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)

spinbox.resize((450, 200))
npt.assert_equal((315, 160), spinbox.textbox_size)
npt.assert_equal((90, 60), spinbox.button_size)