Skip to content

Make a button

Buttons in Tick UI use an invisible-widget idiom. You draw the visual, then call ui.Button("id") over the same rect. Button claims hit-testing and returns whether the user clicked. This page covers the pattern, including hover state and animation hooks.

To make a button

  1. Push a layout rect for the button. Use ui.Auto() to scope the rect:
    using (ui.Auto().Center(120, 36))
    {
        // visual + Button() go here
    }
    
  2. Draw the visual first. FillRounded, Text, and any decorations render in the rect:
    ui.FillRounded(ui.IsHovered() ? hoverColor : restColor, 5);
    ui.Text("Save", "roboto_mono", 14, textColor, TextAlign.MiddleCenter);
    
  3. Call Button over the same rect.
    if (ui.Button("save"))
        SaveDocument();
    

Button is invisible. It does not draw anything; it only claims hit-testing for the current rect and returns true on the frame the user presses inside it.

Read hover state

ui.IsHovered() returns whether the current rect is the hovered widget. Read it before drawing the visual to pick a colour or scale.

IsHovered reads state computed in BeginFrame. The state is constant for the duration of the frame, so calls before and after Button return the same answer. For the model behind hover resolution, refer to Input and focus.

Buttons in a list

Repeated buttons need unique IDs. Wrap each iteration in PushID so the inner Button("save") calls do not collide:

for (int i = 0; i < items.Count; i++)
{
    ui.PushID(items[i].Id);

    using (ui.Auto().PushRelativeRect(rowRect))
    {
        ui.FillRounded(ui.IsHovered() ? hoverColor : restColor, 5);
        ui.Text(items[i].Name, "roboto_mono", 14, textColor, TextAlign.MiddleCenter);

        if (ui.Button("row"))
            Open(items[i]);
    }

    ui.PopID();
}

Without PushID, every row's Button("row") produces the same ID hash and only one row responds. For the identity model, refer to IDs.

Add an animation

Wrap the button in a PushAnimate scope to animate hover, press, or appearance. Read animated values from the returned context and pass them to the draw calls for colours and sizes.

var ctx = ui.PushAnimate("save_button")
    .Init().Color().Set(restColor).Done()
    .Enter().Color().Blend(restColor, hoverColor, 0.2f).Done()
    .Leave().Color().Blend(hoverColor, restColor, 0.2f).Done()
    .BoolWhileTrue("hover", ui.IsHovered())
    .Apply();

ui.FillRounded(ctx.GetColor(), 5);
ui.Text("Save", "roboto_mono", 14, textColor, TextAlign.MiddleCenter);

if (ui.Button("save"))
    SaveDocument();

ui.PopAnimate();

For the full animation API, refer to Add an animation. For the model behind animation contexts, refer to Animation.

Pitfalls

Calling Button before drawing the visual. : IsHovered returns the previous frame's state when read before Button claims the current rect. Draw first, then call Button.

Forgetting PushID inside a loop. : Every iteration produces the same ID. Only one row responds to clicks. Pair PushID(item.Id) and PopID() around each iteration.

Drawing outside the rect. : Button hit-tests against the current rect, not against where you drew the visual. If the visual extends past the rect (a drop shadow, for example), the rect needs to grow or the decoration needs to render outside the hit area.

The button never registers a click. : Another panel is hovered. Buttons inside non-hovered panels do not respond. Refer to Input and focus for the priority order.

Additional resources

  • Make a textbox: the same invisible-widget idiom for text input.
  • Add an animation: the full procedure for hover and press animations.
  • State: why widget state is keyed by ID.
  • IDs: the identity model used here.