Skip to content

Sizing Behaviors

morphui.uix.behaviors.sizing

Comprehensive sizing behaviors for Kivy widgets.

This module provides all size-related functionality through three main behaviors: - Size bounds/constraints (minimum and maximum size limits) - Interactive resizing (drag to resize with mouse) - Auto-sizing (content-driven automatic sizing)

The sizing behaviors support: - Lower and upper bound size constraints - Edge and corner resizing with visual feedback - Automatic sizing based on content and children - Cursor changes and hover effects for resize - Aspect ratio preservation - Event system for resize and auto-size operations

MorphSizeBoundsBehavior(**kwargs)

Bases: EventDispatcher

A behavior that provides size constraint functionality.

This behavior adds lower and upper bound properties for constraining widget dimensions. It automatically resolves these bounds considering the widget's inherent minimum and maximum dimensions, providing a clean interface for size constraint enforcement.

Features
  • Lower Bounds: Minimum size constraints with fallback to inherent minimums
  • Upper Bounds: Maximum size constraints with infinity fallback
  • Automatic Resolution: Computed properties handle constraint logic
  • Flexible Configuration: Disable constraints with negative values
Properties
  • :attr:size_lower_bound: Minimum width and height constraints
  • :attr:size_upper_bound: Maximum width and height constraints
  • :attr:_resolved_size_lower_bound: Computed lower bounds (read-only)
  • :attr:_resolved_size_upper_bound: Computed upper bounds (read-only)

Examples:

Basic size-constrained widget:

from kivy.uix.widget import Widget
from morphui.uix.behaviors.sizing import MorphSizeBoundsBehavior

class ConstrainedWidget(MorphSizeBoundsBehavior, Widget):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        # Set minimum size to 100x50, maximum size to 500x300
        self.size_lower_bound = [100, 50]
        self.size_upper_bound = [500, 300]

    def constrain_size(self, new_size):
        from morphui.utils import clamp
        width = clamp(new_size[0], 
                     self._resolved_size_lower_bound[0],
                     self._resolved_size_upper_bound[0])
        height = clamp(new_size[1],
                      self._resolved_size_lower_bound[1], 
                      self._resolved_size_upper_bound[1])
        return (width, height)

Using with negative values to disable constraints:

# Only constrain width, not height
widget.size_lower_bound = [100, -1]  # Min width 100, no min height
widget.size_upper_bound = [500, -1]  # Max width 500, no max height

size_lower_bound = VariableListProperty([-1, -1], length=2) class-attribute instance-attribute

Lower bound size constraints [width, height].

Prevents the widget from being resized smaller than these dimensions. Useful for maintaining usability and preventing widgets from becoming too small to interact with. Use [0, 0] to disable minimum size constraints. If a negative value is specified for a dimension, the widget's inherent minimum size will be used.

:attr:size_lower_bound is a :class:~kivy.properties.VariableListProperty and defaults to [-1, -1].

size_upper_bound = VariableListProperty([-1, -1], length=2) class-attribute instance-attribute

Upper bound size constraints [width, height].

Prevents the widget from being resized larger than these dimensions. Use [-1, -1] to disable maximum size constraints.

:attr:size_upper_bound is a :class:~kivy.properties.VariableListProperty and defaults to [-1, -1].

constrain_size(size)

Apply size bounds constraints to a given size.

This convenience method applies both lower and upper bound constraints to a size tuple, returning the constrained dimensions.

PARAMETER DESCRIPTION
size

The size to constrain (width, height)

TYPE: Tuple[float, float]

RETURNS DESCRIPTION
Tuple[float, float]

The constrained size (width, height)

Examples:

# Constrain a proposed size
new_size = (80, 600)  # Too small width, too large height
constrained = self.constrain_size(new_size)
# Returns (100, 300) if bounds are [100, 50] to [500, 300]

MorphAutoSizingBehavior(**kwargs)

Bases: EventDispatcher

Behavior for automatic widget sizing based on content.

