I'm working on Flex component which can be resized using handles on the right and left (that is, if you click and drag the left side, the component will grow to the left, if you click and drag on the right, it will grow to the right).
Right now I am using:
var oldX:Number = this.x;
this.x = event.stageX + (initial.x - initial.stageX); // Move the left edge to the left
this.width += oldX - this.x; // Increase the width to compensate for the move to the left
But that makes the right side jump around, and generally looks ugly.
What is the "right" to do this? Is there something fundamental I've gotten wrong?
Thanks!
Edit: The jitter occurs on the right side of the component. When I set this.x, the component moves to the left, the screen redraws, then the width is updated, and the screen redraws again.
I'm not a Flex guy, but I imagine your jitter is something to do with the Flex framework internally using Stage.invalidate() or some such thing to cause redraws during frame execution.
I imagine there's probably a "framework" way to deal with the problem, but for a workaround to make your change into one atomic operation you could update the object's transform directly, along these lines:
var dx:Number = // however you're finding this
var m:Matrix = new Matrix();
m.scale( scaleX*(width-dx)/width, scaleY );
m.translate( x+dx, y );
transform.matrix = m;
That should work for a simple graphic, but I'm not sure if it would work with a component that probably needs to catch events that the dimensions have changed and redraw itself. I think that's the only way to make your update atomic though, so if something along those lines doesn't help then you'll need an answer from someone more up on the framework.
Had the same problem.
The problem is that the stage isn't refreshing enough.
Just add stage.invalidate(); after your 2 assignments ...
Depending on how you have the listeners for the handles set up, the problem could be that the resize code is called over and over again because when you set the new x position for the component the handle will go under the mouse cursor effectively calling the resize code again and so on.
Related
I tried to create a sort of parallax with Angular and OnScroll event, however when scrolling the text that appears is blinking. Do you know how to make the effect rendering smoother onscroll ? Maybe with CSS only ?
The script created
https://stackblitz.com/edit/angular-b5cmf7?file=src%2Fapp%2Fapp.component.html
The main function that make the effect possible is this one
#HostListener('window:scroll', ['$event'])
isScrolledIntoView(){
const offsetHeight = this.testDiv.nativeElement.children[0].offsetHeight;
const rect = this.testDiv.nativeElement.getBoundingClientRect();
if( rect.bottom <= window.innerHeight){ // when the div is visible in viewport change the height (not more than the height of the content)
let height = (window.innerHeight - rect.bottom);
if(height<offsetHeight){
this.height = height;
}else{
this.height = offsetHeight
}
}
}
As soon as I saw your blitz I recognised an issue I've run into many times. It's to do with how you calculate values on a per-frame basis. I'm not sure how best to put this in words, but it's something like this:
Don't base your calculations on values that will be modified by the result.
It's a bit abstract, but specifically in your case, the problem is with the relationship between rect.bot and this.height. With each call of your onscroll function, you modify the height of your section. But modifying the height will also modify the value of rect.bot, and on the next scroll event, you'll get a different result, so height gets modified from a different base, resulting in the oscillation you observed.
To avoid this, you want to base all your calculations only on values that are not affected by the result. In this case, I would recommend using rect.top, which doesn't care what the height of the section is.
Furthermore, I'd recommend using interpolation, because it gives you more control. The idea is you want to pick these values:
at what percentage of the window height should the reveal commence (effectively, this.height = 0)
At what percentage of the window height should the section be fully revealed (this.height = offsetHeight)
How should I interpolate between these two values? (easiest is linear)
So I've forked your blitz, and I've gone ahead and outright stolen the clamp and invlerp functions from here:
https://www.trysmudford.com/blog/linear-interpolation-functions/
You can read about these interpolation functions and their uses in that link.
you'll see I've made two variables called "startPercentage" and "endPercentage" – these are in relation to the value of rect.top as a percentage of window innerHeight. You can play around with these to get different speeds or qualities of parallax to get the effect of your liking.
Here's the fork:
https://stackblitz.com/edit/angular-lcyc4q?file=src%2Fapp%2Fapp.component.ts
And if you feel like it, look into other interpolation functions to experiment with ease-in and ease-out for different effects.
When the user scrolls in a QTextBrowser in my application, I want to retrieve the position in the document that they've scrolled to (offset in the document, not the GUI position.)
If I can make the cursor jump to that location, I can get QTextCursor.position(). But I don't see a way to make the cursor jump to the visible location in the browser. The cursor stays where it is when I scroll.
I do not completely understand the description of your problem, but maybe you can determine the cursor (i.e. the position in text document) of the beginning and the end of the visible area by calling https://doc.qt.io/qt-5/qtextedit.html#cursorForPosition
QRect rect = textBrowser->rect();
QTextCursor firstVisible = textBrowser->cursorForPosition(rect.topLeft());
QTextCursor lastVisible = textBrowser->cursorForPosition(rect.bottomRight());
I have not tested it, but I think you get the idea. Maybe you will need to use the rect of textBrowser->viewport() instead of the rect of textBrowser. You need to experiment a bit with this to find what works for you.
Based on V.K.'s answer, here is my solution in Python:
browserRect = self.mainText.rect()
newCursor = self.mainText.cursorForPosition(browserRect.topLeft())
self.mainText.setTextCursor(newCursor)
textPos = mainText.textCursor().position()
Actually, just using QPoint(0,0) probably would work just as well, since the upper left of the browser's rectangle is pretty close to (0,0).
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.
I have the following problem: I want to resize a window using QPropertyAnimation, so as to look nice, but the window also instantly moves while the resizing takes place, while I don't want it to, I just want to resize the window and not to alter its position value.
So, here's my code:
animation = new QPropertyAnimation(this, "geometry");
animation->setDuration(150);
animation->setStartValue(QRect(preferences::x(), preferences::y(), width, window_height_min));
animation->setEndValue(QRect(preferences::x(), preferences::y(), width, window_height_min+expand_general_to));
animation->start();
where width and window_height_min and expand_general_to are my own variables that handle the amount of the resizing that has to be done. BUT, preferences::x() and preferences::y() truly handle the position of my window, so why it moves, while prefereces::x() will be the same both of the times? (in the start and in the end value) ?
Thanks in advance for any answers!
Please try to set size property.
animation = new QPropertyAnimation(this, "size");
animation->setDuration(150);
animation->setStartValue(QSize(width, window_height_min));
animation->setEndValue(QSize(width, window_height_min+expand_general_to));
animation->start();
I've noticed that the default behaviour for a DataGrid's vertical scroll bar is to scroll one row at a time. This is all well and good when the rows are all uniform and small (e.g. displaying a single line of text), but gets really ugly as soon as you have rows with variable heights.
I'm curious, is there a way to make DataGrid scrolling "smooth"? For instance, is there a way to have the DataGrid scroll by a set number of pixels, lines of text, etc. rather than scrolling one row at a time?
So far, the only solution I've managed to come up with is to place the DataGrid in a Canvas and have the Canvas do the scrolling instead of the DataGrid. The issue with this approach, though, is that as soon as the Canvas scrolls far enough, the DataGrid headers scroll off-screen. Ideally, I'd like to get the smooth-scrolling nature of the Canvas, but also keep the DataGrid headers visible. Is that possible?
The way that ItemRenderer's work in Flex 3 makes smooth scrolling difficult to achieve. Basically Flex recycles item renderers scrolled off of the top of the list as the display objects used for new data at the bottom of the list. Adobe's implementation of most list components in Flex 3 creates and adds these items as they come on to the screen rather than just off the screen, so they "pop in" and smooth scrolling isn't available. I'm not sure why they couldn't have done it in a similar manner for items +/- one position above or below the current scroll pane, but they didn't, and we're stuck with sticky scrolling by default.
Work-arounds do exist, though the one you've noted (dropping the datagrid into a canvas) negates the display-object saving intention of item renderers and incurs a performance cost. This will be fixed for most list-based Flex components in Flex 4, though it won't be fixed immediately for DataGrid. The DataGrid / AdvancedDataGrid component is maintained by a separate team based in India, last time I heard, and so it tends to be a bit behind the rest of the SDK.
I'd recommend trying something similar to this implementation of a smooth-scrolling list by Alex Harui. I'm not sure exactly how well it'd work for DataGrid or AdvancedDataGrid, but this is the most intuitive technique I can think of for making the list scroll correctly.
Try this... It's still based on Alex's code that was mentioned above. His should still be a great start for removing the snap-to-row behavior. Original source:
http://blogs.adobe.com/aharui/2008/03/smooth_scrolling_list.html
Alex's original some code for smooth vertical scrolling but that was not an issue I had with the DataGrid. It was smooth scrolling horizontally that I needed. I am using the DataGrid in an unorthodox manner for analyzing plain text reports output by our database (great way of providing visual feedback on a document). The code below allows content to go off screen and the user can scroll without that snap-to-column behavior.
You can adapt this to use the same math routines for vertical scrolling and then it will make scrolling possible and ignore the snap to row behavior. In particular switch the usage of the listContent.move method to move the contents vertically and use a inverse of the rounded pixel value you calculate from the vertical scroll bar (as opposed to my using the horizontal).
This method is bit simpler than Alex's method from the link above - a lot less code so try adapting and see how it works.
override protected function scrollHandler(event:Event):void
{
// Override the default scroll behavior to provide smooth horizontal scrolling and not the usual "snap-to-column" behavior
var scrEvt:ScrollEvent = event as ScrollEvent;
if(scrEvt.direction == ScrollEventDirection.HORIZONTAL) {
// Get individual components of a scroll bar for measuring and get a horizontal position to use
var scrDownArrow:DisplayObject = horizontalScrollBar.getChildAt(3);
var sctThumb:DisplayObject = horizontalScrollBar.getChildAt(2);
// I replaced maxHorizontalScrollPosition in Alex's code with "1300" to fix my exact application. In other situations you may finding using some property or different value is more appropriate. Don't rely on my choice.
var hPos:Number = Math.round((sctThumb.y - scrDownArrow.height) / (scrDownArrow.y - sctThumb.height - scrDownArrow.height) * 1300);
// Inverse the position to scroll the content to the left for large reports
listContent.move(hPos * -1, listContent.y);
}
// Go ahead and use the default handler for vertical scrolling
else {
super.scrollHandler(event);
}
}