Closed HeinrichApfelmus closed 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.
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.)
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.
Thanks, will take a look!
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?
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.
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:
setAttr
or even emptyEl
become obsolete and lots of new names will take their place, so most of the existing code will break anyway.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.
@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.
@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.
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:
#.
or setClass
. The code became a bit longer, but that didn't bother me.set (attr "src") (show number)
syntax as it neatly decomposes everything ("I want to set something, and the thing I want to set is an attribute, and its name is src
"). It does require parentheses, though.Neutral:
#
cannot be applied to an Element
directly, it has to be lifted to an IO Element
first. Example: element el # set cssClass "xy" ...
. It's not pretty, but didn't feel bad about it either.Dislike:
Window
parameter is really annoying. The TP
monad wasn't so bad, after all.appendTo
combinator feels wrong to me. It's not functional in style as it relies on destructive updates to work. This reliance on destructive updates also forces us to distinguish between Element
(a particular element that one can apply updates to) and IO Element
(a recipe for creating an element) and we have to convert between the two all the time. Compare this to the grid
combinator, which builds a parent element from a given list of child elements.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.
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:
set
function is more consistent than the old functions, as you mention. However, in my code at least, I found myself repeatedly using the same set text
, set cssClass
, etc that the old #x
combinators facilitated. At some point I thought, "Why am I changing these? I am comfortable with those combinators and my code already uses them. They save keystrokes. I think I'll keep them," but the DOM module isn't exposed in the cabal file. :-/Window
, but then stopped because I felt as if I would need to thread a window
parameters throughout a lot of functions. This would have been rather tedious so I decided to work on other things instead. Perhaps we can institute something like withWindow doStuff
. Since I only use one window anyway, it would be pretty easy to add one or two withWindow
calls where I need them, although I expect the actual implementation of withWindow
to be challenging, perhaps more than it's worth.set
. Something like img # set cssClass "item" # on hover $ \_ -> doSomething
. I felt like my flow was interrupted having to pull out the element directly with on hover e $ ...
, but this is no different than the old way.appendTo
doesn't leave the same kind of bad taste in my mouth as it does yours, but I'd welcome something more functionally styled.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.
Nice!
Concerning the #x
combinators, I wasn't bothered by their absence because I've come to think that code readability (consistency, explicit patterns) trumps keystroke reduction. Still, I agree that less keystrokes would be better. Fortunately, they always appear in the same pattern, namely
createElement
# set cssClass "foo"
# set text "bar"
# appendTo otherElement
and my hope is that this can be absorbed in the planned more functional set of combinators. For changing the text or the class after the fact, for instance in response to an event, the relative verbosity of set text
should be fine.
Concerning the on
, the only reason I didn't include it in the #
chain is operator precedence and parentheses. The expression
element e # set text "..." # on hover $ \_ -> ...
will be parsed as
(element e # set text "..." # on hover) $ \_ -> ...
which is not what we want at all. Due to precedence and the lambda expression, the event handler has to be put into parentheses, which is only pleasant if the handler is just a single line. Otherwise, a separate on
will be more convenient.
The only way to break the lambda expression I can think of right now is to use list notation,
set widget [on hover :~ \_ -> doSomething , attr "src" := show value ]
This is how wxHaskell does it.
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.
I'll try to test this guy out this weekend. I'm trying to track down a nasty bug that has otherwise eluded me.
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?
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.
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
Window
parameter when appending new elements to existing ones, like the body.Dom Element
, instead they are created using set text
.Hopefully I'll figure something out there as well.
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.
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.
Ok! Let me know if there's anything I can help with. I'm eager to make a first release on hackage.
@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.
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.
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.
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.
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.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.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.
Nice!
id_
attribute.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.
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.
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).
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.
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 Notice
s is pretty easy since the high level stuff is in IO
anyway.
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.
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).
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.
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?
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.
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.
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.
Thanks for the reminder. I'll start the effort ASAP.
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.
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.
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.
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.
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.
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.
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.
Unfortunately, I have not been able to reproduce the issue so far. Here are some other, unrelated comments in the mean time.
#+
operator had precedence such that map
after it did not require parentheses. Currently I find I'm having to use #+ (map ...
src
instead of (attr "src")
.JQuery
file and not the Events
file. I would have expected them all to be in Events
.#+
is versatile enough to handle single elements, but I still found I wanted a function to append individual elements.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.
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:
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.
sendvalue
is not a standard HTML event. For the moment, I would like to postpone this to version 0.2.#+
is not as convenient for partial application, though, so example code would help.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 .
Any luck with the event issue? If not, then let's release what we have!
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.
Awesome, and thanks again for your help! It is alive, err, I mean, uploaded to hackage.
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
andcolumn
that make it very easy to align elements. For instance, the following screen layoutwas created by the following code
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
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?