HeinrichApfelmus / threepenny-gui

GUI framework that uses the web browser as a display.
https://heinrichapfelmus.github.io/threepenny-gui/
Other
441 stars 77 forks source link

Layout combinators to simplify GUI layout #24

Closed HeinrichApfelmus closed 11 years ago

HeinrichApfelmus commented 11 years ago

I would like to simplify the API for GUI layout.

Since our focus is on GUIs, I wonder if we should downplay the full HTML capability and instead provide a few combinators for aligning GUI elements. WxHaskell has a layout combinators like grid, row and column that make it very easy to align elements. For instance, the following screen layout

was created by the following code

do
    f           <- frame    [ text := "CRUD Example (Simple)" ]
    listBox     <- singleListBox f []
    createBtn   <- button f [ text := "Create" ]
    deleteBtn   <- button f [ text := "Delete" ]
    filterEntry <- entry  f [ ]

    firstname <- entry f [ ]
    lastname  <- entry f [ ]

    let dataItem = grid 10 10 [[label "First Name:", widget firstname]
                              ,[label "Last Name:" , widget lastname]]
    set f [layout := margin 10 $
            grid 10 5
                [[row 5 [label "Filter prefix:", widget filterEntry], glue]
                ,[minsize (sz 200 300) $ widget listBox, dataItem]
                ,[row 10 [widget createBtn, widget deleteBtn], glue]
                ]]

Even though the layout is fairly complex, it can be specified in just 18 lines of Haskell code, half of which are essentially just a list of widgets.

The thing is that HTML allows very rich and aesthetically appealing user interfaces, but the design tends to take a lot of time (also because HTML+CSS has an impoverished box model compared to, say, TeX). When writing a GUI application, I wager that programmers probably to prototype the functionality first and upgrade the design later. Hence, a couple combinators for quickly creating layouts might come in very handy.

What is your opinion of the combinators grid, row, column?


Concerning style, WxHaskell specifies attributes as

set x [ attr := value, attr2 := value ]

but I don't think that this is worth copying. I prefer the elegant Ji style that uses chains of # in a clever way.

However, I would like to reduce the number of variants of #, #+, #= and so on, and streamline this part of the API as well. Unfortunately, there are many possibilities for doing so, and I have a hard time making decisions without guidance from example GUI code.


@daniel-r-austin, I would would be very interested in a screenshot of the GUI program you mentioned. Also, did you create elements mostly with the Haskell code (new and so on), or did you specify the layout by writing a HTML page first? What about GUI elements that are created dynamically, like elements of, say, a shopping cart?

fluffynukeit commented 11 years ago

Grid, row, and column make sense to me. In fact, the GUI I am working on (see below) uses a CSS grid with sizing and positioning tweaked using the CSS. It was indeed a pain but the CSS approach thankfully has tons of resources available online and is pretty complete, if messy. I am concerned that using these combinators will clash with the CSS itself, and the "philosophy" of HTML pages in that style/layout is defined in the CSS and content in the HTML. Adding the combinators as a quick-and-dirty alternative (but not replacement) to CSS sounds good to me.

I do prefer the Ji style to the wxhaskell you provided. I don't have a problem with the # variants, though, but I would be open to alternatives.

I have posted a short blog entry on the current status of my GUI here. This program is a game utility for the game Torchlight 2, which is a hack-n-slash action game. The game has a limited inventory space, and my goal with this program is to enable users to extract item data from the game's save files, store it separately, search it, and when desired import it back in. The Haskell source for the GUI is here. On the root level of the project is a GUI directory that has the starting HTML file as well as the CSS file.

Almost all the elements are created dynamically with new. My starter HTML file is essentially empty. The GUI has some tester elements still in it, like the "Hello!" message. Below the hello message is a status string that is updated as the program does stuff. I am following a process similar to the one you describe in that first I want it to work, and only after that will I strive to make it pretty.

What I want to do next GUI wise is add a search box and a list of database results in the bottom half that is currently empty. I want users to be able to select and drag one of these search results into the grid at the top, which would have the effect of injecting the item into the game's save file. I need to learn and implement some database Haskell stuff before then as well as implement/test writing to the game's save files.

