Skip to content

Hover Behaviors

morphui.uix.behaviors.hover

Hover behaviors for Kivy widgets.

This module provides two hover behaviors:

  • MorphHoverBehavior: Basic hover detection (enter/leave events)
  • MorphHoverEnhancedBehavior: Advanced hover with edge/corner detection

Choose the appropriate behavior based on your needs: - Use MorphHoverBehavior for simple hover effects - Use MorphHoverEnhancedBehavior for complex position-aware interactions

MorphHoverBehavior(**kwargs)

Bases: EventDispatcher

Basic hover behavior that detects mouse enter and leave events.

This is a lightweight behavior that provides core hover functionality without the overhead of edge and corner detection. Use this when you only need basic hover states for simple effects like color changes, cursor updates, or simple animations.

Events
  • :meth:on_enter: Fired when mouse enters the widget
  • :meth:on_leave: Fired when mouse leaves the widget
Properties
  • :attr:hover_enabled: Enable/disable hover detection
  • :attr:hovered: Current hover state
  • :attr:enter_pos: Position where mouse entered (widget coords)
  • :attr:leave_pos: Position where mouse left (widget coords)
  • :attr:current_pos: Current mouse position (widget coords)

Examples:

Simple hover color change:

from kivy.uix.label import Label
from morphui.uix.behaviors.hover import MorphHoverBehavior

class HoverLabel(MorphHoverBehavior, Label):
    def on_enter(self):
        self.color = (1, 0, 0, 1)  # Red on hover

    def on_leave(self):
        self.color = (1, 1, 1, 1)  # White when not hovering

Hover with position tracking:

class PositionTracker(MorphHoverBehavior, Widget):
    def on_enter(self):
        print(f"Mouse entered at: {self.enter_pos}")

    def on_leave(self):
        print(f"Mouse left at: {self.leave_pos}")

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

Enable or disable hover behavior and event detection.

When set to False, the behavior will not track mouse position or fire hover events, effectively disabling all hover functionality. This is useful for temporarily disabling hover effects without removing the behavior from the widget.

Setting this to False will also clear any current hover state and stop all hover-related event dispatching until re-enabled.

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

Examples:

# Temporarily disable hover during animations
widget.hover_enabled = False
animation.start(widget)
animation.bind(on_complete=lambda *args: setattr(widget, 'hover_enabled', True))

# Conditionally enable hover based on widget state
widget.hover_enabled = not widget.disabled

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

Indicates whether the mouse is currently over the widget.

This property is automatically updated based on mouse position and widget bounds. You can bind to this property to react to hover state changes.

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

is_displayed property

Check if widget is displayed (has root window).

on_mouse_pos(instance, pos)

Handle mouse position changes from Window.

This method is automatically called whenever the mouse moves. It updates hover state and position tracking based on collision detection with the widget bounds.

PARAMETER DESCRIPTION
instance

The Window instance (unused)

TYPE: Any

pos

Mouse position in window coordinates

TYPE: Tuple[float, float]

on_enter()

Event fired when mouse enters the widget.

Override this method in subclasses to add custom hover behavior. The mouse position where the widget was entered is available in :attr:enter_pos.

on_leave()

Event fired when mouse leaves the widget.

Override this method in subclasses to add custom hover behavior. The mouse position where the widget was left is available in :attr:leave_pos.

MorphHoverEnhancedBehavior(**kwargs)

Bases: MorphHoverBehavior

Enhanced hover behavior with edge and corner detection.

This behavior extends the base hover functionality with detailed edge and corner detection. It's ideal for widgets that need to respond differently based on where the mouse is positioned within the widget bounds.

Additional Events (beyond base hover)
  • :meth:on_enter_edge: Fired when mouse enters any edge
  • :meth:on_leave_edge: Fired when mouse leaves any edge
  • :meth:on_enter_corner: Fired when mouse enters any corner
  • :meth:on_leave_corner: Fired when mouse leaves any corner
Additional Properties
  • :attr:hovered_edges: List of currently hovered edges
  • :attr:hovered_corner: Currently hovered corner
  • :attr:edge_detection_size: Size of edge detection area in pixels
  • Individual edge properties: left_edge_hovered, right_edge_hovered, etc.
Common Use Cases
  • Resizable widgets with visual resize handles
  • Tooltips that change based on widget area
  • Cursor changes for different widget regions
  • Complex hover animations based on position

Examples:

Resizable widget with visual feedback:

from kivy.uix.widget import Widget
from kivy.core.window import Window
from morphui.uix.behaviors.hover import MorphHoverEnhancedBehavior

class ResizableWidget(MorphHoverEnhancedBehavior, Widget):
    def on_enter_edge(self, edge):
        if edge in ['left', 'right']:
            Window.set_system_cursor('size_we')  # Horizontal resize
        else:
            Window.set_system_cursor('size_ns')  # Vertical resize

    def on_enter_corner(self, corner):
        if corner in ['top-left', 'bottom-right']:
            Window.set_system_cursor('size_nwse')
        else:
            Window.set_system_cursor('size_nesw')

    def on_leave(self):
        Window.set_system_cursor('arrow')  # Reset cursor

