Eto Button - Border Type/Removal

I made progress thanks to the code you shared @clement. I now have a gradient brush fill working (I was trying to test other drawable options). And managed to create a Pixel Layout so that I can “layer” some of these drawables on top of themselves visually.

I have two questions:

  1. Is there a way to layer drawables on top of eachother visually without a PixelLayout? It seems that I can only add rows, cells, or stacking with the other layout types? I tried wrapping in a panel and adding the drawable graphics on a panel but that wasn’t working for me.

  2. While the Custom Toggle Switch graphics show up in general, the Green/Red circles do not. My guess is they are there “somewhere” but perhaps way off screen? To me, self.Bounds.TopLeft seems like it could work to position these circles properly (at least on the general UI overall) so I would love an insight if you see why this is not the case.

  3. When I switched to PixelLayout, it seems to have scaled the “background graphics” to about half size of what they were intended to be. I tried to scale it by 2 to test if it was just a general size issue but then it gets cropped oddly. You can see in the snip below it should be filling the whole “translucent panel” less the padding width as it properly does on the left and top but the right side extents are off and not filling to the edge. I can’t seem to figure out why but I’ll keep looking. I also noticed in the SampleSwitch code you shared, if you set the Dialog to resizable and scale the window, the circles get stretched yet the pill stays persistently sized. Perhaps this is the same thing happening to the Form or circle drawable I’m trying to create in my modification?

image

image

Here’s the code I’m working off of thus far:

#! python 2

import Eto
import Rhino

# Code For The Custom Toggle Switch
class SwitchControl(Eto.Forms.Drawable):
    def __init__(self, state):
    
        # Custom Control Style Variables
        self.radius = 30
        self.highlight_color = Eto.Drawing.Colors.LightGoldenrodYellow
        self.border_color = Eto.Drawing.Color(180,180,180)
        self.background_color = Eto.Drawing.Colors.Transparent
        self.enabled_color = Eto.Drawing.Color(255,40,5)
        self.disabled_color = Eto.Drawing.Color(152,237,90)
        
        self.Size = Eto.Drawing.Size(60,self.radius)
        
        self._hover = False
        self._state = state
        

        self._rect = Eto.Drawing.RectangleF.Inflate(self.Bounds, -5, -5)

        self.g_brush = Eto.Drawing.LinearGradientBrush(self._rect, self.highlight_color, self.background_color,0)

        self._path = Eto.Drawing.GraphicsPath.GetRoundRect(self._rect, 10)
        self._pen1 = Eto.Drawing.Pen(self.highlight_color, 3)
        self._pen2 = Eto.Drawing.Pen(self.border_color, 2)

    def OnPaint(self, e):
        try:
            
            # draws the switch background pill shape
            e.Graphics.FillPath(Eto.Drawing.Colors.Transparent, self._path)

            e.Graphics.DrawPath(self._pen2, self._path)
            
            if self._state == True:
                # draws the green circle
                r = Eto.Drawing.RectangleF(self.Bounds.TopLeft, self.Bounds.MiddleBottom)
                r.Inflate(-10,-10)
                e.Graphics.FillEllipse(self.enabled_color, r)
                
            elif self._state == False:
                # draws the red circle
                r = Eto.Drawing.Rectangle(self.Bounds.MiddleTop, self.Bounds.BottomRight)
                r.Inflate(-10,-10)
                e.Graphics.FillEllipse(self.disabled_color, r)
            
            if self._hover:
                # draws the pill shape outline on hover
                e.Graphics.DrawPath(self._pen1, self._path)
                # draws the switch background pill shape with gradient fill
                e.Graphics.FillPath(self.g_brush, self._path)
            
        except Exception as ex:
            print ex

    def OnMouseEnter(self, e):
        self._hover = True
        self.Invalidate()

    def OnMouseLeave(self, e):
        self._hover = False
        self.Invalidate()

    def OnMouseDown(self, e):
        # Rhino.RhinoApp.WriteLine("Clicked")
        pass

    def OnMouseUp(self, e):
        if e.Buttons == Eto.Forms.MouseButtons.Primary and self._hover == True:
            self._state = not self._state
            self.Invalidate()

    def OnPreLoad(self, e):
        pass

