How to use Eto.Forms in Rhino/Grasshopper (part 1)
Eto.Forms is the cross-platform(Mac or PC) library that comes with Rhino that you can access through API. This library provides the basic building blocks of a graphic user interface(GUI). If you ever need to customize a window/dialog to interact with the Rhino user, this is the library you are looking for. For example, when you type "rebuild" in rhino and call up the command, a window pops up instead the command line sequence of inputs. This way you see all the parameters in one place and have the opportunity to adjust every one of them before committing the change. Eto.Forms would allow you to make your own windows just like the rebuild window.
To clarify, Eto.Forms belongs to a larger library called Eto and it also includes Eto.Drawing, which is used in tandem with Eto.Forms often. Also these features are accessed through API so no GUI to make the GUI. Sorry for those who have used visual designer in Microsoft Visual Studio (but hey this is really gonna be a breeze for you if you are a master of Windows.Forms). If you are not familiar with programming, Eto.Forms would be quite a stretch to use. However, the HumanUI plugin for Grasshopper is a nice place to start for non-coders, to learn some of the fundamentals of building up a GUI.
In the larger picture, Eto library is actually not too difficult to pick up. Sure it takes some time to understand but there are certain patterns you can follow to quickly have a decent window running. I'm here to hopefully offer some pointers.
First of all look at the resources. By now as a programmer you should know that looking through documentation is actually going to take most of your time coding. The actually typing of codes is fast. The API doc is here. McNeel's website also has helpful contents for you to get started. I'm linking the Rhino Python page of McNeel because I code in Python and I still think it's concise and easy to write. But Eto library itself is a .NET resource, I believer, and therefore can be used with C#. This blog post assumes we are coding in Python, GhPython particularly.
Why GhPython? Two main reasons. One is that GhPython offers this simple compiling mechanism that can quickly turn the GhPython component into a "plugin". This will not only protect your source code, but also slightly increase computation speed. Two is that the Grasshopper environment has a lot of existing interactions with the Rhino document. For example, the use of API to change how objects are previewed in the viewport is a bit of a pain but Grasshopper has the nifty preview component that takes care of that. We can use many ready made GH components as the back end of the customized window.
Second of all know what you are building. Presumably you are reading through this because you have a project that necessitates a user interface that is sophisticated enough to handle a variety of parameters. The simple sequence style inputs of the rhino command line isn't sufficient anymore. Great! Now make a wireframe. A visual mock-up of the interface will tremendously speed up the actually coding and help organize your GUI structure. Again I recommend HumanUI, as I myself have used it to quickly get a working prototype so I know what I'm shooting for. After the wireframe, we can get our hands dirty.
This McNeel page is quite useful in telling us what type of controls (a button, an input text box, or a slider etc.) there are in Eto.Forms. These are very common. Because Eto.Forms is cross-platform, there is some limit to what kind of controls it can have. Some control objects may be unique to Mac or PC. But I think most critical interactions haven been captured with existing Eto controls. There isn't a toggle but that is probably easily replaced with a check box.
This image (sometimes AdBlock blocks the images...) is taken straight from McNeel's website explaining Eto. For those not familiar with coding a GUI, this still isn't going to make too much sense on its own. The point of the diagram is to show how you "attach" objects to "containers" for any interface windows. The dialog form is really a base box. The layout grid is a smaller box. The controls are buttons, check boxes and other trinkets you can interact with. You place the trinkets in small boxes, which are then arranged and placed in the large base box. To quickly show you, here is an example below. If you look at McNeel's sample codes you'll see they used a modal dialog, meaning the dialog would freeze whatever behind it, similar to a warning popup. I'm using a what people call a modeless window - Eto.Forms.Form() - to allow user to click around in the Grasshopper canvas and Rhino model-scape. More on this subject later.
Here look at line 13,14,27,28. If you comment out line 16 to 25 and click the launch button, you will see this:
Yep. An empty "base box" sized 300 by 300 pixels. Line 13 initiates an Eto.Forms.Form object. Line 14 says it should be sized 300x300. If x is True on line 27, line 28 runs, which shows the Eto.Forms.Form on your screen. Now going back to line 16, we see that an Eto.Forms.StackLayout object is initiated. This is a "smaller box" that can hold controls like buttons. Line 17-19 initiates 3 button objects. Line 21 to 23 add the buttons to the small box, which is the StackLayout() we created earlier. Line 25 finally puts the small box into the big box. Now if you launch the window it actually shows the interactive controls.
This is fundamentally the building process of a GUI with Eto.Forms and it's a process repeated over and over. In comparison to back end algorithms, front end scripting often is tedious. 20 lines of code here only gets the ugliest window to barely show any "dead" interactive elements whereas 20 line of algorithmic codes may be able to compute some complex geometries for you.
In McNeel's example the "small box" is a DynamicLayout(). That would likely save you a few lines of styling codes by expanding trinkets to occupy the entire space of their container box, but then you lose a level of deliberateness. Line 14 is an example of styling code. It tells the computer how elements are rendered on-screen. Here the Eto.Drawing library comes to life. Very often styling parameters are defined by objects in the Eto.Drawing library. If I were to type
window.Size = [300,300]
the script will break. Although an intuitive way to dictate window dimensions, using a pair of numbers won't be accepted as window.Size attribute. However if you use this code
column_of_buttons.Spacing = 5
the buttons will be spaced at 5 pixels. StackLayout object's Spacing attribute can take an integer. See in this snippet that the buttons have a little breathing room in-between?
Actually the StackLayout is directly equivalent to the "Stack" component of HumanUI, which brings me back to an earlier statement. Learning to use HumanUI is substantially helpful in breaking into Eto.Forms or any GUI for that matter. GUIs underlying structures are quite similar to each other, although the freer you are to stylize the interface the more sophisticated the API doc will be. To see in perspective, I assure you that Eto.Forms is pretty simplified as a beginner GUI library. There aren't over-crowding docs on how to customize the tickers on a NumericStepper() control. So make sure you head over to the docs of Eto and you will see the attributes or methods for styling objects such as StackLayout() or a Button(). Be sure to go up to parent level if an object has inheritance.
Make sure you click on the Panel, or even Container or Control etc. to see what other methods and attributes applicable to the DynamicLayout class of objects. Here is a challenge. See if you can get the buttons in my example to be in the middle of the window. Hint: when a Button() is added to the Items attribute of a StackLayout() like in line 21, the Button() object is cast under-the-hood into a StackLayoutItem() object, which then has a HorizontalAlignment attribute. You may to need explicitly cast first and assign HorizontalAlignment before adding.
I'll write up a part 2 to this post to cover the benefits of using modeless Eto.Forms.Form as well as how to rig up the controls to actually do anything. Before that, take some time to turn a wireframe into real GUI.