Cannot change polygon 'size' every repeat in Builder - psychopy

I'm using Builder v1.80.06
I can vary the position of a polygon every repeat easily enough
e.g. I have a Positions list
positions=[[1,1],[1.1,0.9],...]
and in the 'Position field' have :
$positions[0]
and then change it's value in a code block on each repeat.
BUT I want to vary the size in a similar manner with a $sizes list but get an error.
Looking at the generated code, the problem is at the object creation stage. the code generated is:
for a hard coded polygon (ie ok)
polygon_1 = visual.Rect(win=win, name='polygon_1',
width=[1.5, .2][0], height=[1.5, .2][1],
ori=0, pos=[0, -0.6],
lineWidth=1, lineColor=[1,1,1], lineColorSpace=u'rgb',
fillColor=[0,1,0], fillColorSpace=u'rgb',
opacity=1,interpolate=True)
for one populated by a variable (not working):
polygon_2= visual.Rect(win=win, name='polygon_2',
width=1.0[0], height=1.0[1],
ori=0, pos=[0,0],
lineWidth=1, lineColor=[1,1,1], lineColorSpace=u'rgb',
fillColor=[1,0,0], fillColorSpace=u'rgb',
opacity=1,interpolate=True)
It complains (rightly) that 1.0[0] makes no sense on the width and height parameters
Even though I have my sizes list instantiated in a code block right at the beginning of the experiment instead of reading $sizes[0] a default float value of 1.0 is used.
Any other suggestions for how to vary the polygon size dynamically at runtime using builder?
I could just take the generated code and drop it into coder I suppose and fix the problem but I want to hand this over to a researcher so would like for them to be able to maintain it.
thanks,

If you set size to be a tuple/list with a pair values [1.2,1.5] or [1,1] does that not fix it?

When you change attributes at runtime, just change the attribute of an existing stimulus instead of instantiating a full new stimulus. The latter is quite heavy on ressources, causing unreliable timing. So do
stim = visual.Rect(win) # instantiation, ressource heavy
stim.attribute = newValue # change attribute. lighter.
I can think of two ways you could do it in a pretty neat way. The first is to set width and height explicitly instead of the size attribute, but using a size-like value. So (removing all parameters not of interest):
polygon_2 = visual.Rect(win)
# Unpack the x,y-sizes to the stimulus .width and .height attributes
newSize = (1.5, 0.2)
polygon_2.width, polygon_2.height = newSize
The second, if the size attribute is really important to use, is to use the Polygin with edges=4 to make it a rectangle:
polygon_2 = visual.Polygon(win=win, edges=4, size=(1.5, 0.2))
# Setting size
polygon_2.size = (0.8, 0.4)
Do try Jon's suggestion first. But the idea with visual.Rect and visual.Circleis to use substitute Polygon's size and vertices for something more relevant. So size can do unexpected things if width/height etc. are not 1.

Related

UICollectionViewLayout with dynamic heights - but NOT using a flow layout