HeinrichApfelmus commented 11 years ago

Wow, thanks a lot Daniel, this is exactly the kind of "beyond my imagination" GUI example that I was looking for. :smiley: I would like to study it further. Since I don't have Torchlight2 installed, could you post a transcript of the final HTML rendering somewhere, for instance as a gist?

The background and icons obviously don't match the traditional system UI styles, but I definitely want to treat the "non-traditional" style on an equal footing. In wxHaskell, they would have required drawing primitives (hence issue #22), but it appears that regular HTML elements -- or maybe SVG elements -- can substitute for these nicely.

I will need some time to mull about a good design for combinators that offer quick-and-dirty layout but can also accommodate custom CSS styling with minimal code changes. The philosophy that HTML is completely free of style specifications is a bit of a lie (elements in sequence are laid out in sequentially, nested elements inherit styles), but hopefully I will come up with something satisfying. In any case, I would like to replace the tedious incantations of new #. "dummyClassNameForLayout" with something more crisp, using Haskell as a "slightly better HTML+CSS", so to speak. (You are kind of using it in this way by starting with an empty HTML page.)

fluffynukeit commented 11 years ago

Here it is. I added a small clickable div to enable saving, which I'm working on now. Also I had to manually fix some tags (mostly all img tags) to get the formatter to work.

HeinrichApfelmus commented 11 years ago

Thanks, will take a look!

HeinrichApfelmus commented 11 years ago

Ok, so I implemented the combinators grid, row, column for making cheap layouts. Example usage.

Essentially, these combinators just wrap everything in a couple of <div> tags and assign the appropriate CSS styles for tabular layout (display:table). Implementation here. The CSS styles are assigned in the form of classes, so it is possible to override their style after the fact. I hope that this gives the best of both worlds: easy layout for everyone who knows nothing about CSS and complete control over layout for CSS gurus. (I really like how simple this implementation turned out to be. Yay for Haskell as a HTML macro language!)


