Toggle with one click

Thank you very much Ben, i appreciate your work and time
I will check it later, and this will be very useful

1 Like

I checked it and it work excellent but it can’t be moved, that’s why i used two columns and one row and the first square is hidden, i will try to use your solution with the previous code


Maybe a long press to move the component, i don’t know if that possible

1 Like

right, I didn’t realize that. At the moment you can group it and move like this.
In the meanwhile I’ll try to change that.

1 Like

Thank you i will check it , to learn more and to make changes if i can.

1 Like
> protected override void Layout()
>         {
>             //Lock this object to the pixel grid. 
>             //I.e., do not allow it to be position in between pixels.
>             Pivot = GH_Convert.ToPoint(Pivot);
>             Bounds = new RectangleF(Pivot, new SizeF(ButtonSize*2, ButtonSize));
>         }
>         
>         private Rectangle Button()
>         {
>             int x = Convert.ToInt32(Pivot.X);
>             int y = Convert.ToInt32(Pivot.Y);
>             return new Rectangle(x+ButtonSize, y, ButtonSize, ButtonSize);
>         }

if you change like this, there will be a (sadly) transparent zone left of the button where you can grab the button and move it. I’d love to investigate further, but this will take some time and learning efforts

B

1 Like

Guys, this discussion is really interesting.
I didn’t have time to test your code, but i bookmarked it and i will study it all once i have time.

Raw idea: flip the switch on the release of the button/click.
Click-press > save current click XY canvas coordinate, but let the component behave normally (it should be draggable/moveable)
Click-release > check, if current XY are equal than old XY, flip the switch.

1 Like

Visual studio is a nightmares always freeze and force closed, i will check the code and try later.

Thanks Riccardo, i don’t have experience to do that with c#, but i wil, try

A little change in the code but still the problem of moving, i try to use MouseButtons.Right but the problem that the context menu appears

using System;
using System.Collections.Generic;
using Grasshopper.Kernel;
using System.Drawing;
using Grasshopper.GUI;
using Grasshopper.GUI.Canvas;
using Grasshopper.Kernel.Types;

namespace singleClick  //adapt to your project
{

    public class SpecialIntegerObject : GH_Param<GH_Boolean>
    {
        public SpecialIntegerObject() :
          base(new GH_InstanceDescription("Toggle", "Toggle", "True/False", "Params", "Input"))
        { }

        public override void CreateAttributes()
        {
            m_attributes = new SpecialIntegerAttributes(this);
        }

        protected override Bitmap Icon
        {
            get
            {
                //return toggleone.Properties.Resources.toggle;
                return null;
            }
        }
        public override GH_Exposure Exposure
        {
            get
            {
                return GH_Exposure.primary | GH_Exposure.obscure;
            }
        }
        public override System.Guid ComponentGuid
        {
            get { return new Guid("{520F0976-35D1-4CC4-925B-2A07BFF17007}"); }
        }

        //private int m_value;
        private bool m_value;
        public bool Value
        {
            get { return m_value; }
            set { m_value = value; }
        }
        protected override void CollectVolatileData_Custom()
        {
            VolatileData.Clear();
            AddVolatileData(new Grasshopper.Kernel.Data.GH_Path(0), 0, new GH_Boolean(Value));
        }

        public override bool Write(GH_IO.Serialization.GH_IWriter writer)
        {
            writer.SetBoolean("SpecialInteger", m_value);
            return base.Write(writer);
        }
        public override bool Read(GH_IO.Serialization.GH_IReader reader)
        {
            reader.TryGetBoolean("SpecialInteger", ref m_value);
            return base.Read(reader);
        }
    }

    public class SpecialIntegerAttributes : GH_Attributes<SpecialIntegerObject>
    {

        public SpecialIntegerAttributes(SpecialIntegerObject owner)
          : base(owner)
        {

        }

        public override bool HasInputGrip { get { return false; } }
        public override bool HasOutputGrip { get { return true; } }

        private const int ButtonSize = 32;

        public bool bo;