Say you have a UICollectionView with a normal custom UICollectionViewLayout.
So that is >>> NOT <<< a flow layout - it's a normal custom layout.
Custom layouts are trivial, in the prepare call you simply walk down the data and lay out each rectangle. So say it's a vertical scrolling collection...
override func prepare() {
cache = []
var y: CGFloat = 0
let k = collectionView?.numberOfItems(inSection: 0) ?? 0
// or indeed, just get that direct from your data
for i in 0 ..< k {
// say you have three cell types ...
let h = ... depending on the cell type, say 100, 200 or 300
let f = CGRect(
origin: CGPoint(x: 0, y: y ),
size: CGSize(width: screen width, height: h)
)
y += thatHeight
y += your gap between cells
cache.append( .. that one)
}
}
In the example the cell height is just fixed for each of the say three cell types - all no problem.
Handling dynamic cell heights if you are using a flow layout is well-explored and indeed relatively simple. (Example, also see many explanations on the www.)
However, what if you want dynamic cell heights with a (NON-flow) completely normal everyday UICollectionViewLayout?
Where's the estimatedItemSize ?
As far as I can tell, there is NO estimatedItemSize concept in UICollectionViewLayout?
So what the heck do you do?
You could naively just - in the code above - simply calculate the final heights of each cell one way or the other (so for example calculating the height of any text blocks, etc). But that seems perfectly inefficient: nothing at all of the collection view, can be drawn, until the entire 100s of cell sizes are calculated. You would not at all be using any of iOS's dynamic heights power and nothing would be just-in-time.
I guess, you could program an entire just-in-time system from scratch. (So, something like .. make the table size actually only 1, calculate manually that height, send it along to the collection view; calculate item 2 height, send that along, and so on.) But that's pretty lame.
Is there any way to achieve dynamic height cells with a custom UICollectionViewLayout - NOT a flow layout?
(Again, of course obviously you could just do it manually, so in the code above calculate all at once all 1000 heights, and you're done, but that would be pretty lame.)
Like I say above the first puzzle is, where the hell is the "estimated size" concept in (normal, non-flow) UICollectionViewLayout?
Just a warning: custom layouts are FAR from trivial, they may deserve a research paper on their own ;)
You can implement size estimation and dynamic sizing in your own layouts. Actually, estimated sizes are nothing special; rather, dynamic sizes are. Because custom layouts give you a total control of everything, however, this involves many steps. You will need to implement three methods in your layout subclass and one method in your cells.
First, you need to implement preferredLayoutAttributesFitting(_:) in your cells (or, more generally, reusable views subclass). Here you can use whatever calculations you want. Chances are that you will use auto layout with your cells: if so, you will need to add all cell's subviews to its contentView, constrain them to the edges and then call systemLayoutSizeFitting(_:withHorizontalFittingPriority:verticalFittingPriority:) within this "preferred attributes" method. For example, if you want your cell to resize vertically, while being constrained horizontally, you would write:
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
// Ensures that cell expands horizontally while adjusting itself vertically.
let preferredSize = systemLayoutSizeFitting(layoutAttributes.size, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
layoutAttributes.size = preferredSize
return layoutAttributes
}
After the cell is asked for its preferred attributes, the shouldInvalidateLayout(forPreferredLayoutAttributes:withOriginalAttributes:) on the layout object will be called. What's important, you can't just simply type return true, since the system will reask the cell indefinitely. This is actually very clever, since many cells may react to each other's changes, so it's the layout who ultimately decides if it's done satisfying the cells' wishes. Usually, for resizing, you would write something like this:
override func shouldInvalidateLayout(forPreferredLayoutAttributes preferredAttributes: UICollectionViewLayoutAttributes, withOriginalAttributes originalAttributes: UICollectionViewLayoutAttributes) -> Bool {
if preferredAttributes.size.height.rounded() != originalAttributes.size.height.rounded() {
return true
}
return false
}
Just after that, invalidationContext(forPreferredLayoutAttributes:withOriginalAttributes:) will be called. You usually would want to customize the context class to store the information specific to your layout. One important, rather unintuitive, caveat though is that you should not call context.invalidateItems(at:) because this will cause the layout to invalidate only those items among the provided index paths that are actually visible. Just skip this method, so the layout will requery the visible rectangle.
However! You need to thoroughly think if you need to set contentOffsetAdjustment and contentSizeAdjustment: if something resizes, your collection view as a whole probably will shrink or expand. If you do not account for those, you will have jump-reloads when scrolling.
Lastly, invalidateLayout(with:) will be called. This is the step that's intended for you to actually adjust your sections/rows heights, move something that's been affected by the resizing cell etc. If you override, you will need to call super.
PS: This is really a hard topic, I just scratched the surface. You can look here how complicated it gets (but this repo is also a very rich learning tool).

Is there a direct way to set wx.TextCtrl size by characters rather than pixels?