I also started to overhaul the mechanism for setting properties and the (#) combinator. These changes may be more controversial.

I would like to synchronize # with the diagrams library, so that it becomes plain reverse function application. The stateful nature of setting attributes is now moved to a new helper function set. The canonical usage looks like this:

new
    # set style [("color","#CCAABB")]
    # set draggable True
    # set children otherElements

Also, I would like to borrow an idea from wxHaskell where properties like style, draggable etc. are abstracted into a new datatype Property which supports both get and set operations. For instance, I would like the following to become possible

canDrag <- someElement # get draggrable
when canDrag $ do
    element otherElement # set draggable False

(The element is a synonym for return and is needed because set expects an IO Element as last argument.) Essentially, this is a systematic way to organize the different getters and setters like setClass, setAttr, allowDrag etc.

What do you think?

fluffynukeit commented 11 years ago

I am liking the way this is going. What I'd like to do this weekend is pull in the latest updates and try to migrate my current GUI layout as a test case. Actually, I might wait until these set/get updates are made as that seems like a larger departure from the old framework, and thus more worthy of investigation. These ideas look good to me on paper so I'd like my opinion to be informed by some experience.

HeinrichApfelmus commented 11 years ago

Ok! I would also like to combine the get/set stuff with a large module reorganization, so it's a good idea to wait for a short while.

My motivation for a module reorganization is twofold:

  1. Many existing setters like setAttr or even emptyEl become obsolete and lots of new names will take their place, so most of the existing code will break anyway.
  2. By dropping prefixes, the properties eat up precious names like title, text, value and so on. By organizing the modules in a clever way, we can allow qualified names for the more specific combinators. Example:

    import qualified Graphics.UI.Threepenny as UI
    import Graphics.UI.Threepenny.Core
    
    main = do
       ...
       elOk <- UI.button
       element elOk
           # set UI.text "Add"            -- set      is unqualified
           # set UI.cssClass "shiny"      -- cssClass is qualified
       on UI.click elOk $ \_ -> do ...
       ...

    This way, everything is in scope with the module prefix UI. but the most fundamental combinators like set, #, etc. are also available vanilla.

fluffynukeit commented 11 years ago

@HeinrichApfelmus , is the big update you want to do complete? Let me know when you think it's ready for me to try out, please. Thanks.

HeinrichApfelmus commented 11 years ago

@daniel-r-austin Oops, sorry, yes. The gist of it is ready by now. There are probably still a few combinators to be added as I convert the other examples, but it's ready to be tried out.

HeinrichApfelmus commented 11 years ago

Ok, I have now converted all the examples and as of commit 5c9b6b01371a2aed3cf6c5f71acbe28909ec4076, the introduction of get/set combinators and the module reorganization are "officially" done and ready to be tried out.


Converting the examples was very instructive for me. Here my experience with the "new" style.

Like:

Neutral:

Dislike:


I will now try to rethink the design and fix the dislikes. Since the browser does DOM manipulation with destructive updates, the distinction between Element and IO Element cannot be avoided, but maybe the appendTo combinator can be discouraged. I'm envisioning a style similar to existing HTML combinator libraries like xhtml or blaze-html.

It's probably a good idea if I make a new branch for that and don't push everything onto master.

fluffynukeit commented 11 years ago

I started to do my transition yesterday (very casually though). It's good to see that we have similar impressions. Here are mine so far:

This was a big redesign, and I really like the consistency improvements that were made. The nits I have are mostly related to convenience of style.

HeinrichApfelmus commented 11 years ago

Nice!

HeinrichApfelmus commented 11 years ago

Ok, I made a new branch html-combinators where we can experiment with creating Elements in a style reminiscent of the various HTML combinator libraries.

The Chat.hs example contains a very first attempt, I'm actually quite pleased. Apparently, the TP monad makes a triumphant return with the new name Dom, but I think that's ok.

fluffynukeit commented 11 years ago

I'll try to test this guy out this weekend. I'm trying to track down a nasty bug that has otherwise eluded me.

HeinrichApfelmus commented 11 years ago

I tried to copy existing HTML combinator library designs, but that didn't work too well because it would have mean to duplicate the attribute/property system to get a nice syntax for setting element attributes on creation. In the end, I reverted the whole thing to the "chained applications of # style". However, the new twist is that #+ now has an entirely different semantics: it appends a list of children to the first argument. I think this makes the creation of document trees very pleasant.

I renamed the Property to Attribute to keep things in line with other GUI libraries like Gtk2Hs or wxHaskell. Unfortunately, that clashes with the notion of attributes for HTML elements. What do you think, is this too confusing?

fluffynukeit commented 11 years ago

I'm just getting around to looking at the last two updates. I haven't tried them out on my own yet, but looking over the Chat.hs, I do like keeping the #. operator instead of using the !. operator for consistency. withWindow looks pretty convenient for both the general and my 1-window use cases, so that's a big plus for me.

Doing appending with a list of children elements looks great. At first I was skeptical because I didn't like the idea of needing to wrap single children in [] to do appending, but it's actually really great because they also serve as make-shift normal parenthesis (). Something like [text $ show timestamp] would normally be encased in parenthesis anyway, so it's cool to use list brackets and also enable appending a list. Very smart.

As for the Property vs Attribute naming, I'm undecided. When I use TPG, I often omit the type signatures, so one name vs the other doesn't have much consequence for me. I guess it depends on our target user demographic; are they people familiar with Haskell GUI toolkits, or are they familiar with html stuff? I know for me I have more experience with HTML, so I couldn't care less whether TPG is consistent with other Haskell GUI toolkits.

HeinrichApfelmus commented 11 years ago

Great!

I have now converted the Buttons example and I'm pleased with the results. The only two things that I still find ugly are

Hopefully I'll figure something out there as well.

HeinrichApfelmus commented 11 years ago

I have now converted all the examples.

Overall, I'm quite happy with the new style, though some things still feel a little awkward to me. But I think it's time to release what we have so far and elicit feedback from a wider public.

fluffynukeit commented 11 years ago

I will convert from my older Ji-ish version of TPG sometime this weekend if I can. My hope is that the conversion is large enough in scope to uncover any hiccups we might have missed so far.

HeinrichApfelmus commented 11 years ago

Ok! Let me know if there's anything I can help with. I'm eager to make a first release on hackage.

fluffynukeit commented 11 years ago

@HeinrichApfelmus , am I correct in understanding that the various drag events carry no event data? This seems like an oversight since we have set dragData str to set the data. We'd want the drag events to provide it to the event handlers.

I'm compiling some notes from my experience. So far the transition from the old version is pretty natural, for the most part.

HeinrichApfelmus commented 11 years ago

Ah, I missed that because the documentation said that "EventData is currently always empty". I pushed a quick fix.

We should probably put the drag & drop stuff into a single module and add a small example that showcases all the different drag & drop events. I'll open an issue for that.

Looking at some of the HTML documentation, I now understand that you needed to implement the getElementById function to use the drag data. Ideally, we would like to use Haskell values like Element directly as drag data. StablePointer from the C FFI come to mind. In a way, the whole project is actually a JavaScript FFI.

fluffynukeit commented 11 years ago

Which HTML documentation do you mean? I don't follow. In any case, I've found the getElementById function to be useful outside of the scope of drag and drop.

fluffynukeit commented 11 years ago

I'm still working on this, but I have at least done my conversion enough that my program compiles, although there are still some unresolved behaviors. Here are some of my thoughts.

  1. The migration to set style, set text, etc combinators is indeed more clear than the usage of #=, ##, and friends. Unless I missed it, there isn't an attribute for "id," perhaps due to name collision. I think having one would be useful. There are already similar convenience functions in draggable, droppable, etc.
  2. Transitioning to the new monad stack has been a huge headache. Part of the issue is that my code was written when IO was the only thing to worry about. Now with ReaderT Window, I find I have to litter my code with calls to element, return, and withWindow. If I had written my code with ReaderT in mind from the beginning, perhaps it would be a clean implementation, but as it stands it's certainly not. The mixing of pure IO and element operations can get pretty complicated. Type errors gave me a lot of trouble.
  3. In the same vein, it wasn't clear to me why getBody returns type IO Element. I think it would be more convenient to include it in the ReaderT stack, so I could do something like withWindow w $ getBody # set children []

So overall the library is pretty cleanly architected, but this might be at the cost of convenience. At the very least, I think more documentation is needed to describe why the functions are divided into Dom and plain IO.

Right now I'm trying to determine why my complete actions when using fadeOut don't get executed. Both this code in TPG and my code hardly changed, so it's a noggin scratcher.

HeinrichApfelmus commented 11 years ago

Nice!

  1. Fair enough, I pushed a small patch that defines a new id_ attribute.
  2. Ok, that's a problem. The overall idea was that DOM is used solely for building elements, whereas IO is used mainly for everything else, like manipulating existing elements. The MissingWords example may be a good prototype: creating elements uses DOM while manipulating elements uses IO.

    I don't think it's possible to get rid of return and element, because they are needed for being able to chain the # operator. This was already true for Ji. Compared to Ji, we now need to give less names thanks to the (#+) combinator, but we also need to use element more often.

    So, the only difference between IO and Dom is that one has to use withWindow occasionally. How much trouble does this combinator give you?

    I would like to take a look at any particularly awkward code examples that you have.

  3. You can actually write getBody w # set children []. (The set operation works for both monads.) Does that help?

It is actually possible to get rid of the Dom monad entirely. The idea is that elements are first created in "limbo" and only then bound to a particular browser window. However, we would have to implement a DOM simulation on the server and a protocol for transferring DOM trees between client and server. I have refrained from doing so unless it's really a problem, but it may just be.

fluffynukeit commented 11 years ago

OK, well that's what I get for pulling an example out of my ass. I guess it's just better to illustrate with code. Forgive the indentation errors due to copy/paste.

frontend messages w = do
set title "FNIStash" (return w)
body <- getBody w

(overlay, overlayMsg) <- withWindow w $ overlay
element body #+ [element overlay]
underlay <- withWindow w $ new # set (attr "id") "underlay"
element body #+ [element underlay]
frame <- withWindow w $ new # set (attr "id") "frame"
element underlay #+ [element frame]
msgWindow <- withWindow w $ controls messages (element frame)
msgList <- liftIO $ onlyBMessages messages

forM_ msgList $ \x -> do
    case x of
        Initializing AssetsComplete -> void $ withWindow w $ stash messages #+ [element frame]
        Initializing Complete -> do
            assignRandomBackground underlay
            crossFade overlay underlay 350               
        Initializing x -> void $ handleInit x overlayMsg               
        LocationContents locItemsList -> withLocVals w locItemsList (updateItem)
        Notice notice -> void $ withWindow w $ noticeDisplay notice # appendTo msgWindow >> return (scrollToBottom msgWindow)
        Visibility idStatusList -> withLocVals w idStatusList $ \e v _ -> setVis v (element e)

This was the result of my "naive" translation of the IO stuff I had before to the newest release. Lots of usages of element, withWindow, and void. It was difficult for me to get everything to type check, and if there was a type error I wasn't sure if it was something I was doing locally or due to type inference in some other piece of code I was calling. Like I said it might have been easier for me if I had partitioned my code more along "building elements" and "everything else" lines from the very beginning.

If you want to see more of the code, you can check out my FNIStash repo in the TPG-update branch. My GUI stuff is in src/FNIStash/UI (just pushed).

HeinrichApfelmus commented 11 years ago

Here's how I would write your example. Since the code is mainly concerned with building elements, I would put it in the Dom monad and slap a withWindow at the outermost level. This probably allows you to drop some of the w argument from functions like withLocVals as well.

Note that in general, it is a good idea to populate children at the time when the element is defined/created. This mirrors the functional style where you, say, define a list by declaring the elements it contains, rather than creating an empty list and updating it to add elements imperatively. This is the essential stylistic change made possible with the new (#+) combinator. Note that the treatment msgList seems to follow the imperative style.

frontend messages w = do
    return w # set title "FNIStash"
    body <- getBody w

    withWindow w $ do
        (overlay, overlayMsg) <- overlay
        frame    <- new # set (attr "id") "frame"
        underlay <- new # set (attr "id") "underlay" #+ [element frame]

        element body #+ [element overlay, element underlay]

        msgWindow <- controls messages (element frame)
        msgList   <- liftIO $ onlyBMessages messages

        forM_ msgList $ \x -> case x of
            Initializing AssetsComplete -> void $
               -- Frame will be moved to become a child element of stash messages . Is this really correct?
                stash messages #+ [element frame]
            Initializing Complete -> void $ liftIO $ do
                assignRandomBackground underlay
                crossFade overlay underlay 350
            Initializing x -> void $
                liftIO $ handleInit x overlayMsg               
            LocationContents locItemsList ->
                withLocVals w locItemsList (updateItem)
            Notice notice -> void $ do
                element msgWindow #+ [noticeDisplay notice]
                -- there seemed to be a mistake concerning  return  here
                liftIO $ scrollToBottom msgWindow
            Visibility idStatusList ->
                withLocVals w idStatusList $ \e v _ -> element e # setVis v

I'm inconclusive about what this code tells us about the distinction between Dom and IO. There is only call to withWindow remaining and the other arguments w can probably be removed. The liftIO are a little annoying, but interleaving the creation of elements and interactivity like crossFade seems odd to me.

It is clearly less noisy to remove Dom entirely, but as said, this incurs an implementation cost.

fluffynukeit commented 11 years ago

Thanks for taking me to school. That code is much improved. I only had to put an additional liftIO before forM_ to get it to type check. And you are indeed correct about the errors in the AssetsComplete section; I made a mistake in the transcription when updating TPG. Unfortunately that didn't fix the issue I'm having with crossFade (which, if I can't figure it out, might ultimately be a problem with animate's complete argument getting invoked), but the new cleanness make it easier to inspect for issues.

WithLocVals makes a call to getElementsById so I'm not sure I can remove the w arguments. I tried using ask to get the window from within withLocVals, but GHC complained about no MonadReader instance. Maybe I'm doing the wrong thing.

As for the interleaving of element creation and interaction, I'm not sure how to do it otherwise but am open to suggestions. The way it is set up now is to process messages from the backend . When the frontend gets a new message, the GUI is updated. Definitely imperative-ish. In crossFade, this is an animation to transition between the loading screen (overlay) and the main GUI content (underlay). When a Notice is processed, this is adding the notice message to a scrolling status message window.

So let's say I make the processing of Notice more functional and use a lazy list like element msgWindow #+ map noticeDisplay myLazyListOfMessages, where noticeDisplay sets the class of the node based on the contructor used for each message. Wouldn't this mean that I I would have to collect all my messages into a single lazy list on the backend and issue a single Notice event that would process the list? I just want to know how big of a project I'll get myself into if I try to implement your style suggestion. Right now most of my high level backend code has access to the Chan since I need it to send events to the frontend, so issuing new Notices is pretty easy since the high level stuff is in IO anyway.

HeinrichApfelmus commented 11 years ago

Ah, concerning the element creation, I think that some imperative style cannot be avoided. In particular, the myLazyListOfMessages idea will not work: it would be equivalent to specifying the layout of the window when all messages have been delivered, but of course, what you actually want to do is to display all the intermediate states. To use functional style here, one would have to make time explicit, at which point we would be doing FRP.

But now I understand how your function works in the first place: apparently, you are reading the channel into a lazy list and the forM loop blocks when there is no element available. Clever! That's a really nice way to write an event loop. It do feel a little uneasy with it, however, because it involves unsaveInterleaveIO to control the order of side effects. I would probably write a small combinator

foreverChannel :: Chan a -> (a -> IO ()) -> IO ()

and use foreverChannel messages in lieu of your forM_ msgList invocation. Your implementation would be something like

foreverChannel chan f = mapM_ f =<< getChanContents chan

whereas a "more pure" implementation would be

foreverChannel chan f = forever $ readChan chan >>= f

Concerning the Dom vs IO issue, I have taken a look at the code and think that doing it properly (which implies being able to move elements between browser windows) would be best done by rewriting the whole backend. The main issue is that unique element IDs are created on the fly in the browser window, but keeping them unique across browser windows would require some communication with the server. At this point, it's probably easier to tackle this as part of issue #23.

However, I think that there is a cheap and mostly working implementation that can eliminate the Dom monad. The drawback is that most attributes will be forced to stay WriteAttr until a better implementation is in place. I will look into coding it soon.

fluffynukeit commented 11 years ago

Thanks for the tips! I'll try them out when I get a chance.

The crossFade function that has been giving me trouble hasn't been resolved, but I think the issue is using set style to set the visibility while also making calls to fadeOut/In. The interaction between the IO and Dom in this case isn't clear to me, but I expect that the simplification you have in mind will make this easier to debug (if the issue still exists then).

HeinrichApfelmus commented 11 years ago

Concerning the weird bug you're encountering, I just noticed that my implementation of the Event stuff introduced a subtle bug.

Namely, I had assumed that each DOM element in the client window is referenced by a unique element ID elid. However, it turns out that a single DOM element on the client may be referenced by multiple elid. The server assumes a unique elid to manage event handlers, so something will go horribly wrong.

In particular, if you create an Element with an id_ attribute, then use getElementById to retrieve it and then register an event handler, the previous event handlers will be lost. This may or may not explain your problem.

I will try to fix this as soon as possible.

HeinrichApfelmus commented 11 years ago

Ok, I think I have the fixed the issue, so that elid are now in bijection with DOM elements. Does that magically improve your problem?

HeinrichApfelmus commented 11 years ago

Alright! I have implemented and pushed a cheap and mostly working solution to remove the Dom monad entirely. Does it help with your code?

Internally, an Element can now have two states: an element can be Alive, which means that it lives as a JS object in a browser window, or it can be in Limbo, which means that we represent it as a function that asks for a browser window and creates the element in this browser window. Updating an element in limbo means to append the update action to the "queue" of actions that have to be executed on creation time.

One consequence of this approach is that some operations like scrollToBottom may be delayed until the element is actually created in a browser window, which may result in an unexpected order of execution. However, I think this is ok for now.

fluffynukeit commented 11 years ago

Unfortunately, the bijection fix does not seem to fix my issue. I can work around it for now (ie settle for some acceptable but not ideal behavior). Going into it with more specificity might be worth a new issue as it seems a bit beyond the scope of the architecture redesigns here.

I must have cloned the repo just before you submitted the new Limbo updates, so trying those out might be next for me.

HeinrichApfelmus commented 11 years ago

Hope you had relaxing holidays, or are still having them, as the case may be. :tent: :smile:

I'm eager to make a first release, so if you have time to check out the Limbo changes and let me know whether they break anything unexpected, that would be awesome.

fluffynukeit commented 11 years ago

Thanks for the reminder. I'll start the effort ASAP.

fluffynukeit commented 11 years ago

I haven't made a lot of progress yet, but working on it so far has been pretty smooth. I have run into an issue though, which I hope is easy to resolve. With this candidate release, how does one get an instance to Window? Must I pass it around myself from my startGUI callback? There's a getWindow internal function that operates on an Element, but it's not exposed as far as I can tell.

Specifically, I have an element (call it "icon") that, when moused over, appends a new element to its window's body. Essentially it works like an info popup (there is a picture on my blog). The event handler is defined deep down with the icon definition itself, which is pretty far removed from the "top level" where the Window is readily accessible.

HeinrichApfelmus commented 11 years ago

Indeed, you would have to pass a Window from the startGUI way down to the callback. The reason is that Element are no longer associated with a Window, they may live in Limbo, after all. That's why I had to remove the getWindow function

However, I could change the Window type to include the limbo and implement a function

getWindow :: Element -> IO Window

Sometimes, this function will return a window corresponding to limbo. Trying to do anything interesting like get cookies on that would result in an empty result or even an exception, though.

Actually, I think this is probably a good idea, even if some functions will throw exceptions. After all, the API makes it look as if Element can be moved between different windows, so it's only natural that you can query which window an element is currently in.

fluffynukeit commented 11 years ago

So Elements are in Limbo before they are attached to Window, and as I understand it Limbo is kind of like building up JS instructions for the driver before they are executed in a particular Window. So the idea is that calling getWindow on an unattached Element is nonsensical, but couldn't getWindow itself be one of those instructions that is built up? For instance, I could call getWindow on an Element in Limbo, then attach that Element to three separate Windows. Attaching causes getWindow to run and return each of the three Windows respectively. Until the Element becomes alive, getWindow just sits in the queue of driver instructions waiting to be executed.

Unrelated, but there are old data definitions in Internal/Types that should be removed before any release.

HeinrichApfelmus commented 11 years ago

Ok, I took the easy way out and implemented the type signature Element -> IO (Maybe Window) instead. :smile: In your case, you can just pattern match on Just to get the desired window, because the event handler can only fire when the element is actually bound to a window.

The trick with building instructions works for things that do not return results, but functions like get cookies have the "problem" that the next IO operation may do a case analysis on the result, so you have to know the result before you can proceed with program execution. There's a good reason why most attributes are WriteAttr right now... I agree that it would be possible for some things like set title to delay the action on the window until the element is attached to a window, but I think the semantics would feel very weird. In contrast, setting the color of an element doesn't have any visible effect until it is created, so that's fine.

Unrelated, but there are old data definitions in Internal/Types that should be removed before any release.

Oh, which ones? It seemed to me that they are all used by the Internal.Core module. Note that the Core module reuses a couple of names.

fluffynukeit commented 11 years ago

Ah, duplicate names, but both used. Never mind about that then.

I did a transition to this release (twice, actually, due to a git snafu). I can compile but am experience some weird behavior I have not yet been able to explain. Most of it is related to events - either callbacks not being fired or elements not being found. Still trying to diagnose these problems but they are taking a while.

HeinrichApfelmus commented 11 years ago

Ok, that doesn't sound too good. Chances are that I have introduced bugs in the code, and I suspect they have to do with the assignment of unique IDs to elements. Is it possible to boil it down to a minimal example?

Alternatively, could you make a mockup variant of FNIStash that runs on OS X and does not require an existing installation of Torchlight 2? That way, I could try to take a look as well! Maybe a couple of dummy icons and dummy item descriptions will do.

fluffynukeit commented 11 years ago

I haven't tested whether the events work on the examples. That would be my first step toward a minimal example. Making a OS X mockup would be a lot harder.

fluffynukeit commented 11 years ago

Unfortunately, I have not been able to reproduce the issue so far. Here are some other, unrelated comments in the mean time.

  1. It would be convenient if the #+ operator had precedence such that map after it did not require parentheses. Currently I find I'm having to use #+ (map ...
  2. Also convenient would be common attributes defined, like src instead of (attr "src").
  3. It's not clear to me why some events are in the JQuery file and not the Events file. I would have expected them all to be in Events.
  4. Ideally I would like Quasiquotes back into the release so my CSS file can be embedded into the executable, and I don't have to worry about paths or copying it to the correct place.
  5. #+ is versatile enough to handle single elements, but I still found I wanted a function to append individual elements.
  6. Is there an emptyEl function available? I wanted its functionality so I did set children [], which seems less explicit to me.

I'll give a good effort to resolve the still lingering event issues, but at some point I'll need to concede defeat.

HeinrichApfelmus commented 11 years ago

Thanks! Don't worry if you can't track down the remaining issue. Right now, I think it's more important to release whatever we have in order to let a larger audience play with it. The version numbering starts at 0.1 for a reason. :smile:

Concering the other points:

  1. This should work already? The following is perfectly fine

    getBody w #+ map element [foo, bar]

    But you're probably using the $ operator? In this case, we have to adhere to the informal golden rule that all other operators must bind tighter than $, no exception for us.

  2. Adding a patch. Most of the HTML attributes are very obscure, though, we should probably only keep a few.
  3. Fair enough. I didn't want to mess with the JQuery module, though, and sendvalue is not a standard HTML event. For the moment, I would like to postpone this to version 0.2.
  4. I would like to have QuasiQuotes back, too, but unfortunately, there is a bug with Haddock. Also, it only applied to the standard CSS file, not custom ones. I suggest to postpone it to version 0.2. I've added a note to the corresponding issue.
  5. and 6. In these cases, I would like to have a single way of doing things to keep the API small. #+ is not as convenient for partial application, though, so example code would help.
fluffynukeit commented 11 years ago

Ok, sounds good! Onward we go.

On Thu, Jul 18, 2013 at 12:45 PM, Heinrich Apfelmus < notifications@github.com> wrote:

Thanks! Don't worry if you can't track down the remaining issue. Right now, I think it's more important to release whatever we have in order to let a larger audience play with it. The version numbering starts at 0.1for a reason. [image: :smile:]

Concering the other points:

1.

This should work already? The following is perfectly fine

getBody w #+ map element [foo, bar]

But you're probably using the $ operator? In this case, we have to adhere to the informal golden rule that all other operators must bind tighter than $, no exception for us. 2.

Adding a patch. Most of the HTML attributes are very obscure, though, we should probably only keep a few. 3.

Fair enough. I didn't want to mess with the JQuery module, though, and sendvalue is not a standard HTML event. For the moment, I would like to postpone this to version 0.2. 4.

I would like to have QuasiQuotes back, too, but unfortunately, there is a bug with Haddock. Also, it only applied to the standard CSS file, not custom ones. I suggest to postpone it to version 0.2. I've added a note to the corresponding issue. 5.

and 6. In these cases, I would like to have a single way of doing things to keep the API small. #+ is not as convenient for partial application, though, so example code would help.

— Reply to this email directly or view it on GitHubhttps://github.com/HeinrichApfelmus/threepenny-gui/issues/24#issuecomment-21197149 .

HeinrichApfelmus commented 11 years ago

Any luck with the event issue? If not, then let's release what we have!

fluffynukeit commented 11 years ago

No such luck. I went back to focusing on FNIStash dev instead of testing TPG. Let's do the release and get it into new hands to see what holds up.

HeinrichApfelmus commented 11 years ago

Awesome, and thanks again for your help! It is alive, err, I mean, uploaded to hackage.