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

UI: Adding Bounding Box & Fixing Alignment issue in TextBlock2D #803

Merged
merged 26 commits into from Jul 29, 2023
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
9a373a1
adding bounding box property
ganimtron-10 Jun 12, 2023
e35ae2c
seperating bounding box function
ganimtron-10 Jun 12, 2023
868a4e4
updating bbox on message change
ganimtron-10 Jun 12, 2023
9bbe987
removing comments
ganimtron-10 Jun 12, 2023
08dcc24
updating alignment
ganimtron-10 Jun 15, 2023
67867f6
updating bbox on resize
ganimtron-10 Jun 15, 2023
b703f0a
adding bounding box and updating alignment
ganimtron-10 Jul 5, 2023
1206b48
adding auto_font_scale property to scale font automatically
ganimtron-10 Jul 5, 2023
2b5e6c2
updating the calculation of the bounding box size
ganimtron-10 Jul 5, 2023
7648da0
updating tests
ganimtron-10 Jul 5, 2023
2e268d3
adding docs
ganimtron-10 Jul 5, 2023
3cae30e
updating aligment calculation
ganimtron-10 Jul 20, 2023
c95b249
avoiding repetative calculation
ganimtron-10 Jul 20, 2023
acb3ad4
removing test changes
ganimtron-10 Jul 20, 2023
3523a0c
adding dynamic_bbox property
ganimtron-10 Jul 21, 2023
537a3f0
reseting size to zero
ganimtron-10 Jul 21, 2023
6cf858d
updating tests
ganimtron-10 Jul 21, 2023
5c9bf87
updating docs
ganimtron-10 Jul 22, 2023
0f1d6c3
updating docs and initializing param
ganimtron-10 Jul 24, 2023
09189f1
updating font size test
ganimtron-10 Jul 26, 2023
4d01700
updating case checking
ganimtron-10 Jul 26, 2023
483ea64
updating size value
ganimtron-10 Jul 28, 2023
9b23d2d
updating helper
ganimtron-10 Jul 28, 2023
0f47c83
updating helper tests
ganimtron-10 Jul 28, 2023
aa56657
updating size for tests
ganimtron-10 Jul 28, 2023
0ecdc84
removing attributes
ganimtron-10 Jul 29, 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
180 changes: 99 additions & 81 deletions fury/ui/core.py
Expand Up @@ -701,6 +701,8 @@ def __init__(
color=(1, 1, 1),
bg_color=None,
position=(0, 0),
auto_font_scale=False,
dynamic_bbox=False
):
"""Init class instance.

Expand Down Expand Up @@ -730,23 +732,33 @@ def __init__(
Adds text shadow.
size : (int, int)
Size (width, height) in pixels of the text bounding box.
auto_font_scale : bool, optional
Automatically scale font according to the text bounding box.
dynamic_bbox : bool, optional
Automatically reisize the bounding box according to the content.
ganimtron-10 marked this conversation as resolved.
Show resolved Hide resolved
ganimtron-10 marked this conversation as resolved.
Show resolved Hide resolved
"""
super(TextBlock2D, self).__init__(position=position)
self.scene = None
self.have_bg = bool(bg_color)
if size is not None:
self.resize(size)
else:
self.font_size = font_size
self.color = color
self.background_color = bg_color
self.font_family = font_family
self.justification = justification
self._justification = justification
self.bold = bold
self.italic = italic
self.shadow = shadow
self.vertical_justification = vertical_justification
self._vertical_justification = vertical_justification
self.auto_font_scale = auto_font_scale
if self.auto_font_scale:
self.actor.SetTextScaleModeToProp()
self.dynamic_bbox = dynamic_bbox
ganimtron-10 marked this conversation as resolved.
Show resolved Hide resolved
self.message = text
self.font_size = font_size
if size is not None:
self.resize(size)
elif not self.dynamic_bbox:
# raise ValueError("TextBlock size is required as it is not dynamic.")
self.resize((0, 0))