The constructor for wx.TextCtrl takes a wx.Size argument, which is in units of pixels. Usually, I don't want to specify the size of a multiline TextCtrl in pixels, but rather in how many characters it can show without scrolling. I find that multiline TextCtrls are often the dominant component in my windows, thus stretching by Sizer is not an option.
The wxPython Phoenix documentation contains a hint as to how to do this, however this is meant more for short text on single line control.
I have started using this utility method:
def _set_textctrl_size_by_chars(self, tc, w, h):
sz = tc.GetTextExtent('X')
sz = wx.Size(sz.x * w, sz.y * h)
tc.SetInitialSize(tc.GetSizeFromTextSize(sz))
along with code like this:
tc = wx.TextCtrl(self, style=wx.TE_MULTILINE)
self._set_textctrl_size_by_chars(tc, 80, 20)
This works, but I consider it a hack. I have looked over the documentation but have not found any other way to do it.
I understand that fonts are not usually monospaced, and using 'X' as a representative character width is inexact, however it's plenty good enough for my usage. Still, it seems there should be some way to do this directly using the wx library.
Using something like text.GetSizeFromTextSize(text.GetTextExtent("99999").x) is indeed the best way to size the text control to fit exactly 5 digits (e.g. a ZIP code in some localities). Notice that this is slightly better than your code because the width of 80 "X"s is not necessarily quite the same as 80 times the width of a single "X". And I'd also recommend using "M" or "W" which can be noticeably wider than "X" in some fonts, but this is not going to changes matters much.
We thought about adding a helper method doing this and it might indeed be useful, but, again, this still won't make things as simple as you'd like because you really need to specify the characters you want to use: "W" for letters, "9" for digits and maybe something like "x" if you want the control to be wide enough to fit the given number of characters on average instead of being wide enough to guarantee fitting the given number of the widest characters because the difference may be noticeable.
The main place where we could make life simpler would be at XRC level and this would be worth doing ("just" a question of time...), but for the code I really don't think we can make things much simpler than what they're now.

Getting the width of a path drawn on a XAML canvas

I'm drawing a canvas programmatically, given a bunch of path data from somewhere else and adding it to the canvas as
// This is actually done more elaborately, but will do for now
PathFigureCollection figures = GetPathFigureCollection();
var path = new Path
{
Data = new PathGeometry { Figures = figures },
Fill = GetFill(),
Stroke = GetStroke(),
StrokeThickness = GetThickness()
};
MyCanvas.Children.Add(path);
Now, I have the canvas in a ScrollViewer, so I want to make sure that I can scroll all the way to reveal the entire path (actually paths - I have several, generated the same way) but no further. I tried this:
var drawingWidth = MyCanvas.Children
.OfType<FrameworkElement>()
.Max(e => Canvas.GetLeft(e) + e.ActualWidth);
MyCanvas.Width = drawingWidth;
This works well for some other elements (the drawing also has a few text blocks and ellipses), but for the paths both Canvas.GetLeft(e) and e.ActualWith (as well as some other things I tried like e.RenderSize.Width and e.DesiredSize.With) all return 0. Since the element that extends farthest to the right is a path, this results in a canvas that is too small.
How do I get the width of the Path elements too?
Ha, found it!
Rewriting the LINQ query as a loop, I could cast paths to Path, and use path.Data.Bounds.Right as the right edge of that element.** I might be able to convert the code back to a LINQ query now that I know what I want to do (I always find them more readable than stateful loops...).
I found this when I, after having perused the link provided by markE where, as a side note, it was stated that
If your design requirements allow more rough approximates, then you will find that cubic Bezier curves are always contained within their control points.
So, if I could find the right-most control point of all the path figures in my path, I would be home. Intellisense did the rest of the job for me :)

Two NSTextFields with interdependent widths in autolayout

