When a Flex component moves directly, so that its x and y properties change, then a "move" event is dispatched on the component. That's all fine.
However, a component may also move as a result of its parent component moving — or its parent moving, and so on. Now when a parent component moves, its children just "move along" without any properties changing or move events being dispatched on the children. Still, the children are moving on the screen. So how can you detect this kind of generalized movement?
One workaround is to capture all move events in the application:
Application.application.addEventListener
(MoveEvent.MOVE, handleMove, true, 0, true);
The third argument is required because move events do not bubble, and so instead have to be captured. The fourth argument is unimportant, and the fifth argument turns on weak references, which is a good idea in this case because we are creating a global reference from Application.application to handleMove — a recipe for memory leaks.
Of course, this will fire too often (once each time anything whatsoever in the application moves), and in a large application could lead to performance problems. If you know that there is some component higher up in the hierarchy that’s sure to stay still, you can put the listener at that point instead of globally, which could reduce the problem.
Still, it would be nice to have a cleaner way to solve this.
Well, you've already suggested the most general solution, but I think it's possible for the child to go through parents/grandparents until it reaches one that is stationary (set by some dynamic property you set), at least this would save you some trouble in figuring out which parent handles which child.
private function addHandlerToStationaryParent(handler:function):void
{
var currentParent:DisplayObjectContainer = parent;
while(currentParent != null)
{
if(currentParent["stationary"] == true)
{
currentParent.addEventListener(MoveEvent.MOVE, handler);
return;
}
}
}
I guess it would be your preference as to whether or not this would be a better solution.
Related
I have a set of controls which have bindings to frequently changing data values. The data comes from a limited hardware-bus. Therefore, it would be better to disable the binding while the control is not visible on screen. The item's visible-property doesn't help in this case. So, how to determine if an Item-based QML widget is currently visible on screen (and not hidden by an overlay or currently outside the visible area)?
Source: https://forum.qt.io/topic/54116/how-to-check-if-a-item-is-currently-visible-on-screen
I have almost the same problem. Hoping someone here has a solution.
Here's what I would try to get working:
First, I presume there is a ScrollView or Flickable in play here? If so, then hook to signals like Flickable::movementEnded().
Second, when that signal fires, use Item::mapToItem() to check if each of your Item's visible rectangle (based on x, y, width, height) intersects your window's contentItem rectangle. Set the result as a boolean on each of your items and make sure the data retrieval is disabled when it is false (using && or a tertiary JS expression).
Or if more convenient, remove the binding when false and reapply it with Qt.binding() when true.
This is a follow-up to How to make mouse events propagate to widgets in `scroll` containers?
TL;DR:
The :layout method I implemented makes the widget draw all over the other widgets (see pictures below). How can I constrain where the :layout method will draw my widgets and where it will allow me to interact with the children?
Longer version:
So I ended up patching the scroll container and what I basically did was that I implemented a :layout method, based on the offset calculations that were already made in the original scroll container code.
This is basically what I did (I'll only put the relevant parts here):
-- this function emits signals `self._private.fps` times a second
local _need_scroll_redraw = function(self)
if not self._private.paused and not self._private.scroll_timer then
self._private.scroll_timer = timer.start_new(1 / self._private.fps, function()
self._private.scroll_timer = nil
self:emit_signal("widget::redraw_needed")
self:emit_signal("widget::layout_changed") -- this is the only
-- line that I added
-- to this function
end)
end
end
local function calculate_info(self, context, width, height)
-- this is a longer function, but in summary here we calculate the
-- ideal size that the child would like to be, we see if the child
-- is bigger than the space we have for drawing, and if it is,
-- we calculate offsets (and we call `_need_scroll_redraw` here)
-- so we later know where and how often to `:draw` and `:fit` it
end
function scroll:fit(context, width, height)
local info = calculate_info(self, context, width, height)
return info.fit_width, info.fit_height
end
function scroll:layout(context, width, height)
local result = {}
local i = calculate_info(self, context, width, height)
table.insert(result, base.place_widget_at(
self._private.widget, i.first_x, i.first_y, i.surface_width, i.surface_height
))
return result
end
-- Also, there was a `:draw` method, but I took it out entirely since
-- if I add the `:layout` method, things get drawn just fine
-- P.S: I also tried to implement what was in the `:draw` method, inside
-- the `:layout` method, so that it'll clip properly. I also tried that idea
-- with the `:before_draw_children` and `:after_draw_children` methods
-- but since I don't know how to use cairo, god knows what I wrote there,
-- but it didn't work
With the default scroll widget, my widget looks like this, but nothing I click on works:
But with the changes I made above, the widget with the rows does scroll, and I can click on each child just fine and have it react, it's just that it draws everything outside of its boundaries, and I can also click on stuff that's outside of the boundaries:
So my question is: how would I go about restricting what the :layout method displays, to have it work the way the default scroll layout works, but still be able to interact with the children?
Untested, but I guess:
function scroll:before_draw_children(context, cr, width, height)
cr:rectangle(0, 0, width, height)
cr:clip()
end
This takes the rectangle describing the size of the scroll widget and clips all children to it, meaning that they cannot draw outside of the scroll widget.
Note that this is only prevents the drawing of the child widgets. It does not make awesomeWM "forget about them". What I mean with that is: You can still click on the where the widget would be if it were visible. The click will still be handled by the widget. Only the actual drawing of the child widget is prevented.
The above is why I asked in your original question whether you want just this one widget to be scrolled, without anything next to it. Because if the scroll widget covers the whole wibox, this whole class of issues just goes away.
And no, as far as I know / see, there is no way to prevent the not-drawn widgets from receiving button clicks.
Basically I've got a QGridLayout with a few widgets in it. The important ones are 2 labels, which I use for drawing images to the screen. Well, if the user wants, he can change the resolution of the incoming images, thus, forcing the Labels to resize.
Let's assume the initial size of the label is 320x240. The user changes the VideoMode to 640x480, the label and the entire GUI resizes perfectly. But when the user switches back to 320x240, the label shrinks, but the Layout/Window does NOT.
I've played around with sizePolicies and sizeHints, and resize(0,0), but nothing did the trick. Could somebody help me with this?
Here some screenshots to clarify the problem:
You need to set the size constraint of the layout holding all your widgets to "SetFixedSize". Although the name doesn't sound like it will work, it ensures that your layout will only use the space it needs. You will not have the problem like you do in your second screenshot.
Example:
mainLayout.setSizeConstraint(QLayout::SetFixedSize);
QLayout::setSizeConstraint(QLayout::SetFixedSize) solves this problem well when you prefer keeping your widget's size fixed at all times--that is, if you'd like it to always be fixed to its "packed" size (which may still vary as the child widgets change size). That is what the "fixed" means there: "fixed" to the correct size, even as the latter varies. (In Qt terms, what I'm calling the "packed" size is simply the widget's sizeHint.)
But a constraint may be too strong a solution in some instances. In particular, if you apply it to a top-level window, then the user will not be free to resize the window. If you don't like that, you can instead perform the "set size to sizeHint" operation instantaneously each time it's needed, rather than imposing it as an unrelenting constraint. The way to do that is to call QWidget::adjustSize().
http://doc.qt.io/qt-5/qwidget.html#adjustSize
Note that if the container whose children are changing size is not the top-level window, then adjustSize() may have to be called recursively on the container and its parents. (In my case I had to do that, anyway. I also tried the size-constraint scheme, and found that applying the constraint at only the topmost level was successful in compacting all levels. I haven't enough knowledge of Qt to comment usefully on these observations, so I merely share them.)
You need to store the original size of your widget parent window before applying any changes to the layout and restore it when the user switches back to the original.
Notice that you need to work with the widget parent window size and not the widget parent size.
in your widget before applying the layout changes:
minimumWindowSize = this->window().size();
when you finished reorganizing the widget to the compact size
this->window().resize(minimumWindowSize);
So that is exactly what i'm doing in mu project.
Resolution os doesn't matter. I have only to have a widget for rendering video, or image in your case.
void MainWindow::resizeEvent(QResizeEvent* event)
{
QMainWindow::resizeEvent(event);
if ((player != 0) && ((player->isPlaying()) || player->isLoaded() || player>isLoaded())){
renderer->resize(ui->mainVideoWidget->width(),ui->mainVideoWidget->height());
resizeFilter();
}
}
I'm trying to make my own component that behaves like a list and supports infinite scrolling (in 1-dimension : vertical or horizontal) - both directions. For eg, a vertically laid out list which the user can scroll up or down forever - without ever hitting the 'last' or 'first' item. A good use for this: a calendar that displays each month as a list item.
Anyway, there are a bunch of things to overcome. The first of which, I think, is to disable the scrollbar's bounce effects (introduced in the latest Flex 4.5 (mobile) SDK).
If I can disable the bounce effects, I'm guessing I can then add/remove items as needed to the list and it would scroll infinitely.
Any ideas?
Krishna
Personally, an infinite list would mean a lot of rework of the core List component. It's a lot of work to reverse engineer and you'll probably hit a wall. I think what you want to do is create a component from scratch and extends SkinnableContainer.
From here on out, you need to decide how to implement and what's the user interaction for an infinite list, then need to implement proper practices and reuse your item renderers.
From my experience, you can simply implement the lazy loading on the List component by adding property change event to the viewport of the list's dataGroup
list.dataGroup.addEventListener( PropertyChangeEvent.PROPERTY_CHANGE, onScrollPropertyChangeHandler );
Then in the event, listen to the vertical scroll position
if ( event.property == "verticalScrollPosition" ){
var listHeight:Number = itemList.height;
var curAnchorPoint:Number = event.newValue + listHeight;
var bottomPositionToLoad:Number = 200; // Start loading when the list nearly reach the bottom minus 200
var anchorToLoadNextPage:Number = itemList.dataGroup.contentHeight - bottomPositionToLoad;
if(curAnchorPoint >= anchorToLoadNextPage){
loadNextPage();
}
}
When loadNextPage() is running, remember to remove the property change event so the loadNextPage will not be called multiple times.
I have Sprites that I want to have move around when I click and hold them and stop when I release them. I have methods that add Event listeners to the Sprites:
public function layOutEventListeners():void
{
var addSpriteEventListener:Function =
function(spr:Dictionary, index:int, vector:Vector.<Dictionary>)
{
spr["sprite"].addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
spr["sprite"].addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
}
gridVec.forEach(addSpriteEventListener);
}`
and methods to handle the events:
public function mouseDownHandler(me:MouseEvent):void
{
trace(me.target.toString());
trace(me.currentTarget.toString());
this.drawSprite(me.target);
this.growByTwo(me.target);
me.stopImmediatePropagation();
me.currentTarget.startDrag(me);
}
public function mouseUpHandler(me:MouseEvent):void
{
trace(me.target.toString());
trace(me.currentTarget.toString());
me.stopImmediatePropagation();
this.originalSize(me.target);
me.currentTarget.stopDrag();
}`
My problem is: when I click on the Sprites, as soon as I move the cursor, the Sprite's registration point snaps to the cursor, and when I release the mouse the Sprite doesn't stop following the cursor. I initially thought it was a problem with pixel collision. I thought the cursor wasn't touching anything on MOUSE_UP, but that proved to be false after I experimented. I even replicated the exact same Event adding and handling methods by starting another project and found that I wasn't having this problem. The test Sprite was simply dragging and dropping like usual, not snapping to the registration point, and being dragged by the point I clicked.
The only difference I can see, and also my only suspicion, is that the Sprites in my original code are being added to a Sprite, which is then being added to the stage, whereas the Sprite in the test project is being added to the root DisplayObject. I'm thinking that somehow the Event propagating down to the container Sprite and dragging and dropping that without dropping the other Sprite. The weird snapping I'm seeing might be the cursor snapping to the object behind the other sprite. Another important thing: when I drop a Sprite on top of another Sprite, that Sprite stops moving like I want it to, but still trails the registration point.
Regardless, I'm really stumped and I really don't know that I'm running over. Any ideas?
This is usually because sometimes the mouse is not over the clip when MOUSE_UP occurs, either because of other clips getting in the way or maybe the player is not refreshing the stage fast enough, etc...
I'm not sure this is your case, but either way it is often recommended to assign the MOUSE_UP event to the stage, so you can safely assure it is always triggered. Make sure to remove the listener on the mouseUp handler though ;)
spr["sprite"].addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
public function mouseDownHandler(me:MouseEvent):void {
stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
}
public function mouseUpHandler(me:MouseEvent):void {
stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
}
The down side is that you lose your clip reference on mouseUp, but you can create a reference by hand on mouseDown, or do the whole thing internally (within the sprite's code).