Best Practices to implement a menu system in a QML game? - qt

I'm relatively new to QML/QtQuick 2 and I quite enjoy it. To start mastering it I am implementing a small fullscreen game. My idea is to have the (simple) game and when the uses presses up the ESC key to pop-up a graphical menu with some settings.
Now I have all the basics running but I'm not completely sure which is the best Declarative way of managing the menu and passing keyboard focus back and forth.
Is it better to statically create the menu and make it hidden in my main file, like this:
MyMenuSystem {
visible: false
}
and then set it to "visible" when I need it? Or defer loading it and use Loader and create a new instance? I prefer to have it declared in my QML code for cleanness, but what is the best practice here? Or is there some component to switch between views that I'm completely missing? I could find very little information and examples on this topic. Thanks!

Your question is kinda broad and could lead to opinionated answers, but since it's a topic close to my heart, I thought I'd try to give some advice.
Loader
I tend to only use Loader when the item I need to load is only relevant under certain circumstances. For example, if a document is open that doesn't support a certain feature, then the UI element that represents that feature can be put into a Loader. A benefit of this (besides a (usually) very small increase in performance) is that it avoids the need to add lots of null checks for that feature. For example, if you don't use a Loader in this case, you might end up with a lot of code that looks like this:
text: optionalFeature.foo ? optionalFeature.foo.bar : ""
With a Loader, you control when the item is loaded, such that it's only loaded when the optional feature is enabled. Then the code becomes much nicer:
text: optionalFeature.foo.bar
Popup
For an in-game menu, I'd use a Popup. This is good if you want the user to be able to see the game in the background, though you can also obscure that with an overlay, if you wanted.
StackView
If you don't want to use a popup, then I'd suggest StackView. You should probably be using this for each screen in your game anyway, as it is a perfect fit for it.

Here's how I managed to do a Menu System:
I used Component to create a Component and createObject() to turn it into an instance of that component.
MyMenu.qml:
import QtQuick 2.9
Popup {
/* Menu Item 1 */
/* etc etc */
}
Game.qml:
Component {
id: myMenuId
MyMenu {
}
}
Game {
id: myGameId
property var dynamicItem
Keys.onPressed: {
if (event.key == Qt.Key_ESC) {
dynamicItem = myMenuId.createObject(myGameId);
dynamicItem.open()
}
}
}

Related

Detect blocking overlay with selenium

I'm testing a website that opens in-browser pop-ups to display object details. These pop-ups are sometimes modal, by which I mean that they render the rest of the screen inoperative and trigger a gray transparent overlay that covers everything but the pop-up. This overlay is intended behavior, which means that I need a way to detect whether or not it was correctly triggered.
However, I am not familiar enough with the implementation of such overlays to determine where in the DOM I should look to find the properties that govern such behavior. As such, I was hoping someone with more information on how such overlays are usually configured could point me in the right direction.
The obvious solution is to simply try to click a button and see what happens but I was hoping to write a method that I could implement throughout the test suite rather than having to write a different check for each circumstance.
For those interested I'm scripting in Java using Selenium.
I know this is old, but it may still help someone else. I had just recently solved a similar problem for our React site. I believe we were using the react-block-ui module to implement our blocking overlays.
Basically, I was able to detect a certain element was blocked by an overlay because of 2 known facts:
The element was within a containing div ("the overlay") that followed a certain naming convention. In our case, it was section-overlay-X.
This overlay would have a class attribute (named av-block-ui) if it was blocking.
(Hopefully, you have access to this information, too... or something similarly useful.)
With this information, I wrote up a couple utility methods to help me determine whether or not that particular WebElement is blocked by an overlay. If it was blocked, throw a ElementNotInteractableException.
For Java:
...
By SECTION_OVERLAY_ANCESTOR_LOCATOR = By.xpath("./ancestor::div[contains(#id, 'section-overlay-')][1]");
...
private WebElement findUnblockedElement(By by) {
WebElement element = driver.findElement(by);
if (isBlockedByOverlay(element)) {
throw new ElementNotInteractableException(String.format("Element [%s] is blocked by overlay", element.getAttribute("id")));
} else {
return element;
}
}
private boolean isBlockedByOverlay(WebElement element) {
List<WebElement> ancestors = element.findElements(SECTION_OVERLAY_ANCESTOR_LOCATOR);
WebElement overlayAncestor = ancestors.get(0);
String overlayClass = overlayAncestor.getAttribute("class");
return !StringUtils.isBlank(overlayClass);
}
Here's my snippet on it:
https://bitbucket.org/snippets/v_dev/BAd9dq/findunblockedelement
This won't work in all situations, but I solved this problem by checking the overflow value of the body element. The flavor of modal I was trying to get past disabled scrolling of the page while it was active.

