
I'll try to summarize the discussion about how to arrange the class/object hierarchy, admittedly with some bias towards my own opinions. I'd just like to confirm if we really have agreed on what I think we've agreed [like many people, I have a tendency to think that everyone agrees with my opinions unless I hear a clear "no" :-) ]. So what have we already agreed on? 1) There will be a datatype Window 2) There will be a datatype Pane 3) ... along with a class IsPane (or PaneClass or ...) class IsPane p where toPane :: p -> Pane 4) There will be a class Container class Container c where -- member functions to be determined And we'll have 4a) instance Container Window 4b) instance Container GridBox Open Issue/Disadvantage of this solution: What type would a "getContainer" function have? Can we have such a function at all? What are the alternatives to a class Container? 5) For a particular widget type Foo, we will have 5a) data Foo 5b) instance IsPane Foo 5c) newFoo :: Container c => c -> [Prop Foo] -> IO Foo 6) GridBox is a layout container that works like Gtk's Table, Java's GridBagLayout and Tk's grid. It should be implemented using Gtk's Table for Gtk, and implemented in Haskell for other platforms. We have 6a) instance IsPane GridBox 6b) instance Container GridBox 6c) Layout constraints can be set using attributes: column :: IsPane p => Attr (GridBox, p) Int (and more attributes as required for a grid layout algorithm) Example: box <- newGridBox container [columns =: 2, rows =: 2] button <- newButton box [...] set (box,button) [column =: 1, row =: 1] 7) Below the "Pane" type and class, we'll try to keep the hierarchy flat (perhaps even absolutely flat), so that we don't have any problems with mismatches between the interface and backend implementations. === Undecided issues: 8) Will there be a datatype for a general Widget (in addition to the datatype for a general Pane (Point 2)) ? data Widget along with class IsWidget w where toWidget :: w -> Widget Pros: *] Common features of Windows and Panes can be implemented very conveniently on platforms where both Windows and Panes are represented by a "Widget" object. *] Having both getWindowSize and getPaneSize seems unnatural; having getSize in a type class sounds nice, but might require that all Pane types explicitly instantiate that class, always with the same implementation, for example: instance HasSize Button where getSize b = getSize (toPane b) Cons: *] Not all platforms have that common representations; implementations on other platforms would have to use a discriminated union type and lots of case expressions, for example: getSize w = case toWidget w of WindowWidget win -> getWindowSize win PaneWidget pane -> getPaneSize pane *] The only common feature of Windows and Panes that I can think of is size and position on screen. *] The Parent/Child relationship is not such a common feature, as there are no cross-platform parent/child relationships between toplevel windows. Also, one widget being contained in another widget is not the same thing as one window being a child of another window (the latter has nothing to do with containment, except with MDI). *] There's no reason to need a general list of widgets, where Panes and Windows can be freely mixed. 9) Do we prefer to use a) too many type classes, i.e. a HasFoo class even when there's only one widget that supports foo, or do we rather want to risk having b) too few type classes, i.e. making foo a plain function when we may later add another widget that supports the exact same foo? I think the performance costs of type classes are absolutely negligible, but what about namespace polution and readability of error messages? So that's all for now, feel free to add your own points. - I'd also like to know what other people think I've agreed to, when in fact I haven't :-). Cheers, Wolfgang

