adventuregamestudio / ags

AGS editor and engine source code
Other
697 stars 159 forks source link

ags4 standard GUI improvements / decluttering #1208

Open fernewelten opened 3 years ago

fernewelten commented 3 years ago

The GUI interface might profit from being redesigned. This is an upshot of a discussion we have had recently in another issue about changes to AGS GUIs.

Feature suggestions for the GUI interface, all open for discussion:

  1. GUI labels should (also) be able to display images and view loops

    • Add an int property Image, to contain a sprite number. This would be mapped to the (new) attribute Label.Graphic. When non-zero, the corresponding sprite will be displayed.
    • Provide for Label.Animate analogous to Button.Animate, to set a label view and loop. Any view that is set in this way would be displayed continuously.
    • Provide for the readonly int attributes Label.View, Label.Loop, and Label.Frame, analogous to the button attributes.
    • Provide for the bool attribute Label.Animating.
  2. GUI buttons should have just one way for reacting to clicks

    • Remove and dismantle the property ClickAction for buttons and its associated logic. All buttons would have the RunScript action.

      • It's quite trivial to put an assignment to Mouse.Mode into an OnClick event, so the SetCursorMode clickaction isn't really needed.
      • So also remove and dismantle the property NewModeNumber
      • For buttons that don't have any action for some reason, it's equally trivial to define an OnClick event that correspondingly, doesn't have statements in it (no action ≙ no statements), so the NoAction clickaction isn't really needed, either.
    • Remove and dismantle the property Clickable and its associated logic. Depending on the use case, it is either equivalent to Enabled set to false(I envisage this to be the most common case by far) or to a button that has an empty clickaction.

    • (In ags3, deprecate all clickactions except for RunScript.)

    • (In ags3, only show the field NewModeNumber in the Editor when ClickAction has been set to SetCursorMode.)

Any further suggestions?

fernewelten commented 3 years ago

As for a use case for Label images and view loops, imagine an adventure that keeps track of Ego's health. This health is displayed in a a permanently visible GUI, as a text and also as a traffic lights icon. Whenever Ego's health gets dangerously low, a dancing danger sign will replace the traffic lights and alert the player.

ivan-mogilko commented 3 years ago

If Label is going to have all the button's graphical capabilities, then it might be better to make Label button's parent struct. Then Button will become a pushable label and inherit everything from it.

Regarding Clickable, it is not equivalent to Enabled. Clickable means whether clicks are intercepted by the control, or go through.

For example, with Clickable = false currently you may have a control displaying some graphic or text, but not intercepting clicks and letting them go to something beyond.

fernewelten commented 3 years ago

Also cf. #931

ivan-mogilko commented 3 years ago

But also, if we speak of redesign, I believe, we must begin with defining the purpose of the objects. What is the purpose and desired behavior of the label? What is the purpose and desired behavior of the button? This will make the goal clear, and any changes in properties will be a consequence of these decisions.

fernewelten commented 3 years ago

If we consider GUIs "from ground up", then there is another issue: AFAICS, AGS doesn't have truly modal GUIs, i.e., GUIs that work in the same way as the “save game” GUI does: call the GUI with a function call, while the GUI runs everything halts, the user needs to click certain buttons to close the GUI, after the user dismisses the dialogue the control flow continues directly after the function call.

To implement this behaviour, some buttons would need to be given a “close the GUI and return” clickaction.

fernewelten commented 3 years ago

AGS GUIs don't have the concept of a “cursor”. Contemporary users expect to see a blinking cursor if they are told to input text. If the GUI features several input boxes, then contemporary users expect to get the visual feedback of a blinking cursor in the text box that has the “focus” and to be able to switch the focus from one box to another by clicking into it.

This makes it very cumbrous to implement text input boxes. It can be done but is very kludgy to write and you write very low-level code.

fernewelten commented 3 years ago

Concerning the built-in save and restore dialogs, those aren't translatable. If a game calls them then their strings don't show up in the games' translate files.

ivan-mogilko commented 3 years ago

In regards to modal GUIs, I don't think this is solvable within only a GUI object, a game logic needs more clear concept of states, which can pause or override each other. That would be useful for both GUI and game scenes.

That is how builtin save, restore and other dialogs are implemented: they have their own states within engine, which update, redraw and react to user input.

ivan-mogilko commented 3 years ago

Concerning the built-in save and restore dialogs, those aren't translatable. If a game calls them then their strings don't show up in the games' translate files.

If I remember correctly, in the past this was done using Global Messages, that were deprecated in AGS since generation 3.*. There was a list of default messages with certain IDs that were corresponding to texts in these dialogs.

But, I actually doubt we should keep built-in dialogs in ags4 at all, they have other problems too. It might be easier to just remove them and let users create their own with normal GUIs. EDIT: or remove existing ones and reimplement in a better way perhaps. I realized the built-in dialogs may still be actually useful for debugging purposes, if game is run in debug mode. I.e. like something testers/players could bring up even in case game developer did not add necessary dialogs. This is just a random thought at the moment.

