Custom UI Elements with Eto.Forms

@curtisw

After digging a bit deeper, we have made a GUI control using Eto’s library.

Below is an example showcasing how a floating Eto-GUI form could potentially improve the modelling experience.

Right now, we are doing some WPF hacks to ensure that the Eto.Forms.Form nests within the parent viewport, so it follows the movement and resizing of the viewport. It would be great if the native Eto can provide more support on this front.

            Eto.Style.Add<Eto.Forms.Panel>(
            "transparent-form", control =>
            {
                var wpfwnd = (System.Windows.Window)control.ControlObject;
                wpfwnd.AllowsTransparency = true;

                wpfwnd.Background = swm.Brushes.Transparent;
                wpfwnd.Topmost = true;
                wpfwnd.ShowActivated = false;
            });

As soon as I set wpfwnd.AllowsTransparency = true; , I could no longer update the form’s position easily. The control needs to be destroyed and recreated whenever I need to update the GUI form’s position.

14 Likes

This is exactly what I was hoping was possible in Rhino. Thank you for sharing this and for your work. Looking forward to digging in more!

Looks amazing. I would buy a course from you about UI and Display Pipeline, and maybe not only me. I never seen something like that in Rhino.

5 Likes

Looks very nice. I am used to WPF, so I have chosen to stick with xeto anyway. Some eto behaviours are different on mac than on Windows, so be aware of that. And if you do not target mac, sticking to WPF only would make your life easier.

Below is made with xeto:

Interoperability between Mac & Windows was not the main motivation for us to create this UI from scratch. It was about creating the best user experience possible and the native controls just didn’t seem to offer the right functionality.

Once we went down this rabbit hole, it became a bit of a self-propelling challenge - how far can we push it beyond what is traditionally possible in Rhino. The irony is, that we still haven’t shipped the plugin, but we’ve learned a ton in the process :slight_smile:

5 Likes

Hi Mariusz, thanks for sharing your thread, it is amazing.

I tried to learn how to create customized screen widget from it. For now I‘ve successfully drew some curves in display pipeline and been able to select them. But one thing I’m curious is how could you draw custom 2d shapes by draw method?