Dynamic tooltips:

class SmartTooltip(MorphHoverEnhancedBehavior, Widget):
    def on_enter_edge(self, edge):
        self.show_tooltip(f"Drag {edge} edge to resize")

    def on_enter_corner(self, corner):
        self.show_tooltip(f"Drag {corner} corner to resize diagonally")

    def on_enter(self):
        if not self.hovered_edges:
            self.show_tooltip("Click to select widget")

hovered_edges = ListProperty([]) class-attribute instance-attribute

List of edges currently being hovered over.

Contains edge names from ['left', 'right', 'top', 'bottom'] that are currently under the mouse cursor. Updated automatically based on mouse position and edge_detection_size.

:attr:hovered_edges is a :class:~kivy.properties.ListProperty and defaults to an empty list.

hovered_corner = StringProperty(None, options=(NAME.CORNERS), allownone=True) class-attribute instance-attribute

The corner currently being hovered over.

Automatically determined from hovered_edges when exactly two adjacent edges are hovered. Possible values are corner names or None.

:attr:hovered_corner is a :class:~kivy.properties.StringProperty and defaults to None.

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

Whether the left edge is currently hovered.

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

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

Whether the right edge is currently hovered.

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

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

Whether the top edge is currently hovered.

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

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

Whether the bottom edge is currently hovered.

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

edge_detection_size = NumericProperty(dp(4)) class-attribute instance-attribute

Size of the edge area for detection in pixels.

Determines how many pixels from the widget edge count as "edge area" for hover detection. Larger values make edges easier to target but reduce the center area.

:attr:edge_detection_size is a :class:~kivy.properties.NumericProperty and defaults to 4.

get_hovered_corner()

Determine corner from currently hovered edges.

RETURNS DESCRIPTION
str | None

Corner name if exactly two adjacent edges are hovered, None otherwise.

get_hovered_edges()

Get list of currently hovered edges.

RETURNS DESCRIPTION
List[str]

List of edge names that are currently hovered.

on_mouse_pos(instance, pos)

Enhanced mouse position handling with edge detection.

Extends the base behavior to also calculate edge and corner hover states based on mouse position within the widget.

PARAMETER DESCRIPTION
instance

The Window instance

TYPE: Any

pos

Mouse position in window coordinates

TYPE: Tuple[float, float]

on_left_edge_hovered(instance, hovered)

Handle left edge hover state changes.

on_right_edge_hovered(instance, hovered)

Handle right edge hover state changes.

on_top_edge_hovered(instance, hovered)

Handle top edge hover state changes.

on_bottom_edge_hovered(instance, hovered)

Handle bottom edge hover state changes.

on_hovered_corner(instance, corner)

Handle corner hover state changes.

Dispatches enter/leave corner events when the corner changes.

PARAMETER DESCRIPTION
instance

The widget instance

TYPE: Any

corner

New corner value

TYPE: str

on_enter_edge(edge)

Event fired when mouse enters any edge.

The edge names are 'left', 'right', 'top', and 'bottom'. This event is triggered whenever the mouse cursor moves into the edge area defined by the edge_detection_size property. You can override this method in subclasses to implement custom behavior when an edge is entered.

PARAMETER DESCRIPTION
edge

The edge being entered

TYPE: Literal['left', 'right', 'top', 'bottom']

Examples:

def on_enter_edge(self, edge):
    self.edge_highlight[edge] = True
    if edge in ['left', 'right']:
        Window.set_system_cursor('size_we')
    else:
        Window.set_system_cursor('size_ns')

on_leave_edge(edge)

Event fired when mouse leaves any edge.

The edge names are 'left', 'right', 'top', and 'bottom'. This event is triggered whenever the mouse cursor moves out of the edge area defined by the edge_detection_size property. You can override this method in subclasses to implement custom behavior when an edge is left.

PARAMETER DESCRIPTION
edge

The edge being left

TYPE: Literal['left', 'right', 'top', 'bottom']

on_enter_corner(corner)

Event fired when mouse enters any corner.

The corner names are 'top-left', 'top-right', 'bottom-left', and 'bottom-right'. This event is triggered whenever the mouse cursor moves into a corner area defined by the intersection of two adjacent edges. You can override this method in subclasses to implement custom behavior when a corner is entered.

PARAMETER DESCRIPTION
corner

The corner being entered

TYPE: Literal['top-left', 'top-right', 'bottom-left', 'bottom-right']

Examples:

def on_enter_corner(self, corner):
    if corner in ['top-left', 'bottom-right']:
        Window.set_system_cursor('size_nwse')
    else:
        Window.set_system_cursor('size_nesw')

on_leave_corner(corner)

Event fired when mouse leaves any corner.

The corner names are 'top-left', 'top-right', 'bottom-left', and 'bottom-right'. This event is triggered whenever the mouse cursor moves out of a corner area defined by the intersection of two adjacent edges. You can override this method in subclasses to implement custom behavior when a corner is left.

PARAMETER DESCRIPTION
corner

The corner being left

TYPE: Literal['top-left', 'top-right', 'bottom-left', 'bottom-right']