--- Wolfgang Thaller
So what have we already agreed on? 1) There will be a datatype Window
okay
2) There will be a datatype Pane
I don't think we need this datatype. Since Windows are not going to be widgets, we simply need Widgets and Windows. As for menu items, these seem to be more of an attribute of a menubar than a widget itself. This also simplifies things. I think we simply need Windows and Widgets.
3) ... along with a class IsPane (or PaneClass or ...) class IsPane p where toPane :: p -> Pane
This is similar to the IsWidget thing which I agree too.
4) There will be a class Container class Container c where -- member functions to be determined And we'll have 4a) instance Container Window 4b) instance Container GridBox
From a programmers perspective, having a Container class is of little or no use. This is especially true since each container might have a different addChild function. (see more comments below)
5) For a particular widget type Foo, we will have 5a) data Foo
okay
5b) instance IsPane Foo
I would prefer IsWidget
5c) newFoo :: Container c => c -> [Prop Foo] -> IO Foo
From a programmer's perspective, this limits options. The main arguments for this is the implementation
I do not like passing the parent to the constructor. details for certain toolkits would be hard. I earnestly think this is worth it though. There are two ways to implement this: 1) Try to work around it on the C side of things. Maybe one could derive a parent that doesn't show children. 2) On the haskell side of things, all attributes and RO variables must be mirrored. This would be easy for most attributes (since once the standard is complete, it would be easy to create a generator for most of the work). I realize that smooth implementations is a strong goal for this project, but this is a really large chunk of logical and grammatical functionality we are sacrificing here. I think my previous post which talks about eliminating Windows deserves some serious thought. Perhaps, once implemented, it can be critiqued for its merits instead of discarded for to its originality.
6) GridBox is a layout container that works like Gtk's Table, Java's GridBagLayout and Tk's grid. It should be implemented using Gtk's Table for Gtk, and implemented in Haskell for other platforms.
My guess is that these different grid layouts have different math behind them. Since there will need to be at least one Haskell implementation, we might as well use it for all of them to enforce standardization. I also strongly support use of HBox's and VBox's. For hand written programs, they are of great use in organization of a gui. See how simple the following code is: a1 <- newHBox [ children := [ ButtonA, ButtonB ] ] a2 <- newHBox [ children := [ ButtonC, ButtonD ] ] b <- newVBox [ children := [ label1, a1, label2, a2 ]] return b Since they are also easier to implement on platforms that do not have Grids . . . these are likely to be the first two layout candidates I'll put in the fltk implementation for critique.
We have 6a) instance IsPane GridBox 6b) instance Container GridBox
Again, I only see a need for instance IsWidget GridBox
6c) Layout constraints can be set using attributes: column :: IsPane p => Attr (GridBox, p) Int (and more attributes as required for a grid layout algorithm)
set (box,button) [column =: 1, row =: 1]
Compared to addToGrid theGrid 1 1 theButton the above code lacks in readability and intuitiveness IMHO.
7) Below the "Pane" type and class, we'll try to keep the hierarchy flat (perhaps even absolutely flat)
I think that for now, we should keep it entirely flat with Windows and Widgets.
9) Do we prefer to use a) too many type classes, i.e. a HasFoo class even when there's only one widget that supports foo, or do we rather want to risk having b) too few type classes, i.e. making foo a plain function when we may later add another widget that supports the exact same foo?
I agree that performance costs are negligible. As far as namespace pollution goes . . . it seems to be negligible as well. Why would someone use a gui library with a title attribute and also really want the class HasTitle as well? It seems that using Has* classes will promote a common standard for naming overloaded haskell functions. Using the Has* classes also allows for unforeseen uses of CGA for custom widget types. For the next version of CGA-example I think I'll implement a few different ways of thinking. One may then try coding in the different versions and come to a conclusion on which is easiest. If this is to be widely used, the user (not the implementor) needs to be our target audience. David J. Sankel