I only found draw2dLine/ Draw2dRectanglar/ Draw2dText, but there are no 2d circle、2d arc and so on . Did you use xform to force geometries to be parallel to the viewport?or there`s other magic to cast custom shapes and icons to screen?

You can draw most (all?) geometry types in the DisplayPipeline, just get a plane perpendicular to your camera view and draw on it:
https://developer.rhino3d.com/api/rhinocommon/rhino.display.displaypipeline/drawcircle

1 Like

Mariusz, Thank you for replying.

I think I’ve reached some point, I drew some clickable shapes and force them to face camera, but somehow those shapes rotate by changing the angle of view.

My guess is the vector from cameradirection kept changing during changing view angle, is it right?

Sorry for being a bit greedy and thank you again for all you posts, there are so inspiring.

Try something like this:

            Doc.Views.ActiveView.ActiveViewport.GetFrustumLine(cursor.X, cursor.Y, out var ln);
            var centerPoint = ln.PointAt(0.5);
            e.Display.Viewport.GetCameraFrame(out var cameraPlane);
            cameraPlane.Origin = centerPoint;

            // use the scale factor to keep the size of your geometry consistent while zooming in & out
            e.Viewport.GetWorldToScreenScale(centerPoint, out var scale); 

3 Likes

getcameraframe() !That’ s it! thank you very much, I’ll make some test.

I second that :wink:

4 Likes

@sonderskovmathias, @andrew.go what would you like to learn?

1 Like

Thirded

I would say a few things come to mind

Eto

  • MVVM (or whatever you used in Eto, how do you separate out all the logic?)
  • Centralised Styling, how do you defined styles and reuse them?
  • How do you create a custom Eto component, and use it elsewhere?
  • Unit Testing?
  • How do you write/debug it? Is it using XEto, Vanilla Eto, do you use the Preview tool?

Pipeline

  • How do you handle user clicking, dragging etc?
  • How do you draw your items and get the gradients?
  • How do you tweak and debug this effectively?

Other

  • How did you design the UI before coding?

– cs

6 Likes

Thanks for the tip.


But alas, it doesn’t work for macOS. It only works on Windows. I’m looking for cross-platform method.

2 Likes

@taraskydon glad I can help!

This is a question for @curtisw
Eto doesn’t support GUI control elements at all and imho it limits its growth.

(sample) maybe help someone how to apply the transformation.

public static class StyleSetter
    {
        public static void SetStyles()
        {
            Eto.Style.Add<Eto.Forms.Panel>("Transpar2", control =>
            {
                var wpfwnd = (System.Windows.Window)control.ControlObject;
                wpfwnd.AllowsTransparency = true;
                wpfwnd.Background = System.Windows.Media.Brushes.Transparent;
                wpfwnd.Opacity = 0.5;
                wpfwnd.Topmost = true;
               // wpfwnd.ShowActivated = true;
            });

        }
    }

    public class PopupMenu : Form
    {
        private Timer timer;
        private int sectorCount = 12;
        private int hoveredSector = -1; // -1 indicates no hover
        private int clickedSector = -1; // -1 indicates no click
        private PointF[] middlePoints;
        private float hoverRadius = 18; // Adjust this radius to increase the area for mouse hover

        private float scale = 0.1f;
        private const float scaleIncrement = 0.1f; // Adjust this for the speed of the animation
        private float rot = 0;
        private const float rotcof = 18;
        int size = 194;

        Drawable drawable;

        public PopupMenu()
        {
            StyleSetter.SetStyles();

            WindowStyle = Eto.Forms.WindowStyle.None;
            Style = "Transpar2";
            //Opacity = 0.5;
            ClientSize = new Size(size, size);
            Padding = 0;

            timer = new Timer
            {
                Interval = 100
            };
            timer.Start();
            timer.Elapsed += Send;

            drawable = new Drawable
            {
                Size = new Size(size, size),
              
               
            };
            BackgroundColor = Colors.Transparent;
            drawable.Paint += CircleMenu;
            drawable.MouseMove += Drawable_MouseMove;
            drawable.MouseDown += Drawable_MouseDown;
            drawable.MouseUp += Drawable_MouseUp;

            StackLayout st1 = new StackLayout()
            {
                Orientation = Orientation.Horizontal,
                HorizontalContentAlignment = HorizontalAlignment.Center,
                VerticalContentAlignment = VerticalAlignment.Bottom,
            };
            st1.Items.Add(drawable);

            Content = st1;

            //  var center = Screen.PrimaryScreen.WorkingArea.Center;
            //  center.X -= 194 / 2;
            //  center.Y -= 194 / 2;
            // Location = new Point(center);
        }

        private void Send(object sender, ElapsedEventArgs e)
        {
            Application.Instance.Invoke(() =>
            {
                scale += scaleIncrement;
                rot -= rotcof;

                // Ensure the rotation does not exceed 180 degrees
                if (rot <= -180)
                {
                    rot = 0;
                    timer.Stop();
                    timer.Dispose();
                }

                // Limit the scale to not exceed 1
                if (scale > 1)
                {
                    scale = 1;
                }

                Invalidate();
            });
        }

        private void CircleMenu(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
           
            // Calculate the center of the scaled outer circle bounds
      
            float centerX = (float)size / 2f;
            float centerY = (float)size / 2f;

            // Transform center
            g.TranslateTransform(centerX, centerY);
            g.ScaleTransform(scale);
            g.RotateTransform(rot);
            g.TranslateTransform(-centerX,- centerY);

            RectangleF outerCircleBounds = new RectangleF(10, 10, 180, 180);
            RectangleF innerCircleBounds = new RectangleF(outerCircleBounds.Left + outerCircleBounds.Width / 3, outerCircleBounds.Top + outerCircleBounds.Height / 3, outerCircleBounds.Width / 3, outerCircleBounds.Height / 3);

            // g.FillEllipse(Brushes.LightGrey, outerCircleBounds);
        }
6 Likes

I’m not sure what you are asking? Eto is all about GUI and has many controls. You can create custom controls with a Drawable. Do you have more specifics of what you mean?

@curtisw
I think I mixed up the terms. I meant.
Heads-Up Display element (HUD controls)

@curtisw it would be great if Eto supported cross-platform transparency and effects such as drop shadows of Forms. Currently, these require platform-specific code from developers. Our lives would be much easier if we could simply set the value and Eto would take care of the rest.