How to change the transient parent of a QML Dialog/Window?

I'm working on a Qt (5.3) desktop application (C++ core with a QML ui) whose main window is an ApplicationWindow and that at some point launches Dialogs.
Since there are differences in the use of dialog modality between Windows and Mac OS X (e. g. An about dialog is rarely modal on Mac OS X but is almost always modal on Windows) and also in the way of presenting some dialogs’ content, I’ve changed the design to allow the implementation of platform specific versions of the dialogs.
For so I created the following DialogLoader:
Loader {
id: dialogFactory
property string dialogName
function platformFolder() {
if (Qt.platform.os === "osx")
return "osx"
return "win"
}
onDialogNameChanged: { source = platformFolder() + "/" + dialogName + ".qml" }
onStatusChanged: {
if (dialogFactory.status === Loader.Error)
console.log("DialogFactory: failed to load file: " + source);
else if (dialogFactory.status === Loader.Ready)
console.log("DialogFactory: file \"" + source + "\" loaded")
}
}
Which I use as follows:
ApplicationWindow {
// …
property alias aboutDialog: aboutDialogLoader.item
// …
DialogLoader { id: aboutDialogLoader; dialogName: "AboutDialog" }
// …
Action { text: qsTr("About..."); onTriggered: aboutDialog.show() }
// …
}
That approach is working fine and it suits my needs except for one thing:
Modal dialogs on Windows don’t behave as they do when I declare them in the ApplicationWindow directly. If the app looses focus when a modal windows is opened and the focus is granted again, the modal window appears behind the main window which causes the app to be inoperative.
After some research I’ve realized that the cause of the problem is that with the loader approach the ApplicationWindow is not acting as the transient parent of the Dialog.
I've found a work-around for this by manually bringing the Dialog to front when that happens:
MainWindow {
// ...
onActiveChanged: {
if (Qt.platform.os === "windows") {
if (active) {
// ...
if (aboutDialog.visible) {
aboutDialog.requestActivate()
}
// ...
}
}
}
// ...
}
but I think it would be better to avoid such work-arounds, if possible. After digging into the Qt documentation without any luck, I decided to post this question, which can be resumed as follows:
Is it possible to change the transient parent of a QML Window? If so, please would you point out how?
Suggestions about a different approach that could avoid the reparent stuff are welcome.
I guess it is impossible to do that. At least in Qt 5.4.
From documentation (default property "data" of QML type "Window")
data : list The data property allows you to freely mix visual
children, resources and other Windows in a Window.
If you assign another Window to the data list, the nested window will
become "transient for" the outer Window.
If you assign an Item to the data list, it becomes a child of the
Window's contentItem, so that it appears inside the window. The item's
parent will be the window's contentItem, which is the root of the Item
ownership tree within that Window.
If you assign any other object type, it is added as a resource.
It should not generally be necessary to refer to the data property, as
it is the default
property for Window and thus all child items are automatically
assigned to this property.
It seems that all dynamic creation of "Window" has wrong transient parent, because of QML Binding limitation. All QML list elements is not modifiable. Thus, it is not recommended to use lists with property bindings. You'll never get notify signal if it changes. So, I guess, QML Engine never knows that new QML element is "Window" type and it necessary to add it to Windows hierarchy.
From documentation (https://www.ics.com/files/qtdocs/qml-list.html):
A list property cannot be modified in any other way. Items cannot be
dynamically added to or removed from the list through JavaScript
operations; any push() operations on the list only modify a copy of
the list and not the actual list. (These current limitations are due
to restrictions on Property Binding where lists are involved.)
Maybe this is a bug, maybe feature. Anyway, for now it is impossible to load "window" type dynamically with proper transient parent.
The best workaround I've found - use modality: Qt.ApplicationModal for dialog.

Display a value from the model and refresh it on change

I have a property in my model:
this.setData({myProp: "Hello World!"})
What I want is only display it in my template and refresh it when it changes.
The solution I currently have is to use a section, bind its refresh to the property and display the property in a macro. My issue is that I feel there should be another, simpler way to do it. It's IMHO too heavy (15ish lines), especially compared to angular's {{myProp}}
Does anyone have a simple and elegant implementation?
Ok, I just had to use the text widget.
{#aria:Text {
bind: {
text : {
to: "myProp",
inside: data
}
}
}/}
It gives wierd results when inserted in the middle of a text, but a bit of CSS will solve it.

