Closed
Description
Duplicate Check
- I have searched the opened issues and there are no duplicates
Describe the bug
When defining both HOVERED
and PRESSED
states for a button property using ControlStateValue
, the PRESSED
state is ignored if HOVERED
is present. This appears to be due to a lack of state precedence handling, where the HOVERED
state always takes precedence over PRESSED
since the button is technically still being hovered while pressed.
Code sample
Code
import flet as ft
def main(page: ft.Page):
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
page.vertical_alignment = ft.MainAxisAlignment.CENTER
button = ft.TextButton(
"Test Button",
style=ft.ButtonStyle(
bgcolor={
ft.ControlState.DEFAULT: ft.colors.with_opacity(0.786, "ffffff"),
ft.ControlState.HOVERED: ft.colors.GREEN,
ft.ControlState.PRESSED: ft.colors.BLUE,
}
),
)
page.add(button)
ft.app(target=main)
When clicking the button:
- On hover: Changes to HOVERED color
- On press: Stays at HOVERED color instead of changing to PRESSED color
- On release while still hovering: Stays at HOVERED color
- On mouse leave: Returns to DEFAULT color
To reproduce
- run the repro code
Expected behavior
The button states should follow a clear precedence order with predictable transitions between states:
State Precedence (highest to lowest):
- DISABLED/ERROR (if present)
- PRESSED
- HOVERED
- DEFAULT
Screenshots / Videos
Captures
controlstate-bug.mp4
Operating System
Windows
Operating system details
windows 11
Flet version
0.24.1
Regression
I'm not sure / I don't know
Suggestions
in the _wrap_attr_dict
method of the Control
class, since this is where Flet handles the ControlState dictionary conversion, we can process the state precedence:
class Control:
# ... etc
def _wrap_attr_dict(self, value: Optional[Union[Dict, Any]]) -> Optional[Dict]:
if value is None or isinstance(value, Dict):
# If it's already a dictionary (state values), process for precedence
if isinstance(value, Dict):
return self._process_state_dict(value)
return value
return {ControlState.DEFAULT: value}
def _process_state_dict(self, state_dict: Dict) -> Dict:
"""
Process a state dictionary to ensure proper state precedence.
Called internally by _wrap_attr_dict.
"""
# If there's only one state, no need for precedence handling
if len(state_dict) <= 1:
return state_dict
# Get the current active states
active_states = set()
if self.disabled:
active_states.add(ControlState.DISABLED)
if self._get_attr("error", data_type="bool", def_value=False):
active_states.add(ControlState.ERROR)
if self._get_attr("pressed", data_type="bool", def_value=False):
active_states.add(ControlState.PRESSED)
if self._get_attr("hovered", data_type="bool", def_value=False):
active_states.add(ControlState.HOVERED)
# Return value based on priority
for state in [
ControlState.DISABLED,
ControlState.ERROR,
ControlState.PRESSED,
ControlState.HOVERED,
ControlState.DEFAULT
]:
if state in active_states and state in state_dict:
return {ControlState.DEFAULT: state_dict[state]}
# Fallback to original default or first available value
return {ControlState.DEFAULT: state_dict.get(
ControlState.DEFAULT,
next(iter(state_dict.values()))
)}
Logs
Logs
Additional details
No response
Metadata
Metadata
Assignees
Labels
Type
Projects
Status
✅ Done