        //Our object is always the same size, but it needs to be anchored to the pivot.
        protected override void Layout()
        {
            //Lock this object to the pixel grid. 
            //I.e., do not allow it to be position in between pixels.
            Pivot = GH_Convert.ToPoint(Pivot);
            Bounds = new RectangleF(Pivot, new SizeF(ButtonSize*2, ButtonSize));
        }
        /// <summary>
        /// This method returns the button at the given column and row offsets.
        /// </summary>
        private Rectangle Button()
        {
            int x = Convert.ToInt32(Pivot.X);
            int y = Convert.ToInt32(Pivot.Y);
            return new Rectangle(x, y, ButtonSize*2, ButtonSize);
        }
        /// <summary>
        /// Gets the value for the given button.
        /// </summary>
   

        public override GH_ObjectResponse RespondToMouseDown(GH_Canvas sender, GH_CanvasMouseEvent e)
        {
            //On one click we'll set the owner value.
            if (e.Button == System.Windows.Forms.MouseButtons.Left)
            {
                bo = !bo;
                RectangleF button = Button();
                if (button.Contains(e.CanvasLocation))
                {
                    Owner.RecordUndoEvent("Square Change");
                    Owner.Value = bo;
                    Owner.ExpireSolution(true);
                    return GH_ObjectResponse.Handled;
                }

            }
            return base.RespondToMouseDown(sender, e);

        }
        public override void SetupTooltip(PointF point, GH_TooltipDisplayEventArgs e)
        {
            base.SetupTooltip(point, e);
            e.Description = "Toggle";
        }

        /// <summary>
        /// This object is rendered as a 1x3 grid of capsules.
        /// </summary>

        protected override void Render(GH_Canvas canvas, Graphics graphics, GH_CanvasChannel channel)
        {
            if (channel == GH_CanvasChannel.Objects)
            {
                //Render output grip.
                GH_CapsuleRenderEngine.RenderOutputGrip(graphics, canvas.Viewport.Zoom, OutputGrip, true);

                //Render capsules.

                Rectangle button = Button();
                GH_Palette palette ;
                if (bo)
                {
                    palette = GH_Palette.Blue;
                }
                else
                {
                    palette = GH_Palette.White;
                }

                GH_Capsule capsule = GH_Capsule.CreateTextCapsule(button, button, palette, bo.ToString(), 4, 4);
                capsule.Render(graphics, Selected, Owner.Locked, false);
                capsule.Dispose();
            }
        }
    }
}
1 Like

nice, I took your code and added the things I’ve learned:
oneClick

here’s the code:

using System;
using System.Collections.Generic;
using Grasshopper.Kernel;
using System.Drawing;
using Grasshopper.GUI;
using Grasshopper.GUI.Canvas;
using Grasshopper.Kernel.Types;

namespace singleClick  //adapt to your project
{

    public class SpecialIntegerObject : GH_Param<GH_Boolean>
    {
        public SpecialIntegerObject() :
          base(new GH_InstanceDescription("Toggle", "Toggle", "True/False", "Params", "Input"))
        { }

        public override void CreateAttributes()
        {
            m_attributes = new SpecialIntegerAttributes(this);
        }

        protected override Bitmap Icon
        {
            get
            {
                //return toggleone.Properties.Resources.toggle;
                return null;
            }
        }
        public override GH_Exposure Exposure
        {
            get
            {
                return GH_Exposure.primary | GH_Exposure.obscure;
            }
        }
        public override System.Guid ComponentGuid
        {
            get { return new Guid("{520F0976-35D1-4CC4-925B-2A07BFF17007}"); }
        }

        //private int m_value;
        private bool m_value;
        public bool Value
        {
            get { return m_value; }
            set { m_value = value; }
        }
        protected override void CollectVolatileData_Custom()
        {
            VolatileData.Clear();
            AddVolatileData(new Grasshopper.Kernel.Data.GH_Path(0), 0, new GH_Boolean(Value));
        }

        public override bool Write(GH_IO.Serialization.GH_IWriter writer)
        {
            writer.SetBoolean("SpecialInteger", m_value);
            return base.Write(writer);
        }
        public override bool Read(GH_IO.Serialization.GH_IReader reader)
        {
            reader.TryGetBoolean("SpecialInteger", ref m_value);
            return base.Read(reader);
        }
    }

