Skip to content

Commit

Permalink
Merge pull request #443 from MyreMylar/dynamic-sizing-2
Browse files Browse the repository at this point in the history
Dynamic sizing fixes part 1
  • Loading branch information
MyreMylar authored Apr 16, 2023
2 parents f4126fd + d2582c4 commit 39f6199
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 74 deletions.
5 changes: 4 additions & 1 deletion pygame_gui/core/interfaces/element_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,17 @@ def set_position(self, position: Union[pygame.math.Vector2,
@abstractmethod
def set_dimensions(self, dimensions: Union[pygame.math.Vector2,
Tuple[int, int],
Tuple[float, float]]):
Tuple[float, float]],
clamp_to_container: bool = False):
"""
Method to directly set the dimensions of an element.
NOTE: Using this on elements inside containers with non-default anchoring arrangements
may make a mess of them.
:param dimensions: The new dimensions to set.
:param clamp_to_container: Whether we should clamp the dimensions to the
dimensions of the container or not.
"""

Expand Down
6 changes: 5 additions & 1 deletion pygame_gui/core/ui_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,12 +198,16 @@ def set_relative_position(self, position: Union[pygame.math.Vector2,

def set_dimensions(self, dimensions: Union[pygame.math.Vector2,
Tuple[int, int],
Tuple[float, float]]):
Tuple[float, float]],
clamp_to_container: bool = False
):
"""
Set the dimension of this container and update the positions of elements within it
accordingly.
:param dimensions: the new dimensions.
:param clamp_to_container: Whether we should clamp the dimensions to the
dimensions of the container or not.
"""
super().set_dimensions(dimensions)
Expand Down
57 changes: 55 additions & 2 deletions pygame_gui/core/ui_element.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,26 @@ def __init__(self, relative_rect: Union[pygame.Rect, Tuple[int, int, int, int]],

self._layer = 0
self.ui_manager = manager
self.ui_container = None
if self.ui_manager is None:
self.ui_manager = get_default_manager()
if self.ui_manager is None:
raise ValueError("Need to create at least one UIManager to create UIElements")

super().__init__(self.ui_manager.get_sprite_group())

self.minimum_dimensions = (-1, -1)
relative_rect.size = self._get_clamped_to_minimum_dimensions(relative_rect.size)

if isinstance(relative_rect, pygame.Rect):
self.relative_rect = relative_rect.copy()
else:
self.relative_rect = pygame.Rect(relative_rect)
self.rect = self.relative_rect.copy()

self.dynamic_width = True if self.relative_rect.width == -1 else False
self.dynamic_height = True if self.relative_rect.height == -1 else False

self.ui_group = self.ui_manager.get_sprite_group()
self.ui_theme = self.ui_manager.get_theme()

Expand Down Expand Up @@ -139,7 +148,6 @@ def __init__(self, relative_rect: Union[pygame.Rect, Tuple[int, int, int, int]],
self.border_width = None # type: Union[None, int]
self.shape_corner_radius = None # type: Union[None, int]

self.ui_container = None
self._setup_container(container)

self.dirty = 1
Expand All @@ -152,6 +160,26 @@ def __init__(self, relative_rect: Union[pygame.Rect, Tuple[int, int, int, int]],

self._focus_set = {self}

def _get_clamped_to_minimum_dimensions(self, dimensions, clamp_to_container=False):
if self.ui_container is not None and clamp_to_container:
dimensions = (min(self.ui_container.rect.width,
max(self.minimum_dimensions[0],
int(dimensions[0]))),
min(self.ui_container.rect.height,
max(self.minimum_dimensions[1],
int(dimensions[1]))))
else:
dimensions = (max(self.minimum_dimensions[0], int(dimensions[0])),
max(self.minimum_dimensions[1], int(dimensions[1])))
return dimensions

def _on_contents_changed(self):
if self.dynamic_width or self.dynamic_height:
self._calc_dynamic_size()

def _calc_dynamic_size(self):
pass

@staticmethod
def _validate_horizontal_anchors(anchors: Dict[str, Union[str, 'UIElement']]):
# first make a dictionary of just the horizontal anchors
Expand Down Expand Up @@ -462,6 +490,7 @@ def _update_absolute_rect_position_from_anchors(self, recalculate_margins=False)
self.rect.top = new_top
new_height = new_bottom - new_top
new_width = new_right - new_left
new_width, new_height = self._get_clamped_to_minimum_dimensions((new_width, new_height))
if (new_height != self.relative_rect.height) or (new_width != self.relative_rect.width):
self.set_dimensions((new_width, new_height))

Expand Down Expand Up @@ -649,18 +678,42 @@ def set_position(self, position: Union[pygame.math.Vector2,
self._update_container_clip()
self.ui_container.on_anchor_target_changed(self)

def set_minimum_dimensions(self, dimensions: Union[pygame.math.Vector2,
Tuple[int, int],
Tuple[float, float]]):
"""
If this window is resizable, then the dimensions we set here will be the minimum that
users can change the window to. They are also used as the minimum size when
'set_dimensions' is called.
:param dimensions: The new minimum dimension for the window.
"""
self.minimum_dimensions = (min(self.ui_container.rect.width, int(dimensions[0])),
min(self.ui_container.rect.height, int(dimensions[1])))

if ((self.rect.width < self.minimum_dimensions[0]) or
(self.rect.height < self.minimum_dimensions[1])):
new_width = max(self.minimum_dimensions[0], self.rect.width)
new_height = max(self.minimum_dimensions[1], self.rect.height)
self.set_dimensions((new_width, new_height))

def set_dimensions(self, dimensions: Union[pygame.math.Vector2,
Tuple[int, int],
Tuple[float, float]]):
Tuple[float, float]],
clamp_to_container: bool = False):
"""
Method to directly set the dimensions of an element.
NOTE: Using this on elements inside containers with non-default anchoring arrangements
may make a mess of them.
:param dimensions: The new dimensions to set.
:param clamp_to_container: Whether we should clamp the dimensions to the
dimensions of the container or not.
"""
dimensions = self._get_clamped_to_minimum_dimensions(dimensions, clamp_to_container)
self.relative_rect.width = int(dimensions[0])
self.relative_rect.height = int(dimensions[1])
self.rect.size = self.relative_rect.size
Expand Down
29 changes: 14 additions & 15 deletions pygame_gui/elements/ui_button.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,6 @@ def __init__(self, relative_rect: Union[pygame.Rect, Tuple[float, float], pygame
if text_kwargs is not None:
self.text_kwargs = text_kwargs

self.dynamic_width = False
self.dynamic_height = False
self.dynamic_dimensions_orig_top_left = rel_rect.topleft
# support for an optional 'tool tip' element attached to this button
self.tool_tip_text = tool_tip_text
Expand Down Expand Up @@ -502,7 +500,7 @@ def set_text(self, text: str, *, text_kwargs: Optional[Dict[str, str]] = None):
any_changed = True

if any_changed:
if self.dynamic_width:
if self.dynamic_width or self.dynamic_height:
self.rebuild()
else:
self.drawable_shape.set_text(translate(self.text, **self.text_kwargs))
Expand Down Expand Up @@ -708,11 +706,6 @@ def rebuild(self):
A complete rebuild of the drawable shape used by this button.
"""
self.rect.width = -1 if self.dynamic_width else self.rect.width
self.relative_rect.width = -1 if self.dynamic_width else self.relative_rect.width

self.rect.height = -1 if self.dynamic_height else self.rect.height
self.relative_rect.height = -1 if self.dynamic_height else self.relative_rect.height

theming_parameters = {'normal_bg': self.colours['normal_bg'],
'normal_text': self.colours['normal_text'],
Expand Down Expand Up @@ -756,25 +749,31 @@ def rebuild(self):
'shape_corner_radius': self.shape_corner_radius,
'transitions': self.state_transitions}

drawable_shape_rect = self.rect.copy()
if self.dynamic_width:
drawable_shape_rect.width = -1
if self.dynamic_height:
drawable_shape_rect.height = -1

if self.shape == 'rectangle':
self.drawable_shape = RectDrawableShape(self.rect, theming_parameters,
self.drawable_shape = RectDrawableShape(drawable_shape_rect, theming_parameters,
['normal', 'hovered', 'disabled',
'selected', 'active'], self.ui_manager)
elif self.shape == 'ellipse':
self.drawable_shape = EllipseDrawableShape(self.rect, theming_parameters,
self.drawable_shape = EllipseDrawableShape(drawable_shape_rect, theming_parameters,
['normal', 'hovered', 'disabled',
'selected', 'active'], self.ui_manager)
elif self.shape == 'rounded_rectangle':
self.drawable_shape = RoundedRectangleShape(self.rect, theming_parameters,
self.drawable_shape = RoundedRectangleShape(drawable_shape_rect, theming_parameters,
['normal', 'hovered', 'disabled',
'selected', 'active'], self.ui_manager)

self.on_fresh_drawable_shape_ready()

if self.relative_rect.width == -1 or self.relative_rect.height == -1:
self.dynamic_width = self.relative_rect.width == -1
self.dynamic_height = self.relative_rect.height == -1
self._on_contents_changed()

def _calc_dynamic_size(self):
if self.dynamic_width or self.dynamic_height:
self.set_dimensions(self.image.get_size())

# if we have anchored the left side of our button to the right of it's container then
Expand Down Expand Up @@ -806,7 +805,7 @@ def on_locale_changed(self):
self.font = font
self.rebuild()
else:
if self.dynamic_width:
if self.dynamic_width or self.dynamic_height:
self.rebuild()
else:
self.drawable_shape.set_text(translate(self.text, **self.text_kwargs))
32 changes: 14 additions & 18 deletions pygame_gui/elements/ui_label.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,6 @@ def __init__(self, relative_rect: pygame.Rect,
object_id=object_id,
element_id='label')

self.dynamic_width = False
self.dynamic_height = False
self.dynamic_dimensions_orig_top_left = relative_rect.topleft

self.text = text
Expand Down Expand Up @@ -97,7 +95,6 @@ def set_text(self, text: str, *, text_kwargs: Optional[Dict[str, str]] = None):
:param text_kwargs: a dictionary of variable arguments to pass to the translated string
useful when you have multiple translations that need variables inserted
in the middle.
"""
any_changed = False
if text != self.text:
Expand All @@ -112,7 +109,7 @@ def set_text(self, text: str, *, text_kwargs: Optional[Dict[str, str]] = None):
any_changed = True

if any_changed:
if self.dynamic_width:
if self.dynamic_width or self.dynamic_height:
self.rebuild()
else:
self.drawable_shape.set_text(translate(self.text, **self.text_kwargs))
Expand All @@ -123,15 +120,9 @@ def rebuild(self):
the displayed text is or remake it with different theming (if the theming has changed).
"""

self.rect.width = -1 if self.dynamic_width else self.rect.width
self.relative_rect.width = -1 if self.dynamic_width else self.relative_rect.width

self.rect.height = -1 if self.dynamic_height else self.rect.height
self.relative_rect.height = -1 if self.dynamic_height else self.relative_rect.height

text_size = self.font.get_rect(translate(self.text, **self.text_kwargs)).size
if ((self.rect.height != -1 and text_size[1] > self.relative_rect.height) or
(self.rect.width != -1 and text_size[0] > self.relative_rect.width)):
if (not (self.dynamic_height or self.dynamic_width) and
((text_size[1] > self.relative_rect.height) or (text_size[0] > self.relative_rect.width))):
width_overlap = self.relative_rect.width - text_size[0]
height_overlap = self.relative_rect.height - text_size[1]
warn_text = ('Label Rect is too small for text: '
Expand Down Expand Up @@ -161,17 +152,22 @@ def rebuild(self):
'text_horiz_alignment_padding': self.text_horiz_alignment_padding,
'text_vert_alignment_padding': self.text_vert_alignment_padding}

self.drawable_shape = RectDrawableShape(self.rect, theming_parameters,
drawable_shape_rect = self.rect.copy()
if self.dynamic_width:
drawable_shape_rect.width = -1
if self.dynamic_height:
drawable_shape_rect.height = -1
self.drawable_shape = RectDrawableShape(drawable_shape_rect, theming_parameters,
['normal', 'disabled'], self.ui_manager)
self.on_fresh_drawable_shape_ready()

if self.relative_rect.width == -1 or self.relative_rect.height == -1:
self.dynamic_width = self.relative_rect.width == -1
self.dynamic_height = self.relative_rect.height == -1
self._on_contents_changed()

def _calc_dynamic_size(self):
if self.dynamic_width or self.dynamic_height:
self.set_dimensions(self.image.get_size())

# if we have anchored the left side of our button to the right of it's container then
# if we have anchored the left side of our button to the right of its container then
# changing the width is going to mess up the horiz position as well.
new_left = self.relative_rect.left
new_top = self.relative_rect.top
Expand Down Expand Up @@ -290,7 +286,7 @@ def on_locale_changed(self):
self.font = font
self.rebuild()
else:
if self.dynamic_width:
if self.dynamic_width or self.dynamic_height:
self.rebuild()
else:
self.drawable_shape.set_text(translate(self.text, **self.text_kwargs))
Expand Down
13 changes: 6 additions & 7 deletions pygame_gui/elements/ui_text_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def __init__(self,
allow_split_dashes: bool = True,
plain_text_display_only: bool = False,
should_html_unescape_input_text: bool = False):

relative_rect.height = -1 if wrap_to_height else relative_rect.height
super().__init__(relative_rect, manager, container,
starting_height=starting_height,
layer_thickness=2,
Expand All @@ -125,7 +125,6 @@ def __init__(self,

self._pre_parsing_enabled = pre_parsing_enabled

self.wrap_to_height = wrap_to_height
self.link_hover_chunks = [] # container for any link chunks we have

self.active_text_effect = None # type: Optional[TextEffect]
Expand Down Expand Up @@ -213,17 +212,17 @@ def rebuild(self):
(self.border_width * 2) -
(self.shadow_width * 2) -
(2 * self.rounded_corner_offset))))
if self.wrap_to_height or self.rect[3] == -1:
if self.dynamic_height:
self.text_wrap_rect.height = -1
if self.rect[2] == -1:
if self.dynamic_width:
self.text_wrap_rect.width = -1

drawable_area_size = (self.text_wrap_rect[2], self.text_wrap_rect[3])

# This gives us the height of the text at the 'width' of the text_wrap_area
self.parse_html_into_style_data()
if self.text_box_layout is not None:
if self.wrap_to_height or self.rect[3] == -1 or self.rect[2] == -1:
if self.dynamic_height or self.dynamic_width:
final_text_area_size = self.text_box_layout.layout_rect.size
new_dimensions = ((final_text_area_size[0] + (self.padding[0] * 2) +
(self.border_width * 2) + (self.shadow_width * 2) +
Expand Down Expand Up @@ -573,7 +572,7 @@ def parse_html_into_style_data(self):
default_font_data=default_font_data,
allow_split_dashes=self.allow_split_dashes)
self.parser.empty_layout_queue()
if self.text_wrap_rect[3] == -1:
if not self.dynamic_height:
self.text_box_layout.view_rect.height = self.text_box_layout.layout_rect.height

self._align_all_text_rows()
Expand All @@ -587,7 +586,7 @@ def redraw_from_text_block(self):
"""
if self.rect.width <= 0 or self.rect.height <= 0:
return
if (self.scroll_bar is None and (self.text_wrap_rect[3] != -1) and
if (self.scroll_bar is None and not self.dynamic_height and
(self.text_box_layout.layout_rect.height > self.text_wrap_rect[3])):
self.rebuild()
else:
Expand Down
Loading

0 comments on commit 39f6199

Please sign in to comment.