def _setup(self):
self.actor = TextActor()
Expand All @@ -762,10 +774,7 @@ def resize(self, size):
size : (int, int)
Text bounding box size(width, height) in pixels.
"""
if self.have_bg:
self.background.resize(size)
self.actor.SetTextScaleModeToProp()
self.actor.SetPosition2(*size)
self.update_bounding_box(size)

def _get_actors(self):
"""Get the actors composing this UI component."""
Expand All @@ -778,11 +787,6 @@ def _add_to_scene(self, scene):
----------
scene : scene
"""
self.scene = scene
if self.have_bg and not self.actor.GetTextScaleMode():
size = np.zeros(2)
self.actor.GetSize(scene, size)
self.background.resize(size)
scene.add(self.background, self.actor)

@property
Expand All @@ -806,6 +810,8 @@ def message(self, text):
The message to be set.
"""
self.actor.SetInput(text)
if self.dynamic_bbox:
self.update_bounding_box()

@property
def font_size(self):
Expand All @@ -827,21 +833,12 @@ def font_size(self, size):
size : int
Text font size.
"""
self.actor.SetTextScaleModeToNone()
self.actor.GetTextProperty().SetFontSize(size)

if self.scene is not None and self.have_bg:
bb_size = np.zeros(2)
self.actor.GetSize(self.scene, bb_size)
bg_size = self.background.size
if bb_size[0] > bg_size[0] or bb_size[1] > bg_size[1]:
warn(
'Font size exceeds background bounding box.'
' Font Size will not be updated.',
RuntimeWarning,
)
self.actor.SetTextScaleModeToProp()
self.actor.SetPosition2(*bg_size)
if not self.auto_font_scale:
self.actor.SetTextScaleModeToNone()
self.actor.GetTextProperty().SetFontSize(size)

if self.dynamic_bbox:
self.update_bounding_box()

@property
def font_family(self):
Expand Down Expand Up @@ -881,13 +878,7 @@ def justification(self):
str
Text justification.
"""
justification = self.actor.GetTextProperty().GetJustificationAsString()
if justification == 'Left':
return 'left'
elif justification == 'Centered':
return 'center'
elif justification == 'Right':
return 'right'
return self._justification

@justification.setter
def justification(self, justification):
Expand All @@ -899,16 +890,8 @@ def justification(self, justification):
Possible values are left, right, center.

"""
text_property = self.actor.GetTextProperty()
if justification == 'left':
text_property.SetJustificationToLeft()
elif justification == 'center':
text_property.SetJustificationToCentered()
elif justification == 'right':
text_property.SetJustificationToRight()
else:
msg = 'Text can only be justified left, right and center.'
raise ValueError(msg)
self._justification = justification
self.update_alignment()

@property
def vertical_justification(self):
Expand All @@ -920,14 +903,7 @@ def vertical_justification(self):
Text vertical justification.

"""
text_property = self.actor.GetTextProperty()
vjustification = text_property.GetVerticalJustificationAsString()
if vjustification == 'Bottom':
return 'bottom'
elif vjustification == 'Centered':
return 'middle'
elif vjustification == 'Top':
return 'top'
return self._vertical_justification

@vertical_justification.setter
def vertical_justification(self, vertical_justification):
Expand All @@ -939,16 +915,8 @@ def vertical_justification(self, vertical_justification):
Possible values are bottom, middle, top.

"""
text_property = self.actor.GetTextProperty()
if vertical_justification == 'bottom':
text_property.SetVerticalJustificationToBottom()
elif vertical_justification == 'middle':
text_property.SetVerticalJustificationToCentered()
elif vertical_justification == 'top':
text_property.SetVerticalJustificationToTop()
else:
msg = 'Vertical justification must be: bottom, middle or top.'
raise ValueError(msg)
self._vertical_justification = vertical_justification
self.update_alignment()

@property
def bold(self):
Expand Down Expand Up @@ -1078,6 +1046,71 @@ def background_color(self, color):
self.background.set_visibility(True)
self.background.color = color

def update_alignment(self):
"""Update Text Alignment.

ganimtron-10 marked this conversation as resolved.
Show resolved Hide resolved
"""
text_property = self.actor.GetTextProperty()
updated_text_position = [0, 0]