    public class SpecialIntegerAttributes : GH_Attributes<SpecialIntegerObject>
    {

        public SpecialIntegerAttributes(SpecialIntegerObject owner)
          : base(owner)
        {

        }

        public override bool HasInputGrip { get { return false; } }
        public override bool HasOutputGrip { get { return true; } }

        private const int ButtonSize = 32;

        public bool bo;

        //Our object is always the same size, but it needs to be anchored to the pivot.
        protected override void Layout()
        {
            //Lock this object to the pixel grid. 
            //I.e., do not allow it to be position in between pixels.
            Pivot = GH_Convert.ToPoint(Pivot);
            Bounds = new RectangleF(Pivot, new SizeF(ButtonSize * 4, ButtonSize));
        }
        /// <summary>
        /// This method returns the button at the given column and row offsets.
        /// </summary>
        private Rectangle Button()
        {
            int x = Convert.ToInt32(Pivot.X);
            int y = Convert.ToInt32(Pivot.Y);
            return new Rectangle(x+3*ButtonSize, y+2, ButtonSize-2 , ButtonSize-4);
        }
        /// <summary>
        /// Gets the value for the given button.
        /// </summary>


        public override GH_ObjectResponse RespondToMouseDown(GH_Canvas sender, GH_CanvasMouseEvent e)
        {
            //On one click we'll set the owner value.
            if (e.Button == System.Windows.Forms.MouseButtons.Left)
            {
                
                RectangleF button = Button();
                if (button.Contains(e.CanvasLocation))
                {
                    bo = !bo;
                    Owner.RecordUndoEvent("Square Change");
                    Owner.Value = bo;
                    Owner.ExpireSolution(true);
                    return GH_ObjectResponse.Handled;
                }

            }
            return base.RespondToMouseDown(sender, e);

        }
        public override void SetupTooltip(PointF point, GH_TooltipDisplayEventArgs e)
        {
            base.SetupTooltip(point, e);
            e.Description = "Toggle";
        }

        /// <summary>
        /// This object is rendered as a 1x3 grid of capsules.
        /// </summary>

        protected override void Render(GH_Canvas canvas, Graphics graphics, GH_CanvasChannel channel)
        {
            if (channel == GH_CanvasChannel.Objects)
            {
                //Render output grip.
                GH_CapsuleRenderEngine.RenderOutputGrip(graphics, canvas.Viewport.Zoom, OutputGrip, true);

                //Render capsules.
                
                GH_PaletteStyle style = GH_Skin.palette_normal_standard;
                GH_Skin.palette_normal_standard = style;
                base.Render(canvas, graphics, channel);
                

                Rectangle button = Button();

                /*if (bo)
                {
                    palette = GH_Palette.Black;
                }
                else
                {
                    palette = GH_Palette.Black;
                }
                */
                GH_Palette palette = GH_Palette.Black;
                GH_Capsule capsule = GH_Capsule.CreateTextCapsule(button, button, palette, bo.ToString(), 4, 4);
                capsule.Render(graphics, Selected, Owner.Locked, false);
                
                capsule.Dispose();
            }
        }
    }
}

though, I have absolutely no idea why the heck this thing is blue…haha

1 Like

Thank you , very nice.
I did similar thing based on an old code of David from the old forum.
I tried to move the circle to the left side but i don’t understand exactly the logic and i hope someone can make the circular button like radio button with two options white and green for example, and the component moved from the other area.
I hope also that Mcneel developers make their own software to create components with button designer like windows form. and the developer can test it directly in Grasshopper.
And this is the code :slight_smile:
Two cjhoices better than one :smiley:


Also i need to fix the problem with the circular button, it change the text but not the output.

using System;
using System.Collections.Generic;
using Grasshopper.Kernel;
using System.Drawing;
using Grasshopper.GUI;
using Grasshopper.GUI.Canvas;
using Grasshopper.Kernel.Types;

namespace singleClick  //adapt to your project
{