2) There will be a datatype Pane
I don't think we need this datatype. Since Windows are not going to be widgets, we simply need Widgets and Windows.
This is simply a difference in terminology. I was avoiding the term "Widget" because many people are used to thinking "Windows are Widgets". Most toolkits (all that I can think of) that use the term "widget" follow this model, and most toolkits (again, all that I know) that don't follow the window=widget philosophy use different terms. But in fact I don't care about how to call it.
As for menu items, these seem to be more of an attribute of a menubar than a widget itself. This also simplifies things.
Probably an attribute of a menu or menu bar, perhaps an object of their own, but definitely not a widget. Also, a menu bar is not a widget because it's not necessarily inside a window.
3) ... along with a class IsPane (or PaneClass or ...) class IsPane p where toPane :: p -> Pane
This is similar to the IsWidget thing which I agree too.
It's the same thing, by a different name.
4) There will be a class Container class Container c where -- member functions to be determined And we'll have 4a) instance Container Window 4b) instance Container GridBox
From a programmers perspective, having a Container class is of little or no use. This is especially true since each container might have a different addChild function. (see more comments below)
Yes, if we use specialized addChild functions, we can forget about the class.
5c) newFoo :: Container c => c -> [Prop Foo] -> IO Foo
I do not like passing the parent to the constructor. From a programmer's perspective, this limits options. The main arguments for this is the implementation details for certain toolkits would be hard. I earnestly think this is worth it though.
I earnestly think it isn't :-).
There are two ways to implement this:
1) Try to work around it on the C side of things. Maybe one could derive a parent that doesn't show children.
That's not possible. The main limitation of Carbon and Motif is not that you have to specify the parent at widget creation; the problem is that you cannot change the parent afterwards (Xt) or that you cannot move a widget to a different window afterwards (Carbon). If we put newly-created widgets in some container where they are invisible, they'll stay there forever :-( .
2) On the haskell side of things, all attributes and RO variables must be mirrored. This would be easy for most attributes (since once the standard is complete, it would be easy to create a generator for most of the work).
Yes, but besides the obvious doubling of work, it would have one more disadvantage. It would make it _very_ hard to allow other native widgets to be wrapped for use with CGA. I would like to be able to create some widgets with platform-specific functionality, and then manipulate it with CGA functions. If those CGA functions insist on moving widgets to different windows, the CGA implementation will have to be able to read out _all possible attributes_ of _all possible widget types_ (even those not directly supported by the CGA) and recreate the native widget elsewhere. Definitely not possible, at least not for Carbon. But I'd really really like to have that feature...
I realize that smooth implementations is a strong goal for this project, but this is a really large chunk of logical and grammatical functionality we are sacrificing here.
Being able to just "cast" a native widget to a CGA widget is important functionality, too.
I think my previous post which talks about eliminating Windows deserves some serious thought. Perhaps, once implemented, it can be critiqued for its merits instead of discarded for to its originality.
You could of course provide an example in order to convince me and everybody else. But for now, I really fail to see the advantage. A Window is a real thing, that I want to be able to create and set attributes of. Why should it be automated away (at great implementation cost, because that really doesn't fit with any backend, and it doesn't fit at all with some backends)? What would we gain by eliminating Windows from the API?
6) GridBox is a layout container that works like Gtk's Table, Java's GridBagLayout and Tk's grid. It should be implemented using Gtk's Table for Gtk, and implemented in Haskell for other platforms.
My guess is that these different grid layouts have different math behind them. Since there will need to be at least one Haskell implementation, we might as well use it for all of them to enforce standardization.
They are so similar that you can use all of them for some time without noticing any differences; also, of the three, Gtk is the only one we support directly, so why not use Gtk's table? But of course, if our own implementation turns out to be better if we use slightly different maths, then I have no problem with that, either.
I also strongly support use of HBox's and VBox's. For hand written programs, they are of great use in organization of a gui.
As are GridBoxes. I have never used HBoxes and VBoxes myself, but they look like they can be easily implemented on top of GridBoxes, so if we're going to implement GridBoxes anyway, H/VBoxes are practically free. I see no reason not to include them if you want them. HBoxes and VBoxes are not completely general, and I think grids are better suited to UI builders, so I do insist on putting grids in sooner or later.
We have 6a) instance IsPane GridBox 6b) instance Container GridBox
Again, I only see a need for
instance IsWidget GridBox
6c) Layout constraints can be set using attributes: column :: IsPane p => Attr (GridBox, p) Int (and more attributes as required for a grid layout algorithm)
set (box,button) [column =: 1, row =: 1]
Compared to
addToGrid theGrid 1 1 theButton
the above code lacks in readability and intuitiveness IMHO.
But row and column are not the only attributes; I just used the default values for the others in the above example. The attributes provided by Gtk's table are, IIRC, row, column, row span, column span; Boolean attributes X expand, Y expand, X fill, Y fill, X shrink and Y shrink (though I haven't yet figured out what the "shrink" attributes really do in Gtk...). Those are more optional attributes than can be managed using 'Maybe' parameters to an addToGrid function, so I tried to use attributes. Also, if we provide a "FixedPlacementBox" (in order to implement other layout algorithms on top of it), we would have attributes that we would want to be able to change after the fact, when recalculating the layout due to window resizing. For that, the above "set" command looks relatively practical and intuitive. There might be better solutions though, only I wasn't yet able to think of them. Cheers, Wolfgang

