Per request of @Tommy804,
Here’s a Python 3 example of an Eto.Forms.Drawable button that responds visually to mouse events:
default:
hovered:
clicked:
Hope that helps some people, cheers!
Code:
#! python 3
"""
Author: Michael Vollrath
Company: Toyblock
Website: https://www.toyblock.co
Version: 1.0
Date: 04.21.2025
Notes: Made with <3 in Dallas, TX
Creates a custom Eto.Form and a custom Drawable to be used
as a button that visually responds to mouse events.
"""
import Eto.Forms as ef
import Eto.Drawing as ed
from Rhino.UI import EtoExtensions
# Code Example for a Custom Eto Form (non-modal)
class CustomForm(ef.Form):
# Intiliaze CustomForm Class
def __init__(self):
super(CustomForm, self).__init__() # super needed for Python 3
# Set Form General Settings
self.Title = "My Custom Form"
self.Size = ed.Size(300, 200) # Set The Overall Form Size
self.ShowInTaskbar = True
EtoExtensions.UseRhinoStyle(self)
# self.WindowStyle = ef.WindowStyle.NONE # Uncomment this to remove the forms window title bar
self.Resizable = True
self.MovableByWindowBackground = False
self.Topmost = False
self.CreateLayout()
def CreateLayout(self):
"""Create a layout for this form and add a custom drawable button to it"""
self.custom_button = CustomButton(starting_state=False)
self.layout = ef.PixelLayout()
self.layout.Size = ed.Size(280, 180)
self.layout.Add(
self.custom_button,
(self.layout.Size.Width - self.custom_button.Width) / 2,
(self.layout.Size.Height - self.custom_button.Height) / 2)
self.Content = self.layout # Add the layout to the form
# Code Example for a Custom Drawable Button that visually responds to mouse events
class CustomButton(ef.Drawable):
# Intiliaze CustomButton Class
def __init__(self, starting_state: bool):
super(CustomButton, self).__init__() # super needed for Python 3
# Size/Position variables
self._padding = 5
self.Size = ed.Size(100 + self._padding * 2, 40 + self._padding * 2)
# Styling variables
self.highlight_color = ed.Color.FromArgb(0, 111, 238, 255)
self.button_enabled_color = self.highlight_color
self.button_disabled_color = ed.Color.FromArgb(0, 111, 238, 60)
self.border_enabled_color = self.highlight_color
self.border_disabled_color = self.highlight_color
self.defaultTextColor = self.highlight_color
self.enabledPen = ed.Pen(self.border_enabled_color, 4)
self.disabledPen = ed.Pen(self.border_disabled_color, 2)
self.hoverPen = ed.Pen(self.highlight_color, 8)
self.strokeWidth = 6
self.radius = 14
# Mouse state tracking variables
self._hover = False
self._mouse_down = False
# Bonus stuff
self._state = starting_state
self.font = ed.Font("Arial", 12)
# This is where everything gets dynamically drawn real-time
def OnPaint(self, e):
try:
"""Basic stuff, fill, border"""
overall_rect = ed.RectangleF(0, 0, self.Size.Width, self.Size.Height) # This is the overall "max size" of the button drawable graphics to be drawn within
button_rect = ed.RectangleF.Inflate(overall_rect, -self._padding, -self._padding) # "Deinflate" to ensure all graphics are drawn "within" the button rect otherwise some clipping may occur
rounded_rect_path = ed.GraphicsPath.GetRoundRect(button_rect, min(self.radius, button_rect.Height * .5)) # Use min here to prevent "radius" from being larger than half the height of the button
e.Graphics.FillPath(self.button_enabled_color if self._state or self._hover else self.button_disabled_color, rounded_rect_path)
e.Graphics.DrawPath(self.hoverPen if self._hover else (self.enabledPen if self._state else self.disabledPen), rounded_rect_path)
"""Bonus stuff below"""
# Set text
text = "click me!" if not self._state else "thanks! ☺️"
text_color = ed.Colors.White if self._state or self._hover else self.defaultTextColor
# Calculate text position to center it in the button
text_size = e.Graphics.MeasureString(self.font, text)
x = (self.Size.Width - text_size.Width) / 2
y = (self.Size.Height - text_size.Height) / 2
# Draw the text
e.Graphics.DrawText(self.font, text_color, x, y, text)
except Exception as ex:
print(f"OnPaint exception: {ex}")
def OnMouseDown(self, e):
try:
self._mouse_down = True
self._state = not self._state
self.Invalidate() # This will ensure the button is redrawn
print("button clicked!")
except Exception as ex:
print(f"OnMouseDown exception: {ex}")
def OnMouseUp(self, e):
try:
self._mouse_down = False
self.Invalidate() # This will ensure the button is redrawn
print("button released...")
except Exception as ex:
print(f"OnMouseUp exception: {ex}")
def OnMouseEnter(self, e):
try:
self._hover = True
self.Invalidate() # This will ensure the button is redrawn
print("button hovered")
except Exception as ex:
print(f"OnMouseEnter exception: {ex}")
def OnMouseLeave(self, e):
try:
self._hover = False
self.Invalidate() # This will ensure the button is redrawn
print("button no longer hovered")
except Exception as ex:
print(f"OnMouseLeave exception: {ex}")
# def OnMouseMove(self, e):
# try:
# print("mouse is moving!")
# except Exception as ex:
# print(f"OnMouseMove exception: {ex}")
def Main():
"""Create a form instance with the drawable, then show the form"""
# Create an Eto.Forms.Form (This is non-modal, meaning it can exist while other operations/input are being used)
form = CustomForm()
# form.Owner = Rhino.UI.RhinoEtoApp.MainWindow
form.Topmost = False
form.Show()
form.BringToFront()
# Execute Script If Script Is Being Run Directly
if __name__ == "__main__":
Main()