    public class SpecialIntegerObject : GH_Param<GH_Boolean>
    {
        public SpecialIntegerObject() :
          base(new GH_InstanceDescription("Toggle", "Toggle", "True/False", "Params", "Input"))
        { }

        public override void CreateAttributes()
        {
            m_attributes = new SpecialIntegerAttributes(this);
        }

        protected override Bitmap Icon
        {
            get
            {
                //return toggleone.Properties.Resources.toggle;
                return null;
            }
        }
        public override GH_Exposure Exposure
        {
            get
            {
                return GH_Exposure.primary | GH_Exposure.obscure;
            }
        }
        public override System.Guid ComponentGuid
        {
            get { return new Guid("{520F0976-35D1-4CC4-925B-2A07BFF17007}"); }
        }

        //private int m_value;
        private bool m_value;
        
        public bool Value
        {
            get { return m_value; }
            set { m_value = value; }
        }
        protected override void CollectVolatileData_Custom()
        {
            VolatileData.Clear();
            AddVolatileData(new Grasshopper.Kernel.Data.GH_Path(0), 0, new GH_Boolean(Value));
        }

        public override bool Write(GH_IO.Serialization.GH_IWriter writer)
        {
            writer.SetBoolean("SpecialInteger", m_value);
            return base.Write(writer);
        }
        public override bool Read(GH_IO.Serialization.GH_IReader reader)
        {
            reader.TryGetBoolean("SpecialInteger", ref m_value);
            return base.Read(reader);
        }
    }

    public class SpecialIntegerAttributes : GH_Attributes<SpecialIntegerObject>
    {

        public SpecialIntegerAttributes(SpecialIntegerObject owner)
          : base(owner)
        {

        }

        public override bool HasInputGrip { get { return false; } }
        public override bool HasOutputGrip { get { return true; } }

        private const int ButtonSize = 32;

        public Image on_image;
        public Image off_image;

        public bool bo;

        //Our object is always the same size, but it needs to be anchored to the pivot.
        protected override void Layout()
        {
            //Lock this object to the pixel grid. 
            //I.e., do not allow it to be position in between pixels.
            Pivot = GH_Convert.ToPoint(Pivot);
            Bounds = new RectangleF(Pivot, new SizeF(ButtonSize*2, ButtonSize));

            //new lines -----------------------------------------------------------------
            base.Layout();

            Rectangle rec0 = GH_Convert.ToRectangle(Bounds);
            rec0.Width += 32;
 
            Rectangle rec1 = rec0;
            rec1.X = rec0.Left + 64;

            rec1.Width = 32;
            rec1.Inflate(-2, -2);
            
            Bounds = rec0;
            ButtonBounds = rec1;
            //----------------------------------------------------------------------------

        }
        /// <summary>
        /// This method returns the button at the given column and row offsets.
        /// </summary>
        private Rectangle Button()
        {
            int x = Convert.ToInt32(Pivot.X);
            int y = Convert.ToInt32(Pivot.Y);
            return new Rectangle(x, y, ButtonSize*2, ButtonSize);
        }
        /// <summary>
        /// Gets the value for the given button.
        /// </summary>
   

        
        public override void SetupTooltip(PointF point, GH_TooltipDisplayEventArgs e)
        {
            base.SetupTooltip(point, e);
            e.Description = "Toggle";
        }

        /// <summary>
        /// This object is rendered as a 1x3 grid of capsules.
        /// </summary>



        private Rectangle ButtonBounds { get; set; }
        protected override void Render(GH_Canvas canvas, Graphics graphics, GH_CanvasChannel channel)
        {
            if (channel == GH_CanvasChannel.Objects)
            {
                //Render output grip.
                GH_CapsuleRenderEngine.RenderOutputGrip(graphics, canvas.Viewport.Zoom, OutputGrip, true);

                //Render capsules.

                Rectangle button = Button();
                GH_Palette palette ;
 
                if (bo)
                {
                    palette = GH_Palette.Blue;
                }
                else
                {
                    palette = GH_Palette.White;
                }

                GH_Capsule capsule = GH_Capsule.CreateTextCapsule(button, button, palette, bo.ToString(), 8, 8);
                capsule.Render(graphics, Selected, Owner.Locked, false);

                GH_Capsule buttonb = GH_Capsule.CreateTextCapsule(ButtonBounds, ButtonBounds, GH_Palette.Black, "", 16, 16);
                buttonb.Render(graphics, Selected, Owner.Locked, false);
                buttonb.Dispose();

            }
        }