This behavior provides three boolean properties that enable automatic sizing of widgets based on their content and children. It can automatically adjust width, height, or both dimensions to fit the minimum required size.

Features
  • Auto Width: Automatically adjust width to fit content
  • Auto Height: Automatically adjust height to fit content
  • Combined Auto Size: Automatically adjust both dimensions
  • Text Integration: Special handling for text-based widgets
  • Content Adaptation: Adapts to minimum/maximum size constraints
Properties
  • :attr:auto_width: Enable automatic width adjustment
  • :attr:auto_height: Enable automatic height adjustment
  • :attr:auto_size: Enable both width and height adjustment

Examples:

Auto-sizing label:

from kivy.uix.label import Label
from morphui.uix.behaviors.sizing import MorphAutoSizingBehavior

class AutoLabel(MorphAutoSizingBehavior, Label):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.auto_size = True  # Adjust both width and height

Auto-width button:

from kivy.uix.button import Button
from morphui.uix.behaviors.sizing import MorphAutoSizingBehavior

class AutoButton(MorphAutoSizingBehavior, Button):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.auto_width = True  # Only adjust width

auto_width = BooleanProperty(False) class-attribute instance-attribute

Automatically adjust widget width to minimum required size.

When True, the widget's width will be automatically calculated and set to the minimum size required to accommodate all its content and children. This is useful for creating widgets that adapt their width based on their packed content, such as buttons that resize based on text length or containers that fit their child widgets.

:attr:auto_width is a :class:~kivy.properties.BooleanProperty and defaults to False.

auto_height = BooleanProperty(False) class-attribute instance-attribute

Automatically adjust widget height to minimum required size.

When True, the widget's height will be automatically calculated and set to the minimum size required to accommodate all its content and children. This is useful for creating widgets that adapt their height based on their packed content, such as labels that resize based on text height or containers that fit their child widgets vertically.

:attr:auto_height is a :class:~kivy.properties.BooleanProperty and defaults to False.

auto_size = AliasProperty(_get_auto_size, _set_auto_size, bind=('auto_width', 'auto_height')) class-attribute instance-attribute

Automatically adjust both width and height to minimum required size.

When True, both the widget's width and height will be automatically calculated and set to the minimum size required to accommodate all its content and children. This is useful for creating widgets that fully adapt their size based on their packed content.

Note that setting :attr:auto_size to True will set both :attr:auto_width and :attr:auto_height to True. Conversely, setting :attr:auto_size to False will set both :attr:auto_width and :attr:auto_height to False. You can also set :attr:auto_size to a tuple of two booleans to independently control width and height.

:attr:auto_size is a :class:~kivy.properties.AliasProperty and defaults to (False, False).

has_text_layout property

Check if the widget uses Kivy's text layout system.

Returns True if the widget has both texture_size and text_size attributes, which is the case for text-based widgets like Label. Used to decide whether to measure and constrain text dimensions during auto sizing.

apply_auto_sizing(auto_width, auto_height)

Enforce auto sizing based on provided flags. This will not change the property values, but will apply the sizing adjustments as if the properties were set.

This method is responsible for applying the appropriate sizing adjustments to the widget based on the provided flags. It stores the original size and size_hint before making any changes, allowing for restoration when auto sizing is disabled. It uses :attr:texture_size if available, otherwise falls back to :attr:minimum_width and :attr:minimum_height.

PARAMETER DESCRIPTION
auto_width

Whether to apply auto width sizing.

TYPE: bool

auto_height

Whether to apply auto height sizing.

TYPE: bool

Notes

For text-based widgets, :attr:text_size is temporarily set to (None, None) to allow the texture to update to its natural (unconstrained) size before applying the new dimensions. This prevents a previously set text_size from wrapping longer text before we have a chance to measure the natural width.

refresh_auto_sizing()

Re-apply the current auto sizing settings.

This method can be called to refresh the auto sizing behavior, for example after dynamic changes to the widget that may affect sizing. It re-applies the sizing adjustments based on the current values of :attr:auto_width and :attr:auto_height.

