Not logged inOpenClonk Forum
Up Topic Development / Developer's Corner / Custom GUIs
- - By PeterW [gb] Date 2011-05-14 14:24
After the discussion on menus has sort-of ended in a solid stalemate, there's something we need to clear up anyway before moving further: How to actually bring those menus to life in the first place. There's already quite a bit that can be done using objects, but I feel by defining a new interface we could more easily integrate new features (especially ones that aren't tied to synchronicity!).

Looking through the various suggestions, we need to be able to at least do the following:
* Placement both relative to objects as well as absolute
* Rules for solving collisions (could theoretically be done in script, not sure)
* Visibility rules for players
* Colored boxes
* Colored lines
* Draw object icons in various sizes
* Draw text
* Draw graphics, with color
* Provide (asynchronous) reactions on mouse-over
* Provide (synchronous/asynchronous) reactions on mouse click

So here's a few examples. Normally, you would obviously wrap these examples with even more helper functions:

message = GUI_Text() {
  placement = Place_Object(this, Align_AboveCenter),
  collide = Collide_GoUp(MessagePriority),
  width = 200, height = 200,
  text = "This text should show above the object!"
};
ShowGUI(message);
[...]
HideGUI(message);


Going through it:
* The "GUI_Text() {..}" syntax should here construct a new proplist, with "GUI_Text()" as the prototype. Do we already have something like that? I feel it would be very natural. Influences see Haskell, Java, or even Torque Script.
* Place_Object(this, Align_AboveCenter) -> placement relative to a game object. HUDs could use Place_Screen(Align_BottomCenter).
* Collide_GoUp(MessagePriority) -> defines how the element position is to be changed if another element (say, another message) collides. Here we say to go up until a good place can be found. Note that we can set priorities.
* width = 200, height = 200 -> static sizing for now. For text it would obviously be a good idea to have something like "auto" here, but that opens another can of worms.
* text = "This text should show above the object!" -> straightforward enough. Should be word-wrapped and clipped according to box

Okay. Let's define a simple menu:

menu = GUI_Box() {
  placement = Place_Object(this, Align_BelowCenter),
  collide = Collide_GoDown(MenuPriority(this)),
  width = 40, height = 20,
  color = RGBa(0,0,0,50),
  children = [
    GUI_Image() {
      x=2, y=2, width=16, height=16,
      id = GetID(this)
    },
    GUI_Box() {
      x=22, y=2, width=16, height=16,
      children=[MakeMenuIcon(Contents())]
    }
  ]
}


* color = RGBa(0,0,0,50) -> Simply coloring the output, could work for almost all GUI elements. Another possibility would be to have different "fill" styles.
* children -> Allows you to place other elements relative to the given element (see below). Also might inherit a few properties, e.g. visibility.
* GUI_Image -> Here used to show the icon of an object definition. Should probably be made general enough to show arbitrary stuff from Graphics.png and/or object graphics. Longest shot: Take ActMap structure to specify animation
* x=2, y=2 -> No placement given, which then defaults to Place_Inherit(Align_TopLeft), meaning that the coordinates given refer to the coordinates of the top-left corner relative to the top-left corner of the containing box.
* GUI_Box() -> Used without color, therefore just defines a box that contained elements might inherit their positions from.

Let's turn to the menu item:

func MakeMenuIcon(object obj, int wdt, int hgt) {
  var mouseOverTag = Format("MouseOver%d", ObjectNumber(obj));
  return GUI_Box() {
    width=wdt, height=hgt,
    color = { (mouseOverTag) = RGBa(255,0,0,50) },
    onMouseEnter = React_SetTag(mouseOverTag),
                onMouseLeave = React_UnsetTag(mouseOverTag),
    onMouseClick = React_Call(this, "MenuItemSelected", obj),
    children = [ GUI_Image() { id = GetID(obj) } ]
  };
}


Okay, here it's getting interesting:
* mouseOverTag -> This is a tag, which defines a state of the GUI. The identifier should be as unique as possible - the way it is done here is relatively crude, real implementation should take more care.
* color = { (mouseOverTag) = RGBa(255,0,0,50) } -> A tag-specific property. Using another bit of not-yet implemented syntax, we construct a proplist that lists the value for each tag where we want the property to actually be set.
* onMouseEnter = React_SetTag(mouseOverTag) -> This is where the tag gets its live. Once the mouse enters the box, the tag gets set and the color of the box should change. The good part here: This doesn't need to be synchronized at all, therefore we have instant reaction!
* onMouseClick = React_Call(this, "MenuItemSelected", obj) -> Another reaction, this time a script call. This will obviously have to go over the control queue in order to be synchronized.
* children = [ GUI_Image() { id = GetID(obj) } ] -> Not sure, but here it would be convenient if it defaulted to inherit wdt and hgt from the container.

Finally a sketch how drag&drop could work. The idea would be to have the following code:
[...]
    dragClass = "ContentsItem",
    dragData = [ this, obj ]
    dragGraphic = [ GUI_Image() { id = GetID(obj) } ],
    nodragGraphic = [ GUI_Image() { id = NoDropPossible } ],
[...]
    dropClass = "ContentsItem",
    onDrop = React_Call(this, "OnMenuItemDropped")
[...]


Which means that the engine would draw "dragGraphic" whenever the mouse drags over a GUI element which has a matching "dropClass", and "nodragGraphic" if there's a mismatch. Once dropped over a matching drop region, the callback is issued - here giving as parameters "this" and "obj" from the viewpoint of the object that created the menu.

Future work:
* adaptive layouts that grow/shrink dynamically (necessary once we get larger amounts of text in there, which might have different size across translations!)
* scrolling
* animations

Thoughts?
Parent - - By Newton [de] Date 2011-05-14 16:47

> Thoughts?


This sounds like a super powerful interface which is both complex in implementation and in usage (in c4script) as it is not at all streamlined to the specific things the C4-GUI used to do. My thought is: It looks pretty unhandy to use in C4Script. Unless you have specific things in your mind what should be done with that which goes beyond 2-3 different menu types, it's not worth the effort.

>Future work:


Scale menus by resolution (viewport size)
Parent - - By PeterW [gb] Date 2011-05-14 18:14

> This sounds like a super powerful interface which is both complex in implementation and in usage (in c4script)


Complex in usage? Right now you have to define a whole object in order to place a GUI element. Building our menus using objects would require dozens of objects each. Reducing that to a prop list is a drastic simplification.

Also note that we obviously would wrap all menus using some kind of CreateMenu wrapper. Normal scripters wouldn't use this directly.

> complex in implementation


Not really. On the engine side it's just a matter of traversing the prop list and performing the associated drawing actions. The only complex part is the collision handling. If we can remove all the old menu code with its dozens of different variations (normal, context, the new bordered ones...), I think this actually simplifies things.

> it's not worth the effort.


Uhm, now you have me confused. Not doing this (or something similar) means that neither of our menu designs are possible and we are stuck with CR menus. Are you saying that you are seriously considering that?
Parent - By Newton [de] Date 2011-05-14 18:30

>Complex in usage? Right now you have to define a whole object in order to place a GUI element.


I meant in comparison to the menu in Clonk Rage, not with our C4Script HUD elements. In Clonk Rage, no ingame objects were involved in a menu. If all the current C4Object HUD elements could be changed to work on this interface, it would certainly be a gain.

>> complex in implementation
>Not really.


Well then I was misjudging the effort. You have a better overview of the engine.
Parent - - By Günther [de] Date 2011-05-15 00:06

> The "GUI_Text() {..}" syntax should here construct a new proplist, with "GUI_Text()" as the prototype. Do we already have something like that? I feel it would be very natural. Influences see Haskell, Java, or even Torque Script.


I don't particularly like making something look like a function call that isn't one. The current syntax is { Prototype = GUI_Text(), ... }. It'd probably be fine with a different keyword - Prototype is a bit too long. Class or Parent perhaps? The "Foo { ... }" option overloads the {} a bit too much - they already have two roles.

Another thing to consider is that magic engine behavior is tied to the proplist itself, not the prototypes - well, except that Objects need a Definition in their prototype chain. Whether that's good or bad I don't know, but we should stay consistent. Definitions are created automatically, Objects with CreateObject and Effects with AddEffect, so these new items should either be created by a function as well or not have magic behavior themselves. That'd result in CreateText({ Text = "Hello World", Align = "left", Color = 0xffcaffee }); or something like obj.ActMap = { Greetings = { Name = "Greetings", Procedure="GUI", GUI = { Type = "Text", Text = "Hello World", ... }}}; obj->SetAction("Greetings");
Reply
Parent - By Newton [de] Date 2011-05-15 13:11

>It'd probably be fine with a different keyword - Prototype is a bit too long. Class or Parent perhaps?


Magic? ;-)
Parent - - By Mafi [de] Date 2011-05-15 13:49
About another name for prototype here's some brain storming (sorted by my personal preference):
from, using, use, is, like, as, outof/out_of/outOf, with