if self.justification == 'left':
ganimtron-10 marked this conversation as resolved.
Show resolved Hide resolved
text_property.SetJustificationToLeft()
updated_text_position[0] = self.boundingbox[0]
elif self.justification == 'center':
ganimtron-10 marked this conversation as resolved.
Show resolved Hide resolved
text_property.SetJustificationToCentered()
updated_text_position[0] = self.boundingbox[0] + \
(self.boundingbox[2]-self.boundingbox[0])//2
elif self.justification == 'right':
text_property.SetJustificationToRight()
updated_text_position[0] = self.boundingbox[2]
else:
msg = 'Text can only be justified left, right and center.'
raise ValueError(msg)

if self.vertical_justification == 'bottom':
text_property.SetVerticalJustificationToBottom()
updated_text_position[1] = self.boundingbox[1]
elif self.vertical_justification == 'middle':
text_property.SetVerticalJustificationToCentered()
updated_text_position[1] = self.boundingbox[1] + \
(self.boundingbox[3]-self.boundingbox[1])//2
elif self.vertical_justification == 'top':
text_property.SetVerticalJustificationToTop()
updated_text_position[1] = self.boundingbox[3]
else:
msg = 'Vertical justification must be: bottom, middle or top.'
raise ValueError(msg)

self.actor.SetPosition(updated_text_position)

def cal_size_from_message(self):
lines = self.message.split("\n")
max_length = max(len(line) for line in lines)
return [max_length*self.font_size, len(lines)*self.font_size]

def update_bounding_box(self, size=None):
"""Update Text Bounding Box.

Parameters
----------
size : (int, int) or None
If None, calculates bounding box.
Otherwise, uses the given size.

"""
if size is None:
size = self.cal_size_from_message()

self.boundingbox = [self.position[0], self.position[1],
ganimtron-10 marked this conversation as resolved.
Show resolved Hide resolved
self.position[0]+size[0], self.position[1]+size[1]]
self.background.resize(size)

if self.auto_font_scale:
self.actor.SetPosition2(
self.boundingbox[2]-self.boundingbox[0], self.boundingbox[3]-self.boundingbox[1])
else:
self.update_alignment()

def _set_position(self, position):
"""Set text actor position.

Expand All @@ -1091,22 +1124,7 @@ def _set_position(self, position):
self.background.position = position

def _get_size(self):
if self.have_bg:
return self.background.size

if not self.actor.GetTextScaleMode():
if self.scene is not None:
size = np.zeros(2)
self.actor.GetSize(self.scene, size)
return size
else:
warn(
'TextBlock2D must be added to the scene before '
'querying its size while TextScaleMode is set to None.',
RuntimeWarning,
)

return self.actor.GetPosition2()
return (self.boundingbox[2]-self.boundingbox[0], self.boundingbox[3]-self.boundingbox[1])


class Button2D(UI):
Expand Down
5 changes: 3 additions & 2 deletions fury/ui/elements.py
Expand Up @@ -131,7 +131,7 @@ def _setup(self):

Create the TextBlock2D component used for the textbox.
"""
self.text = TextBlock2D()
self.text = TextBlock2D(dynamic_bbox=True)

# Add default events listener for this UI component.
self.text.on_left_mouse_button_pressed = self.left_button_press
Expand Down Expand Up @@ -1445,7 +1445,8 @@ def _setup(self):
self.handle.color = self.default_color

# Slider Text
self.text = TextBlock2D(justification='center', vertical_justification='middle')
self.text = TextBlock2D(justification='center',
vertical_justification='middle')

# Add default events listener for this UI component.
self.track.on_left_mouse_button_pressed = self.track_click_callback
Expand Down
74 changes: 38 additions & 36 deletions fury/ui/tests/test_core.py
Expand Up @@ -222,6 +222,7 @@ def _check_property(obj, attr, values):
_check_property(text_block, 'bold', [True, False])
_check_property(text_block, 'italic', [True, False])
_check_property(text_block, 'shadow', [True, False])
_check_property(text_block, 'auto_font_scale', [True, False])
_check_property(text_block, 'font_size', range(100))
_check_property(text_block, 'message', ['', 'Hello World', 'Line\nBreak'])
_check_property(text_block, 'justification', ['left', 'center', 'right'])
Expand Down Expand Up @@ -384,57 +385,58 @@ def test_text_block_2d_justification():


