I have a custom text widget and I am trying to test that the widget has a certain intrinsic size for some text string.
void main() {
testWidgets('MongolRichText has correct size for string', (WidgetTester tester) async {
await tester.pumpWidget(MongolRichText(text: TextSpan(text: 'hello'),));
final finder = find.byType(MongolRichText);
expect(finder, findsOneWidget);
// How do I check the size?
});
}
How do I check the intrinsic size of the widget?
I'm not trying to limit the screen size as in this question.
I found the answer in the Flutter source code, so I am posting this as a Q&A pair. My answer is below.
The WidgetTester has a getSize method on it that you can use to get the rendered size of the widget.
void main() {
testWidgets('MongolRichText has correct size for string', (WidgetTester tester) async {
await tester.pumpWidget(Center(child: MongolText('Hello')));
MongolRichText text = tester.firstWidget(find.byType(MongolRichText));
expect(text, isNotNull);
final Size baseSize = tester.getSize(find.byType(MongolRichText));
expect(baseSize.width, equals(30.0));
expect(baseSize.height, equals(150.0));
});
}
Notes:
Putting the custom widget in a Center widget makes it wrap the content. Otherwise getSize would get the screen size.
I got the actual numbers by running the test and seeing what the actual values should be. They seemed reasonable (MongolRichText is vertical text), so I updated the test with the expected numbers to make the test pass.
This solution was adapted from the Futter Text widget testing source code.
Related
The essence of the problem is that I want to write my own version of the AppBar that would include content as another Compose function. After looking at the source code of the current CollapsingTopAppBar implementation, I saw the following lines:
#Composable
private fun TwoRowsTopAppBar(
...
scrollBehavior: TopAppBarScrollBehavior?
) {
...
val pinnedHeightPx: Float = 64.dp
val maxHeightPx: Float = 152.dp
LocalDensity.current.run {
pinnedHeightPx = pinnedHeight.toPx()
maxHeightPx = maxHeight.toPx()
}
// Sets the app bar's height offset limit to hide just the bottom title area and keep top title
// visible when collapsed.
SideEffect {
if (scrollBehavior?.state?.heightOffsetLimit != pinnedHeightPx - maxHeightPx) {
scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx
}
}
...
Surface(...) {
Column {
TopAppBarLayout(
...
heightPx = pinnedHeightPx
...
)
TopAppBarLayout(
...
heightPx = maxHeightPx - pinnedHeightPx + (scrollBehavior?.state?.heightOffset
?: 0f),
...
)
}
}
}
As I understand it, scrollBehavior is used to handle the collapse and expansion behavior. In the current implementation, just constant values are put in heightOffsetLimit. And since I need my appbar implementation to be able to contain content of any size, I need to somehow know the size of this content in advance and put this value in heightOffsetLimit.
I have already written the code for my AppBar, so that it also contains content. But since I can't pass the height value of the content to scrollBehavior, the AppBar doesn't collapse to the end.
you need to calculate the height that the appbar will have before drawing it into the screen. I have followed this issue and solved my problem with the last solution. hope it helps:
Get height of element Jetpack Compose
use the content you can put (ex. an image or a huge text) as the MainContent
use your appbar as the DependentContent and use the size given in lambda to give the height to your appbar
finally set placeMainContent false as I believe you don't need to draw the image (or any other composable) directly in a box
and you will good to go
I'm using a Appium methods to take a screenshot and crop a perticular part of that screenshot according to coordinates and size of an element.
The way I do this:
Take a screenshot
This is done with getScreenShotAs() method
Crop out the part of that image
This is done
image.getSubimage(getElementCoordinateX(element),
getElementCoordinateY(element),
getElementWidth(element),getElementHeight(element));
public static int getElementWidth(MobileElement element) {
return element.getSize().getWidth();
}
public static int getElementHeight(MobileElement element) {
return element.getSize().getHeight();
}
public static int getElementCoordinateX(MobileElement element) {
return element.getLocation().getX();
}
public static int getElementCoordinateY(MobileElement element) {
return element.getLocation().getY();
}
I tested this approach on Android and it works as intended, but on iOS it crops out totally different part of the screenshot and I'm sure that it's the right element that's being located.
Developers told me that iOS apps work with frames and that I'm probably getting the bounds coordinates and not the frame's coordinates. I didn't find a way to interact with them using Appium. Is there a way to make this work as intended?
you can get screenshot of element directly using:
Ruby:
element = driver.find_element(:predicate, "type == 'XCUIElementTypeSlider'")
screenshot = driver.element_screenshot_as(element ,:base64)
This should crop the screenshot automatically and correctly...
So, I have a QComboBox.
If the currentText() is too long for the widget then I want to show an ellipsis.
Like this :
So :
void MyComboBox::paintEvent(QPaintEvent * )
{
QStylePainter painter(this);
QStyleOptionComboBox opt;
initStyleOption(&opt);
painter.drawComplexControl(QStyle::CC_ComboBox, opt);
QRect rect = this->rect();
//this is not ideal
rect.setLeft(rect.left() + 7);
rect.setRight(rect.width() - 15);
//
QTextOption option;
option.setAlignment(Qt::AlignVCenter);
QFontMetrics fontMetric(painter.font());
const QString elidedText = QAbstractItemDelegate::elidedText(fontMetric, rect.width(), Qt::ElideRight, this->currentText());
painter.drawText( rect, elidedText, option);
}
This is working flawlessy.
The problem is the code in between the comments, because I am hardcoding the distances from the left and right border. It makes me cringe.
The result without that code is:
Does anybody know a more general way to do this, without hardcoding?
Thank you
Where the text should be drawn exactly depends on the used style. You can get information about (some of) the positioning of subelements with QStyle::subControlRect. The subcontrol that matches the combo box text best seems to be QStyle::SC_ComboBoxEditField, though if the item has an icon, this needs to be taken into account as well. If the items do not have icons, you can go with
QRect textRect = style()->subControlRect(QStyle::CC_ComboBox, &opt, QStyle::SC_ComboBoxEditField, this);
QFontMetrics fontMetric(painter.font());
const QString elidedText = QAbstractItemDelegate::elidedText(fontMetric, textRect.width(), Qt::ElideRight, this->currentText());
opt.currentText = elidedText;
painter.drawControl(QStyle::CE_ComboBoxLabel, opt);
You might want to have a look at how e.g. QFusionStyle::drawControl works for details.
In general, if you want all your combo boxes to elide the text, you should consider implementing your own QProxyStyle and only override MyStyle::drawControl for QStyle::CE_ComboBoxLabel.
This is the solution I've been using:
void CustomComboBox::paintEvent(QPaintEvent * /*event*/)
{
QStyleOptionComboBox opt;
initStyleOption(&opt);
QStylePainter p(this);
p.drawComplexControl(QStyle::CC_ComboBox, opt);
QRect textRect = style()->subControlRect(QStyle::CC_ComboBox, &opt, QStyle::SC_ComboBoxEditField, this);
opt.currentText = p.fontMetrics().elidedText(opt.currentText, Qt::ElideRight, textRect.width());
p.drawControl(QStyle::CE_ComboBoxLabel, opt);
}
This approach is very similar to a combination of your sample code and the snippet E4z9 suggested. I just thought I'd include the whole method for others coming here in the future.
I need to develop a control which is similar to the Nested Grid in the Smart GWT.
User will be having a column for expansion images, when user clicking on the image in a particular row, a sub grid has to be opened there itself. Here all remaining rows needs to move down.
How can i achieve that functionality? Can anybody give me some clues so that i can proceed.
I have already a grid which is a celltable with custom header(with search functionality implemented).
Thanks,
Saritha.
Create your nested widget (myNestedWidget) that you want to show. It should have a CSS style "position: absolute", unless your grid is added to the LayoutPanel (or similar), in which case you can position your widget directly. Let's call the parent widget of your grid gridParentWidget.
In your CellTable add the following handler:
myTable.addCellPreviewHandler(new Handler<myObject>() {
#Override
public void onCellPreview(CellPreviewEvent<myObject> event) {
if ("click".equals(event.getNativeEvent().getType())) {
if (event.getColumn() == 0) {
int top = myTable.getRowElement(event.getIndex()).getAbsoluteBottom();
int left = myTable.getRowElement(event.getIndex()).getAbsoluteLeft();
myNestedWidget.getElement().getStyle().setTop(top, Unit.PX);
myNestedWidget.getElement().getStyle().setLeft(left, Unit.PX);
gridParentWidget.add(myNestedWidget);
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
#Override
public void execute() {
int height = myNestedWidget.getOffsetHeight();
myTable.getRowElement(event.getIndex()).getStyle().setHeight(height + "px");
]
});
}
}
});
}
This is obviously an outline of the solution. The details of the implementation may vary slightly depending on which widgets you use for your parent widget and your nested widget. If you change z-indexes somewhere, you have to take it into account too. You also need to make sure that your nested widget fits into the width of your grid, or you'll need to wrap it in a ScrollPanel and set a width to it explicitly.
I can't seem to create two oscillators with independent gain envelopes.
The code below creates two buttons which each play a sine tone at a different pitch. When I click on the first button, I hear the tone grow in volume as it should. But, when I click the second button, the tone reacts as if it is connected to the gain of the first tone. For example, if I click the second button (turning on the second tone) while the first tone is at volume 1, the second tone will enter at volume 1, even though it is supposed to envelope from 0 to 1 to 0 over the course of 10 seconds.
Can I only have one gain node per audio context? Or is there some other reason that the gains of these oscillators are being connected? In addition, after I play the tones once, I cannot play them again, which makes me especially think that I am doing something wrong. : )
Thanks. A link is below and the code is below that. This is my first post here so let me know if you need anything else. This code must be run in versions of Chrome or Safari that support the web audio api.
http://whitechord.org/just_mod/poly_test.html
WAAPI tests
<button onclick="play()">play one</button>
<button onclick="play2()">play two</button>
<script>
var context;
window.addEventListener('load', initAudio, false);
function initAudio() {
try {
context = new webkitAudioContext();
} catch(e) {
onError(e);
}
}
function play() {
var oscillator = context.createOscillator();
var gainNode = context.createGainNode();
gainNode.gain.value = 0.0;
oscillator.connect(gainNode);
gainNode.connect(context.destination);
oscillator.frequency.value = 700;
gainNode.gain.linearRampToValueAtTime(0.0, 0); // envelope
gainNode.gain.linearRampToValueAtTime(0.1, 5); // envelope
gainNode.gain.linearRampToValueAtTime(0.0, 10); // envelope
oscillator.noteOn(0);
}
function play2() {
var oscillator2 = context.createOscillator();
var gainNode2 = context.createGainNode();
gainNode2.gain.value = 0.0;
oscillator2.connect(gainNode2);
gainNode2.connect(context.destination);
oscillator2.frequency.value = 400;
gainNode2.gain.linearRampToValueAtTime(0.0, 0); // envelope
gainNode2.gain.linearRampToValueAtTime(0.1, 5); // envelope
gainNode2.gain.linearRampToValueAtTime(0.0, 10); // envelope
oscillator2.noteOn(0);
}
/* error */
function onError(e) {
alert(e);
}
</script>
</body>
</html>
Can I only have one gain node per audio context? Or is there some other reason that the gains of these oscillators are being connected? In addition, after I play the tones once, I cannot play them again, which makes me especially think that I am doing something wrong. : )
You can have as many gain nodes as you want (this is how you could achieve mixing bus-like setups, for example), so that's not the problem. Your problem is the following:
Remember that the second parameter to linearRampToValueAtTime() is time in the same time coordinate system as your context.currentTime.
And your context.currentTime is always moving forward in real time, so all your ramps, curves, etc. should be calculated relative to it.
If you want something to happen 4 seconds from now, you'd pass context.currentTime + 4 to the Web Audio API function.
So, change all your calls linearRampToValueAtTime() in your code, so that they look like:
gainNode2.gain.linearRampToValueAtTime(0.0, context.currentTime); // envelope
gainNode2.gain.linearRampToValueAtTime(0.1, context.currentTime + 5); // envelope
gainNode2.gain.linearRampToValueAtTime(0.0, context.currentTime + 10); // envelope
And that should take care of your issues.
BTW you have a stray double quote in your BODY opening markup tag.
Ask Will Conklin
gainNode2.gain.linearRampToValueAtTime(0.1, context.currentTime + 5); // envelope