I’m trying to put together what seems to be a simple case of two NSTextFields with dynamic width and fixed spacing in between. I cannot figure out an effective way to do so though.
I’m looking to get something like this:
The blue boxes are the NSTextFields. When more text is entered into one, it should grow and thus make the other one shrink, maintaining the lead space, trailing space and the spacing in between the fields. The first one should take the priority if both of the fields have too much text. Each field will also clearly have a maximum and a minimum possible width it can reach.
How would I go around handling this, preferably utilising IB autolayout as much as possible?
It seems to me that all of constraints you mentioned directly translate into interface builder --
First view has width >= something.
First view has width <= something
Same for Second view.
Space between views is fixed.
Second view wants to be as small as possible (have its width at 0) but this has lower lower priority than the previous constraints and lower priority than inner content size constraints.
The code I had to add to my view controller, after applying the constraints as per the ilya’s answer:
In controlTextDidChange (_controlWidthConstraint refers to the fixed width constraint of the input; it’s probably 0 by default for the second input):
// Get the new width that fits
float oldWidth = textControl.frame.size.width;
[input sizeToFit];
float controlWidth = textControl.frame.size.width;
// Don’t let the sizeToFit method modify the frame though
NSRect controlRect = textControl.frame;
controlRect.size.width = oldWidth;
textControl.frame = controlRect;
_controlWidthConstraint.constant = controlWidth;
The key lies in invalidating the intrinsicContentSize for the text field when text is input.
You can check a sample project here, to get you on the right track.

Zedgraph textobj X location depends on text length?

I have a Zedgraph textobj which I want to place always in the same x, y position (ASP.NET image). I noticed that the text doesn't always show in the same starting x position. It shifts depending on the text's length. I tried to have the text to have the same length by padding it with spaces. It helped a little but the result is not always consistent. I am using PaneFraction for coordType.
What's the proper method to have a piece of text to always show in the same x position. I am using textobj as a title because the native title property always shows up centered and I need my title be left aligned to the graph.
No, it does not depend on text lenght, however...
It depends on various other things:
Horizontal and vertical align of the text box (see: Location )
Current size of the pane. The font size is scaled dynamically to fit the changing size of the chart.
Counting proper positions to have TextObj (or any other object) always at the same place is quite hard. So you need avoid as much as you can any numbers/fractions in your location coordinates. ZedGraph sometimes calculates the true position in quite odd way then.
You haven't provided any code, so it's hard to tell if and where you made the mistake (if any). But, if I were you, I would do something like that:
TextObj fakeTitle = new TextObj("some title\n ", 0.0, 0.0); // I'm using \n to have additional line - this would give me some space, margin.
fakeTitle.Location.CoordinateFrame = CoordType.ChartFraction;
fakeTitle.Location.AlignH = AlignH.Left; // Left align - that's what you need
fakeTitle.Location.AlignV = AlignV.Bottom; // Bottom - it means, that left bottom corner of your object would be located at the left top corner of the chart (point (0,0))
fakeTitle.FontSpec.Border.IsVisible = false; // Disable the border
fakeTitle.FontSpec.Fill.IsVisible = false; // ... and the fill. You don't need it.
zg1.MasterPane[0].GraphObjList.Add(fakeTitle);
I'm using ChartFraction coordinates instead of PaneFraction (as drharris suggests) coordinates to have the title nicely aligned with the left border of the chart. Otherwise it would be flushed totally to the left side (no margin etc...) - it looks better this way.
But make sure you didn't set too big font size - it could be clipped at the top
Are you using this constructor?
TextObj(text, x, y, coordType, alignH, alignV)
If not, then be sure you're setting alignH to AlignH.Left and alignV to AlignV.Top. Then X and Y should be 0, 0. PaneFraction for the coordType should be the correct option here, unless I'm missing your intent.
Alternatively, you can simply download Zedgraph code, edit it to Left-align the title (or even better, provide an option for this, which should have been done originally), and then use it in production. Beauty of open source.