ivan-mogilko commented 3 years ago

To elaborate on what I said about modal GUI and "states"...

When we talk about a modal GUI the immediate question is "how modal it is" or rather "what does it pause/block" and what it does not. I.e. should it block all other GUIs unconditionally? should it pause animations everywhere? Should it pause script callbacks and which ones? What if user wants to keep some other on-screen animation at all times; what if they want to have a background process running in script?

In the past AGS had several built-in states which had very inconsistent behavior, some blocking whole game, others blocking only parts of it. For example, you may check "Run game loops while dialog options are displayed" option in general settings. That hints that initially dialog options were blocking whole game but then someone complained that they wanted to keep updating the room underneath. I'd like to avoid this kind of problems in the future.

That said, I don't really have a fully fledged idea in my mind right now, and the "ideal" implementation would probably be too much to start with. But I still think that we must define very clear rules about game states and how they may be configured by game developer, and only then use these rules when implementing new states such as modal user GUI state and others.

To implement this behaviour, some buttons would need to be given a “close the GUI and return” clickaction.

I cannot agree with this, I'd rather agreed with your previous statement that ClickAction should be deprecated. My strong belief is that we must have a design rule for all the future changes which sais that whenever there's an engine action it should be first of all doable in script (using engine API), and there must not be any builtin "magic actions" or "special behaviors" unless they utilize something already available in engine API. That should guarantee that users are able to customize or override the behavior. Like in the above example, user may want to close modal GUI not by button but by some other means / on some condition. Or they may want to do extra operations when the "close" button is pressed. That's why I believe that closing modal GUI should also be doable by a command in script.

messengerbag commented 3 years ago

In terms of blocking, would it make sense to not make it a property of the GUI itself, but of the call that displays it?

I'm thinking something like int GUI.Show(BlockLevel=eNoBlock) which, depending on the enum BlockLevel could block the script or even other threads (like Display() does currently) while the GUI is shown. Then you could have a GUI.Hide(int retVal) which would hide the GUI and end the block, passing retVal as the return value of the GUI.Show() function (so that you could for example pass 1 if the user presses OK and 0 if they press Cancel).

Another thing: when you talk about "modal" GUIs, that tends to suggest that other GUIs and interactions would be disabled. In some cases that might be a side effect of blocking scripts, but this seems like something you might want to control independently.

morganwillcock commented 3 years ago

My strong belief is that we must have a design rule for all the future changes which sais that whenever there's an engine action it should be first of all doable in script (using engine API), and there must not be any builtin "magic actions" or "special behaviors" unless they utilize something already available in engine API.

Potentially it depends on how easy the scripting language is to extend (and perhaps this is an argument to look at how extensible it is first before embarking on any grand re-design) but I think a good approach is that the engine provides the most minimal unopinionated design that it can, mainly data structures, resource management, script hooks, and that anything that could be described as 'behaviour' should be implemented with the script language itself. This is not to say that users are expected to write the behaviour, but that common opinionated behaviours go back into scripts that are shipped as part of distribution or templates, hooked in such a way that the user experience is the same.

In terms of removing magic, possibly the above includes composing game objects from scripts too. If behaviours are composable and objects generic then there is conceptually no difference between removing an object from a room, turning a character invisible, or closing a GUI window. But if there needs to be a difference I (AGS user) can add it. Same goes for click handling, positioning, tweening, etc. Where there needs to be magic is what cannot be reasonably done within the scripting language (I do not want to write my own audio or video decoders, path finding algorithms, etc.) and what needs to be enforced (I should not be allowed to break engine initialisation, shutdown, loading valid image data from a file, etc.).

Another thing: when you talk about "modal" GUIs, that tends to suggest that other GUIs and interactions would be disabled. In some cases that might be a side effect of blocking scripts, but this seems like something you might want to control independently.

In an extreme case where someone feels the predefined arrangement of event processing and blocking doesn't fit what they are trying to do, they would be able to change it. Ideally the bar should be so low that modifying the behaviour is accessible to everyone who goes looking to do it.

Any further suggestions?

I appreciate that this wasn't what you were originally asking for, @fernewelten but...

If we consider GUIs "from ground up"

^ ...this is how I'm approaching it...

GUI labels should (also) be able to display images and view loops

If the aim is to remove the magical hardcoded behaviours and add flexibility then I think it it is better to ask what a GUI label is, because any game object that has graphics and doesn't react to a click sounds a lot like like a GUI label.

GUI buttons should have just one way for reacting to clicks

I think a generic click handler gives you this by default. Considering the cursor state shouldn't be a special case because it should be equally valid to change behaviour based on any other element of state.