I'm trying to design how I want my code in my Rust GUI library to be organized in a sane way. The way that I'd do it in OOP (which I'm explaining mainly to express my design goals and constraints) is to have an abstract base class called Widget
that implements functionality common to all widgets (e.g. storing the ID of a widget, rendering a solid background color, caching preferred size for layout, and some other basic stubs that can be overwritten when needed (but not needed in most cases) later). Then, I'd add a PaneWidget: public Widget
that overwrites some of the aforementioned behavior (e.g. overwrites the rendering function to render the background color and then child widgets). There is then a subclass of PaneWidget
for each possible layout that overwrites an abstract function in PaneWidget
to actually place the children, modifying a protected data structure in the process. Then, there's another subclass of Widget: DelegateWidget: public Widget
that for the most part just trampolines all calls to a delegated widget member, but has facilities for catching events that that delegated widgetÂą and its children emit and processing them locally rather than simply passing them up the hierarchy as normal, thus allowing a user of my library to concretely subclass this widget to encapsulate a set of existing widgets as a single widget, and eventually their main program view will simply be a single widget in this way (of course, with other smaller ones inside it allowing for many levels of organization).
This seems like it'd work really well in an OOP language, but when I try to translate it to Rust's style -- trying to use composition instead of inerhitance, I find that I'm just rewriting inheritance myself in various ways, be it creating a Widget trait that functions like the abstract base class and having all sorts of duplicated functionality in each widget (e.g. event catching in each delegate, and facilities for storing the location of each child widget in the PaneWidget
s, and recursing over the tree to find widgets as well²). I also tried to create a Widget struct that can enum between a SimpleWidgetDelegate, PaneWidgetDelegate, and DelegateWidgetDelegate each of which implements the non-shared functionality and then shared functionality is in Widget. This sounded great at first, but then I realized that it would have made the Widget struct absolutely massive and very convoluted (spaghetti) trying to deal with three different use cases, so I decided to have, as an implementation detail of the Widget struct, SimpleWidgetDelegateWrapper, PaneWidgetDelegateWrapper, and DelegateWidgetDelegateWrapper all of which implement the unique functionality with a shared interface via a trait and then the Widget struct has the shared functionality among all three, but…. “DelegateWidgetDelegateWrapper” it’s like I’m writing some horrifically convoluted enterprise Java – and I like Java, but even this is too much for me.
So, I ask, what is the proper Rusty way of doing this? I’m just reinventing OOP it seems, but spilling its insides out which is exactly what I’m trying to avoid by writing this GUI library (gtk is gobject, and gobject is OOP’s insides spilled out and it’s…. unpleasant go check out gtk-rs’s rustdoc if you don’t believe me).
1: The event system is to call spin() on the root widget frequently, which then calls it on all child widgets thus recursing over the hierarchy. Each spin() call returns 0 or more events, after checking for input, etc.
2: Since in Rust you can’t keep a reference to child widgets in the case of a DelegateWidget
, I want a find(&self, id: &str) → Option<&dyn Widget>
function (with mut too) on the Widget
trait/base class, in almost all simple widgets (e.g. a label) it needs to check the current id against a stored id (which needs some code to store it) and then return either Some(self) or None depending on that check.