def test_text_block_2d_size():
text_block_1 = ui.TextBlock2D(position=(50, 50), size=(100, 100))

npt.assert_equal(text_block_1.actor.GetTextScaleMode(), 1)
npt.assert_equal(text_block_1.size, (100, 100))
text_block_0 = ui.TextBlock2D()

text_block_1.font_size = 50
npt.assert_equal(text_block_0.actor.GetTextScaleMode(), 0)
npt.assert_equal(text_block_0.size, (0, 0))

text_block_0.font_size = 50
npt.assert_equal(text_block_0.size, (0, 0))

text_block_0.resize((500, 200))
npt.assert_equal(text_block_0.actor.GetTextScaleMode(), 0)
npt.assert_equal(text_block_0.size, (500, 200))

text_block_1 = ui.TextBlock2D(dynamic_bbox=True)

npt.assert_equal(text_block_1.actor.GetTextScaleMode(), 0)
npt.assert_equal(text_block_1.font_size, 50)
npt.assert_equal(text_block_1.size, ((len("Text Block") *
text_block_1.font_size, text_block_1.font_size)))

text_block_1.font_size = 50
npt.assert_equal(text_block_1.size, (len("Text Block") *
text_block_1.font_size, text_block_1.font_size))

text_block_2 = ui.TextBlock2D(position=(50, 50), font_size=50)
text_block_1.resize((500, 200))
npt.assert_equal(text_block_1.actor.GetTextScaleMode(), 0)
npt.assert_equal(text_block_1.size, (500, 200))

npt.assert_equal(text_block_2.actor.GetTextScaleMode(), 0)
npt.assert_equal(text_block_2.font_size, 50)
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter('always', RuntimeWarning)
text_block_2.size
npt.assert_equal(len(w), 1)
npt.assert_(issubclass(w[-1].category, RuntimeWarning))
text_block_2 = ui.TextBlock2D(
text="Just Another Text Block", dynamic_bbox=True, auto_font_scale=True)

text_block_2.resize((100, 100))
npt.assert_equal(text_block_2.actor.GetTextScaleMode(), 1)
npt.assert_equal(text_block_2.size, (len("Just Another Text Block") *
text_block_2.font_size, text_block_2.font_size))

text_block_2.resize((500, 200))
npt.assert_equal(text_block_2.actor.GetTextScaleMode(), 1)
npt.assert_equal(text_block_2.size, (100, 100))
npt.assert_equal(text_block_2.size, (500, 200))

text_block_2.position = (100, 100)
npt.assert_equal(text_block_2.position, (100, 100))

window_size = (700, 700)
show_manager = window.ShowManager(size=window_size)
text_block_3 = ui.TextBlock2D(size=(200, 200))

text_block_3 = ui.TextBlock2D(
text='FURY\nFURY\nFURY\nHello',
position=(150, 100),
bg_color=(1, 0, 0),
color=(0, 1, 0),
size=(100, 100),
)
npt.assert_equal(text_block_3.actor.GetTextScaleMode(), 0)
npt.assert_equal(text_block_3.size, (200, 200))

text_block_3.resize((500, 200))
npt.assert_equal(text_block_3.actor.GetTextScaleMode(), 0)
npt.assert_equal(text_block_3.size, (500, 200))

show_manager.scene.add(text_block_3)
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter('always', RuntimeWarning)
text_block_3.font_size = 100
npt.assert_equal(len(w), 1)
npt.assert_(issubclass(w[-1].category, RuntimeWarning))
npt.assert_equal(text_block_3.size, (100, 100))

text_block_3.font_size = 12
npt.assert_equal(len(w), 1)
npt.assert_(issubclass(w[-1].category, RuntimeWarning))
npt.assert_equal(text_block_3.font_size, 12)
text_block_3.message = "Hey Trying\nBig Text"
npt.assert_equal(text_block_3.size, (500, 200))


# test_ui_button_panel(recording=True)