I'm working on putting a GUI on some Racket software I wrote. I've been playing around with racket/gui (and racket/gui/easy, which seems like a much more Racket-esque approach).
What I'm finding is that racket/gui isn't quite as sophisticated as other GUI libraries I've used. Is it by design to keep r/g simple, or does it lack more sophisticated features only because nobody has yet contributed them yet? Would feature requests (and features) be welcome? Development on r/g doesn't seem to be terribly active at the moment.
Unfortunately, r/g has 3 distinct backends so even if I figure out how to make it work on one platform, I'd be much less useful on a second, and useless on the third.
There's plenty of GUI features I'd love to have, and I might even be able to contribute for one backend, but if the goal is for r/g to stay simple above all, then I'll look elsewhere for my GUI.
I'm interested in examples of missing features you'd like. I've found racket/gui very useful for cross-platform desktop apps, though of course I have pet peeves (e.g. Scrolling `racket/gui` panels with the mouse) and wishlist items.
If you aren't aware of it, Framework: Racket GUI Application Framework has a lot of additional functionality implemented on top of racket/gui. (This manual has a lot of room for improvement!)
I agree with Philip's comments: most of the core pieces are there, and occasionally something from modern GUI style is missing. Mostly then I've gone without as I built https://github.com/benknoble/frosthaven-manager.
My second is also the docs: as we wrote in our paper, racket/gui is a white-box (transparent) framework that typically requires deep understanding of its inner workings (and the racket/class library) to use. Documentation can help this: more examples that are not DrRacket-sized (and that don't come with DrRacket's specific challenges) would be a great help. The same goes for Framework.
In the meantime, if you can do what you want performantly with racket/gui/easy, I encourage you to try it—it's a black-box framework in that components are opaque. This makes it easier to learn and use. The hooks that it offers into racket/gui guts have, in addition to being extremely valuable for complex programs, tempted me to learn bits and pieces of racket/gui in more depth.
From my perspective, I don't think "the goal is for r/g to stay simple above all".
Great!
That said, there is also room for incremental improvement.
That is encouraging. I'd potentially be willing to prototype features on one platform, if other people will help out on the rest.
I'm interested in examples of missing features you'd like.
Since you asked:
The table/tree/list views don't seem to have any concept of a separate data model (i.e., on-demand backing data, rather than "add all data at once"), or a view model (i.e., ability to use custom controls for cell views/editors). In fact, I'm not sure they support editing at all. And are there any user-level events, e.g., being able to detect selection changes, or column moves or resizes, or being able to put right-click menus on rows and headers? (I saw some very nice tables in ActivityLog2, and dug into the source to see how they were made. They're not GUI tables. They're just pictures which happen to be mostly text, in rows/columns. Clever.)
The only drag-n-drop support is the ability to accept a single file dropped onto an entire window.
You can't make your own objects draggable.
You can't accept drops onto a specific control or region.
You can't accept any type other than a (single) pathname.
(Coincidentally, this was also the first item on the "Feature requests/issues reported on Hacker News" issue from 2018, which was in response to a remark that "If no one asks for it, we don't know what to implement next", which is totally fair! -- but in 6 years nobody has yet touched any of the 14 items which were brought up, so I don't think it was actually anyone's limiting factor.)
The clipboard support is also lacking. For example, I don't see any way to find out what types are available. Just today, I discovered the get-clipboard-data function doesn't return #f for unknown types, as per the documentation (filed: #328) -- which itself is not a deal-breaker, but when I run into basic bugs like this (i.e., it doesn't do what the first sentence of the documentation says), I wonder if I'm the first person to ever try using this interface, and what else is missing.
Scrolling behaves strangely (filed as #324, though I see there have been other issues filed regarding scrolling). I started looking into this, and I really don't understand how scrolling is implemented in racket/gui -- it doesn't seem to use the native scrolling functionality at all, and I can't even decipher the comment which (I think) explains why not.
Examples of other native widgets I want to use: Spinner, Separator, Stepper, ProgressBar, media controls, calendar (date/time) picker, other button types, popovers, ...
Is performance a feature? I ran into trouble with putting many controls on one panel (filed: #311), due to the deep invisible hierarchies that racket/gui builds behind the scenes. There are some very pretty racket/gui applications but they all appear to be mostly custom drawing, with not many native widgets. Using just native controls, I'm getting around 1fps on my new Ryzen system, which is unusable for the types of programs I want to write.
I did run across that, but it seems to be mostly convenience functionality on top of racket/gui, not anything wholly new that I couldn't do myself. I'm also not sure how well it plays with racket/gui/easy, which is a much nicer abstraction I really want to use. (I really only want to write GUIs if I can do it in a declarative/functional style. If my programs end up being just a long line of (send ...) calls, I might as well write in Python, which is designed for that style of programming, or Racket FFI to GTK, which would give me 100% access to the native toolkit.) Framework isn't bad -- it just doesn't really address any of my main issues with racket/gui.
Thanks for responding! I'm trying not to sound too old and bitter. Racket is really very nice overall.
In the meantime, if you can do what you want performantly with racket/gui/easy, I encourage you to try it
Oh, I have -- 1/7 of its open bug reports is mine! Not only do I think that racket/gui/easy is the most reasonable way to write GUIs in Racket, but I think the "easy" label is wrong. It might be easier, but it'd be more accurate to say it's just a better abstraction.
(In the same vein, it's also "easier" to have bignums, perhaps, but I don't have to import a special "integer/easy" to use them, and we usually just call them "integers".)
I assume that racket/gui is object-oriented (unlike every other Racket library) simply because it started out as a thin wrapper over a foreign object-oriented library. If I had a magic wand, I'd rename "racket/gui" to "racket/gui/lowlevel", and "racket/gui/easy" to "racket/gui".
I'm not sure what you mean by "performantly", though. I've not run into any cases where racket/gui/easy has any detectable performance hit. It's the racket/gui layer which gives me performance issues.
Thanks for your interest and the potential of improving things! There's no question that there is more to do and having more folks interested in putting time into is just wondeful.
As for what would be desired and how to think about organizing the code, beyond what's already been said, I'd say that racket/gui has worked out to be something that collects functionality that is mostly bringing a common API to the different GUIs as a low-level tool to build platform-independent APIs on top of. Of course, this isn't such a well-defined thing and we've missed the mark in various ways over the years, but that seems to be the best way to think about things going forward. The framework is then one attempt to build higher-level facilities on top of racket/gui that are platform independent (this also is best though of as an "in spirit" guideline too, as it turned out). It's definitely the case that the framework isn't the easiest thing to use, but that guideline is what we've used to decide where to put any new functionality. So, in your list, I'd say that the table/tree/list views that have more sophisticated data/view models would be in racket/gui if their actual implementations were platform-specific (and, probably, looked different to get a platform-native feel) but probably belong in a library on top if not (and the framework would welcome that, for sure! but I understand if folks would prefer to put it elsewhere). The drag-n-drop support, however, definitely needs low-level APIs and some thought to make a racket-level interface that works well (enough) on all platforms, so it seems at least some part of that belongs in racket/gui. Ditto for the clipboard.
I would say that improving the performance would be awesome. For deep invisible hierarchies, we have some support (c.f., panel vs pane) but I'm sure there is more improvement that can be done.
I've experimented on-and-off with extending racket/gui with extra controls from the "outside", for apps that don't need cross-platform support. It's possible, if you're willing to rely on some of racket/gui's internals, though somewhat more painful than working on regular Racket code. The code is here and it includes the beginnings of an outline view with a separate data model (for macOS), so maybe check that out for inspiration if that's an approach you'd be interested in. I may return to that project at some point, but, for now, I've settled on writing my GUIs using the platform toolkit (since I mostly care about macOS and iOS) and embedding Racket to handle the business logic.
My application starts to experience some problems with a lot of data on the screen after a while; I wonder if it’s due to the nested container problem you mention? (I’m also running a web-server that maintains a few long-running connections with it, though, and that could be part of the problem.)
Anyway, I just checked in and am glad to see continued community interest in and development of gui-easy. I really need to extract my helpers as a package
In an ideal world, there would be support for more platform-dependent GUI elements.
It is however a difficult to task to keep up with 3 platforms at once.
It's more realistic to what Bogdan experimented with: adding elements ad hoc when needed.
However, it is not simple to do - and I think a tutorial or elaborate example of how to add GUI elements would help. It's not easy to write though. In the case macOS it requires knowledge of: Appl'es developer documentaiton, Objective C, the FFI and the conventions used in racket/gui.
I am not 100% what kind of drag-and-drop you are after, but in some case it is possible to use a pasteboard. Alex has a very nice explanation in his blog:
for now, I've settled on writing my GUIs using the platform toolkit (since I mostly care about macOS and iOS) and embedding Racket to handle the business logic.
Yep, this can be a feasible approach. I think the main reason I'm not doing that myself right now is simply because both of my main projects are still in the "research/experimental" phase, and it's hard to split up a program when you don't even know what it's going to do yet.
the beginnings of an outline view with a separate data model (for macOS)
I've used the outline view on macOS, and it's pretty good. I think it includes all of the features that I'd want, so this could be a great starting point for a cross-platform Racket interface.
The performance issues I see with racket/gui appear right away, not gradually, so my suspicion is that this is something different.
An easy way to check is starting your program with GTK_DEBUG=interactive and seeing if the widget hierarchy looks similar to your Racket program. If it's got 10 extra layers of GtkFixed and GtkEventBox, you may be seeing the same issue that I am.
@JustinZed, @soegaard: Those look like neat programs, but they both look different than what I mean by "drag-n-drop". Those appear to require custom drawing on a custom Racket canvas, and can only drag those objects within that canvas.
System drag-n-drop can occur from any region (in your application), to any region (even across applications). The two endpoints negotiate the type of transfer (copy, move, link, etc) and the type of data (often by MIME-type, but by UTI on macOS). The programs get to specify what the object looks like during the drag, and what the target window looks like during a hover.
This requires OS support, and isn't wrapped by racket/gui yet, so you'd need FFI or a native library. I'm sure we could put a nice Racket interface on it, but I don't know what it might look like yet.
I need to check, but I am almost certain that kind of drag-and-drop is supported.
If I remember correctly, you can drag-and-drop an rkt-file on DrRacket.
Later:
See the documentation for window% and on-drop-file.
I don't actually know, but when I first saw it I assumed the easy suffix was following @bogdan's net/http-easy, which similarly provides abstractions over net/http.
In case you aren't aware, Racket can be configured to use GTK instead of the "native" toolkit on Mac OS and Windows (though I think the GTK/Windows support is not regularly tested: I was surprised when I stumbled across it in the source). You can structure your code as a racket/gui program but, when needed, use the get-client-handle method of window<%> to grab a GtkWidget pointer and drop down into GTK.
Native Widgets & Custom Drawing
(The following is a somewhat vague impression I've had: hopefully people who actually know things can chime in, either with supporting evidence or with explanations of what I'm getting wrong.)
(P.S.: This ended up being very long! I've been ruminating on some of these topics for a while, though I still think it's a bit amorphous.)
A number of platform GUI toolkits seem to have "recently"(-ish) undergone some sort of transition:
One pattern I notice is that, in the "traditional" toolkits, widget objects are resource-intensive and do not scale well. Here's an extended fragment of an even longer old discussion:
I have to do a simple spreadsheet editor and I wonder
whether Racket suits my needs. The main challenge
is that the spreadsheet editor should be able to edit
tables as big as 1000x1000 or 10000x100 cells.
… [I]nstances of button% (or generally control<%>) in a scrolling
panel will not scale well. The racket/gui library is not designed for
it.
I'm not sure how much the problem is in racket/gui versus the
underlying toolkits. Your example program scrolls nicely for me on
Windows and Mac OS X, but not Unix/Gtk, but I would not conclude from that
experiment that the problem is in Gtk. The problem might be the
Gtk-specific part of the implementation of racket/gui. Also, I
wouldn't expect any of the platforms to scale to 1000x1000 buttons.
When I tried 100x100 on Mac OS X, it took a couple of seconds to create
all of the buttons.
I think you would have to use canvas% and draw/manage the grid and
controls manually. It's possible that the classes of embedded-gui
will be useful, if you can set up a suitable harness for snips. (I've
always wanted to make a table% class to go along with text% and pasteboard%, but I never got around to it.)
I don't think that a big-grid GUI application like Excel will use
controls for very much in its main window. Generally it is managing
all that itself.
It might use a few plain windows, such as one for the column names
on top, another for the row names on the left, and then a big one for
the main grid client area. Just to make it easier to clip output.
If the user clicks in a button-like area, it will handle that itself.
e.g. If you click somewhere in the column header, it will calculate
which column, and draw that column as selected.
If the user clicks in a region it calculates to be a cell, it might
create a text-edit control there for in-place editing -- but just
temporarily, and destroy it when editing finishes.
All the logic for scrolling... managed itself.
At least, that's how I did Windows GUI stuff like this, 15+ years ago.
Usings hundreds or thousands of windows/controls was just too much
overhead to get desirable speed and space. Although the overhead might
be less, now, I imagine if you want a really crisp UI it's probably
much the same story.
For a million cells like that, when using any language and toolkit that
I know of, I would probably implement it with a mix of manual drawing
and using the occasional toolkit widgets in only small numbers at a time
(only for actively editing of a single cell).
…
I think that this simple approach of a manually-drawn grid and minimal
use of toolkit controls will be fast with even a million cells, without
much programming difficulty.
The "new" toolkits (or new features/emphasis in existing toolkits) seem to move toward approaches reminiscent of the "Virtual DOM" used by some JavaScript UI frameworks. They seem to be trying to make toolkit widgets more scalable and, at least in some cases, rendering APIs are getting closer to functional programming: for example, GTK Scene Graph Kit's GskRenderNodes are immutable. Some are doing more sophisticated compositing, doing more work on the GPU, or otherwise pursuing performance gains.
AIUI, gui/easy also takes steps in this direction through its functional shell of lightweight view<%> objects. But I think this would be a fruitful direction for future work, both in higher-level abstraction over racket/gui and in identifying platform functionality that should be supported.
In racket/gui—and, I think, in "traditional" toolkits (or ways of using them) more generally—a canvas<%>, be it a canvas% or an editor-canvas%, is basically a "leaf" from the toolkit's perspective. Outside, you have one set of relatively high-level abstractions like message%, radio-box%, and group-box-panel%. Inside, you have a surface for Cairo drawing (racket/draw): any abstractions you create over it, even ones as powerful as the editor<%> and and snip% systems provide, still look to the platform just like drawing.
The "new" toolkits seem to be trying in various ways to remove some of these weaknesses and restrictions.
One motivation is performance: as the blog post I linked to above about the motivations for GSK put it, "we can incrementally transition from the current immediate more rendering model to a more structured tree of rendering operations that can be reordered and optimized for the target graphics layer."
More interesting to me are the possibilities for expressiveness. I think many, perhaps all, of the "new" toolkits have functionality that we could use to allow toolkit widgets to be placed inside of canvas<%>es (or perhaps some new canvas<%>-like abstraction would be needed), solving problems like those encountered by racket/visr. Those cross-platform GUI toolkits that mostly eschew "native" widgets face similar challenges, and some seem to have found solutions. For example, Flutter can embed an android.view.View or an iOS UIView, and on most platforms Qt[2] can wrap a "native" pointer with QWindow::fromWinID() and embed it in Qt Widgets via QWidget::createWindowContainer() or in (the latest version of) Qt Quick via WindowContainer.
I don't have an especially concrete conclusion. I, for one, find it very helpful to hear from people like @bogdan and @ken about their experiences working with the platform toolkits directly as a way to think about what might be useful to have in racket/gui.
For some kinds of functionality, an alternative to finding common API for all platform backends would be to adopt some cross-platform library, analogous to the way we use Cairo for racket/draw. (There are also obvious downsides to this approach!) For "scene graph"-like functionality, GSK, QRhi (the Qt Rendering Hardware Interface), and Webrender (a Rust rendering engine used by Firefox—not actually web-related itself) are all cross-platform.
Well, one concrete step would be adding support for GTK 4: I have given thought to this, but there are several other things I'd need to finish before I could work on it myself.
I am least clear on the details for WinUI: I haven't routinely used Windows, much less programmed on it, since the days of Visual Basic 6. One of the reasons I'm enthusiastic about racket/gui (and Racket's cross-platform portability more generally, e.g. Windows path support) is that I can write portable code that almost always "Just Works" for the Windows users I need to support. ↩︎
As a KDE Plasma user, Qt is the "native" toolkit for my platform, but Qt also brings its own widgets and rendering when used on other platforms. ↩︎