But I don't think 'class' is a good idea because it implies IMO that there is a distinction between the classes and proplists.
Mafi
Parent - - By Günther [de] Date 2011-05-15 21:53

> But I don't think 'class' is a good idea because it implies IMO that there is a distinction between the classes and proplists.


On the other hand, class is a lot better known. Whether a slightly wrong idea about what's going on is better than no idea or a totally false idea (Prototype having no special behavior) is a difficult question.

I'm also not fond of using a totally new term, though "Is" would be a nicely short keyword.
Reply
Parent - By Newton [de] Date 2011-05-16 00:01
If Prototype really bears some magic behaviour, I think Peter's suggestion to put this into the syntax

Action() { Procedure="Chop", Length=3, ... };

is quite a nice idea. Otherwise, the "Prototype" or whatever you name it just looks like another property of the proplist. I didn't mistake that syntax with a function call (or function defintion) the first time I saw it.
Parent - - By PeterW [gb] Date 2011-05-16 11:32 Edited 2011-05-16 11:46

> The current syntax is { Prototype = GUI_Text(), ... }. It'd probably be fine with a different keyword - Prototype is a bit too long.


Well, my main "problem" with that is that it throws in the prototype with the other options, ignoring that it is definitely magical in the sense that it might cause a lot of properties to spring to life additionally. I feel that warrants a central place in syntax.

