Eto - DynamicLayout - Change Single Row Height Dynamically

Hello,

I have the following code in which I am attempting to change the height of a row when that rows custom button is hovered on.

I’m successfully changing the “visual state” of the button and returning it’s index.

However, I’m struggling to simply update only this Row’s padding dynamically.
I am trying to “shift” the other buttons down vertically and when a button is no longer hovered on, they should all “tighten up” again.

Here’s the current “hover state” on a button on the form:

Here’s a crude sketch of what I am trying to achieve:

No buttons hovered:

Hover on button 01:

Hover on button 02:

Here is my code attempt thus far:

#! python3

import Rhino
import Eto.Drawing as drawing
import Eto.Forms as forms

main_layout_instance = None
current_button_index = None

button_width = 120
button_full_height = 120
button_height = button_full_height / 4


# Code for Custom Toolbar Buttons
class CustomButton(forms.Drawable):

    # Initialize CustomButton Class
    def __init__(self, state: bool):
        super(CustomButton, self).__init__()

        global main_layout_instance

        self._hover = False

        self.Size = drawing.Size(button_width, button_full_height)
        self.Radius = 15
        self._b_rect = drawing.RectangleF(0, 0, self.Bounds.Width, self.Bounds.Height / 4)
        self._h_rect = drawing.RectangleF(0, 0, self.Bounds.Width, self.Bounds.Height)
        self._path = drawing.GraphicsPath.GetRoundRect(self._b_rect, self.Radius)
        self._h_path = drawing.GraphicsPath.GetRoundRect(self._h_rect, self.Radius)

    def OnPaint(self, e):
        try:
            # draws the shape outline on hover
            e.Graphics.FillPath(drawing.Colors.BlueViolet if self._hover else drawing.Colors.DarkGray, self._h_path if self._hover else self._path)

        except Exception as ex:
            Rhino.RhinoApp.WriteLine(f"Error in OnPaint: {ex}")

    def OnMouseEnter(self, e):
        try:
            global current_button_index
            current_button_index = result_buttons.index(self)
            # Rhino.RhinoApp.WriteLine(f"Button[{current_button_index}] Hovered")
            self._hover = True
            self.Invalidate()
            main_layout_instance.update_row_height(self)
        except Exception as ex:
            Rhino.RhinoApp.WriteLine(f"Error in OnMouseEnter: {ex}")

    def OnMouseLeave(self, e):
        try:
            global current_button_index
            current_button_index = None
            self._hover = False
            self.Invalidate()
        except Exception as ex:
            Rhino.RhinoApp.WriteLine(f"Error in OnMouseLeave: {ex}")

    def OnMouseDown(self, e):
        try:
            pass
        except Exception as ex:
            Rhino.RhinoApp.WriteLine(f"Error in OnMouseDown: {ex}")

    def OnMouseUp(self, e):
        try:
            if e.Buttons == forms.MouseButtons.Primary and self._hover:
                global current_button_index

        except Exception as ex:
            Rhino.RhinoApp.WriteLine(f"Error in OnMouseUp: {ex}")

    def OnPreLoad(self, e):
        pass


test_results = ["Test Button 01", "Test Button 02", "Test Button 03", "Test Button 04"]

result_buttons = []
for result in test_results:
    result_buttons.append(CustomButton(False))


class DynamicLayoutRowTest(forms.Form):
    def __init__(self):
        super().__init__()
        self.Title = "Dynamic Row Height Test"
        self.ClientSize = drawing.Size(300, 600)
        # self.Padding = drawing.Padding(10)
        self.Resizable = True

        global main_layout_instance
        main_layout_instance = self  # Storing reference to DynamicLayoutRowTest instance

        # Create the dynamic layout
        self.dynamic_layout = forms.DynamicLayout()
        self.dynamic_layout.DefaultSpacing = drawing.Size(5, 5)
        self.dynamic_layout.Padding = drawing.Padding(10)

        # Create and add some buttons to the layout
        self.buttons = []
        for result in result_buttons:
            result.main_layout = self  # Set the layout reference for each button
            self.dynamic_layout.AddRow(result)
            self.buttons.append(result)

        self.Content = self.dynamic_layout

    def update_row_height(self, button):
        global current_button_index
        Rhino.RhinoApp.WriteLine(f"Button[{current_button_index}] Hovered")
        # Clear the dynamic layout and rebuild it with updated heights
        self.dynamic_layout.Invalidate()
        # self.dynamic_layout.Clear()

        for i, btn in enumerate(result_buttons):
            padding = drawing.Padding(0, button_height) if i != current_button_index else drawing.Padding(0, button_full_height)  # Dynamically Change Row Height If Row Index Matches Current Button Index
            self.dynamic_layout.AddRow(btn)
            btn.Padding = padding


def main():
    form = DynamicLayoutRowTest()
    form.Owner = Rhino.UI.RhinoEtoApp.MainWindow
    form.Show()


if __name__ == "__main__":
    main()


Thanks for any guidance and help!

The general approach is to modify the controls inside of the DynamicLayout and the layout will follow, well, dynamically :slight_smile:

Instead of tweaking the layout padding, adjust the height of the buttons.

3 Likes

@mrhe thank you so much, that makes much more sense haha.

passing the Drawable “heights” as the dynamic layout control.Height property did the trick, I would have never guessed that but now it feels obvious, so thanks for pointing it out!

    def RefreshLayout(self, index):
        Rhino.RhinoApp.WriteLine(f"Button[{index}] Hovered")

        for i, result in enumerate(result_buttons):
            result.Height = button_height if i != index else button_full_height
            self.dynamic_layout.AddRow(result)
1 Like