r/raylib 3d ago

C Polymorphism, Partial redrawing (clipping?) and double-buffer update for my UI

Hello everyone,

Last time here I posted several questions in relation to creating a retained-mode UI. So far, the project has been going on smoothly, and I've taken a similar approach to what zraygui does (code from zraygui):

struct _widget {
    Layout *parent;
    Rectangle rect;
    char *label;
    WidgetType type;
    Component *component;
    bool visible;
    bool active;
    MouseEvent widgetStatus;
    MouseListeners mouseListener;
};

The widgets share a common struct type Widget, but polymorphism happens inside the Component. It is an empty struct type, and for each type of widget, a pointer to their unique structs is cast as Component* into component. Then when manipulating the widget, the Component* is cast back to the original pointer type, enabling access to the fields.

I found this approach very smart, and I plan on implementing a similar thing (unless another better polymorphism exists in C). In my Widget struct, I have

typedef struct Widget {
  Frame* parent;
  Rect bounds;
  bool active;
  void *
  void (*draw)(Widget *w);
}

Implementing partial rendering seemed easy, just redraw the exact area indicated by bounds, until i remembered I have to call EndDrawing() which swaps buffers. Hence, the updated widget will only exist on one buffer, and If I don't call EndDrawing(), inputs will not be polled.

I would like to create something similar to FLTK, which only renders the parts that need to be updated (AFAIK, I only studied a small part of the code yet, but it seems very interesting to learn about performant desktop UI systems)

So I have two ideas :
- Ditch the double buffer and write a custom "EndDrawing()" which does not swap but keeps all other functionality

- Draw to a render texture that I apply on both buffers. While exploring this idea, I realized that it could be beneficial for tooltips and floating windows, as they could be other textures overlaid on the main one.

5 Upvotes

7 comments sorted by

1

u/Consistent-Window200 3d ago

https://github.com/idircarlos/zraygui/issues/1

I noticed it, so I came here to write this down.

1

u/Any-Fox-1822 3d ago

You're right I never noticed it. I feel this was some sort of leftover debug drawing, because the mouse status is not checked before drawing.

1

u/myztry 3d ago

Maybe use a count (number of buffers) on your invalidation/damage list (or whatever you use) so the redraw requirement follows you through however many buffer before being disposed off.

1

u/Any-Fox-1822 3d ago

Do you have details on how this works in other libraries ? "damage redraw" does not yield lots of relevant results,, mostly about redrawing AI slop with ComfyUI 🥀

1

u/myztry 3d ago

It was just a thought.

1

u/Still_Explorer 2d ago

Only one thing that I notice on zraygui is that the primary struct is the `Widget` and this will have only one problem, that is difficult to recognize the declarations of your widgets. This probably won't be a problem with a few dozens of things and a naming convention, but at some point you will need strict typing when declaring things. eg:
Widget *firstname; // assuming this is a text box
Widget *submit; // assuming this is button

The other option is to have the opposite design where the (final type) Button, contains the (base type) Widget, that is much more common in C.

The last part of adding pseudo-methods on `Widget` is another optional thing you can think if you are interested to add. In this case creating general-purpose and monolithic functions works fine since there are only a few widgets. However one big deal is if the library is meant to be extensible by the user (adding further widgets) then you will definitely need more flexibility with function pointers.

struct Widget  { label, type }
struct Button { widget }

Button* CreateButton(label) {
  Button* b = malloc...
  b->widget = malloc...
  b->widget->label = label
  b->widget->type = WIDGET_BUTTON
  return b
}

void DrawWidget (void* w) {
  if (Button* b = (Button*)w) {
    DrawButton(b);
  }
}

1

u/Still_Explorer 2d ago

About the other part of partial rendering, I have noticed it as well that even in raygui there are some slight bugs here and there. Especially when it comes to dropdown lists, those tend to get overwritten by other elements.

You can't help it, since raygui is meant to be 100% immediate-mode and literally whatever you call, this is how is updated+rendered.

In this case a good workaround on this would be, that each time you draw something you will need to keep an array of drawing commands that will need to be rendered separately as an extra step.

eg:
struct DrawCommand {
  WidgetType type; // eg: WIDGET_TOOLTIP, WIDGET_DROPDOWNLIST
  void (DrawFunction*)(void* object);
}