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

ControlState precedence issue with button states #4409

Open
1 task done
Bbalduzz opened this issue Nov 22, 2024 · 0 comments
Open
1 task done

ControlState precedence issue with button states #4409

Bbalduzz opened this issue Nov 22, 2024 · 0 comments
Assignees
Labels
bug Something isn't working controls
Milestone

Comments

@Bbalduzz
Copy link

Bbalduzz commented Nov 22, 2024

Duplicate Check

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

  1. 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):

  1. DISABLED/ERROR (if present)
  2. PRESSED
  3. HOVERED
  4. 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

@ndonkoHenri ndonkoHenri added bug Something isn't working controls labels Nov 25, 2024
@ndonkoHenri ndonkoHenri self-assigned this Nov 26, 2024
@ndonkoHenri ndonkoHenri added this to the Flet v0.26.0 milestone Nov 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working controls
Projects
Status: 🆕 New
Development

No branches or pull requests

2 participants