# Code For The Main Toolbar UI
class MainToolbar(Eto.Forms.Form):
    def __init__(self):
        # Set Form General Settings
        self.Title = "Main Toolbar"
        self.Size = Eto.Drawing.Size(500, 120)  # Set The Overall Form Size
        self.WindowStyle = Eto.Forms.WindowStyle.None
        self.Resizable = False
        padding = 30 # Set Form Padding
        self.Padding = Eto.Drawing.Padding(padding)
        self.MovableByWindowBackground = True

        # Custom Control Style Variables
        self.radius_1 = 30

        # Add Transparent Style To The Form Background Panel
        self.Styles.Add[Eto.Forms.Panel]("transparent", self.MyFormStyler)
        self.Style = "transparent"

        # Custom Controls
        self.switch = SwitchControl(True) # Pass True Argument For Switch To Start In Activated State

        # Set UI Location 
        def SetLocation(): 
            # Get Rhino Window Dimensions (Pixels)
            s = Rhino.UI.RhinoEtoApp.MainWindow.Screen     
            w = s.WorkingArea.Width 
            h = s.WorkingArea.Height

            # self.Location = vp_bottom_center
            self.Location = Eto.Drawing.Point((w / 2 - (self.Width / 2)), (h - self.Height + padding)) # Locate Form At Bottom Center Of Screen (WIP)
        SetLocation()

        # Set Custom Dark Mode Colors
        def SetDarkModeCustomColors():
            self.dark_mode = Rhino.Runtime.HostUtils.RunningInDarkMode # Get Current Rhino Theme For Custom Dark Mode UI Functions

            if self.dark_mode:
                self.background_color = Eto.Drawing.Color(220,220,220)
                self.midground_color = Eto.Drawing.Color(200,200,200)
                self.foreground_color = Eto.Drawing.Color(180,180,180)

            else:
                self.background_color = Eto.Drawing.Color(40,40,40)
                self.midground_color = Eto.Drawing.Color(10,10,10)
                self.foreground_color = Eto.Drawing.Color(0,0,0)
        SetDarkModeCustomColors()
        
        # Setup bitmap to have background graphics drawn on
        pixelformat = Eto.Drawing.PixelFormat.Format32bppRgba
        bitmap = Eto.Drawing.Bitmap(self.Size, pixelformat)
        self.graphics = Eto.Drawing.Graphics(bitmap)

        self.DrawBackgroundGraphics() # Call The Function That Creates The Graphics

        self.bg_graphics = Eto.Forms.ImageView()
        self.bg_graphics.Image = bitmap

        # Update / Populate Layout
        def UpdateLayout():
            # Add Items To Layout
            self.layout = Eto.Forms.PixelLayout()
            self.layout.Add(self.bg_graphics, 0,0) # Add Background Graphics
            self.layout.Add(self.switch, 180,0)
            
            self.Content = self.layout
        UpdateLayout()

    # Handle Overall Background Transparency
    def MyFormStyler(self, control):
        self.BackgroundColor = Eto.Drawing.Colors.Transparent
        window = control.ControlObject
        if hasattr(window, "AllowsTransparency"):
            window.AllowsTransparency = True
        if hasattr(window, "Background"):
            brush = window.Background.Clone()
            brush.Opacity = 0.1
            window.Background = brush
        else:
            color = window.BackgroundColor
            window.BackgroundColor = color.FromRgba(0,0,0,0)

    # Create The Background Graphics
    def DrawBackgroundGraphics(self):


        # Create Background Graphics

        brush_1 = Eto.Drawing.SolidBrush(self.background_color)
        rect_1 = Eto.Drawing.Rectangle(0,0, self.Width, self.Height)
        path_1 = Eto.Drawing.GraphicsPath.GetRoundRect(rect_1, self.radius_1,self.radius_1,5,5)
        self.graphics.FillPath(brush_1, path_1)

        button_padding = 10
        button_height = 40
        radius_inner = self.radius_1 - button_padding

        brush_2 = Eto.Drawing.SolidBrush(self.midground_color)
        rect_2 = Eto.Drawing.Rectangle(button_padding,button_padding, self.Width - (button_padding * 2), button_height)
        path_2 = Eto.Drawing.GraphicsPath.GetRoundRect(rect_2, radius_inner,radius_inner,radius_inner,radius_inner)
        self.graphics.FillPath(brush_2, path_2)

        self.graphics.Dispose()

    def OnDarkModeButtonClick(self, sender, e):
        self.dark_mode = sender.Checked
        if sender.Checked:
            # Rhino.RhinoApp.WriteLine("Dark Mode On")
            Rhino.ApplicationSettings.AppearanceSettings.SetToDarkMode()
        else:
            # Rhino.RhinoApp.WriteLine("Dark Mode Off")
            Rhino.ApplicationSettings.AppearanceSettings.SetToLightMode()


def EstablishForm():
    form = MainToolbar()
    form.Owner = Rhino.UI.RhinoEtoApp.MainWindow
    form.Show()

if __name__=="__main__":
    EstablishForm()


Thank you for any tips!

Hi @michaelvollrath,

It is the only way i know of to layer things.

i first thought you’re not seeing the circle(s) because you fill the pill shape on hover after drawing the circle but it seems the coords where not right. I’ve changed the code a bit so the circle position is using hard coded pixel numbers instead.

It could be that it was the opposite. In a normal layout eg. Dynamic or Table it will stretch the drawable (as in my example) but in a PixelLayout it should not stretch. I recommend to assign a background color to any layouts when designing the dialog so you can actually see what is padding, spacing etc.

btw. It seems that when MovableByWindowBackground is enabled it’s not possible to capture the MouseUp event. So i disabled that.

MV_SwitchTest.py (7.6 KB)

_
c.

Good to know, thank you! I’ll proceed this way for now. I saw in the documentation it sort of was “use at your own risk” but I believe this is the kind of control I want for the graphics I’m trying to create.

That makes sense, thank you.

In further testing, I believe you are right about that and I have a value set incorrectly that is not the full size I need.

Good to know, I intend for this UI to be “fixed” in place anyways, and had left that on to pull it around and check if any “loose bits” were off screen or when I docked it but I’ll leave it off.

I really appreciate your help! Your code update works flawlessly and I’m able to massage the graphics to my liking now and back into the fun part of “styling” the whole thing.

Thank you so much for your continued help!