        public override GH_ObjectResponse RespondToMouseDown(GH_Canvas sender, GH_CanvasMouseEvent e)
        {
            //On one click we'll set the owner value.
            if (e.Button == System.Windows.Forms.MouseButtons.Left)
            {
                bo = !bo;
                RectangleF button = Button();
                if (button.Contains(e.CanvasLocation))
                {
                    Owner.RecordUndoEvent("Square Change");
                    Owner.Value = bo;
                    Owner.ExpireSolution(true);
                    return GH_ObjectResponse.Handled;
                }

            }
            return base.RespondToMouseDown(sender, e);

        }
    }
}

this should fix the black button issue:
code Error

though, I’m not sure it will be beatuiful to change the position of the button, as the connection point will float in space.

1 Like

Yes this fix the problem thank you.
You are about OutputGrip this also should fixed when move the circular button, maybe by change rec0.X

1 Like

It work fine now, is there a way to use custom color (like green) in GH_Palette?

Thank you very much @benedict for your help.

And how to avoid these white edges?

2 Likes

as it is connected to the pivot position, I guess it’s simpler to draw them both and switch between the buttons that are passed to the render override. that would be my guess. this would also allow you to easily switch between colors (like green) for the GH_Palette palette value.

1 Like

I allow myself to tag @DavidRutten for this question. I’m sorry David, I tried like two nights to understand why this renders out blue, but no success. I tried the changes from Custome Node Color? - #6 by DavidRutten but had no success. There must be a ton of useless snippets in the code and I understand a little more every day, but this blue canvas thing is driving me nuts!

is it correct that I need to render the canvas with base.Render and all the elements (in this case a button only) on the canvas with capsules?
thank you

Ben

Thank you Ben, sadly visual basic always freeze and i can’t even modify one line.
I find another example of David and look simple and maybe better.

using System;

using System.Drawing;

using Grasshopper.Kernel;

using Grasshopper.Kernel.Attributes;

using GH_IO.Serialization;

using Grasshopper.GUI.Canvas;

using Grasshopper.GUI;

namespace Testing

{

  public class SuperDuperComponentAtteributes : GH_ComponentAttributes

  {

    public SuperDuperComponentAtteributes(SuperDuperComponent owner)

      : base(owner)

    { }

    private RectangleF _baseBounds;

    private RectangleF _thisBounds;

    private RectangleF _buttonBounds;

    protected override void Layout()

    {

      base.Layout();

      _baseBounds = Bounds;

      _buttonBounds = Bounds;

      _buttonBounds.Y = Bounds.Bottom + 10;

      _buttonBounds.Height = 32;

      _thisBounds = RectangleF.Union(_baseBounds, _buttonBounds);

      // Overwrite the Bounds property to include our external button.

      Bounds = _thisBounds;

    }

    protected override void Render(GH_Canvas canvas, Graphics graphics, GH_CanvasChannel channel)

    {

      // Re-instate the Bounds computed by the base class Layout method.

      // But only during calls to the base class.

      Bounds = _baseBounds;

      base.Render(canvas, graphics, channel);

      Bounds = _thisBounds;

      if (channel == GH_CanvasChannel.Objects)

      {

        string text;

        if ((Owner as SuperDuperComponent).MinMax)

          text = "MinMax";

        else

          text = "MaxMin";

        GH_Capsule button = GH_Capsule.CreateTextCapsule(_buttonBounds, _buttonBounds, GH_Palette.Pink, text);

        button.Render(graphics, Selected, Owner.Locked, Owner.Hidden);

      }

    }

    public override GH_ObjectResponse RespondToMouseDoubleClick(GH_Canvas sender, GH_CanvasMouseEvent e)