This method preserves the original size and size_hint before re-applying the sizing adjustments, ensuring that the widget can return to its original size if needed.

on_auto_size_updated(*args)

Event fired after auto sizing has been applied or refreshed.

This event can be used to perform additional actions after the widget's size has been adjusted based on its content. It is triggered at the end of the :meth:apply_auto_sizing method.

MorphResizeBehavior(**kwargs)

Bases: MorphSizeBoundsBehavior, MorphHoverEnhancedBehavior, MorphOverlayLayerBehavior

A behavior that enables widgets to be resized by dragging edges and corners.

This behavior combines size bounds, enhanced hover detection, and overlay layer functionality to provide interactive resizing capabilities. It automatically detects when the mouse is over resizable edges or corners and provides visual feedback through edge highlighting and cursor changes.

Features
  • Edge Resizing: Resize by dragging left, right, top, or bottom edges
  • Corner Resizing: Resize diagonally by dragging corners
  • Visual Feedback: Highlighted edges and appropriate cursor changes
  • Size Constraints: Minimum and maximum size limits via inheritance
  • Aspect Ratio: Optional aspect ratio preservation
  • Animation: Smooth transitions for visual feedback
  • Events: Comprehensive event system for resize operations
Events
  • :meth:on_resize_start: Fired when resize operation begins
  • :meth:on_resize_progress: Fired during resize (real-time updates)
  • :meth:on_resize_end: Fired when resize operation completes
Properties
  • :attr:resize_enabled: Enable/disable resize functionality
  • :attr:resizable_edges: List of edges that can be resized
  • :attr:size_lower_bound: Minimum width and height constraints (inherited)
  • :attr:size_upper_bound: Maximum width and height constraints (inherited)
  • :attr:preserve_aspect_ratio: Whether to maintain aspect ratio

Examples:

Basic resizable widget:

from kivy.uix.widget import Widget
from morphui.uix.behaviors.sizing import MorphResizeBehavior

class ResizableWidget(MorphResizeBehavior, Widget):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.size = (200, 150)
        self.pos = (100, 100)
        # Set size constraints
        self.size_lower_bound = [50, 50]
        self.size_upper_bound = [400, 300]

    def on_resize_start(self, edge_or_corner):
        print(f"Starting resize from {edge_or_corner}")

    def on_resize_end(self, edge_or_corner):
        print(f"Finished resizing")

Resizable widget with aspect ratio preservation:

class AspectResizableWidget(MorphResizeBehavior, Widget):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.preserve_aspect_ratio = True
        self.size_lower_bound = [100, 75]  # 4:3 ratio minimum

resize_enabled = BooleanProperty(True) class-attribute instance-attribute

Enable or disable resize functionality.

When set to False, the widget will not respond to resize operations, but hover detection and visual feedback will still work. This is useful for temporarily disabling resize while maintaining visual consistency.

:attr:resize_enabled is a :class:~kivy.properties.BooleanProperty and defaults to True.

resizable_edges = ListProperty(NAME.EDGES) class-attribute instance-attribute

List of edges that can be used for resizing.

Controls which edges of the widget can be dragged to resize. Can contain any combination of 'left', 'right', 'top', 'bottom'. Corner resizing is automatically enabled when two adjacent edges are both resizable.

:attr:resizable_edges is a :class:~kivy.properties.ListProperty and defaults to all edges (left, right, top, bottom).

preserve_aspect_ratio = BooleanProperty(False) class-attribute instance-attribute

Whether to preserve the widget's aspect ratio during resize.

When True, the widget will maintain its width-to-height ratio during resize operations. This is particularly useful for images, video players, or other content where aspect ratio is important.

:attr:preserve_aspect_ratio is a :class:~kivy.properties.BooleanProperty and defaults to False.

resizing = BooleanProperty(False) class-attribute instance-attribute

Indicates whether a resize operation is currently in progress.

