[HN Gopher] The complexity that lives in the GUI
       ___________________________________________________________________
        
       The complexity that lives in the GUI
        
       Author : yes_but_no
       Score  : 304 points
       Date   : 2021-02-14 10:41 UTC (12 hours ago)
        
 (HTM) web link (blog.royalsloth.eu)
 (TXT) w3m dump (blog.royalsloth.eu)
        
       | dale_glass wrote:
       | What kind of GUI app has a performance problem with the message
       | bus?
       | 
       | We're in the age of 4K, 60 FPS rendering. If any GUI application
       | has a message bus that's strained enough to impact performance,
       | then either the application isn't made for humans (because if all
       | that stuff is doing anything it'd result in a screen updating far
       | faster than it could be read), or there's some horrible bug
       | somewhere that produces a flood.
        
         | rwmj wrote:
         | In reality none, but developers often think it's not a "real"
         | message bus unless you have to install it on a separate cluster
         | of machines (like AMQP), or it breaks your desktop randomly
         | (dbus). The idea that a message bus could be part of the
         | application and very lightweight is unexpected.
        
       | spion wrote:
       | > I still don't know what the proper solution to this problem
       | would be. Keep your state manipulations as simple as possible and
       | try not to share any data between different models. Every time I
       | went forward with some fancy listener-binding mechanisms, I've
       | ended up causing subtle circular listener recalculations that
       | were extremely hard to debug.
       | 
       | The answer for me has been pervasive use of MobX computeds
       | everywhere. See https://mobx.js.org/getting-started
        
       | smhg wrote:
       | > There is also another way of making GUIs called Immediate Mode
       | that is commonly used for drawing user interfaces in games. In
       | this mode the GUI components are no longer subscribing and
       | waiting for events to come, but are instead a part of the main
       | loop that runs at 60 fps and re-render themselves based on the
       | current "global" state.
       | 
       | > Immediate Mode GUIs somehow never reached the critical mass and
       | you are probably not going to find it outside of the games
       | industry.
       | 
       | Isn't this what React is built on? I think this was part of the
       | 'original' selling point by Pete Hunt:
       | https://youtu.be/x7cQ3mrcKaY (2013). Around 20:00 in that video
       | he compares React with the Doom3 engine.
        
         | [deleted]
        
         | jonathanaird wrote:
         | Yes, Flutter does this as well and they're both very popular.
         | When done properly, UI is a pure function of state and you have
         | some simple mechanism for notifying the UI that state has
         | changed.
        
           | millstone wrote:
           | How does this work with e.g. a text editor? It doesn't seem
           | practical to have your UI be a pure function of an input
           | which is 100+ MBs of text and formatting.
        
       | strictfp wrote:
       | You can also use the original web page model; the state is on the
       | server, every button click generates an action to change state,
       | and the updated ui is regenerated fron scratch based on the new
       | state.
        
       | jonathanstrange wrote:
       | Here is my personal opinion on it (not sure if it's the right
       | one, though): Write the GUI independently of the model, let the
       | GUI components update and communicate among each other as they
       | like, and perform some validation in these GUI components.
       | Translate between model and view only at one well-defined point
       | at which there is also a final validation at the model side, and
       | make sure model and view are only loosely coupled.
       | 
       | Good GUIs have way too many requirements to be controlled (via a
       | controller) by the model. As a typical example, the fact that a
       | button should only be activated if there is data in a textbox has
       | usually nothing to do with the underlying model. The model should
       | only ever contain valid and consistent data.
        
         | jayd16 wrote:
         | I agree but inevitably you get team members who want to "reduce
         | complexity" by removing similar datatypes. One man's clean
         | adapter is another's DRY violation.
        
         | bob1029 wrote:
         | This is basically our approach with Blazor now. I am not sure
         | Microsoft has even picked up on or documented our pattern yet,
         | but we do something where:
         | 
         | 1) Define a domain model + service(s) that fundamentally
         | addresses the logical business functionality without any notion
         | of a specific UI or its stateful nature. This service should be
         | written in terms of a singleton and injected as such. This is
         | something that should be able to be 100% unit testable, and
         | could be exposed as a prototype in a console application or
         | back a JSON API if necessary (i.e. pretend you are writing a
         | SAAS).
         | 
         | 2) Define UI state service(s) that take dependency on one or
         | more domain services. These should be scoped around the logical
         | UI activity that is being carried out, and no further. The goal
         | is to minimize their extent because as we know more state to
         | manage means more pain. These would be injected as Scoped
         | dependencies (in our use of Server-Side Blazor), such that a
         | unique instance is available per user session. Examples of
         | these might be things like LoginService, UserStateService,
         | ShoppingCartService, CheckoutService, etc. These services are
         | not allowed to directly alter the domain model, and must pass
         | through domain service methods on injected members. Keep in
         | mind that DI is really powerful, so you can even have a
         | hierarchy of these types if you want to better organize UI
         | event propagation and handling.
         | 
         | 3) Define the actual UI (razor components). These use the
         | @inject statement to pull in the UI state services from 2
         | above. In each components' OnRender method, we subscribe to
         | relevant update event(s) in the UI service(s) in order to know
         | when to redraw the component (i.e. StateHasChanged). Typically,
         | we find that components usually only inject one or 2 UI state
         | services at a time. Needing to subscribe to many UI state
         | events in a single component might be a sign you should create
         | a new one to better manage that interaction.
         | 
         | This is our approach for isolating the domain services from the
         | actual UI interactions and state around them. We find that with
         | this approach, our UI is quite barren in terms of code. It
         | really is pure HTML/CSS (with a tiny JS shim) and some minor
         | interfacing to methods and properties on the UI state services.
         | This is the closest realistic thing I've seen so far in terms
         | of the fabled frontend/backend isolation principle. Most of the
         | "nasty" happens in the middle tier above, but it is still well-
         | organized and easy to test. By enforcing immutability on the
         | domain services, we ensure discipline with how the UI state
         | services must consume the model. Blazor then goes on to
         | eliminate entire classes of ridiculous bullshit by not forcing
         | us to produce and then consume an arbitrary wire protocol to
         | get to our data.
        
           | pbourke wrote:
           | Very interesting - thanks for posting this.
           | 
           | > By enforcing immutability on the domain services, we ensure
           | discipline with how the UI state services must consume the
           | model
           | 
           | Could you expand on that part? Do you mean that your domain
           | services use an append-only approach to manage state?
        
             | bob1029 wrote:
             | Not necessarily append-only, but making sure that when we
             | get a copy of something from a method - i.e. GetUser(),
             | that the UI or its state services cannot cause mutations to
             | propagate back down into the store without first going
             | through another explicit method like UpdateUserName().
             | 
             | We don't play around with event sourcing right now, but it
             | might be feasible at and above the UI state services if we
             | wanted to do things like replay a user interaction with our
             | app. The only caveat is that our domain services are very
             | very messy in terms of side effects, so the practical
             | utility is bounded to isolated UI testing.
        
           | UnpossibleJim wrote:
           | First, thanks for this response. It's super interesting and
           | get's my mind spun up in a lot of directions it wasn't going
           | before.
           | 
           | Second, with this type of modeling on the separation of GUIs
           | and system, what type of movement do you think there will be
           | in Microsoft and Google going towards an even more minimal
           | computer, almost entirely reliant on the cloud space for
           | compute power. Google's machines are practically there, but
           | from what you've mentioned above seems like a more fully
           | "realized" approach than Google's.
        
         | kaoD wrote:
         | This sounds exactly like what I settled on as React+Redux best
         | practices. There's UI logic+data (that belongs in components)
         | and business logic+data (that belongs in the Redux store)
         | loosely coupled via actions and selectors.
        
       | panic wrote:
       | I've found the best way to handle this problem is a variation on
       | "lift the state up", but instead of binding synchronous listeners
       | to state changes in the model, have these state changes mark any
       | involved views as "dirty". Then, after all events have been
       | processed for the current run loop, go through all the dirty
       | views and call a single update function on each.
       | 
       | For the example given in the article, the update function could
       | look something like                   def View.update():
       | if model.lightTurnedOn:             self.backgroundColor = red
       | else:             self.backgroundColor = ibmGray
       | 
       | This way, all view property changes happen in one place, where
       | you can read the code and understand how the view will appear in
       | each possible state. Circular listener loops are impossible, and
       | view properties for animations can even be computed by calling
       | update twice (once before and once after the state change).
        
         | BiteCode_dev wrote:
         | That's basically what modern JS frontend frameworks do as well.
         | I suppose as soon as something becomes too complex, keeping
         | track of all interactions is just too complicated and
         | rerendering the entire world efficiently looks like an easier
         | problem to deal with.
         | 
         | I like the current trend of going back to renderless components
         | as well. This way you separate the state changes from the way
         | it looks like. Feels like each component is a miniature MVC
         | framework with front and a back.
        
           | panic wrote:
           | _> I suppose as soon as something becomes too complex,
           | keeping track of all interactions is just too complicated and
           | rerendering the entire world efficiently looks like an easier
           | problem to deal with._
           | 
           | In fact, it can actually be less work, since you're
           | coalescing changes into a single update() call rather than
           | sprinkling them across observer callbacks. Also, if your
           | update function starts running too slowly, you can always
           | make it more precise by keeping track of which states have
           | changed internally to the view. For example, if setting the
           | background color takes a long time for whatever reason, you
           | can do something like this:                   def
           | View.update():           if self.lightWasTurnedOn !=
           | model.lightTurnedOn:             if model.lightTurnedOn:
           | self.backgroundColor = red             else:
           | self.backgroundColor = ibmGray
           | self.lightWasTurnedOn = model.lightTurnedOn
           | 
           | Now backgroundColor will only be set if lightTurnedOn
           | actually changed since the last update.
        
         | badsectoracula wrote:
         | FWIW this is a common approach - you can see it even in Win32
         | API's invalidation mode (which goes back to the 80s) where you
         | mark parts of the UI as "invalid" and eventually (when you
         | start pumping events again in the main loop) the window system
         | combines all the invalid areas and sends you a message to paint
         | ("validate") the window.
         | 
         | Several toolkits and frameworks provide for "after all other
         | events have been processed" hooks/events for such logic, e.g.
         | Delphi has the TApplication.OnIdle event and later versions as
         | well as Lazarus/FreePascal have dedicated controls for this
         | event and "idle timers" meant to be used for updating any
         | invalidated parts of the UI after all other events have
         | finished. Similarly wxWidgets has wxEVT_UPDATE_UI and i'm
         | almost certain that Qt has something similar too - though i
         | can't find it now.
        
       | mwcampbell wrote:
       | > Immediate Mode GUIs somehow never reached the critical mass and
       | you are probably not going to find it outside of the games
       | industry.
       | 
       | And that's a good thing, because so far, AFAIK, no one has
       | implemented accessibility (e.g. for screen readers) in an
       | immediate-mode GUI. I hope to work on that problem sometime soon.
        
       | codeflo wrote:
       | I've recently become interested in immediate mode UIs, and find
       | that there are surprising similarities to React. In both cases,
       | components are just functions that must explicitly render their
       | child components and pass down any shared state. It's
       | conceptually a very clean way to handle UI state.
       | 
       | However, React introduces a lot of complexity to avoid
       | unnecessary DOM updates, which makes me wonder about the
       | viability of an immediate mode GUI in the browser using canvas.
        
         | wdfx wrote:
         | I've worked on an app with an IMGUI on canvas. It was amazingly
         | fast and responsive. Nothing which was built after that to try
         | and replace it was anywhere near as performant.
        
           | TeMPOraL wrote:
           | Given how IMGUI is a bunch of branches with code that redraws
           | the same pixels all the time, 60x per second, I'm still
           | surprised this is considered very (if not most) efficient UI.
           | It would imply retained-mode UI frameworks are _strongly_
           | undershooting their theoretically possible performance.
        
             | flohofwoe wrote:
             | See for yourself ;)
             | 
             | https://floooh.github.io/sokol-html5/imgui-highdpi-
             | sapp.html
             | 
             | There are also demos for other immediate-mode UI systems on
             | the parent page (Nuklear and microui):
             | 
             | https://floooh.github.io/sokol-html5/
             | 
             | As I wrote in another thread, Immediate Mode UI doesn't
             | imply how the UI rendering is implemented, only how the API
             | works.
        
         | fassssst wrote:
         | The problem, as always, is how to make immediate mode stuff
         | work with accessibility tools.
         | 
         | The value of essentially creating and mutating a tree structure
         | like the DOM is that things like screen readers and UI
         | automation tools can read it. With canvas they just see a big
         | bitmap.
        
           | [deleted]
        
         | rzzzt wrote:
         | These are WebGL/WASM based:
         | 
         | - https://github.com/Erkaman/pnp-gui
         | 
         | - https://github.com/jnmaloney/WebGui
         | 
         | I guess accessibility is what takes the biggest hit with these
         | implementations.
        
       | hermitcrab wrote:
       | I find that Qt signals and slots works pretty well for managing
       | complexity in GUIs. In this case you would connect a signal that
       | is emitted when inventory table changes state to a slot that
       | changes the appearance in the user avatar. This would probably be
       | done in the pane/dialog that contains them both. They 2
       | components would remain nicely decoupled.
       | 
       | This approach isn't without it's own challenges of course. For
       | example it is sometimes hard to keep track of what is going on in
       | complex applications with cascades of signals and slots. Some
       | people also hate the fact that signals and slots use auto
       | generated code, but I have never really found that to be a
       | problem in practise.
        
         | nyanpasu64 wrote:
         | If one user interaction triggers a "on user interaction"
         | signal, which causes "value changed" signals to fire, is it
         | possible that a widget which depends on multiple of these to
         | get redrawn multiple times?
         | 
         | I'm optimistic about Qt 6's QProperty (I don't know how it
         | compares to FRP or, as someone else mentioned, MobX), but Qt 6
         | currently does not have KDE libraries, or Linux themes to fit
         | into desktop environments or distros.
        
           | hermitcrab wrote:
           | Yes, potentially, if that is how you program it. But
           | typically you call QWidget::update() which repaints the
           | widget next time through the event loop, if anything changes.
           | Calling QWidget::update() several times will usually only
           | call one repaint.
           | 
           | One thing you have to watch out for it that programmatic
           | changes can fire signals. If you don't want this you have to
           | add
           | QObject::blockSignals(true);...QObject::blockSignals(false);
           | around your call.
        
         | CarVac wrote:
         | I agree, but isn't that just a message bus (mentioned in the
         | article)?
        
           | hermitcrab wrote:
           | I never really thought of it in those terms. But I guess
           | signals and slots are just way to publish/subscribe to a
           | message bus. It is quite a nice abstraction of it IMHO (which
           | is perhaps why it didn't occur to me!).
        
           | joezydeco wrote:
           | It is, he's just pointing out that the Qt developers figured
           | this out a long time ago. Web developers like to think
           | they're pioneers when it comes to this stuff.
        
       | FpUser wrote:
       | >"the GUI always ends up being a ridiculous mess"
       | 
       | Well no. There are applications with nice well thought out GUIs.
       | 
       | >"Congratulations, a large amount of your effort will go towards
       | resolving weird message bus problems as opposed to writing the
       | business logic of your app"
       | 
       | Sorry but I do not resolve "weird message problems". I use my own
       | publish-subscribe mostly asynchronous message bus for my GUI apps
       | (actually I use it also for non GUI parts as well). Components
       | (visible or not and including running threads) can subscribe to
       | events. It does not exhibit any performance / memory problems and
       | in combination with the global app state object makes programming
       | interactions a piece of cake.
        
         | nikisweeting wrote:
         | Every codebase is clean and elegant when in the head of a
         | single person. How big is your codebase and how many people
         | work on it?
        
           | FpUser wrote:
           | >"Every codebase is clean and elegant when in the head of a
           | single person."
           | 
           | Simply not true. I recently had to salvage business rules
           | from a codebase written by single person over the course of
           | many years. It was probably one of the messiest code I've
           | ever seen.
           | 
           | >"How big is your codebase and how many people work on it"
           | 
           | Depends on a project. On some I work alone. Some had 2-3
           | persons. The biggest team I've ever had to lead was about 35
           | people (not all developers). Properly organizing and
           | splitting work and using mostly experienced people the
           | resulting code was very decent and quite possible to grasp.
           | Also well documented.
        
             | nikisweeting wrote:
             | I don't mean objectively clean and elegant, I mean in the
             | eye of the beholder it's easy for a tiny codebase to be
             | clean.
             | 
             | But a system that may be clean and elegant for a 1-person
             | gig may end up falling to bits if you attempt to have 100
             | people working on it.
        
       | vikingcaffiene wrote:
       | I've written front end for nearly all of my 13 years in this
       | field. I've written some absolute dumpster fires in my time and
       | have also written some very large and complex UI's successfully.
       | I would like to think I have some insight to add here. I agree
       | with a lot of the points brought up in this article. I'd like to
       | add a few more points about why I think FE can be such a pain to
       | get right:
       | 
       | 1. Mixed concerns and a lack of application layering. In React
       | code bases and others like them, its not uncommon for me to find
       | business logic embedded directly inside the components
       | themselves. This makes the component inherently coupled to the
       | specific implementation its being used and can only be re-usable
       | in other contexts by passing in modifier flags to the props etc.
       | In my opinion, components should be mostly of the stateless
       | flavor and delegate anything not directly related to their
       | immediate UI concerns elsewhere. This increases the likelihood of
       | re-usability of your components and makes testing business and
       | component logic much much more straight forward.
       | 
       | 2. This might just be my personal experience, but I've noticed a
       | bit of a dismissive attitude around design patterns and
       | traditional computer science principles among my front end
       | brethren. YAGNI and all that. While I think its fair that the UI
       | != to the back end, I think frequently the baby gets thrown out
       | with the bath water. For instance, I frequently leverage
       | dependency injection in my front end work as a way to make my
       | code more flexible and testable. It's hard to sell the benefits
       | of something like DI until your application grows in complexity
       | to the point that you are dealing with things like nested mocks
       | in your tests and/or need to make a non trivial change to some
       | piece of functionality. I've been seeing the winds start to shift
       | a bit on this which is encouraging.
       | 
       | 3. Most of the time there is little to no overall _conceptual
       | integrity_ to a given front end code base. Its uncommon for me to
       | come into an existing code base and have anyone be able to tell
       | me what the architecture is comprised of. Things like what logic
       | lives where and why. I'm not saying people _don't_ do this, but
       | in the more gnarly code bases I've encountered, this "accidental
       | architecture" is more common.
       | 
       | 4. Front end is still see as "easy" and the place you stick the
       | less experienced engineers first. I sincerely hope this doesn't
       | come off like I am gatekeeping or anything. I work with some
       | absolutely brilliant people who are only a year or two into their
       | career and much smarter than me. IMO its less about skill and
       | more about having been burned enough times to know where
       | unexpected complexity lie.
       | 
       | I love front end. Its challenging and interesting and maddening.
       | My hope is that it will continue to mature and grow to the point
       | that it gets the same respect as other disciplines within this
       | field. These days I work full stack so its not all I do, but it
       | will always be my favorite. :)
        
       | ChrisMarshallNY wrote:
       | This is a great discussion on the challenges of designing UI for
       | complex applications.
       | 
       | In the aggregate, what ends up being most effective for me, is
       | rapid prototyping, and "paving the bare spots."[0]
       | 
       | I find that I do a terrible job of predicting user mental models.
       | 
       | The rub with prototypes, is that they can't be lash-ups, as they
       | _inevitably_ end up as ship code. This means that a lot of good
       | code will get binned. It just needs to be accepted and
       | anticipated. Sometimes, I can create a standalone project for
       | code that needs to go, but I still feel has a future.
       | 
       | So there's always a need for fundamental high quality.
       | 
       | What has been useful to me, is Apple's TestFlight[1]. I write
       | Apple software, and TestFlight is their beta distribution system.
       | 
       | I start a project at a very nascent stage, and use TestFlight to
       | prototype it. I use an "always beta" quality approach, so the app
       | is constantly at ship quality; although incomplete.
       | 
       | It forces me to maintain high quality, and allows all
       | stakeholders to participate, even at very early stages. It's
       | _extremely_ motivating. The level of enthusiasm is off the
       | charts. In fact, the biggest challenge is keeping people in low
       | orbit, so they don't start thinking that you are a "WIZZARD"
       | [sic].
       | 
       | It also makes shopping around for funding and support easy. You
       | just loop people into the TestFlight group. Since the app is
       | already at high quality, there's no need for chaperones or
       | sacrifices to The Demo Gods.
       | 
       | I like that it keeps development totally focused on the actual
       | user experience. They look at the application entirely
       | differently from me.
       | 
       | [0] https://littlegreenviper.com/miscellany/the-road-most-
       | travel...
       | 
       | [1] https://developer.apple.com/testflight/
        
         | tebbers wrote:
         | Great post and that first link is brilliant, thank you for
         | sharing. I agree with you regarding your attitude to developing
         | software - just get it out there first and then adjust the UX
         | later.
        
       | mattgreenrocks wrote:
       | I'm not fond of the reasoning employed in the message bus section
       | that decries the fact that misuse results in a big mess.
       | 
       | No programming paradigm can stand up to rushed/flawed mental
       | models.
       | 
       | The domain can become quite complex; it is wishful thinking to
       | believe that a single approach could drain it of all complexity.
        
       | [deleted]
        
       | [deleted]
        
       | hyberbole_1234 wrote:
       | The best approach I've seen is separating State and Views and
       | having some form of property diffing.                 struct
       | LightState {         var isLightOn: Bool       }
       | class LightViewController {                let lightView =
       | UIView()       }              class StateDirector<
       | LightState,         LightViewController> {                  let
       | lightState: LightState                  init(state: LightState) {
       | self.lightState = state         }              func bind(
       | view: LightViewController         ) {                     //
       | Every time is turned on changed call this
       | LightState.add(             listener: self,             for
       | keyPath: \.isTurnedOn,             handle:
       | .method(Self.handleLightChange)         }              func
       | handleLightChange(isOn: Bool) {
       | view.lightView.backgroundColor = isOn ?             .green : .red
       | }       }
       | 
       | This allows clear separation of State, View and Changes.
       | 
       | You can then just model your state in your reducer and "simulate
       | your views" inside Unit tests
        
       | diggan wrote:
       | I'm fairly sure I've said this before here and elsewhere, but
       | bears repeating, especially for this post.
       | 
       | Statecharts is currently probably the most undervalued tool when
       | it comes to programming GUIs with state. Statecharts are a
       | continuation of state machines, but with less footguns and better
       | abstractions to be able to build larger systems.
       | 
       | In the end, you either build a GUI that are using state machines
       | implicitly, or explicitly. Tends to be less prone to bugs if you
       | do so explicitly.
       | 
       | If you're interested, here is some starting points (copied from
       | an older comment of mine):
       | 
       | Here is the initial paper from David Harel: STATECHARTS: A VISUAL
       | FORMALISM FOR COMPLEX SYSTEMS (1987) -
       | https://www.inf.ed.ac.uk/teaching/courses/seoc/2005_2006/res...
       | 
       | Website with lots of info and resources:
       | https://statecharts.github.io/
       | 
       | And finally a very well made JS library by David Khourshid that
       | gives you lots of power leveraging statecharts:
       | https://github.com/davidkpiano
       | 
       | While we're at it, here are some links to previous submissions on
       | HN regarding statecharts with lots of useful and interesting
       | information/experiences:
       | 
       | - https://news.ycombinator.com/item?id=18483704
       | 
       | - https://news.ycombinator.com/item?id=15835005
       | 
       | - https://news.ycombinator.com/item?id=21867990
       | 
       | - https://news.ycombinator.com/item?id=16606379
       | 
       | - https://news.ycombinator.com/item?id=22093176
        
         | sillysaurusx wrote:
         | I really don't mean to be dismissive of your contribution, so
         | please take this in the best light possible:
         | 
         | Yuck. Use continuations with anonymous fns that tell the GUI
         | what to do next. The state is in the closure implicitly!
         | 
         | I wish I had an articulate counterargument, but just look at
         | this: https://xstate.js.org/docs/#promise-example
         | 
         |  _Statecharts are a formalism for modeling stateful, reactive
         | systems._
         | 
         | Beautiful is in the eye of the beholder, and never moreso when
         | the programmer is blind to the cost of repetition and state.
         | 
         | Arc got it right. http://www.paulgraham.com/arcchallenge.html
         | 
         |  _Write a program that causes the url said
         | (e.g.http://localhost:port/said) to produce a page with an
         | input field and a submit button. When the submit button is
         | pressed, that should produce a second page with a single link
         | saying "click here." When that is clicked it should lead to a
         | third page that says "you said: ..." where ... is whatever the
         | user typed in the original input field. The third page must
         | only show what the user actually typed. I.e. the value entered
         | in the input field must not be passed in the url, or it would
         | be possible to change the behavior of the final page by editing
         | the url._                 (defop said req         (aform
         | [onlink "click here" (pr "you said: " (arg _ "foo"))]
         | (input "foo")            (submit)))
        
           | diggan wrote:
           | In no way was that dismissive, I thank you for providing
           | another perspective, that's always welcome in my book!
           | 
           | I don't necessarily agree with all the implementation details
           | of xstate, in particular to where the logic tend to be
           | located in practice, and the reliance on the Actor model for
           | many things in the wild. I rather try to guide people to
           | Statecharts as a paradigm overall, and if you happen to use
           | JS, I think xstate is probably the most mature library there.
           | But as all libraries/frameworks, they can be over-relied
           | upon.
           | 
           | If you're in the Clojure/Script world, which is where I
           | mainly locate myself, then https://lucywang000.github.io/clj-
           | statecharts/ is all you need and so far the library I've had
           | the best luck with.
        
             | davidkpiano wrote:
             | XState creator here, what kinds of implementation details
             | do you not agree with? Curious to know.
        
             | sillysaurusx wrote:
             | I came back to edit my comment and remove the harsh words,
             | but you left a very kind response. Sorry.
             | 
             | This is actually a fascinating example:
             | https://lucywang000.github.io/clj-statecharts/docs/get-
             | start...
             | 
             | ... because it highlights precisely my criticism.
             | ;; define the machine       (def machine
             | (fsm/machine          {:id      :lights           :initial
             | :red           :context nil           :states
             | {:green  {:on                     {:timer {:target  :yellow
             | :actions (fn [& _]
             | (println "transitioned to :yellow!"))
             | }}}            :yellow {:on                     {:timer
             | :red}}            :red    {:on                     {:timer
             | :green}}}                :on {:power-outage :red}
             | }))
             | 
             | I just ... don't understand why anyone would do it this
             | way. The code itself already says what to do. Adding this
             | sort of data only subtracts from clarity with no additional
             | flexibility.
             | 
             | You might argue that the data model makes it flexible. But
             | I look at that and go, that's what a function is for. The
             | only thing you need is `(println "transitioned to
             | :yellow!")` inside of a function called transition-to-
             | yellow, or if you're feeling adventurous, a function called
             | transition-to which takes yellow as an argument.
        
               | diggan wrote:
               | I guess your comment highlight the real problem of trying
               | to describe tools for handling complex scenarios with
               | just simple examples, they often don't make sense because
               | the first thought is always "Why not just have one
               | function instead of all of that?".
               | 
               | If you're just printing some text to the console when
               | doing one thing, then surely one function is enough. Once
               | you start having some state and different views depending
               | on values, normal state machines might be enough. But
               | eventually, your application might grow so much in scope
               | that you want to have state machines nested in other
               | state machines, and you want to avoid the classic "state
               | explosion" that you end up with when using state
               | machines, then Statecharts are a very useful tool.
               | 
               | But before that, do the simplest thing that can work for
               | your use case, complexity is evil and all that...
               | Statecharts are simply a tool for handling complex and
               | intertwined state, not for simple stoplights. Unless
               | those stoplights are all connected to each other and also
               | control/read other state machines, then Statecharts might
               | be right for you.
               | 
               | Not sure if you took a look at the links I put before,
               | but the https://statecharts.github.io/ website has a "Why
               | should you use statecharts?" section with small
               | descriptions and links to more elaborate information as
               | well. Might give you a better view than what I can give.
        
               | sillysaurusx wrote:
               | Fair! I think we just have different perspectives. HN is
               | enormously complex (it has far more complexity than most
               | people realize or truly appreciate), yet it handles every
               | case without any state machine:
               | https://github.com/shawwn/arc/blob/arc3.1/news.arc
               | 
               | And it's nothing but a long list of functions that use
               | closures.
               | 
               | I did take a look at your examples. I just ... well,
               | we'll have to agree to disagree, and I'll try not to be
               | so harsh in my criticisms of what I perceive to be ugly
               | ideas. Just because I think an idea is bad, doesn't mean
               | it's bad.
               | 
               | But there is one specific critique: the state machine
               | approach will make programs longer, and consciously
               | choosing to make programs longer seems fraught with
               | danger. Every additional character you type is an
               | additional character for bugs to hide in.
               | 
               | This moves the subjective "I don't like the style" to
               | something concrete: What's the shortest program you can
               | write with a state machine? My argument is that it's
               | "significantly longer than the equivalent program without
               | a state machine."
        
               | eternalban wrote:
               | Given a bug-free spec to code generator, your only source
               | of bugs can be in the state chart specification, so not
               | bugs but rather errors.
        
               | Folcon wrote:
               | From my perspective, it's better as a data structure
               | because I can do more with it.
               | 
               | It's much easier for me to introspect, and I can easily
               | build dynamic state-machines by changing a data
               | structure, take a look at interceptors[0] as an example.
               | There the stack is dynamically alterable based on what is
               | within the request and each piece of middleware can look
               | at the current context, analyse it and behave
               | accordingly.
               | 
               | I write a lot of workflow based systems with dynamically
               | changing functionality based on user input. This sort of
               | thing is invaluable in that context.
               | 
               | - [0]: http://pedestal.io/guides/what-is-an-interceptor
        
             | Folcon wrote:
             | Thank you for this, I didn't know this existed for
             | clojurescript =)... Much appreciated!
        
         | bmitc wrote:
         | There is an edX course that covers state charts. It's
         | excellent, and Davis Harel is one of the co-instructors.
         | 
         | https://www.edx.org/course/programming-for-everyone-an-intro...
        
           | pixel_tracing wrote:
           | This isn't really solving issues for most teams, how do you
           | handle maintenance and tech debt of these state charts? And
           | why only javascript example? You're missing mobile
        
             | diggan wrote:
             | Statecharts are the solution to technical debt, not the
             | source, as you're formalizing the possible states the user
             | can be in, and "locking" it to that. You can even apply
             | analysis to your codebase to figure out if you're actually
             | covering all possible states and transitions.
             | 
             | If you take a look at the syllabus of the course, it's not
             | about JavaScript, it's about the formalism and
             | understanding the core concepts, without locking you to a
             | particular technology. Whatever they are teaching in the
             | course, you can apply to JavaScript/Swift as much as you
             | can apply it to Desktop/Mobile.
             | 
             | Disclaimer: haven't actually taken the course, but planning
             | to and I've read the description of it.
        
               | pixel_tracing wrote:
               | This isn't answering my original question, how do you get
               | teams to adopt this?
               | 
               | Until this process is made easy this will always just be
               | a fever dream of fools in ivory towers
        
               | diggan wrote:
               | I'm sorry, your "original" question doesn't seem to exist
               | in your previous comments, so hard for me to answer it...
               | 
               | You get people to adopt technology or paradigms by
               | explaining the benefits and drawbacks of adopting that
               | set of technology/paradigm and then discussing it
               | together with your team/s. Not sure why it would be
               | different for Statecharts compared to any other paradigm?
               | 
               | What process is too hard for you now exactly, to describe
               | states or something? You're already doing this implicitly
               | when you write UI code with state. Statecharts only
               | changes it from being implicit to being explicit. If
               | you're having a hard time actually naming your states,
               | you can use tools like https://sketch.systems/ to explore
               | what you're building with a simple DSL, then implement it
               | properly in the programming language of your choice.
        
               | pixel_tracing wrote:
               | Teams and technical debt go hand in hand. I don't mean to
               | sound snarky but this isn't really practical for large
               | teams to adopt at large companies.
               | 
               | 1. writing code adds "debt" 2. Your solution is to now
               | add state charts too which also adds "debt"
               | 
               | Where are these state charts tracked? Who maintains them?
               | When product asks engineering to change code => you now
               | also update state charts. Added technical debt.
               | 
               | If this is difficult for you to understand (the problem
               | I'm describing is very common at large companies) I'm
               | happy to expand more on it.
               | 
               | Thoughts?
        
               | diggan wrote:
               | I'm not sure if you're a developer or not, I'm just gonna
               | assume you're not in order to hopefully be able to
               | describe things better.
               | 
               | Yes, writing code can add debt, but not all code is debt.
               | "Debty" code is code that can be better, but was needed
               | in order to take shortcuts temporarily, probably in order
               | to trade moving faster now against moving faster in the
               | future. If you're taking shortcuts you're gonna have to
               | fix it in the future, or deal with having to move
               | slower/more careful because of it.
               | 
               | And yes, the solution to code debt is to go back and fix
               | it properly. When it comes to UI programming, I'd argue
               | that doing implicit state machines with conditionals
               | scattered all over the place (which is the de facto
               | standard today everywhere I look), is code debt, which
               | can be fixed by REPLACING it with explicit Statecharts.
               | It can also be fixed in other ways obviously, but besides
               | the point here.
               | 
               | The developers would obviously be responsible for the
               | code they write and the Statecharts are all handled in
               | the same source control system your developers already
               | use (typically Git today), so nothing really changes
               | here.
               | 
               | And yes, when you figure out that you have to change the
               | code to do something different, you're gonna have to ask
               | developer to change the code. The same goes for updating
               | Statecharts (that also exist in the code). If you have to
               | change the states/transitions, you're gonna have to
               | update the code that handles the states/transitions. This
               | is the same no matter if you use Statecharts or not.
               | 
               | In the end, Statecharts is not a programming language
               | itself, it's just a way of doing programming. Basically
               | like how functional programming is a way of programming,
               | or object oriented programming is one way, Statecharts is
               | a different way where you're upfront with what states
               | exist in the application.
        
               | pixel_tracing wrote:
               | Again you've described a paradigm like functional
               | programming but failed to address my original question.
               | I've been programming for over a decade now, and have
               | seen this symptom over and over again. Someone sees shiny
               | new paradigm => realizes is holy grail => fails to see
               | how it fits into _actual_ working teams. My problem is
               | that you are going to have a hard time selling this
               | academic approach to _PRACTICAL_ teams doing every day
               | work with every day deadlines and bottom line business
               | dollars. Until you make this seamless and easy the extra
               | work is just going to be _extra_ work which will be
               | ignored.
               | 
               | I say this as a FAN of Statecharts and functional
               | programming. I myself love this idea. I am looking and
               | prodding people like you for SOLUTIONS to make teams
               | adopt this.
               | 
               | So far you've failed to convince me on how to sell this
               | to teams or make it easy to integrate.
        
               | diggan wrote:
               | > So far you've failed to convince me on how to sell this
               | to teams or make it easy to integrate.
               | 
               | Yeah, because I'm not trying to convince you of anything.
               | It's a tool in your toolbox, use it when you think it's
               | advantageous, otherwise use your other tools. I couldn't
               | care less of what you chose to do or how you "sell this
               | to teams". I'm a developer who simply chooses the best
               | tool for the job, sometimes that's Statecharts and
               | sometimes it's something else. Also don't have any idea
               | about what ideas are circling around in "Academia" as I'm
               | far away from that ecosystem and only focus on shipping
               | working products.
               | 
               | If you're looking for something "easy" in particular,
               | then whatever you don't know is gonna be hard. Such is
               | the life of a developer, where sometimes the simpler way
               | is harder but worth it in the end. React was hard for
               | people to grok in the beginning as well, but that doesn't
               | mean it's bad, it just means people are not familiar with
               | it. If you're just looking for easy solutions then yeah,
               | feel free to stop improving and continue use the stuff
               | you already know or is familiar to you.
               | 
               | So in the end, do what you will with the knowledge and
               | experience I've shared with you, I have zero interest in
               | selling you anything and I'm simply discussing stuff here
               | on HN as I'm curious about things and want to have
               | discussions with people who are also curious about
               | things, but this discussion stopped being productive a
               | long time ago.
        
           | diggan wrote:
           | That's amazing, didn't know that, thanks a lot for sharing!
           | Fitting that it starts today as well :)
        
             | bmitc wrote:
             | No problem! And as just a note, it starts everyday since it
             | is a reoccurring self-paced course. But if I remember
             | correctly, the instructors still answer questions.
             | 
             | I really enjoyed learning about the hierarchical state
             | machines using statecharts.
        
       | chris_wot wrote:
       | Um, isn't this what the Mediator pattern was designed to solve?
       | 
       | https://en.wikipedia.org/wiki/Mediator_pattern
        
       | onion-soup wrote:
       | Don't libraries like react solve this?
        
         | tomaszs wrote:
         | React makes it even harder actually. Angular has some ways to
         | help. Ember has it. Vue - not so much
        
       | protoman3000 wrote:
       | A related question, how do GUIs actually resolve quickly on which
       | element under the cursor a click lands?
       | 
       | How is the click propagated recursively through every component
       | and is the position compared repeatedly at every step?
        
         | badsectoracula wrote:
         | They just enumerate all child windows, these events are not
         | frequent enough for this to be a performance bottleneck. The
         | check is something along the lines of                   Win*
         | GetChildAt(Win* w, int x, int y)         {             size_t
         | i;             if (x >= w->ScreenX1 && y >= w->ScreenY1 &&
         | x <= w->ScreenX2 && y <= w->ScreenY2) {                 for
         | (i=0; i < w->ChildCount; i++) {                     Win* child
         | = GetChildAt(w->Child[i], x, y);                     if (child)
         | return child;                 }                 return w;
         | }             return NULL;         }
         | 
         | Call this on the root window and you have the deepest child at
         | the given coordinates.
         | 
         | (though there is usually a bit extra logic for handling, e.g.,
         | invisible and disabled windows)
        
         | pixel_tracing wrote:
         | I believe this can be resolved using a quad tree to resolve
         | point locations and map them to a view in a view hierarchy
        
       | rwmj wrote:
       | I remember in my first real job I wrote a GUI for an RTOS (all
       | the way up starting with the hardware, device driver, ...). Not
       | knowing anything about how GUIs worked, or about events, I had a
       | main loop which redrew the GUI on every action. This article
       | tells me I wasn't completely wrong, these are called "Immediate
       | Mode" GUIs!
        
       | c-smile wrote:
       | TL;DR: there is no one silver bullet for the UI in general.
       | 
       | As a rule practical and manageable UIs use all these approaches
       | at the same time.
       | 
       | Components, class based and/or functional (fe: UserSection,
       | InventoryTable ), are in principle loosely coupled. They may not
       | know about each other and use messages to communicate.
       | 
       | The Flight by Twitter ( https://flightjs.github.io/ ) was/is
       | probably the first _practical_ thing that pioneered that
       | approach.
       | 
       | The Flight is not even a framework but rather a design principle.
       | 
       | In fact it is an anti-framework - it postulates that in practice
       | each component may have its own optimal internal architecture.
       | One component is better to use React-ivity, another - Angular-ish
       | data binding, etc. On some, immediate mode drawing is the most
       | natural.
       | 
       | Smaller the task (component) - greater the chance to find silver
       | bullet for it.
        
       | 1vuio0pswjnm7 wrote:
       | I rarely ever use a GUI. Certainly not for any recreatonal web
       | use. Unless I'm using a GUI, I do not load the graphics layer. I
       | stay in textmode. No X11, Wayland or whatever is compiled in.
       | Ideally I have a designated computer on the local network that
       | has a graphics layer, a full corporate GUI OS. When I need
       | graphics to view stuff I can send it over the local network to
       | the GUI computer. Otherwise I keep recreational use on text-only
       | computers, away from the whiz-bang corporateOS-controlled
       | computer.
        
       | tomaszs wrote:
       | After 20 years of working with frontends mostly I consider that
       | there are three causes of problems with GUI:
       | 
       | - it is badly designed. If state is too hard to manage it means
       | often that the design is bad
       | 
       | - state is not the most important part of the UI. Interaction and
       | being elastic to changes are the most important things
       | 
       | - frontend is often overengineered in a bad way. When you use bad
       | solutions like React or Redux there is no change there won't be
       | any problems.
        
       | ggm wrote:
       | Neither is between two choices: he's presenting Three.
       | 
       | Small point, but it grated.
        
       | pixel_tracing wrote:
       | Article presents a bunch of problems on UI but no solutions...
       | perplexing.
        
       | phtrivier wrote:
       | This seems to miss the 'ui=f(state, effects)' model that's sort
       | of what's behind react+redux or elm or similar ideas.
       | 
       | Or is that really the same as immediate mode ?
        
       | RoyalSloth wrote:
       | Author here. This is not related to this discussion, but does
       | anybody know what causes these lines to appear in my server logs?
       | <REDACTED> - - [14/Feb/2021:22:26:45 +0100] "GET /posts/the-
       | complexity-that-lives-in-the-gui/ HTTP/1.1" 200 16798 "-"
       | "HackerNews/1391 CFNetwork/1220.1 Darwin/20.3.0"       <REDACTED>
       | - - [14/Feb/2021:22:26:45 +0100] "GET /posts/the-complexity-that-
       | lives-in-the-gui/ HTTP/1.1" 200 16798 "-" "HackerNews/1391
       | CFNetwork/1220.1 Darwin/20.3.0"       <REDACTED> - -
       | [14/Feb/2021:22:26:45 +0100] "GET /posts/the-complexity-that-
       | lives-in-the-gui/ HTTP/1.1" 200 16798 "-" "HackerNews/1391
       | CFNetwork/1220.1 Darwin/20.3.0"
       | 
       | The requests usually come from a certain ip multiple times until
       | fail2ban bans it. It's not just one offender, there are multiple
       | behaving like that.
        
         | tn1 wrote:
         | It's probably an iOS app client for HN. CFNetwork is like their
         | URLConnection I think. You're blocking people who want to read
         | your article!
         | 
         | As for why it occurs so often in quick succession, perhaps
         | there's a bug in the app causing it to fetch several times
         | instead of once.
        
           | RoyalSloth wrote:
           | Thanks, I thought it was a bug in some app and just wanted to
           | be sure, so I don't have to babysit the server.
           | 
           | If anybody knows which app is that, please tell the
           | maintainer that they have a serious bug. It's night here, so
           | I am logging off.
        
       | valand wrote:
       | Another option is injecting an optional dependency without
       | connecting the box.
       | 
       | I see OP doesn't mention this option, but is slightly related to
       | both option 1 (connect the box) and 3 (message bus / event
       | emitter). This option is similar to how OS provides an API to
       | user space application program. For example Windows provides an
       | API to an application to flash its window without exposing all of
       | its state like mentioned in option 1.
       | https://docs.microsoft.com/en-us/windows/win32/api/winuser/n...
       | 
       | Here's the detail:
       | 
       | A self-powered object, let's call it workingIndicatorObject, can
       | be introduced to work on the working indicator. It provides 1.)
       | `getState() -> WORKING/NOT-WORKING` a function to get its state,
       | and 2.) `register() -> None`, a function to register user
       | activities. These functions are dependencies for UserSection
       | component and InventoryTable component respectively. In term of
       | lifetime and hierarchy, it precedes and outlives both components.
       | 
       | The workingIndicatorObject MUST have its own lifecycle to
       | regulate its internal state. Its core lifecycle MUST NOT be
       | managed under the same event loop as the GUI components, assuming
       | the program is written on top of a framework that has it (React,
       | Vue, etc). This is to assure that it doesn't directly affect the
       | components managed by the framework (loose coupling). Although, a
       | binding MAY be made between its state and the framework-managed
       | event loop, for example, in React environment, wrapping it as a
       | React hook. An EventEmitter can also be provided for a component
       | to listen to its internal event loop.
       | 
       | Injecting it as a dependency can use features like React Context
       | or Vue's Provide/Inject pattern. Its consumer must treat it as an
       | optional dependency for it to be safe. For example, the
       | UserSection component can omit drawing the "working" indicator if
       | it doesn't receive the `getState` function as a dependency.
       | 
       | Internally, the workingIndicatorObject can use a set. The set is
       | used to store a unique value (a Symbol in Javascript). `register`
       | function creates a Symbol, stores it in the set, and deletes it
       | after $TIMEOUT, while firing event both at addition and deletion.
       | `getState` function is a function that returns `set.size()`. When
       | bound to the component, an addition to the set fires an "update"
       | command to the component, which redraw the component along its
       | internal state. This is just one example of implementation and
       | there are other simpler ways to have the same behaving
       | workingIndicatorObject.
       | 
       | This approach allows UserSection and InventoryTable to only know
       | `getState()` and `register()` function and nothing else, and
       | having both of them optional. Using static type system such as
       | TypeScript can help a lot in this since we can pin down the
       | signatures of both function to `null | () => WORKING |
       | NOT_WORKING` and `null | () => void`, where we can enforce both
       | dependent components to check if it exists and to call it with
       | the correct types, otherwise the compiler yells.
        
       | cpill wrote:
       | > I'd love to hear what the functional programming camp has to
       | say about this problem, but I guess they are too busy with
       | inventing yet another $20 term for a 5 cent concept
       | 
       | hA! Hit the nail on the head :P
        
       | brundolf wrote:
       | The fundamental challenge of GUIs is that they have state as a
       | core concern. Unlike most systems, state is not an implementation
       | detail that can be refactored away. It is central to the problem
       | domain. The _end user_ sees and cares about having a stateful
       | system.
       | 
       | The hardest part of dealing with state in a complex system is
       | maintaining consistency in different places. Some instances of
       | this this can be avoided by creating single-sources-of-truth, but
       | in other cases you can't unify your state, or you're dealing with
       | an external stateful system (like the DOM), and you have no
       | choice but to find a way to keep separate pieces of state in
       | sync.
       | 
       | I should probably write a blog post on this
        
         | rwmj wrote:
         | The way Tcl/Tk dealt with this was nice: You could watch the
         | value in a variable, getting notified when the variable
         | changed. GUI controls could therefore exactly reflect the
         | content of variables. Of course this comes with a certain
         | hidden overhead.
         | 
         | This complete example will toggle the checkbox every second
         | (1000ms), or the user can click to update the variable. The
         | checkbox watches variable "v".                 #!/usr/bin/wish
         | checkbutton .button -variable v -text "Click me"       pack
         | .button              proc run {} {           global v
         | after 1000 run           set v [expr !$v]       }       run
        
           | Jasper_ wrote:
           | This was the "two-way data binding" model that Angular used
           | and React famously knocked down. It's been part of UI
           | toolkits for a long time. Remember Object.observe?
        
           | brundolf wrote:
           | MobX is the equivalent for the space of reactive web
           | frameworks, and I agree it's a fantastic model. The cost is
           | that it requires a little "magic", but the benefit is that
           | the entire question of synchronizing state virtually
           | disappears, leaving you to focus on modeling and controlling
           | state (which are still nontrivial)
        
             | trulyme wrote:
             | No. Just no. You simply can't use "MobX" and "fantastic" in
             | the same sentence.
             | 
             | There are just so many problems with MobX. For example
             | reactions - ever tried figuring out why some value changes
             | in a bigger app? Not to mention abysmal tooling (compared
             | to Redux DevTools). But the biggest problem is that
             | everything is... sprinkled with magic. Just give me
             | Redux(-toolkit), thank you very much, I can understand
             | these much more deeply. /rant
             | 
             | If I sound confrontational, sorry about that... I just had
             | the misfortune of inheriting a MobX app. Magic indeed.
        
         | dustingetz wrote:
         | I/O, not state (well they are the same thing mostly). Async,
         | latency, concurrency, failure, effects
        
         | tenaciousDaniel wrote:
         | You could conceive of a GUI as merely a projection of internal
         | state. If you do, then a GUI could be a completely stateless
         | layer that exists solely to render visual content
         | parametrically.
         | 
         | I've been developing a convention in React over the last year
         | that uses this idea and it's very very nice. I'm also trying to
         | write a platform-agnostic UI language specifically for
         | designers that makes this a first class concept.
        
           | davidcuddeback wrote:
           | > You could conceive of a GUI as merely a projection of
           | internal state.
           | 
           | That mental model has been the most natural for me as well.
           | It really clicked for me when reading Trygve Reenskaug's
           | descriptions of MVC [1], particularly this paragraph
           | (emphasis mine) and the illustration that follows:
           | 
           | > The essential purpose of MVC is to bridge the gap between
           | the human user's mental model and the digital model that
           | exists in the computer. _The ideal MVC solution supports the
           | user illusion of seeing and manipulating the domain
           | information directly._ The structure is useful if the user
           | needs to see the same model element simultaneously in
           | different contexts and /or from different viewpoints.
           | 
           | [1]:
           | https://folk.universitetetioslo.no/trygver/themes/mvc/mvc-
           | in...
        
           | mdoms wrote:
           | So Redux.
        
           | riquito wrote:
           | Unless you rewrite the content of every input and textarea
           | and reposition the cursor at every input event, or monitor
           | which element has focus at any given time, you have some DOM
           | components with an internal state.
        
             | theknocker wrote:
             | People do this sometimes. It can work out pretty well.
        
             | erikpukinskis wrote:
             | Which... you mostly can do that with React.
             | 
             | Focus is the hardest one, because that can move around
             | really erratically. But even that sometimes has to be done
             | in state. IE11 doesn't have descendant focus selectors so
             | at my last job I wrote a hook for our dropdown children to
             | dump their focus state into a React context so our
             | renderers could stay pure and not rely on CSS and therefore
             | DOM state.
             | 
             | Just last week I implemented a hook to reposition a tooltip
             | based on the mouse cursor.
             | 
             | You do need to think about DOM state a little when doing
             | these things. But I would argue that's somewhat separate
             | from the activity of building a React UI. It's pretty rare
             | you can't just render purely based on the React state,
             | using off-the-shelf hooks.
        
           | gusmd wrote:
           | That's essentially declarative programming for GUIs, like,
           | say, Flutter does. You have some state provider, and the GUI
           | is fully re-rendered when it is notified of a state change.
           | There's many different actual implementations of this
           | paradigm, but that's it in a nutshell. I've been having lots
           | of fun learning this, coming from a more traditional MVC
           | pattern in Qt and the likes.
           | 
           | See this: https://flutter.dev/docs/get-started/flutter-
           | for/declarative
        
           | legulere wrote:
           | What you describe is the model and view part of the MVC
           | pattern: The view is just a projection of the model.
           | 
           | How does the UI get notified of changes? (like the article
           | discusses some changes might come from different part of the
           | UI, or might even come from external like in a chat client)
           | 
           | How do you handle actions of the user? (Of course in the
           | controller in MVC, but how does it work exactly?)
        
           | brundolf wrote:
           | You're just redefining "GUI" as "the MVC View layer". It's
           | good to make the View a pure projection - this is what React
           | approximates - but the state doesn't stop being a concern of
           | the GUI, it just gets pushed to other places in the system
           | (the internal state of HTML elements, the app's store whether
           | that's in Redux or otherwise, even the server if we're
           | talking about a server-rendered app (I would actually argue
           | the main benefit of a client-rendered app is that it's much
           | less onerous to add GUI state; of course the flip-side is
           | that you end up with a lot _more_ GUI state because it 's so
           | easy to add))
        
         | jayd16 wrote:
         | Agreed. To some degree UI programming faces many of the same
         | problems you see when building a distributed system.
        
         | marcus_holmes wrote:
         | I was wondering why this was all so much easier back in VB than
         | it is in the browser, and I think you're right: we've got to
         | keep the state in the DOM and the "useful" state in the program
         | in sync. Whereas in VB there was only one program so only one
         | source of truth (though it was possible to get your GUI
         | desynced from your database, you kinda had to work hard to do
         | that).
         | 
         | I think this is what Elm solved (and then by plagiarising the
         | same approach, React and Vue). Make the interface declarative
         | rather than imperative and voila - state is automatically in
         | sync.
        
       | scarredwaits wrote:
       | Good explanation, but the article reads as if React never
       | happened. Maybe it was written by someone who's only worked on
       | desktop apps?
        
         | cdaringe wrote:
         | Agreed. A nice concise way to summarize my gut synopsis.
        
       | MaxBarraclough wrote:
       | > Clicking on buttons will start triggering events which will
       | modify the state in the model that will in turn start triggering
       | event listeners causing your GUI to flash like a christmas tree.
       | The problem of data bindings and change listeners is that they
       | make it really easy to introduce a hidden circular event
       | listeners that will trigger one another multiple times (event A
       | changes state B and change of state B triggers event A).
       | 
       | Agree with both of these points. You can no longer treat
       | assignment simply as the way you mutate data, you also have to
       | anticipate its effects in the data-binding system.
       | 
       | I imagine the circular event problem could be addressed with
       | static analysis, but I don't know of any framework that does
       | this.
        
         | MontagFTB wrote:
         | This is a flaw in how the widgets have been implemented. When
         | they serve as both the view and the controller, a common
         | misarchitecture is to "loop back" a value change in the view
         | back into the controller. This loop should _only_ happen as a
         | result of the user interacting with the UI. To fix this, the
         | controller does not modify its value authoritatively. Rather,
         | it makes a request of the model to change the value it
         | represents, then gets a confirmation or correction from the
         | model once the logical state of the application has stabilized.
        
         | jffhn wrote:
         | Had a similar issue while using a JTree, where programmatic
         | nodes modifications generated events as if user had clicked on
         | the tree, such as I had to override and disable a few JTree
         | treatments to avoid triggering a wave of undesired side
         | effects.
         | 
         | That's why in some framework I'm working on, the only events
         | are those from input devices (keyboard, etc.) and windowing,
         | views don't generate any and are just here to paint the state
         | and indicate to the controller where things are on the screen.
        
         | amelius wrote:
         | Isn't this what React and precursors were invented for?
        
           | MaxBarraclough wrote:
           | I have to plead ignorance here. Does it have a way of
           | detecting circular events?
        
             | amelius wrote:
             | The idea is to separate the state of the application from
             | the presentation, which in practice leads to not having
             | circular dependencies in the first place. And you can
             | update the display after all changes are finished, so you
             | won't get the flashing xmas tree effect.
        
               | TeMPOraL wrote:
               | I thought TFA meant flashing in the metaphorical sense -
               | it's the code paths that light up, not things on the
               | screen.
               | 
               | Circular dependencies are an unfortunate fact of life,
               | and I wish we had tools to deal with them, instead of
               | desperately trying to avoid them.
               | 
               | A good test for a UI paradigm is, how do you handle a UI
               | like this:                 A = [50] [====|     ]       B
               | = [10] [|         ]       C = [60] [=====|    ]       A +
               | B = C
               | 
               | Where [===| ] things are sliders, all three are
               | changeable, and the relation A+B=C must always hold.
        
               | kilburn wrote:
               | The problem is under-specified. When you move a slider,
               | how should the other two adjust? There are many possible
               | solutions and you haven't specified any preference here.
               | 
               | The easiest solution is to maintain a fixed order of
               | preference, something like:                 values =
               | [a:0, b:0, c:0]       fn onChange(key, value) {
               | values[key] = value         for (k,v) in values {
               | if (k !== key) values[k] = adjust(k)         }       }
               | fn adjust(key) {         switch(key) {           case
               | 'a': return max(0, values[c] - values[b])           case
               | 'b': return max(0, values[c] - values[a])           case
               | 'c': return values[a] + values[b]         }       }
               | 
               | The alternative is to maintain the latest selections made
               | by the user and use that as the iteration order.
               | 
               | Whatever approach you go with, the "single source of
               | truth" approach of react/vue/svelte + state (whether it
               | is state in hooks, redux, mobx or whatever) holds. The
               | "values" above is the source of truth, and the components
               | just reflect that.
               | 
               | In other words: from a state point of view you don't have
               | three sliders each with a value but a "three-valued
               | component that happens to be shown as three sliders".
        
               | amelius wrote:
               | You can add a constraint solver on top of it. So when
               | e.g. C changes, you solve for A and B. You then update
               | the state of A, B, C and redraw. This kind of interaction
               | can be handled by a separate library which is not
               | necessarily part of a GUI system.
        
               | auggierose wrote:
               | A constraint solver is overkill here, in particular as
               | you cannot solve for A and B uniquely, you might want to
               | add some further rule, like A and B should grow
               | proportionally, when C is modified.
               | 
               | But otherwise, yes, just use a framework that separates
               | State from UI and has bidirectional updates, like
               | SwiftUI.
        
             | diggan wrote:
             | React itself doesn't have any way of "listening" to events,
             | so the data is only flowing in one direction always. Redux
             | et al introduces more things on top of that, where you can
             | fire off events ("Actions" in Redux terms) that
             | react/trigger other events, but doesn't really have
             | anything to do with React itself.
             | 
             | Then that some people make React components change state on
             | render/mount/update, is a different failure, but not really
             | a failure of React as much as a failure of the one using
             | React.
             | 
             | But in general no, React doesn't really prevent you from
             | having a component updating and re-rendering because some
             | state changed, and because of that, changes the state again
             | and thus re-renders again, forever.
        
       | walkingpigeons wrote:
       | At the end of the article it said something about immediate mode.
       | I do think React or Vue are kind of working like this now? Both
       | requires you define your state of the each component and define
       | how it should look like based on the state values. When you
       | update the state it will then update the view automatically as
       | well based on what you've defined (JSX/template). It is not
       | 30/60FPS though, it is re-rendered when it knows there is a state
       | change (i.e. setState is triggered)
        
       | heycosmo wrote:
       | I have a simple rule for GUI design: build trees not graphs.
       | Write components that accept a state snapshot and broadcast
       | changes. If component A listens for state changes from B, then A
       | is a parent node of B. If A sends state to B, then A is a parent
       | of B. Components reconcile state before broadcasting changes
       | toward the root of the tree.
       | 
       | Often there is a price paid in brevity, but I believe it is worth
       | it. It may seem annoying to propagate a click explicitly through
       | 5 parent components just to sum clicks into a count widget, but
       | as soon as a short circuit is made, you've created a graph, and
       | you lose the ability to isolate GUI sub-trees for
       | testing/debugging.
        
         | nikisweeting wrote:
         | This makes visual redesigns take foreeeeever though. Imagine
         | moving a component from the main area of your app into a menu
         | dropdown in the navbar, now you have to tear out all those
         | props that you painstakingly passed down through 10
         | intermediary layers.
        
       | flohofwoe wrote:
       | IME, working with an immediate mode UI framework automatically
       | gets rid of most such "architecture astronaut" problems.
       | 
       | But I found that it's almost impossible to describe to someone
       | used to event-/callback-driven UIs _why exactly_ that is. You
       | really need to try it yourself on a non-trivial UI to  "get it".
        
         | amelius wrote:
         | Yes, but immediate mode causes more CPU load (or draining of
         | batteries) since you have to redraw everything all the time.
        
           | forrestthewoods wrote:
           | > immediate mode causes more CPU load
           | 
           | Does it? Computers are FAST. Webdev stacks are many things,
           | but it ain't fast or performant.
           | 
           | Has anyone built a moderately complex UI in a good immediate
           | mode UI and in a good retained UI? I'd be very curious to
           | know the actual results.
        
           | panic wrote:
           | It's easy to skip drawing frames when no state changes, and
           | for frames where state does change, you can use techniques
           | like rxi's cached software rendering to avoid redrawing the
           | entire screen:
           | https://rxi.github.io/cached_software_rendering.html
        
             | amelius wrote:
             | How does it handle scrolling? This is typically very fast
             | and smooth (performed by bitblt and redrawing only the
             | newly exposed part on every frame), but in immediate mode
             | you'd have to redraw the entire screen on every frame.
        
               | panic wrote:
               | Modern renderers typically draw scrollable content into a
               | separate texture (or collection of smaller textures that
               | tile the scrollable region) and use the GPU to scroll
               | rather than bitblting on the CPU. You can use the same
               | technique to render the scrollable part of the UI in an
               | immediate mode system.
        
               | flohofwoe wrote:
               | I think this whole efficiency thing is a common
               | misconception. Only the public API appears "immediate
               | mode", the internal implementation doesn't need to be,
               | and usually isn't (e.g. it keeps mutating state between
               | frames instead of building everything from scratch
               | again).
               | 
               | The user-side code basically describes what the UI should
               | look like in the current frame, those "instructions" are
               | recorded, and this recording is reasonably cheap.
               | 
               | The UI backend can then figure out how to "diff" the new
               | instruction stream against the current internal state and
               | render this with the least changes to the screen.
               | 
               |  _However_ some immediate mode UI systems came to the
               | conclusion that it might actually be cheaper to just
               | render most things from scratch instead of spending lots
               | of processing resources to figure out what needs to be
               | updated.
               | 
               | In conclusion: "Immediate Mode UI" doesn't say anything
               | how the UI is actually rendered or generally how the
               | internals are implemented, it only describes how the
               | public API works.
        
               | amelius wrote:
               | Perhaps I should give a different example: a listbox
               | filled with 1 million items, with a scrollbar.
               | 
               | If the public API requires you to give a new paint
               | command on every frame (everytime the scrollbar is
               | dragged), then regardless of whether the underlying
               | rendering engine performs each of these paint commands,
               | you still have to run through every item of the list (and
               | so does the diff'ing code), making this a O(N) operation
               | on every frame.
        
               | flohofwoe wrote:
               | From what I've seen (in Dear ImGui), this is solved by
               | not giving the 1 million items to the API, instead you
               | can query what range of the list is currently visible,
               | and only describe those to the API (in Dear ImGui this is
               | called a ListClipper).
               | 
               | But I guess different UI frameworks have different
               | solution for this. Creating and updating a 1 million item
               | list wouldn't be a cheap operation in a traditional UI
               | system either.
        
               | jcelerier wrote:
               | > Creating and updating a 1 million item list wouldn't be
               | a cheap operation in a traditional UI system either.
               | 
               | IDK, works pretty well with Qt's item model system
        
               | coldtea wrote:
               | That's becuase it too (in all probability) doesn't render
               | 1 million offscreen elements to show the handful (100?
               | 1000?) on screen elements.
               | 
               | So it does it just like an immediate mode GUI does it,
               | and only renders what it must.
               | 
               | Not sure about QT specifically, but all other non-
               | immediate mode GUIs I know use the same trick, and
               | immediate mode GUIs like React also use it.
        
               | amelius wrote:
               | Ok. Yes my point is not that you can't find efficient
               | ways around the limitations, but my fear is that you end
               | up with a system that is less ergonomic in the end.
        
               | nikki93 wrote:
               | These aren't just workarounds. It's often the natural
               | immediate mode api. A lot of immediate mode "scroll view"
               | systems just have a child lambda passed with an index for
               | which element to render, and it only calls the lambda for
               | what's actually in view. (eg: the Gio API in Go)
               | 
               | Concrete examples of code that is not immediate mode api,
               | vs. code that is immediate mode api, both implementing
               | the same thing, can help discuss / reflect on that fear.
               | IME immediate mode is great for mapping tree-ish data to
               | tree-ish UI; when things have a lot of nontree linkages
               | or reordering (eg. when you do drag and drop) it gets
               | trickier. React's JSX / createElement API also feels
               | somewhat immediate mode, tbh; the updates are just
               | scheduled to fire on state changes.
        
               | amelius wrote:
               | > A lot of immediate mode "scroll view" systems just have
               | a child lambda passed with an index for which element to
               | render
               | 
               | This sounds a lot like paintEvent() in traditional OO-
               | style GUI systems; i.e. event-driven.
               | 
               | So my understanding now is that with immediate-mode
               | callbacks happen within the scope of a function-call
               | rather than from some event-loop. I probably have to look
               | into it deeper to get a good understanding. It is still
               | unclear where state is stored (e.g. for the position of a
               | scroll-view), and if state is passed through the function
               | call tree on every frame.
        
               | nikki93 wrote:
               | Yeah the in depth look helps before forming conclusions
               | like "you have to render everything every frame." Key
               | thing is immediate mode can often mean just the sense of
               | the API, not necessarily the draw scheduling.
               | 
               | re: widget-local state -- React is one of the most
               | popular models for that. Basically if widgets are
               | identified over time by the sequence of ids (includes
               | array indices like React keys) on the path to them from
               | the root, state is coherent across that identity, and
               | mount / unmount events exist at the boundary of the
               | identity. Callstack / push-pop based APIs like ImGUI
               | maintain this sequence either impicitly or explicitly in
               | the stack. Then there is some API to read / write to the
               | state store (like React hooks or ImGUI generic state
               | storage) with optional update triggering in async APIs
               | like React's.
        
               | wdfx wrote:
               | It's not difficult to implement a 'virtual' list which
               | renders only the items which are visible. It's a little
               | trickier if your list items are not all the same height,
               | but not impossible.
        
             | layer8 wrote:
             | That doesn't seem too different anymore to the
             | invalidate/paint paradigm of traditional UI frameworks
             | (e.g. win32 GDI).
        
           | jameshart wrote:
           | What if you have your 'immediate mode' produce not a direct
           | rendering of the GUI, but a virtual object model describing
           | what the structure of the GUI should be at that moment? Then
           | you diff that object model against the actual object model of
           | the GUI and apply the changes....
           | 
           | That's how react works. It's effectively an 'immediate mode'
           | abstraction above a component based UI.
           | 
           | That avoids the component connecting and wiring problems, and
           | creates the simple determinism that makes immediate mode
           | systems easier to reason about.
        
         | panic wrote:
         | Immediate mode is great, but it can be hard to implement nice
         | custom event handling on top of it. For example, say you have a
         | collection of buttons. You want to be able to click on one
         | button (the button highlights), drag onto another button (that
         | button highlights and the first button unhighlights), and
         | release on that second button to trigger its action. This is a
         | real issue I ran into while building an immediate-mode UI... I
         | found it hard to implement an interaction like this without a
         | reified "layout" that I could query as the mouse moved around.
        
           | boterock wrote:
           | I was doing something like that with Godot [1], the approach
           | I took was setting some global "dragged_item" when the drag
           | started, and then each drop target would check if the
           | dragged_item was compatible and if the mouse was within its
           | bounds. This way each drop target would do the check instead
           | of some drag manager having to check whatever was under the
           | cursor.
           | 
           | [1] https://github.com/0xafbf/aether/tree/master/addons/godot
           | _im...
        
         | Const-me wrote:
         | I used and implemented immediate mode GUIs, and I think it's
         | only good for simple stuff.
         | 
         | Accessibility is hard. The OS needs to know visual tree to be
         | able to conclude things like "this rectangle is a clickable
         | button with Hello World text".
         | 
         | Complex layouts are hard. Desktop software have been doing
         | fluid layouts decades before the term was coined for web apps,
         | because different screen resolutions, and because some windows
         | are user-resizable. Layout can be expensive (one reason is
         | measuring pixel size of text), you want to reuse the data
         | between frames.
         | 
         | Animations are hard. Users expect them because smartphones
         | introduced them back in 2007: https://streamable.com/okvhl Note
         | the kinetic scrolling, and soft "bumps" when trying to scroll
         | to the end.
         | 
         | Drag and drop is very hard.
        
           | Chris_Newton wrote:
           | When building more complicated UIs, an architectural pattern
           | I have often found useful is to have two distinct levels of
           | state/data behind the rendering code, so my hierarchy looks
           | something like this:
           | 
           | 1. Application data
           | 
           | 2. Presentation data
           | 
           | 3. Presentation rendering
           | 
           | The first is the single source of truth for your application
           | state, what we often call a "model" or "store". It's where
           | you represent the data from your problem domain, which is
           | usually also the data that needs to be persistent.
           | 
           | The second is where you collect any additional data needed
           | for a particular way of presenting some or all of your
           | application data. This can come from the current state of the
           | view (list sort order, current page for a paginated table,
           | position and zoom level over a map, etc.) or the underlying
           | application data (building a sorted version of a list, laying
           | out a diagram, etc.) or any combination of the two (finding
           | the top 10 best sellers from the application data, according
           | to the user's current sort order set in the UI). This is
           | often a relatively simple part of the system, but there is no
           | reason it has to be: it could just as well include setting up
           | a complicated scene for rendering, or co-ordinating a
           | complicated animation.
           | 
           | The final part is the rendering code, which is a translation
           | from the application and presentation data into whatever
           | presentation is required. There isn't any complicated logic
           | here, and usually no state is being maintained at this level
           | either. The data required for rendering all comes ready-
           | prepared from the lower layers. Any interactions that should
           | change the view or application state are immediately passed
           | down to the lower layers to be handled.
           | 
           | The important idea is that _everything_ you would do to keep
           | things organised around the application data also applies to
           | the presentation data. Each can expose its current state
           | relatively directly for reading by any part of the system
           | that needs to know. Each can require changes of state to be
           | made through a defined interface, which might be some sort of
           | command /action handler pattern to keep the design open and
           | flexible. Each can be observable, so other parts of the
           | system can react to changes in its state.
           | 
           | It just happens that now, instead of a single cycle where
           | application data gets rendered and changes from the UI get
           | passed back to the application data layer, we have two
           | cycles. One goes from application data through presentation
           | data to rendering, with changes going back to the application
           | data layer. The other just goes from the presentation data to
           | the rendering and back.
           | 
           | I have found this kind of architecture "plays nicely" with
           | almost any other requirements. For example, data sync with a
           | server if our GUI is the front end of a web app can easily be
           | handled in a separate module elsewhere in the system. It can
           | observe the application data to know when to upload changes.
           | It can update the application data according to any
           | downloaded information via the usual interface for changing
           | the state.
           | 
           | I have also found this kind of architecture to be very
           | flexible and to fit with almost any choice of libraries for
           | things like state modelling, server comms, rendering the
           | actual view, etc. Or, if your requirements are either too
           | simple to justify bringing in those dependencies or too
           | complicated for a ready-made library to do what you need, you
           | have a systematic overall structure in place to implement
           | whatever you need directly, using all the same software
           | design and scaling techniques you normally would.
        
             | Const-me wrote:
             | What you talking about is very similar to MVVM as
             | implemented in various MS XAML frameworks: https://en.wikip
             | edia.org/wiki/Model%E2%80%93view%E2%80%93vie... IMO that's
             | very good approach to the problem.
             | 
             | > laying out a diagram
             | 
             | I think layout belongs to the views (you call them
             | presentation rendering).
             | 
             | View models provide data to be visualized, but it's rarely
             | a good idea to compute pixel positions, or handle low-level
             | input there. These things are coupled with views too much.
             | 
             | Similar to animations. Unless the app being developed is a
             | video editor or similar where animation is the core
             | functionality, animations don't affect models nor view
             | models, they merely prettify GUI state transitions or
             | provide progress indication. No need to wire them all the
             | way into view models. Some frameworks even run them on a
             | separate thread called "compositor thread" so they play
             | fine even if the main thread is blocked or starved for CPU
             | time.
        
           | flohofwoe wrote:
           | Apart from accessibility, isn't this mostly caused by the
           | "immediate mode API" versus "immediate mode implementation"
           | confusion I described in another sub-thread?
           | 
           | There's nothing in the idea that forbids immediate mode UI
           | frameworks from keeping any amount of internal state between
           | frames to keep track of changes over time (like animations or
           | drag'n'drop actions), the difference to traditional UI
           | frameworks is just that this persistent state tree is hidden
           | from the outside world.
           | 
           | Layout problems can be solved by running several passes over
           | the UI description before rendering happens.
           | 
           | For accessibility, the ball is mainly in the court of the
           | operating system and browser vendors. There need to be
           | accessibility APIs which let user interface frameworks
           | connect to screen readers and other accessibility features
           | (this is not a problem limited to immediate mode UIs, but
           | custom UIs in general).
        
             | Const-me wrote:
             | > There's nothing in the idea that forbids immediate mode
             | UI frameworks from keeping any amount of internal state
             | between frames to keep track of changes over time
             | 
             | When you call ImGui::Button("Hello World") it has no way of
             | telling if it's the same button as on the previous frame,
             | or a new one. You're simply not providing that data to the
             | framework.
             | 
             | It's often good enough for rendering, they don't really
             | care if it's an old or new button as long as the GPU
             | renders identical pixels. When you need state however
             | (animations certainly do), the approach is no longer
             | useful.
             | 
             | A framework might use heuristics like match text of the
             | button or relative order of things, but none of them is
             | reliable enough. Buttons may change text, they may be
             | dynamically shown or hidden, things may be reordered in
             | runtime.
             | 
             | > Layout problems can be solved by running several passes
             | over the UI description before rendering happens.
             | 
             | You're correct that it's technically solvable. It's just
             | becomes computationally expensive for complex GUIs. You
             | can't reliably attach state to visual elements, gonna have
             | to re-measure them every frame.
             | 
             | > There need to be accessibility APIs which let user
             | interface frameworks connect to screen readers
             | 
             | Windows has it for decades now. For Win32 everything is
             | automatic and implemented by the OS. Good custom-painted
             | GUI frameworks like WPF or UWP handle WM_GETOBJECT message
             | and return IAccessible COM interface. That COM interface
             | implements reflection of the complete GUI, exposing an
             | objects hierarchy. I'm not saying it's impossible to do
             | with immediate rendering, just very hard without explicit
             | visual tree that persists through the lifetime of the
             | window.
        
               | shrimpx wrote:
               | > When you call ImGui::Button("Hello World") it has no
               | way of telling if it's the same button as on the previous
               | frame, or a new one.
               | 
               | Can you solve this by adding stable IDs to the API, so
               | you'd call `ImGui::Button("my-button-id", button_text)`?
        
               | Const-me wrote:
               | To consistently assign these IDs you gonna have a tree on
               | your side of the API. You'll then have two visual trees,
               | one on your side on the API, another one on the
               | framework's side of the API. And then you gonna be
               | debugging things.
               | 
               | Who and when should destroy backend visual nodes? If you
               | won't use an ID in some frame, does it indicate the
               | backend should drop the related state? What if an
               | animation is still playing on the element in question?
               | What if the missing control re-appears later i.e. was
               | only hidden temporarily? What should backend do if you
               | reuse same ID for different things?
               | 
               | One can implement simple stuff using any approach,
               | immediate GUI is often the simplest of them. It's when
               | use cases are complicated you need a full-blown retrained
               | mode OO framework. Win32, HTML DOM, MS XAML, QT, UIKit
               | are all implemented that way, that's not a coincidence.
        
             | enqk wrote:
             | these APIs are UI Automation on windows and NSAccessibility
             | on macos
        
         | badsectoracula wrote:
         | I've tried it at the past and "get it" but that doesn't mean i
         | "agree with it" - to me immediate mode GUIs are the graphical
         | equivalent to...                   Running = -1         WHILE
         | Running           PRINT "1) Add record"           IF HasRecords
         | THEN             PRINT "2) Delete record"             PRINT "3)
         | Edit record"           END IF           PRINT "H) Help"
         | PRINT "X) Exit"           INPUT "Choice: ", I$           IF
         | I$="1" THEN AddRecord           IF (I$="2") AND HasRecords THEN
         | DeleteRecord           IF (I$="3") AND HasRecords THEN
         | EditRecord           IF I$="H" THEN ShowHelp           IF
         | I$="X" THEN Running = 0         WEND
         | 
         | ...which, sure, it is ridiculously simple (like imguis) but it
         | can very quickly become unwieldy the more you ask from your
         | user interface. There is a reason why UIs moved beyond that.
        
           | flohofwoe wrote:
           | I think when you look at projects like Tracy, there's no
           | question that immediate mode UIs also work well for non-
           | trivial use cases:
           | 
           | https://github.com/wolfpld/tracy
        
             | badsectoracula wrote:
             | My point isn't that you can't make them work for non-
             | trivial cases, after all as long as you can draw something
             | on screen you can do pretty much anything. The point is how
             | well that is done.
             | 
             | For example from a quick look at the code it looks like
             | this project needs to draw in a bunch of unrelated to each
             | other libraries just to get some basic GUI functionality
             | you'd find even in toolkits from the 80s provide (e.g.
             | drawing text or using file dialogs).
             | 
             | And check these sources [0] and [1], this is doing at least
             | as much bookkeeping as in a "retained" GUI toolkit - except
             | it also has to do things that such a toolkit would do
             | automatically, like explicitly drawing the widgets [2].
             | People at the past were complaining how tools like Visual
             | Basic were mixing presentation and logic, yet the worst you
             | could do in these tools is to have the logic in event
             | handlers (...which if you think a bit about it at least
             | that does make a bit of sense), yet here is actual
             | application logic being part of the drawing code in [3]
             | (this is inside a method View::DrawPlayback, scroll a bit
             | upwards to find it).
             | 
             | Now sure, someone might say that this isn't the best
             | example of immediate GUIs... but this is the one that was
             | brought up as a good example. And TBH all it convinced me
             | is that if (for some reason) i had to use ImGui (the
             | library), to spend some time building a retained GUI
             | wrapper around it :-P.
             | 
             | [0] https://github.com/wolfpld/tracy/blob/master/server/Tra
             | cySou...
             | 
             | [1] https://github.com/wolfpld/tracy/blob/master/server/Tra
             | cyVie...
             | 
             | [2] https://github.com/wolfpld/tracy/blob/d8c1dae9e120c2780
             | 1e762...
             | 
             | [3] https://github.com/wolfpld/tracy/blob/d8c1dae9e120c2780
             | 1e762...
        
             | jcelerier wrote:
             | > , there's no question that immediate mode UIs also work
             | well
             | 
             | I sincerely hope no one looks at the screenshot on that
             | page and thinks "this is something that works well". This
             | UI screams "I've been made that way because that was the
             | easiest way in the tool I'm built with" (which is _bad_ -
             | good tools should not dictate the form of the resulting
             | product)
        
               | flohofwoe wrote:
               | How would a profiling tool UI need to look differently in
               | your opinion?
               | 
               | And why do you think the UI looks that way because of
               | restrictions of the UI framework?
               | 
               | I'm quite sure if the UI would have been written with
               | (for instance) Qt or React, it would look exactly the
               | same, because the UI requirements would be the same
               | (minus the different default style). The question is
               | whether this could have been achieved with less, or
               | "cleaner" code (which I doubt).
        
               | jcelerier wrote:
               | example with Qt: https://www.youtube.com/watch?v=6ogEkQ-
               | vKt4
        
               | keeganpoppen wrote:
               | seems pretty information dense to me. tufte would
               | approve. but surely he's no match.
        
               | coldtea wrote:
               | > _I sincerely hope no one looks at the screenshot on
               | that page and thinks "this is something that works well".
               | This UI screams "I've been made that way because that was
               | the easiest way in the tool I'm built with"_
               | 
               | That's where you'd be wrong. Profilers traditionally have
               | looked like this regardless of the tool/GUI lib/GUI
               | paradigm they'd been made with.
               | 
               | It's the domain need (to show lots of information,
               | graphs, timelines, stack trees, etc, to compare) that
               | asks for this...
        
               | jcelerier wrote:
               | > Profilers traditionally have looked like this
               | regardless of the tool/GUI lib/GUI paradigm they'd been
               | made with.
               | 
               | the profiler I use most (hotspot) definitely looks much
               | cleaner: https://www.youtube.com/watch?v=6ogEkQ-vKt4
        
               | coldtea wrote:
               | It's the same thing split in N tabs.
               | 
               | Not a distinction made because of immediate vs retained
               | GUI.
        
         | erwincoumans wrote:
         | Agreed, in particular Dear Imgui is pretty nice, and high
         | performance.
        
       | nikisweeting wrote:
       | Excellent writing, this is a great article to show a junior
       | frontend engineer who would otherwise spend their next years
       | having to crystalize these ideas on their own. I wish I had read
       | this 8 years ago.
        
       | IshKebab wrote:
       | This is a really good take on the (still unsolved IMO) problem,
       | though I suspect it only makes sense to people who have
       | experienced all the issues personally, since so many of them are
       | "it gets really awkward with big programs" type problems.
       | 
       | He did miss out a pretty significant flaw of message busses - the
       | producers and consumers of messages are completely decoupled,
       | which makes debugging really annoying because you don't have
       | anything like a stack trace. You just know that your component
       | received a message, and good luck trying to figure out where it
       | was sent from and why.
       | 
       | That's also a big problem with magical reactivity systems like
       | Vue. Your stack trace is pretty much "yeah something changed
       | somewhere so we're updating this component. Don't try and figure
       | it out."
        
         | pixel_tracing wrote:
         | There's always a stack trace. The question is figuring out deep
         | your eventing layer is. Also dispatching an event on one thread
         | and receiving it in a component on another thread essentially
         | negates the trace in this sense too (sometimes depending on
         | your environment).
         | 
         | A solution I've always had is to build your message bus with
         | logging in mind initially
        
           | IshKebab wrote:
           | > There's always a stack trace.
           | 
           | I think you misunderstood. Of course there's always a stack
           | trace; you're still executing code. But with message buses
           | and magic reactivity systems your stack trace always just
           | goes to `mainEventLoop()` or `processEvents()` or whatever.
           | 
           | It doesn't go to the thing that actually caused the change as
           | it would if you used direct function calls. I'm not saying
           | it's a deal breaker, it's just a notable downside of those
           | architectures.
        
         | RoyalSloth wrote:
         | > the producers and consumers of messages are completely
         | decoupled, which makes debugging really annoying because you
         | don't have anything like a stack trace
         | 
         | This is actually a really good point. I don't know why you were
         | downvoted.
        
       | erdo wrote:
       | So nice to see this universal issue being discussed, it doesn't
       | seem to get as much attention as it deserves.
       | 
       | IMO, a great solution here is along the lines of "Lift the state
       | up" / "MV(X)". But... there is a vital detail which is usually
       | missed when deciding how exactly the state in your M gets passed
       | to your V for display: you must refresh your entire V when M
       | changes in any way, not just the bit of V that you think changed.
       | It's the only way to completely remove these difficult to test,
       | hard to spot edge cases that the article discusses.
       | 
       | This is almost impossible to talk about without specific
       | examples, so a while back I wrote such an example that I think
       | distills the core problem, and demonstrates how refreshing the
       | entire V not only solves the problem but typically takes less
       | code to do it: https://dev.to/erdo/tutorial-spot-the-deliberate-
       | bug-165k
        
       | continuational wrote:
       | I ran into a buch of similar problems in the past personally, so
       | much that I ended up writing my own library.
       | 
       | The home page is a bit ugly, but it contains a range of examples
       | that are commonly awkward to implement in other UI libraries:
       | 
       | http://www.react4s.org/
        
       | madhadron wrote:
       | I mean, of course you lift the state. It's just like a database.
       | You have a normalized model containing the state, and the GUI is
       | a view on it. Events from the GUI trigger transactions that
       | update the state and then every element is triggered to update
       | itself based on the new state. That should never trigger a loop
       | of events, because a view updating itself from the model should
       | never trigger transactions on the database.
       | 
       | Many complicated views have their own internal models for things
       | like where they are scrolled, what columns are shown, or what
       | elements of a tree are expanded. But those compound views are
       | written so that, from the outside, they appear exactly the same
       | as any other view.
       | 
       | > I'd love to hear what the functional programming camp has to
       | say about this problem
       | 
       | It's called functional reactive programming.
        
         | erdo wrote:
         | Agree completely other than the functional bit (but I'm not
         | sure if that was a recommendation or just Google keyword help).
         | At least for Android, I've found that maintaining state when
         | handling screen rotations etc becomes a real headache if the
         | GUI binding code is functional (there is nowhere nice and clear
         | to hang the state without storing it in an rxStream or
         | similar). So far I favour reactive code absolutely, and
         | functional (ish - I'm still using DI for example) code right up
         | to the view layer, but then drop out for the last mile of data
         | binding
        
         | nikisweeting wrote:
         | This is the key line that the original article is missing imo,
         | which solves much of the headache that they claim exists with
         | message passing/raising state:
         | 
         | > a view updating itself from the model should never trigger
         | transactions on the database
         | 
         | i.e. a view rendering from state/messages, should never re-emit
         | new state/messages.
        
           | Chris_Newton wrote:
           | I thought the article was too dismissive of concepts like
           | MVC, immediate mode and functional programming, given that
           | ideas coming from those directions might offer the answers
           | the original author is searching for.
           | 
           | The separation of an underlying data model from any
           | particular way of presenting its current state is a powerful
           | idea that has proven its value many times. We've used it to
           | build user interfaces, from early GUIs when we didn't yet
           | have the kinds of libraries and platform APIs we enjoy today,
           | through games with unique rendering requirements and a need
           | to keep frame rates up, to modern web and mobile app
           | architectures. The same principle is useful for building non-
           | user interfaces, too, in areas like embedded systems where
           | you are "presenting" by controlling some hardware component
           | or distributed systems where you are "presenting" to some
           | other software component as part of the larger system.
        
       ___________________________________________________________________
       (page generated 2021-02-14 23:01 UTC)