    {

      if (_buttonBounds.Contains(e.CanvasLocation))

      {

        // If the double-click happened on our button, we need to handle the event.

        SuperDuperComponent sd = Owner as SuperDuperComponent;

        if (sd == null)

          return GH_ObjectResponse.Ignore;

        sd.RecordUndoEvent("MinMax toggle");

        sd.MinMax = !sd.MinMax;

        sd.ExpireSolution(true);

        return GH_ObjectResponse.Handled;

      }

      // If not, we need to let the base class handle the event.

      // Just to make sure the base class doesn't get confused, we should once again

      // pretend that the Bounds are as expected.

      Bounds = _baseBounds;

      GH_ObjectResponse rc = base.RespondToMouseDoubleClick(sender, e);

      Bounds = _thisBounds;

      return rc;

    }

  }

  public class SuperDuperComponent : GH_Component

  {

    public SuperDuperComponent()

      : base("Super Duper", "SupDup", "Tinned awesomeness, consume before 6/2018", "Test", "Test")

    {

      MinMax = true;

    }

    public override void CreateAttributes()

    {

      m_attributes = new SuperDuperComponentAtteributes(this);

    }

    public static readonly Guid SuperDuperId = new Guid("{0CD86E8B-FA36-47A5-9D71-9C678F96B13A}");

    public override Guid ComponentGuid

    {

      get { return SuperDuperId; }

    }

    public override GH_Exposure Exposure

    {

      get { return GH_Exposure.primary; }

    }

    /// <summary>

    /// Gets or sets whether the logic ought to be MinMax or MaxMin.

    /// </summary>

    public bool MinMax { get; set; }

    protected override void RegisterInputParams(GH_InputParamManager pManager)

    {

      pManager.AddIntegerParameter("Gary", "G", "Who the man?", GH_ParamAccess.item);

      pManager.AddIntegerParameter("Cooper", "C", "Damn right!", GH_ParamAccess.item);

    }

    protected override void RegisterOutputParams(GH_OutputParamManager pManager)

    {

      pManager.AddIntegerParameter("Super", "S", "Million dollar trooper", GH_ParamAccess.item);

      pManager.AddIntegerParameter("Duper", "D", "Puttin' on the Ritz", GH_ParamAccess.item);

    }

    protected override void SolveInstance(IGH_DataAccess DA)

    {

      int a = 0;

      int b = 0;

      if (!DA.GetData(0, ref a)) return;

      if (!DA.GetData(1, ref b)) return;

      if (MinMax)

      {

        DA.SetData(0, Math.Min(a, b));

        DA.SetData(1, Math.Max(a, b));

      }

      else

      {

        DA.SetData(0, Math.Max(a, b));

        DA.SetData(1, Math.Min(a, b));

      }

    }

    public override bool Write(GH_IWriter writer)

    {

      writer.SetBoolean("MinMax", MinMax);

      return base.Write(writer);

    }

    public override bool Read(GH_IReader reader)

    {

      MinMax = reader.GetBoolean("MinMax");

      return base.Read(reader);

    }

  }

}
1 Like

Fologram addon have an option to add a small button to the component to synchronize, how we can do this?
Maybe this help to add a similar button to the slider with two inputs to change minimum and maximum values.

This code from the old forum to create radio button, how we can use it instead of the red button?

private void DrawRadioButton(Graphics graphics, PointF center, bool checked)

{

  if (checked) {

    graphics.FillEllipse(Brushes.Black, center.X-6, center.Y-6, 12, 12);

  }

  else

  {

    graphics.FillEllipse(Brushes.Black, center.X-6, center.Y-6, 12, 12);

    graphics.FillEllipse(Brushes.White, center.X-4, center.Y-4, 8, 8);

  }

}

thank you very much @anon39580149 , this looks indeed very interesting! I sadly have to put this aside for a while, my clients start to be unpatient with me spending time in code learning, haha. However, this example provides again many enlightments to me, and adding these overrides opens a bunch of new universes to me:-) thank you, really! I strongly appreciate this exchange!!

have a good start for the week

Ben

1 Like