Maybe something like "derive GUI_Text() {..}" or "delegate GUI_Text() {...}"?

Edit: Also note that I didn't plan for the prop lists to be particularly magical. I just planned to traverse them.
Parent - - By Mafi [de] Date 2011-05-16 15:50
But why the suffix parens? They are confusing.
Parent - By PeterW [gb] Date 2011-05-16 16:09
Well, because here GUI_Text is a function, obviously. Could also be made a global constant, but depending on how our wrappers turn out, we might want to give parameters.
Parent - - By Günther [de] Date 2011-05-16 16:29

> Maybe something like "derive GUI_Text() {..}" or "delegate GUI_Text() {...}"?


Looks almost like the proposed proto operator for Javascript.

> Well, my main "problem" with that is that it throws in the prototype with the other options, ignoring that it is definitely magical in the sense that it might cause a lot of properties to spring to life additionally. I feel that warrants a central place in syntax.


Ack. I'm also reconsidering the choice of using a property for the prototype - a separate field, only accessible with an engine function, might work better. There's a proposal for a new "dict" type for Javascript that works like objects/proplists without any magic properties. While the difference between zero and one magic property is a lot smaller than the difference between zero and all the stuff a Javascript object has, the advantage of foo.Prototype over GetPrototype(foo) is not particularly compelling.

> Also note that I didn't plan for the prop lists to be particularly magical. I just planned to traverse them.


In which case they do not strictly need Prototypes.
Reply
Parent - - By PeterW [gb] Date 2011-05-16 16:36

> In which case they do not strictly need Prototypes.


What would be the alternative? Is there one that could have equally convenient syntax and composability properties?
Parent - - By Günther [de] Date 2011-05-16 18:29
I gave one example above:
obj.ActMap = { Greetings = { Name = "Greetings", Procedure="GUI", GUI = { Type = "Text", Text = "Hello World", ... }}}; obj->SetAction("Greetings");
In general, it shouldn't matter whether a property is in a proplist or a prototype of the proplist. So for data the engine processes, the alternative is specifying the property on the proplist itself and the engine replacing nil-properties with appropriate defaults. I'm not saying that's better than a prototype - I used a prototype for the Actions - just that we don't need them.
Reply
Parent - - By PeterW [gb] Date 2011-05-16 18:55 Edited 2011-05-16 18:58
That's not really composable though. If I want to, say, provide a template for a box with certain properties (a window?), I have to more or less awkwardly set properties myself. Here's my rough planning:

func GUI_Window() {
  return GUI_Box() {...};
}

func GUI_MessageWindow(string msg) {
  return GUI_Window() {
    children = ...
  };
}


Not sure about the details, though. Especially because I could foresee the need for a "children +=" there :)
Parent - - By Günther [de] Date 2011-05-16 20:01
If you just want to use the feature in implementation details of the library functions, you don't need lots of syntax sugar. Also, using freshly constructed proplists as prototypes only has disadvantages over setting the relevant properties directly. So if you can't stand the repetition in "p.foo=bar; p.baz=blub;", let's solve that syntax problem first :-)
Reply
Parent - - By PeterW [gb] Date 2011-05-17 11:28
Well, we could make it mean exactly that. We could also implement that the "x { bla = z }" syntax causes x to change instead if there's only one reference to it.
Parent - By Günther [de] Date 2011-05-17 13:07

> We could also implement that the "x { bla = z }" syntax causes x to change instead if there's only one reference to it.


No way, that's far too subtle.

Also, on reflection, saving two or three characters per changed property is just not worth the confusion two ways for doing the same thing will cause. I know the JSON syntax is nice and all, but we don't have to use it for everything ;-)
Reply
- - By Zapper [de] Date 2013-02-05 21:34
This is WIP btw!
Attachment: CustomMenus.jpg - Custom Menus! (43k)
Parent - By Sven2 [de] Date 2013-02-05 21:55
Pebbles agrees!
Up Topic Development / Developer's Corner / Custom GUIs

Powered by mwForum 2.29.7 © 1999-2015 Markus Wichitill