I don't have any objections to what Wolfgang proposed. Comments: On Sun, May 04, 2003 at 12:33:54AM +0200, Wolfgang Thaller wrote:
2) There will be a datatype Pane [..] different terms. But in fact I don't care about how to call it. Me neither. In gth2hs I use WidgetClass and I am happy as long as we don't use this name.
5c) newFoo :: Container c => c -> [Prop Foo] -> IO Foo
I do not like passing the parent to the constructor. From a programmer's perspective, this limits options. The main arguments for this is the implementation details for certain toolkits would be hard. I earnestly think this is worth it though.
I earnestly think it isn't :-). A big advantage of forcing the user to pass the parent: You cannot put a widget into two containers. If you need to create a branch without a parent you may as well use a lambda expression.
It would make it _very_ hard to allow other native widgets to be wrapped for use with CGA. [..] But I'd really really like to have that feature... Having backend specific widgets is a must. I am not afraid that users complain that passing the parent restricts them too much since some
[..] professional toolkits require that anyway.
6) GridBox is a layout container that works like Gtk's Table, Java's GridBagLayout and Tk's grid. It should be implemented using Gtk's Table for Gtk, and implemented in Haskell for other platforms.
My guess is that these different grid layouts have different math behind them. Since there will need to be at least one Haskell implementation, we might as well use it for all of them to enforce standardization.
They are so similar that you can use all of them for some time without noticing any differences; also, of the three, Gtk is the only one we support directly, so why not use Gtk's table? But of course, if our own implementation turns out to be better if we use slightly different maths, then I have no problem with that, either.
Right now I definitely want to use Gtk's table. Xt and Gtk are the only toolkits that support resizable dialogs. While the dialog is resized the grid layout code is executed all the time and implementing that in Haskell will be quite slow (just because of crossing the C/Haskell boundary). This is just not necessary on Win32/Carbon since it only needs to be done once when the widget is created.
I also strongly support use of HBox's and VBox's. For hand written programs, they are of great use in organization of a gui.
As are GridBoxes. I have never used HBoxes and VBoxes myself, but they look like they can be easily implemented on top of GridBoxes, so if we're going to implement GridBoxes anyway, H/VBoxes are practically free. I see no reason not to include them if you want them. HBoxes and VBoxes are not completely general, and I think grids are better suited to UI builders, so I do insist on putting grids in sooner or later.
The HBox and VBox you presented, David, seem to be overly simplified. For each widget in a box Gtk needs to know if the widget expands, stays its natural size or pads excess space. I guess other toolkits work differently. I think we should start with the easiest layout mechanism, which I suppose is the grid. [..]
set (box,button) [column =: 1, row =: 1] [..] the above code lacks in readability and intuitiveness IMHO.
I don't really like that either. Could we provide layout information of W when we create W? Axel.

Axel Simon wrote:
Right now I definitely want to use Gtk's table. Xt and Gtk are the only toolkits that support resizable dialogs. While the dialog is resized the grid layout code is executed all the time and implementing that in Haskell will be quite slow (just because of crossing the C/Haskell boundary). This is just not necessary on Win32/Carbon since it only needs to be done once when the widget is created.
Let's start out with the GtkTable feature set. When (if) we get the urge to add features that GtkTable doesn't have, we should already have both implementations to compare performance. One more remark: You seem to assume that we will never need dynamic resizing on Mac OS and Windows... Both platforms _do_ have resizable dialogs (the File Open & Save dialogs, for example), although most dialogs are fixed size (conversely, I've also seen non-resizable Gtk dialogs). Also, we'd want to use some layout mechanism for document windows, too, and they definitely are resizable. So if the C->Haskell->C rountrip is really too slow, then we're in trouble. Fortunately, I'm convinced that it's fast enough: I've used HOpenGL, and I've seen GLUT windows resize absolutely fluently (where each frame was rendered by Haskell code).
[..]
set (box,button) [column =: 1, row =: 1] [..] the above code lacks in readability and intuitiveness IMHO.
I don't really like that either. Could we provide layout information of W when we create W?
The difficult thing about it is that the type of the layout information depends on the container... We could use something like this newButton :: Container c => [Prop Button] -> c -> [Prop (c,Button)] -> IO() so that we can write button <- newButton [title =: "Do Something"] box [column =: 1, row =: 1, columnspan =: 2] ... and we still can use the unintuitive set notation for changing it: set (box, button) [row =: 2] If we disallowed changing of layout information after widget creation, it would perhaps be possible to simplify the type of newButton (but how?). I would, however, strongly argue for keeping that feature, mainly because I'd like to do the following: I'd implement a FixedPlacementBox (at least for MacOS), which is a normal container that uses pixel coordinates and sizes as layout constraints. Then, I'd use some onResized callback to implement dynamic re-layouting, so I obviously need to change layout constraints afterwards. Cheers, Wolfgang

Thank you Axel and Wolfgang for convincing me.
Perhaps, later, an CGA extention that allows for fully
dynamic layout (including changing parents, etc.)
could be developed later. It would clearly require a
lot more from it's backends.
--- Axel Simon
The HBox and VBox you presented, David, seem to be overly simplified. For each widget in a box Gtk needs to know if the widget expands, stays its natural size or pads excess space. I guess other toolkits work differently.
You're right about that. Qt, what I'm most used to, defines this information to be an attribute of the object, which makes a lot of sense. Here is what I wrote earlier about that: http://article.gmane.org/gmane.comp.lang.haskell.gui/461
I think we should start with the easiest layout mechanism, which I suppose is the grid.
HBox and VBox are probably the first think I am going to implement in the example. If we have a clear-cut definition of a grid, I'll do that too. David J. Sankel
participants (3)
-
Axel Simon
-
David Sankel
-
Wolfgang Thaller