How to skin buttons in flex 3?

Just out of curiosity, I am making an effort to optimize every part of our flex app (which is a small part of our app in general). Currently, I am working on optimizing all of the buttons/skins. I have linked a few of the buttons that I use, and some sample code I am using to generate them.
Please advise on how to make this more efficient, usable, and just better overall. Thanks!
As you can see, our buttons can be pretty different, but have a similar look and feel. Currently, I am creating 'stateful skins,' by setting up something like this:
skin: ClassReference('com.mysite.assets.skins.NavigationButtonSkin');
Then, NavigationButtonSkin looks something like this:
public class NavigationButtonSkin extends UIComponent {
// imports, constructor, etc
protected override function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
// initialize fillColors, fillAlphas, roundedCorners, etc
switch( name ){
case 'upSkin':
fillColors = [getStyle('backgroundColor'),getStyle('backgroundColor2')];
break;
// do the same for overSkin, downSkin, disabledSkin, etc
}
// use this.graphics to draw background
// use this.graphics to draw border on top of background
}
}
I commented out some of the straight forward parts, but let me know if this is a bad/inefficient way of doing this - and how to improve.
Thanks!
In terms of performances, it would be better that your skin inherits from ProgrammaticSkin instead of UIComponent.
ProgrammticSkin itself inherits from Shape and provides utility methods for skinning such as verticalGradientMatrix, drawRoundRect, ...
That's all I can say looking at your code.
Good point is you use programmatic skin instead of bitmap/swf based skins.
Okay, I'm not getting where you're getting at with this. You just want to know if you're doing it right? I'm assuming that your skin: ClassReference('com.mysite.assets.skins.NavigationButtonSkin'); is added to the css of a Button, which is good, however I don't see why you're doing it all in Actionscript. Seems inefficient and essentially you're losing all the ability of mxml layouts and support for Catalyst (if you'd ever need it in the future).
Try creating a skin in Flash Builder, it'll create an MXML with the default button skin where you can just edit it as you please. It's also A LOT easier to do state based design using mxml over actionscript. You should modify from there on and have a separate skin for each button types.
EDIT: oh crap, didn't see this was Flex 3... Get with the program ;) Just listen to what Florian said.

Flex Truncating Button Labels

