> Did I miss something important that should be covered?
Accessibility. Outside of that everything seems reasonable. But I don't know much about cross-platforms GUI. I expect someone will come and explain that Delphi or something like that already does everything the author wants except for the integration with Clojure.
I would add that I'm not sure which value Clojure provides here. Does the REPL adds anything to the live-reload environment? If not, the choice of Clojure seems to be arbitrary.
live-reload or hot-reload I've seen usually refer to systems that preserve state in a running application (so you're not reloading from scratch). So your application never actually stops running when new code is loaded and you get the same sensation that you're editing the application "in-place."
That being said even in that environment having a REPL is nice (although not as much of a quantum leap so much as an incremental improvement) because you can use code to directly poke at your application but is not actually used by your application instead of only being able to write code that your application actually uses.
I think they are very similar, but different enough that it makes sense to use different words. Using just the word "REPL" doesn't give much insight into the iceberg of capabilities that lisps like Clojure, Common Lisp, etc. provide. I wrote a long post about some of the differences: https://news.ycombinator.com/item?id=28475647
Would love a better word or phrase to differentiate from e.g. the way people experience a python or haskell REPL.
For the richer version others have suggested "hot reloading", "live reloading", "live patching", etc. I think all of these are descriptive enough, and most importantly, don't overload a term that many people already understand to mean something different
I agree that the term "REPL" is poorly chosen, since read-eval-print-loop equally applies to a Bash prompt. But in this case, it's even more than hot reloading, because you are making changes in the running application, not just in the saved source code.
With hot reloading, you can change the appearance or behavior of a button and see it reflected immediately. With a Clojure REPL, you can do that but also load in different data, trigger actions, etc.
Then it's possible an even broader term is needed, but "REPL", to non-lispers' minds in 2021, means narrowly "a prompt where you can type code and then hit enter and see a result". I think "hot-reloading" would be a dramatically more descriptive term even if it isn't perfectly descriptive.
Yes, I've been saying "live interactive development" instead of REPL more recently, and I think it conveys the idea much better. Since in Lisps, a REPL means:
> a live integrated inside an application REPL that lets you evaluate and hot-swap every single line of code at will and on-demand all at runtime without ever needing to restart the application, while also being able to inspect the runtime state at will
We do have a term for it, it's Image based programming. The primary means of interacting with said image is quite often the REPL, and so the two may have become synonymous.
To me that term also implies that images are the primary means of distribution, as in (most) Smalltalks. That said, it's still probably a better term than REPL for the Clojure experience.
Yes, didn't mean to imply it was unique to Lisp. And when there is little distinction between what you distribute and what you develop in then it's very apt.
Right, but there is a distinction in Clojure between what you distribute and what you develop. You aren't shipping an image, you're shipping compiled source code.
Modern hot reloading and how lispy REPLs work are similar in many ways, i.e. they allow you to make changes to a running application without stopping it, however there are differences in their experience and how they accomplish it.
The primary difference in experience is the unit of change that you can make with these systems, which is typically based on the unit of compilation of the language you're using.
For example if you're using a language whose unit of compilation is a file, IME you must reload every change in the file at once. This has upstream effects to how hot reloading is accomplished, because if you're reloading an entire file where you've only _actually_ changed one function, then your hot reloading system has two options:
1. It reloads everything in the file, meaning you're potentially losing state that could have been preserved across the change. This typically leads developers to spread definitions across many files in order to be able to make more fine grained changes to their running system.
2. It uses some heuristic to detect whether state should be preserved or not, typically via some static analysis. This can be opaque to developers working on applications, and adds a lot of complexity to the hot reloading system, but is probably the best one can do if your unit of compilation is a file.
Lisps, on the other hand, typically make the unit of compilation a "form", i.e. a well-formed syntax tree like (foo (bar baz)). This, combined with some editor tooling, means that I can make extremely fine grained changes to the system by selectively evaluating forms within a file, preserving its state except for the form that was evaluated.
The way this works in practice is: I have my editor open and connected to my running application using an extension. I place my cursor on a form I have changed and use a hot key to compile and send that form to the running application. I can view the return value of that form in my editor, and if there were any side effects (e.g. re-defining a variable or function), my application will now exhibit that new behavior.
Side note: the benefits of being able to evaluate forms and see their output in my editor is hard to state. It turns your editor from a one-way communication tool (I make changes, hit save, and those changes get made to the application) to a bidirectional tool, where I can both change and inspect the running system. I wish more languages had robust tooling for this.
The other difference that you tend to see between languages (even between languages with just hot reloading) is late vs. early binding. Lisps are typically very late bound, so you don't need to recompile and/or restart your entire application on every change.
Other languages who bind earlier have to deal with stale bindings. Even JS hot reloading has to deal with this problem in complex ways because most bundlers use closures as modules and bind their inter-module dependencies on instantiation, which means that even though you in theory don't need to recompile or reload anything except the one JS function you just changed, in practice you have to reload the file and any files that depend on it, which puts us back in the position of needing to use some sort of static analysis to detect what has actually changed in order to precisely invalidate application state.
I hope this mountain of text helps highlight the subtle differences between hot reloading and a lisp REPL. It's not just the tooling but the specific semantics of the language that make a difference when trying to make changes to a running system.
Source: my experience working with and developing tools for hot reloading and REPL development in JS, ClojureScript and Clojure
The work is funded by a Clojure community group (and a Clojure-using company), and Tonsky has written a bunch of awesome other Clojure (see DataScript[1]).
So it seems as though the decision to use Clojure was not made for technical reasons, but it's not exactly arbitrary.
Live reload gets you most of the way for UI programming, yes.
REPL is the secret sauce for writing any time of program, not only UI. It lets you explore, play, test, inspect and break things very quickly and without losing context before you find the final shape of your code.
I can’t live without it, but it’s hard to explain the benefits to the people who have never experienced it. You have to try it to really be convinced
I think the article is conflating people's acceptance of web apps with people's appreciation of web apps. Web apps (or electron apps) are always worse than a well-written native counterpart in every verifiable UX metric. Let's not kid ourselves: web apps and electron apps are developer-experience focused, not user centered.
> Web apps (or electron apps) are always worse than a well-written native counterpart in every verifiable UX metric.
That is true.
> Let's not kid ourselves: web apps and electron apps are developer-experience focused, not user centered.
That isn't true. I spend most of my days on Linux. Before web and electron apps, the alternatives were not well-written native apps. The alternatives were "Use another program". As a user, I'm really glad many things are web-centric these days.
I think you're benefiting from the coincidence that developers (managers?) chose Electron + 1 code base and void of well-written apps due to low market share. Sure, an electron app is better than a broken side project. But a fully native linux app would handily beat the electron app.
If there were a larger market share, there would be more well-written native apps. If devs focused on users, the easily-ported mediocre electron app wouldn't exist.
But the electron app IS a web app and has all the flaws of a web app, plus one more flaw: it doesn't share memory with the browser even though it's the same code (more or less). I believe their are some smaller electron-like projects that are lighter weight, though you're still stuck with a non-native web app in a shell.
Java apps weren't an alternative? Wasn't the initial promise of Java in the late 90s to write once and run anywhere? Not applets but the desktop applications using Swing or AWT.
> Web apps (or electron apps) are always worse than a well-written native counterpart in every verifiable UX metric
Seems like you have the data to back up this statement so if you do, please share :) I'm especially curious about the notion of "every verifiable UX metric" as UX tends to be very subjective. Could you please share what metrics these are that can independently confirm that a native app is always better than a web app?
I suppose it might have been what I inferred when the author said "Yet it has been a massive success. I think it means our industry is craving for a good desktop UI framework."
> I think the fundamental problem of the web is that its APIs are too high‑level.
Frameworks like Flutter (at least one backend) [1] [2] and Gio [3] use low-level APIs like WebGL and WebAssembly. That might not be the right choice for a web-first framework but seems reasonable for apps that would otherwise be desktop-only and need low-level access to be compatible with the native version.
It looks like he's using Skia for rendering so the WebGL/WebAssembly Skia port that Flutter uses might be a particularly good fit.
I assume a Clojure project would use Clojurescript if it had a web target, but I'm not familiar enough to know if the Clojure/Clojurescript differences [4] pose a big obstacle for this sort of UI framework or not.
This is a great writeup and good to see I'm not the only one looking at JVM for cross platform desktop apps.
Recently I've been experimenting with TornadoFX and it is very nice to work with. It's a Kotlin wrapper for JavaFX providing a "declarative builder DSL" to compose views. It uses a MVVM approach where the View components are bound to the ViewModel using observables. JavaFX supports CSS for styling components including variables without the clunky web css variable syntax. And with Kotlin coroutines/suspend functions it's trivial to add async IO for network and disk access.
Combine all that with modern JVM and Gradle tooling for native packaging (deb/rpm/msi/dmg) and access to the huge ecosystem of JVM libraries.
Of course there are drawbacks. JVM startup time isn't great but even with Spring Dependency Injection my project starts up in under a second which is fine for a long-running desktop app imho. It will never be as fast or consume as little memory as a native toolkit does but to me it's the pragmatic sweet spot in between native toolkits and packaging a full webbrowser like Electron.
I'm not affiliated with TornadoFX or JavaFX, just a happy user that has rediscovered joy in building desktop apps again.
I have been enjoying working on a cljfx project recently. Extremely fast to get a prototype together, proper multi core support for number crunching, whole JVM ecosystem to reach out for functionality. Probably not for the faint hearted but it has the advantage that it exists today.
JavaFX is really under-rated. The primary argument against it being pushed by JetBrains seem to be related to exotic text rendering features, and/or bugs. OK, well, it sounds a lot easier to do some targeted upgrades there than throw it all out. The core tech is really solid. It's been neglected in the past few years but desktop tech doesn't change that fast. Throwing out a toolkit designed for complex desktop apps and trying to adapt a mobile toolkit doesn't seem likely to lead to better results.
Main thing I feel concerned with JavaFX is that it is no longer being bundled with the JRE. Why is it not being considered a first class citizen of the Java Environment? This means that even the tiniest project using FX should have dependency management to ensure that JRE versions and FX versions and other supporting jars all align up properly to work. Meanwhile AWT/Swing just works right out of the box.
I'll admit I learned JavaFX at the same time as the Clojure wrapper (which fights against the paradigm a little, perhaps) but what I've seen is fine. Big component ecosystem, and styling that appears to be quite powerful if you want it.
For a Clojure programmer, yes. And for my particular use cases I’m happy with this over, for example, React Native plus ClojureScript because I can bring in JVM libraries and have easy threading.
But if I were starting from a completely blank slate or building a startup around something (especially consumer based) I’d probably make different choices.
"The web started to get more suitable for apps probably since Gmail in 2004. It’s very convenient, yet still very limited and has shortcomings."
Yes. I worked for over a decade to make web apps behave more like native apps. I finally realized it was a fruitless endeavor. The browser and the web are amazing but, trying to make them behave like native apps just doesn't offer the same experience and, in most cases, offers a worse experience. All one need as proof are all the Electron apps or the Apple Music desktop app.
And yet Visual Studio Code is doing quite well for a "slow and bloaty" Electron app. It starts many times faster, and in many areas (not all) also feels snappier than both Xcode and Visual Studio (and being an IDE versus "just a text editor" isn't an excuse, VSCode can be turned into a full blown IDE with a few extensions).
I think a more appropriate comparison for VSCode would be Nova, which is angling for roughly the same IDE-capable editor space. And uses some of the same technologies (LSP, TextMate themes) I’m not at my desk so I can’t compare at the moment, but I remember Nova feeling vastly more responsive and quite a bit faster from launch to usable.
And VSCode performance (especially launch) degrades significantly once you have several workspaces open at once. I’ve learned to dread upgrade launches because the release notes tab amplifies that degraded performance even more (which is baffling because that’s just one more web thing in the same web context right?!).
Don’t get me wrong, I love VSCode, and it’s worlds better than most other Electron apps I have to use. I even went back to it from Nova because it better supports my workflow. But it is definitely slower than it could be if it were native.
And is it still as performant with all those extensions that make it remotely close to a full blown IDE? Because as far as I know they can make it slow to a crawl.
It is not built with Catalyst. Podcasts is, but Music is not. And presumably he's referring to the webview-based parts of Apple Music, i.e. the browsing piece of it. The native parts of the app (library browsing and management) work very well in my experience.
If you go to something like the For You screen, you will see a pause while the content is loaded remotely. And if you hit Command-R, it will reload, like a web page.
I think the other commenters are right that Music.app is written as a native macOS app, but it definitely leans heavily on web views for content, especially in the streaming parts of the app.
Like it's broken beyond all recognition and hope with all interactions and expectations of a native app just gone. See this review: https://www.youtube.com/watch?v=gE8ZikfrpFU
Compose is still data-oriented! It's not only possible to pass a Button around, it's encouraged. The representation you should pass is a higher-order function, or lambda:
val button: @Composable () -> Unit = { Button(...) }
List(button) // definitely possible
The advantage is encapsulation: Button as a lambda exposes no external state, so you don't create tangled graphs of data dependencies in your UI.
You should definitely take another look at Compose, because this concept maps extremely well onto Lisps in general (code is data) and Clojure in particular (tight composition of immutable state).
I find encapsulation more of a disadvantage than advantage. I want access to internals when I need to. Passing lambdas is not data-oriented, and it does not help to prevent tangled data dependencies
Hmm? I’m on a 165Hz screen, Arch Linux/Sway, and Firefox and Chromium both do 165Hz just fine¹, both with CSS animations and with JavaScript requestAnimationFrame. I would expect Electron to behave the same way.
To be sure, you’ll have to be a tad more careful at higher frame rates, but my point is that it can render smooth 144Hz animations.
—
¹ When run under Wayland; XWayland is apparently still locked to 60Hz.
> but my point is that it can render smooth 144Hz animations.
It can't, generally. There's a specific subset of animations that browsers can offload to the GPU: anything that doesn't touch the layout. So, transform/translate. That's it.
Opacity and affine transformations cover most animations that people want, though certainly not all. SVG is also generally more amenable to GPU powering. (And if they’d catch up with Firefox’s WebRender, then even more would be done on the GPU.)
Beyond that, though, you’re underestimating what browsers are capable of. You’ll certainly hit limits much sooner, and it’ll be more sensitive to other system load (thus more prone to jank), but it’s not hard to drive layout-affecting animations at 144Hz provided you careful about isolating the affected areas. The “don’t touch layout, animate transformations” narrative that got pushed hard some years back was much more about mobile than desktop—desktop can be more efficient with transformations, but even layout-based animations will generally be smooth on desktop platforms. (In the past, Firefox struggled more than Chrome on those sorts of things, but WebRender has helped to completely reverse that.)
As an absolutely toy pathological example, on my admittedly powerful laptop (Ryzen 5800HS, using integrated GPU) in Chromium (since we’re talking Electron and not WebRender), moving a series of ten inline-block 80px emojis by setting their margin-left to random layout-dependent values every frame (so that’s ten forced relayouts per frame, and quadratic layout behaviour) tends in profiling to take around 3–4ms, occasionally spiking briefly just above the 6ms budget my 165Hz display gives and causing jank (… not that I can tell visually with the emoji flickering all over the place anyway, but the profiler tells me!). For comparison, just one emoji (dodging the quadratic stuff) tends to take around 1ms, showing that a lot of the costs here are constant. URL for the code:
> it’s not hard to drive layout-affecting animations at 144Hz provided
And you've literally written how hard it is: "hit limits much sooner, and it’ll be more sensitive to other system load (thus more prone to jank)" and then you couldn't reliably animate 10 emojis. Ten. Ten. And you call animating ten emojis a pathological case.
I’m calling it a pathological case because it’s forcing style recalculation and layout ten times per frame, and that there’s a quadratic path in the layout involved, being done ten levels deep. Neither of these are realistic: an even vaguely sensible approach will not force style recalculation or layout during the callback at all, and trigger no quadratic layout path. Also in cases where you do need to animate something that’s part of the layout (not particularly common, by the way), it’s decidedly rare to want to animate more than one or perhaps two such element at a time—ten is massive overkill for the sort of thing we’re talking about (general-purpose desktop software). And alternatives to the web are normally not actually going to be better—the web has had an insane amount of effort put into making something not particularly amenable to good performance faster than it has any right to be, so that it tends to be able to outperform the theoretically potentially superior, even if it sometimes requires some care to achieve that. (By this, I mean to intimate the vague idea that by using a browser you’re not painting yourself into a corner where it’s simply impossible to get good performance without rewriting from the ground up, which is absolutely a problem with mainstream desktop GUI frameworks.)
Anyway, take my ten emoji example and reduce it to no layout dependencies (the usual case) or forced recalculations (as you should be doing) and you’re back down to mostly just over 1ms. A hundred elements is then normally 2.5–3ms, with occasional brief spikes of 6–7ms (the spikes at this point are larger proportionally to the baseline than in the pathological ten case). And to remind you, this is for animating a comparatively large number of decent-sized independent elements in the most expensive way possible. It’s significantly more complex than a realistic UI animation test, though certainly a far cry from what is possible in something designed for the purpose, or even Firefox thanks to WebRender: it’s still comfortably hitting over 100fps at a thousand of these emoji, including all this layout recalculation—change a non-layout property instead and it can go way further. (Meanwhile, I try pasting my thousand-element 23KB data: URI into Chromium, and it completely locks up, and killing the process is my only recourse.)
> I’m calling it a pathological case because it’s forcing style recalculation and layout ten times per frame
It's only pathological because the web makes it pathological. Phones routinely do complex layout animations that are unimaginable on the web. And yet, "we're doing layout calculations on ten emojis, it's pathological" is the norm on the web.
> t’s decidedly rare to want to animate more than one or perhaps two such element at a time
Yes. It's rare on the web. Because the web is unbelievably inefficient and slow when it comes to any animations beyond a very limited subset.
> And alternatives to the web are normally not actually going to be better—the web has had an insane amount of effort put into making something not particularly amenable to good performance
I have no idea where this verifiably false mindset comes from. Anyone you ask is "yes, the web is very performant, you can't do better, the alternatives can't be better". And yet you use apps, and frameworks, and approaches that are magnitudes faster, more performant, and create significantly more complex interactions and layouts than the web could ever hope to achieve.
Yes. I'm talking about your phone. I'm talking about your desktop. Right now my iPhone screen with two notifications displays more animations, at a smoother framerate than your "pathological example".
> Anyway, take my ten emoji example and reduce it to no layout dependencies (the usual case)
Ah yes. The usual case. That's why it's still impossible to animate height: auto, or an item being removed from a list without complex hacks because on the web everything simple is hard beyond all reasoning, and everything hard is impossible.
As someone who believes Electron is genuinely the best existing option for cross-platform desktop apps (and possibly desktop apps in general), I'm excited to see someone trying to make something better by incorporating that context and that progress instead of ignoring it. The goals and the ideas here sound well-thought-out, and not tone-deaf, and I really hope this goes somewhere.
One aspect of Electron app, is that typically you can use the most of the same code in the browser. Would you expect to see the same result from this project; ClojureScript code running in the browser and JVM-backed Clojure running on the desktop?
Like you, I am excited to see this approach. There may be a lot of negative opionions about Electron, but you can't deny the inroads it has made.
It seemed like the author hinted at that (and I believe Flutter sets a precedent for it), but I have no firsthand knowledge of the project
With that said: I got the sense the author is mainly interested in targeting high-complexity desktop apps, which tend to have less need to be available in both forms
Now I am curious, why is Electron a better option than putting a simple webserver in your code and then open it in the users favourite browser? You get the same options in terms of what the JS can do, except you aren't limited to Javascript on the backend and you don't need to ship the Chrome runtime to a user that already has one.
1) Packaging/general polish. With the server solution the user doesn't get a desktop icon, or a single .app/.exe, etc. Their window manager doesn't work with the app the normal way. Etc.
2) Certain other native integrations. An electron app can populate the Mac menu bar, it can use native right-click menus, it can do rich notifications that aren't constrained to the browser's notifications API, it can show a red dot in the system dock, etc.
3) Stability of pinning to a specific Chromium version.
This can work for some things- mostly developer tools where users may not care as much about polish and may care more about reducing install size. But it's not really a replacement.
> Recent versions of Qt use the native style APIs of the different platforms, on platforms that have a native widget set, to query metrics and draw most controls, and do not suffer from such issues as often
It's an implementation detail to mimic the platform look. If you don't want that, you can use custom styles/stylesheets. The point is, e.g. QPushButton is not a wrapper around NSButton on mac, or BUTTON hwnd on win.
This is really cool. It's great to see someone pushing for good desktop applications. I'm not an electron hater, but I don't love it either, and I'm happy to see new alternatives emerge, in a fun language like clojure no less.
Maybe it's a question of personal taste, but Pascal (in any of its newer forms) isn't a langauge I like working with. Up-front variable declaration, verbose flow control, next to no support for functional programming... LCL is nice, I especially like how small the result is, but in the last ~30 years, programming language design has progressed a lot and Pascal just seems archaic, and not in a good way. Modern PL design isn't just about "syntax sugar" as the FreePascal community would have you believe.
It was slightly before my time so I'm not really sure why it suddenly died. I can say the author's correct that a declarative style has proven to be more productive in the time since, but there was a big gap there between the declarative shift and when swing died off.
As someone who's worked on both declarative and procedural UI, I'd say declarative is harder to work with. It's much more difficult to debug. I think the largest benefit of declarative is that it largely mitigates unresponsive UI code, though that too is pretty easy with a little care.
I've worked with both too (only really on the web, fwiw) and I have the opposite feelings. It feels like a huge waste of my mental energy to manually keep state in sync across different parts of the app when that's pretty well a solved problem at this point.
While I appreciate the time invested in researching all this, I feel this is "reinventing the wheel". Native GUIs almost always offer superior performance and already offer everything that the developer(s) of a new framework would have to reinvent. Thus, a wrapper around native GUIs makes the most sense. (GUI frameworks on Linux though does need an improvement or a complete revamp. )
In practice wrapper types have been done many times before, and failed to make something good.
Rendering everything yourself is actually as performant or potentially more performant than a 'native' set because what everyone is doing in the end, including native frameworks, is rendering on a rectangle using opengl, vulkan or metal.
It's not the rendering that's the issue - it's the work involved in making them conformant to the different OS guidelines, and dealing with the intricacies and quirks of each OS. It's more practical to extend and enhance what is lacking, rather than rebuild the whole thing again in 4 (Windows, macOS, Linux, FreeBSD) different ways.
One question regarding wrappers around native GUIs as someone with little experience writing GUI software: how does the wrapper handle different UI/UX guidelines? It’s one thing to wrap a common button class around Win32, AppKit, Qt, and GTK buttons. It’s another thing to have the application simultaneously comply with the UI/UX guidelines for each desktop environment.
> how does the wrapper handle different UI/UX guidelines?
Most of it is automatically handled by the native interface of the OS when it renders the UI. Some cases have to be dealt by the wrapper library developer, and some of it has to be done by the developer creating the app using the wrapper library.
For example, a Window usually has the default UI elements of a Title Bar, the title text, Max-minimize buttons, window resize handlers etc. In Windows, this is rendered with the max-minimise buttons on the top-right corner, and the title left aligned in the title bar (if I remember right). On macOS, the same Window will be rendered with the max-minimise button on the top-left corner and the title centered in the title bar.
When you create a window on MS Windows OS using the Win32 API for it - http://www.winprog.org/tutorial/simple_window.html - the rendered window will be, by default, according to Microsoft UI / UX guidelines. Similarly, when you create a window using the cocoa framework on macOS, the window will be rendered, by default, according to the UI / UX guidelines of Apple - https://github.com/lukakerr/NSWindowStyles .
This highlights how some UI / UX guidelines are baked into the native frameworks.
But if the wrapper library developer wants to offer some multi-platform custom UI component not available natively, they will have to ensure that the component is compliant with UI / UX guidelines of the OS they are rendered in.
(If you want to see this in action with an actual wrapper library, check out Lazarus IDE - https://www.lazarus-ide.org/index.php ... create a window on it on MS Windows, Linux or macOS with its GUI designer and compile the code to the see the native window it generates, on each OS.)
Most of the other things described in the article are also already available in the native GUI frameworks. It would make more sense to extend and enhance what is lacking, rather than re-inventing the whole thing again (which, as you noticed, can be a pain if you have to conform to different OS guidelines).
I can't speak for other platforms, but for GTK it's pretty obvious what is actually a GTK app, and what uses GTK as a wrapper. Sure, it might have the GTK titlebar and context menus. But all good GTK apps don't use a titlebar for the app title, they put widgets inside of it. They use libadwaita and get adaptive layouts for smaller app sizes.
Reusing native stuff is fine - it provides rendering, text, accessibility, window management, and other hard stuff for you. But it's not equivalent to a native app, because you'll never conform to each platform's UI guidelines without significant work per app.
True, that's an issue - maintaining the wrapper library and keeping featuring parity with native UI features is a cumbersome task and many do fall behind in that. I think most wrapper libraries are still on GTK2, and perhaps working on GTK3. That said, wrapper libraries do help you make it much easier to develop for multiple platforms with one core code base. The final work of tweaking and customising for each OS, before distribution, is true even if you build the product with the native OS frameworks. (Some compromises will ofcourse have to be accepted.)
> CSS in JS as a good idea (we won’t start from CSS separated from layout, but it’s good to not to repeat HTML’s mistake).
The HTML and CSS separation is great for accessibility. It is also great for machine processing. On the web "content is the king" which might not be true on native apps, but I tend to feel that separation of content and its _visual_ appealing is a good thing in general, only if CSS would have substantially less quirks.
Interesting that yesterday's submission of this got buried. Let me repeat and elaborate because it's somewhat close to my heart.
> I plan to focus on the high‑quality desktop instead of finding a mediocre middle ground between desktop and mobile.
Oh god damn it. This space is severely under-served. It's only filled by Xamarin Forms, React Native (kinda), Flutter and web. They all kind of suck for desktop apps.
It doesn't even have to do both well. It just has to be a great experience on one platform and an okay one on others. Phone apps can be good desktop apps with minimal modification (as Apple realizes 5 years after Microsoft almost did) and not many things are worse than happening to use one platform and your data/functionality just not being available on it.
Stop it, please. Smartphones are computers. They're used for the same things and increasingly so. Stop pretending they're fundamentally different. They're not.
Guess why the Web is eating everything. It's WORA. And there's close to no competition.
> Phone apps can be good desktop apps with minimal modification
I don't really agree here. Phones and desktops are very different, and have very different UI needs.
> Stop it, please. Smartphones are computers. They're used for the same things, even increasingly so. Stop pretending they're that fundamentally different.
Navigating with a mouse and keyboard is not the same thing at all ad navigating with your fingers on a touchscreen.
The UI features required are different, but like on websites, mobile-first works very well. There are some pretty native-feeling Catalyst apps on macOS: Voice Memos (has flaws), Jira (very native feel), Vectornator (pretty full featured and perfectly native feeling Mac app) and even unmodified iOS apps (Metatext) that run great, as well as smartphone-styled Apps (Telegram) that are great to use.
Sure. Keyboard shortcuts for text. Right click instead of long press. More precision. Hoveing. Swiping back on the trackpad instead of the screen. There are differences - but they're really not that fundamental. Minimal differences.
We can certainly argue about whether mobile first allows an app to use the full potential of the desktop. Maybe not. But I honestly don't care that much if the alternative is no app.
Ah yes. Because an app optimized for touch on a tiny screen will scale beautifully to work with precise controls via keyboard and mouse/trackpad on large and huge screens.
As evidenced by all the professional apps that require little to no modifications.
Small primitive apps with little functionality and barely anything beyond text. Or really limited in their functionality. Or have to hide the absolute vast majority of their interface behind multiple clicks.
Also. Telegram isn't "smartphone-style". It's a native app.
> It sounds like you’re imagining Photoshop in a mobile form factor
Yes, I am. Because here's the list of apps I actually use daily:
- IntelliJ IDEA
- Figma/Sketch
- GitUp
- Terminal/iTerm
Oh, look. All of them cannot be "it's just mobile apps, just blow them up, they will work with little to no midification"
You’re talking about turning desktop apps into phone apps. I’m talking about turning phone apps into desktop apps. The former is hard, the latter is easy.
> Telegram isn't "smartphone-style". It's a native app.
With 95-100% (depending on usage) smartphone UX. If the iOS apps on M1 macs situation wasn’t such a fiasco I’d like to compare the two. My prediction: Loss of keyboard features, drag & drop, not much else (GAIN of image editing).
> Or have to hide the absolute vast majority of their interface behind multiple clicks.
It’s arguable whether this is a good thing or not, but that’s exactly where desktop software is going, likely to reduce clutter. It’s not a necessity for a convergent approach to work, however. Of course you can expand menus and such depending on your platform (one might say in a “responsive” fashion).
> You’re talking about turning desktop apps into phone apps.
I'm talking about the expectations of a desktop app.
> The former is hard, the latter is easy.
It definitely isn't easy.
There's no easy way to transform an app optimised for imprecise touch on small screens into an app requiring precision inputs on a large screen.
There's no easy way to transform an app optimised to display information on a small screen into an app optimised to display information on a large screen.
> With 95-100% (depending on usage) smartphone UX.
It doesn't matter how much usage it gets on the phone. Telegram app on Mac OS is a native app (it's a native app on Windows, too).
> If the iOS apps on M1 macs situation wasn’t such a fiasco
You can't even comprehend why it's a fiasco.
> It’s arguable whether this is a good thing or not, but that’s exactly where desktop software is going, likely to reduce clutter.
It's not "reducing clutter". It's people who spend most of their lives on the phone not understanding how desktop software works and its requirements. You are a prime example of one such person.
> Of course you can expand menus and such depending on your platform
There's no easy way to "just expand the menus". Then you either get a bad experience on the small screen or a bad experience on the big screen, because requirements are completely different.
> Navigating with a mouse and keyboard is not the same thing at all ad navigating with your fingers on a touchscreen.
I wonder how many computer users are actually using a mouse in 2021. I reckon most people use laptops now, which means that people are navigating by touch, either with a trackpad or a touchscreen (I think lots of window laptops have touch screens?)
There’s no way to get actual numbers on this…. is there?
IMHO using a touchpad on a laptop is still much closer to using a mouse than using an iPhone or iPad with touch input. I think it's mainly because the touchpad is so closely integrated with the keyboard. For instance a touch-screen MBP (with the touch screen as replacement of the touchpad) would destroy this close keyboard/touchpad relationship.
It's just the hardware that's not user-facing which is similar between smartphones and computers. But the whole input model is fundamentally different. Software must follow and not dictate how humans interact with the hardware, and that's fundamentally different between smartphones, tablets and computers.
> Oh god damn it. This space is severely under-served. It's only filled by Xamarin Forms, React Native (kinda), Flutter and web. They all kind of suck for desktop apps
This might prove the OPs point, that trying to target both creates a mediocre middle ground? And could explain why you're unsatisfied with approaches that chose to do that.
Extra platforms mean more maintenance effort of course and desktop platforms tend to be less forgiving regarding widget differences, which tilts the playing field against mobile-first toolkits.
Apple has the advantage of having visually converged for a large part, making differences less noticeable, and of course they control their platform.
I tend to think a desktop toolkit would have the best chance at providing a toolkit for both platforms. It’ll have to support touch input and other smartphone-isms either way, as Windows runs on tablets, Mac users expect flawless touchpad interactions and Linux runs on tablets and phones.
I read somewhere when someone was criticizing Rust for GUI stuff, that lack of OO and inheritance can be a real problem. Is that also true for Clojure?
Modern languages like Rust/Go/Clojure typically incorporate polymorphism without the need of inheritance and get more out of it rather than less. The type of polymorphism and malleability you get from Clojure is much more expressive than class inheritance.
You're losing the OO mental model, but gain very loose coupling (data coupling) and code that is easier to reason about. (The trade-off here is not for everyone though.)
On top of that, Clojure is an extremely flexible, interactive language, which is fantastic especially for GUI programming (as mentioned in the article).
GUIs are one of the domains where object orientation shines. The ability to express in code a set of physically bounded widgets with visual a representation that matches their state is probably a biggest win for OOP. FP, I guess its not that intuitive.
You criticize Electron and then proceed to make pretty much the same mistake by using ... JVM. I don't think this is going to fly as a better alternative to JavaScript. It's just one evil exchanged for another.
And I'll be extremely surprised if you manage to do smooth animations at 144Hz in Clojure without killing battery life.
Why use the language with the most horrible startup time for a GUI framework though? The core might be C/C++, but users will write their app in Clojure. GraalVM also still has big limitations.
1) Author wants to use Clojure and is funded by a Clojure organization.
2) Author wants write-once/run-anywhere, which the JVM provides.
3) Author wants live interactive development which Clojure can provide.
4) Author wants multi-core support, and fast runtime, which JVM provides.
5) Author wants a declarative data oriented approach, which Clojure is good at.
Finally, the author acknowledges they are concerned about startup time, but believe their is hope either with GraalVM native compilation support, or a custom Clojure runtime.
There would still be JVM startup time even if some parts of the process delegate to native code. Though the OP acknowledges this and still thinks this is the best approach (and plans on mitigating the issue as much as possible). I'm of a similar mind: desktop apps tend to be long-lived and a few tens or even hundreds of milliseconds at startup shouldn't really matter.
JVM startup time is not great but it's not horrible either. Especially in the context of desktop apps that are often started once and kept running for a long time. With some care it is definitely possible to spin up a JVM and display an initialized window within 1 or 2 seconds. Which is roughly the same time as Gimp or Krita or Thunderbird take to startup on my machines.
I even have a few git-style CLI tools that bootstrap a JVM on each invocation without using optimized graalvm/native images.
JWM (a window library that I plan to use) shows a fully initialized window in 600-700 ms on M1 mac for me. That’s without Clojure, but with JVM overhead
Just to add some reference to why it can take a long time to start a REPL (for example):
> As it turns out, Clojure start time is a complicated and multi-dimensional topic. Clojure projects are slow to start not only because of JVM — JVM itself starts in ~50 ms — but because of JVM specifics the classes are loaded slowly. Clojure projects are slow to start not only because of Clojure — Clojure itself starts in ~1 second — but because of Clojure specifics, the namespaces, especially not AOT-compiled one, are loaded slowly. And so on.
My experience playing the Java Minecraft tells me that it's going to be a challenge to render smooth and consistent animations on top of the JVM. The garbage collector tends to create huge frame drops. And 144Hz and more is definitely possible in Chrome/Edge.
Minecraft is (used to be?) a badly optimized game, you can hardly take away any conclusion from that.
And the JVM has multiple of the best GCs out of any runtime, with a specific low latency one which promises <1 ms stops, at which point the OS’s scheduler will have more latency. Also, it’s not like JS is not a GCd language..
Accessibility. Outside of that everything seems reasonable. But I don't know much about cross-platforms GUI. I expect someone will come and explain that Delphi or something like that already does everything the author wants except for the integration with Clojure.
I would add that I'm not sure which value Clojure provides here. Does the REPL adds anything to the live-reload environment? If not, the choice of Clojure seems to be arbitrary.