This property is True while the user is actively dragging an edge or corner to resize the widget, and False otherwise. It can be used to conditionally change behavior or appearance during resize operations. For example, you might want to disable certain interactions while resizing is happening. The :class:morphui.uix.behaviors.states.MorphStateBehavior listens to this property and so does the :class:morphui.uix.behaviors.layer.MorphOverlayLayerBehavior.

:attr:resizing is a :class:~kivy.properties.BooleanProperty and defaults to False.

resize_edge_or_corner property

The edge or corner currently being used for resize operation (read-only).

This property is set when a resize operation starts and cleared when it ends. It indicates which edge ('left', 'right', 'top', 'bottom') or corner ('top-left', 'top-right', 'bottom-left', 'bottom-right') is being dragged to resize the widget. If no resize operation is in progress, this will be None.

hovered_resizable_edges property

List of currently hovered edges that are resizable.

Only edges present in :attr:resizable_edges will be included. If resize is disabled, this will always be an empty list.

hovered_resizable_corner property

Currently hovered corner if it is resizable, else None.

on_touch_down(touch)

Handle touch down events for resize operations.

This method initiates a resize operation if the touch occurs over a resizable edge or corner. It sets up the necessary state for the resize operation to proceed.

RETURNS DESCRIPTION
bool

True if touch was handled for resize

on_touch_move(touch)

Handle touch move events during resize operations.

This method updates the size and position of the widget being resized based on the current mouse position. It applies size constraints and dispatches the appropriate resize event.

PARAMETER DESCRIPTION
touch

Touch event

TYPE: MotionEvent

RETURNS DESCRIPTION
bool

True if touch was handled for resize

on_touch_up(touch)

Handle touch up events to end resize operations.

This method finalizes the resize operation, resets state, and dispatches the resize end event.

PARAMETER DESCRIPTION
touch

Touch event

TYPE: MotionEvent

RETURNS DESCRIPTION
bool

True if touch was handled for resize

on_resize_start(edge_or_corner)

Event fired when a resize operation starts.

This event is dispatched when the user starts dragging an edge or corner to resize the widget. Override this method to add custom behavior at the start of resize operations.

PARAMETER DESCRIPTION
edge_or_corner

The edge or corner being used for resize ('left', 'right', 'top', 'bottom', or corner names like 'top-left')

TYPE: str

Examples:

def on_resize_start(self, edge_or_corner):
    print(f"Starting resize from {edge_or_corner}")
    self.opacity = 0.8  # Make widget semi-transparent during resize

on_resize_progress(edge_or_corner, new_size, new_pos)

Event fired during resize operations with new dimensions.

This event is dispatched continuously while the user drags to resize the widget. The default implementation applies the new size and position to the widget. Override this method to customize resize behavior or add validation.

PARAMETER DESCRIPTION
edge_or_corner

The edge or corner being used for resize

TYPE: str

new_size

New widget size (width, height)

TYPE: Tuple[float, float]

new_pos

New widget position (x, y)

TYPE: Tuple[float, float]

Examples:

def on_resize_progress(self, edge_or_corner, new_size, new_pos):
    # Custom validation
    if new_size[0] < 100:
        return  # Don't allow width less than 100

    # Apply resize
    super().on_resize_progress(edge_or_corner, new_size, new_pos)

    # Update content
    self.update_content_layout()

on_resize_end(edge_or_corner)

Event fired when a resize operation completes.

This event is dispatched when the user releases the mouse after resizing the widget. Override this method to add custom behavior at the end of resize operations, such as saving the new size or triggering layout updates.

PARAMETER DESCRIPTION
edge_or_corner

The edge or corner that was used for resize

TYPE: str

Examples:

def on_resize_end(self, edge_or_corner):
    print(f"Finished resizing from {edge_or_corner}")
    self.opacity = 1.0  # Restore full opacity
    self.save_size_to_config()  # Save new size

on_leave()

Override parent on_leave to reset cursor.