First and foremost, I apologize for any vagueness in this question. At this point, I'm simply trying to get some new ideas of things to try in order to diagnose this bug.
Anyway, the problem I'm having is with an application that's using a custom moduleloader. That moduleloader has been compiled into an swc and the moduleloader is being instantiated via its namespace. This all works perfectly fine. The problem I'm encountering is specific to mx:button controls used within modules. For whatever reason, their labels are being truncated so, for example, Sign In is showing up with an ellipsis, as Sign ...
After quite a bit of fooling around I have been able to establish the following:
This problem only seems to occur within modules. If a button control is used in the main mxml, the label does not get truncated.
The button control whose label is being truncated does not have a width specified (setting its width to 100% or a specific pixel width doesn't fix the issue)
The button control is using the default padding (messing with the padding by setting left and right to 5 or any other value doesn't help matters either).
We are not using any embedded fonts so I've ruled that out as a possibility as well.
mx:CheckBox and mx:LinkButton are equally impacted by this problem although mx:CheckBox also seems to not want to show its checkbox, it just shows the truncated label.
A potential side affect of this is that attaching a dataprovider to mx:ComboBox causes the combobox control to throw a drawing error but I'm not entirely certain that it's related to the above problem.
One interesting thing I did find while perusing the net for an answer was a mention of fontContext and its relationship to IFlexModuleFactory. There's no specification for fontContext within our implementation of moduleloader so I'm not entirely certain if this could be the issue. In any case, if anyone has any ideas, it would be hugely appreciated. On the other hand, if you know exactly what ails me and can provide me with an answer, I might just wet myself with excitement. It's late. I'm tired. I NEED my Flex app to play nice.
Thanks in advance,
--Anne
Edit: To clarify what I'm looking for with this question, I really just need to know the following:
Could this issue be caused by a namespace conflict?
What else can potentially override the default behavior of labels if no CSS has been implemented?
Has anyone encountered a problem with inheritance being lost while using a custom implementation of moduleloader?
Has anyone encountered this problem or a similar problem with or without using moduleloader?
I'm not sharing any code with this question simply because I'd have to share the entire application and, unfortunately, I can't do that. Again, I'm not looking for the end all, be all solution, just some suggestions of things to look out for if anyone has any ideas.
I've been dealing with this issue myself, off and on and in various forms, for a year, and while I haven't figured out just what's causing it yet, there's clearly a mismeasurement happening somewhere along the line.
What I have been able to to, though, is work around it, essentially by subclassing button-type controls (in my case, Button, LinkButton, PopUpButton, et. al.) and assigning their textField members instances of a UITextField extension whose truncateToFit element simply returns false in all cases:
public class NonTruncatingUITextField extends UITextField
{
public function NonTruncatingUITextField ()
{
super();
}
override public function truncateToFit(s:String = null):Boolean
{
return false;
}
}
The custom component just extends Button (or whatever other button-type control is the culprit -- I've created a half-dozen or so of these myself, one for each type of control), but uses a NonTruncatingTextField as its label, where specified by the component user:
public class NonTruncatingButton extends Button
{
private var _truncateLabel:Boolean;
public function NonTruncatingButton()
{
super();
this._truncateLabel = true;
}
override protected function createChildren():void
{
if (!textField)
{
if (!_truncateLabel)
textField = new NonTruncatingUITextField();
else
textField = new UITextField();
textField.styleName = this;
addChild(DisplayObject(textField));
}
super.createChildren();
}
[Inspectable]
public function get truncateLabel():Boolean
{
return this._truncateLabel;
}
public function set truncateLabel(value:Boolean):void
{
this._truncateLabel = value;
}
}
... so then finally, in your MXML code, you'd reference the custom component thusly (in this case, I'm telling the control never to truncate its labels):
<components:NonTruncatingButton id="btn" label="Click This" truncateLabel="false" />
I agree it feels like a workaround, that the component architecture ought to handle all this more gracefully, and that it's probably something we're both overlooking, but it works; hopefully it'll solve your problem as you search for a more definitive solution. (Although personally, I'm using it as-is, and I've moved on to other things -- time's better spent elsewhere!)
Good luck -- let me know how it works out.
I've used the custom button and link button class solutions and still ran into problems - but found a workaround that's worked every time for me.
Create a css style that includes the font you'd like to use for you label. Be sure to check 'embed this font' right under the text selection dropdown. Go back and apply the style to your button (or your custom button, depending on how long you've been bashing your hear against this particular wall), and voila!
Or should be voila...
I just came across this issue and solve it this way:
<mx:LinkButton label="Some label"
updateComplete="event.target.mx_internal::getTextField().text = event.target.label"
/>;
I've had some success preventing Flex's erroneous button-label truncation by setting labelPlacement to "bottom", as in:
theButton.labelPlacement = ButtonLabelPlacement.BOTTOM;
Setting the label placement doesn't seem to help prevent truncation in some wider button sizes, but for many cases it works for me.
In cases where you can't use a bottom-aligned button label (such as when your button has a horizontally aligned icon), janusz's approach also seems to work. here's a version of janusz's .text reassignment technique in ActionScript rather than MXML:
theButton.addEventListener(FlexEvent.UPDATE_COMPLETE, function (e:FlexEvent):void {
e.target.mx_internal::getTextField().text = e.target.label;
});
The preceding code requires you to import mx_internal and FlexEvent first, as follows:
import mx.events.FlexEvent;
import mx.core.mx_internal;
And here are the results…
Before (note truncation despite ample horizontal space):
After:
The only downside to this approach is you lose the ellipsis, but in my case I considered that a welcome feature.

Resources