Codename One: how to get the width in millimeters of a component? - codenameone

I created an app to calculate the sizes of a multi-image, in order to use them in the Codename One designer.
As you can see in the screenshot below, my app has a slider and I get the width (in millimeters) of it. The problem is that the value of the selected width is incorrect. I tested the app on two Android devices and the measured lengths are different from the ones reported by the app.
You can see the full source code, however the relevant code is the following:
Label value = new Label("Move the cursor on the slider...");
Style thumbStyle = new Style();
thumbStyle.setFont(Font.createSystemFont(Font.FACE_MONOSPACE, Font.STYLE_BOLD, Font.SIZE_LARGE), true);
Slider slider = new Slider();
slider.setMaxValue(1000);
slider.setMinValue(0);
slider.setProgress(20); // Set the starting value
slider.setThumbImage(FontImage.create("|", thumbStyle));
slider.setEditable(true); // to it works as a slider instead of a progress bar
slider.addActionListener(e -> {
Integer valueSelected = slider.getProgress();
Integer sliderWidth = slider.getWidth();
Double inch = sliderWidth.doubleValue() / (slider.getMaxValue() - slider.getMinValue()) * valueSelected / 100.0;
Integer millimeters = Double.valueOf(inch * 25.4).intValue();
value.setText("Value selected: " + millimeters.toString() + " mm");
});
Thank you very much for any help

Millimeter measures aren't accurate. A device can return different values or ratios for the convert method than the value it returns for the density flag.
Unfortunately, Googles test suite to certify a device as "good" doesn't actually cover these things. There isn't much we can do about that.

Related

How to control/scale image size in Codename One?

I used the (new) GUI Builder and inserted an image (by way of adding a Label). However, it appears too big. Is there anyway I can scale and control the size? (I saw something which points to cloudinary but that seems too complicated. I just want to simply scale down the image.)
There are several ways to resize images in Codename One and I will mention few below:
1.
Use MultiImages in the GUI Builder. With this multiple sizes of images are generated from one image based on the sizes you specified. In your GUI Builder, Click Images -> Add Multi Images -> Select your image -> Check Preserve Aspect Ratio -> Increase the % that represents the percentage of the screen width you want the image to occupy. Set any DPI you don't require to 0.
2.
Use ScaledImageLabel or ScaledImageButton, it will resize the image the fill available space the component is occupying.
3.
Scale the image itself in code (This is not efficient, though):
public static Image getImageFromTheme(String name) {
try {
Resources resFile = Resources.openLayered("/theme");
Image image = resFile.getImage(name);
return image;
} catch (IOException ioe) {
//Log.p("Image " + name + " not found: " + ioe);
}
return null;
}
Image resizedImage = getImageFromTheme("myImage").scaledWidth(Math.round(Display.getInstance().getDisplayWidth() / 10)); //change value as necessary
4.
Mutate the image (Create an image from another image).

Pixel vs Percentige at Mobile env

Im currently building fps-like game for android environment.
I had notice that if I make object with use of pixel low resolution devices can play game so easy than hight resolution phones.
If I use percentage for building objects this time bigger devices gain advantage. Such as Tablets have great size than phones and they can shot my object easly.
I want my objects exact same size on every device is it possible?
More specificly I use python-kivy is it possible to define object as cm/ft or etc.
You can make a relative_size and relative_position method. And make them relative to the windows width or height.
You get the size of the window from the Window class.
Remember only to make the objects size (w,h) relative to only one of width or height. Or your objects will be warped.
from kivy.uix.widget import Widget
from kivy.graphics import Canvas,InstructionGroup,Color,Ellipse
from kivy.core.window import Window
from kivy.app import App
class MyCanvas(Widget):
def __init__(self,**kwargs):
super(MyCanvas,self).__init__(**kwargs)
#Window.size = (200,100)
self.size = Window.size
self.orientation = "vertical"
self.ball = InstructionGroup()
self.ball_size = self.relative_size(10,10)
self.color = Color(0, 1, 1)
self.ellipse = Ellipse(size=self.ball_size,pos=self.relative_position(100,50,self.ball_size))
self.ball.add(self.color)
self.ball.add(self.ellipse)
self.canvas.add(self.ball)
def relative_position(self,x,y,obj_size=(0,0)): # pass obj_size if you want the position to be the center of the object11
x = ( self.width / 100.0 ) * x - obj_size[0]/2.0
y = ( self.height / 100.0 ) * y - obj_size[1]/2-.0
return (x,y)
def relative_size(self,w,h):
return (self.width/float(w),self.width/float(h)) # only make it relative to your width or heigh
# or your object will be warped
class MyApp(App):
def build(self):
return MyCanvas()
if __name__ == "__main__":
MyApp().run()
The relative_position method here will now be a percantage. So you pass from 0-100 in both directions. If you want something else, change the 100s in tht method.
Try to uncomment the #Window.size = (200,100) and play with the window size, and see how it works.
You could also make an event if your application changes size, like if your phone changes orientation.
As I did not make that, this will only work for the size the application started with.

FSharpChart with Windows.Forms very slow for many points

I use code like the example below to do basic plotting of a list of values from F# Interactive. When plotting more points, the time taken to display increases dramatically. In the examples below, 10^4 points display in 4 seconds whereas 4.10^4 points take a patience-testing 53 seconds to display. Overall it's roughly as if the time to plot N points is in N^2.
The result is that I'll probably add an interpolation layer in front of this code, but
1) I wonder if someone who knows the workings of FSharpChart and Windows.Forms could explain what is causing this behaviour? (The data is bounded so one thing that seems to rule out is the display needing to adjust scale.)
2)Is there a simple remedy other than interpolating the data myself?
let plotl (f:float list) =
let chart = FSharpChart.Line(f, Name = "")
|> FSharpChart.WithSeries.Style(Color = System.Drawing.Color.Red, BorderWidth = 2)
let form = new Form(Visible = true, TopMost = true, Width = 700, Height = 500)
let ctl = new ChartControl(chart, Dock = DockStyle.Fill)
form.Controls.Add(ctl)
let z1 = [for i in 1 .. 10000 do yield sin(float(i * i))]
let z2 = [for i in 1 .. 20000 do yield sin(float(i * i))]
plotl z1
plotl z2
First of all, FSharpChart is a name used in an older version of the library. The latest version is called F# Charting, comes with a new documentation and uses just Chart.
To answer your question, Chart.Line and Chart.Points are quite slow for large number of points. The library also has Chart.FastLine and Chart.FastPoints (which do not support as many features, but are faster). So, try getting the latest version of F# Charting and using the "Fast" version of the method.

How do I convert a WPF size to physical pixels?

What's the best way to convert a WPF (resolution-independent) width and height to physical screen pixels?
I'm showing WPF content in a WinForms Form (via ElementHost) and trying to work out some sizing logic. I've got it working fine when the OS is running at the default 96 dpi. But it won't work when the OS is set to 120 dpi or some other resolution, because then a WPF element that reports its Width as 96 will actually be 120 pixels wide as far as WinForms is concerned.
I couldn't find any "pixels per inch" settings on System.Windows.SystemParameters. I'm sure I could use the WinForms equivalent (System.Windows.Forms.SystemInformation), but is there a better way to do this (read: a way using WPF APIs, rather than using WinForms APIs and manually doing the math)? What's the "best way" to convert WPF "pixels" to real screen pixels?
EDIT: I'm also looking to do this before the WPF control is shown on the screen. It looks like Visual.PointToScreen could be made to give me the right answer, but I can't use it, because the control isn't parented yet and I get InvalidOperationException "This Visual is not connected to a PresentationSource".
Transforming a known size to device pixels
If your visual element is already attached to a PresentationSource (for example, it is part of a window that is visible on screen), the transform is found this way:
var source = PresentationSource.FromVisual(element);
Matrix transformToDevice = source.CompositionTarget.TransformToDevice;
If not, use HwndSource to create a temporary hWnd:
Matrix transformToDevice;
using(var source = new HwndSource(new HwndSourceParameters()))
transformToDevice = source.CompositionTarget.TransformToDevice;
Note that this is less efficient than constructing using a hWnd of IntPtr.Zero but I consider it more reliable because the hWnd created by HwndSource will be attached to the same display device as an actual newly-created Window would. That way, if different display devices have different DPIs you are sure to get the right DPI value.
Once you have the transform, you can convert any size from a WPF size to a pixel size:
var pixelSize = (Size)transformToDevice.Transform((Vector)wpfSize);
Converting the pixel size to integers
If you want to convert the pixel size to integers, you can simply do:
int pixelWidth = (int)pixelSize.Width;
int pixelHeight = (int)pixelSize.Height;
but a more robust solution would be the one used by ElementHost:
int pixelWidth = (int)Math.Max(int.MinValue, Math.Min(int.MaxValue, pixelSize.Width));
int pixelHeight = (int)Math.Max(int.MinValue, Math.Min(int.MaxValue, pixelSize.Height));
Getting the desired size of a UIElement
To get the desired size of a UIElement you need to make sure it is measured. In some circumstances it will already be measured, either because:
You measured it already
You measured one of its ancestors, or
It is part of a PresentationSource (eg it is in a visible Window) and you are executing below DispatcherPriority.Render so you know measurement has already happened automatically.
If your visual element has not been measured yet, you should call Measure on the control or one of its ancestors as appropriate, passing in the available size (or new Size(double.PositivieInfinity, double.PositiveInfinity) if you want to size to content:
element.Measure(availableSize);
Once the measuring is done, all that is necessary is to use the matrix to transform the DesiredSize:
var pixelSize = (Size)transformToDevice.Transform((Vector)element.DesiredSize);
Putting it all together
Here is a simple method that shows how to get the pixel size of an element:
public Size GetElementPixelSize(UIElement element)
{
Matrix transformToDevice;
var source = PresentationSource.FromVisual(element);
if(source!=null)
transformToDevice = source.CompositionTarget.TransformToDevice;
else
using(var source = new HwndSource(new HwndSourceParameters()))
transformToDevice = source.CompositionTarget.TransformToDevice;
if(element.DesiredSize == new Size())
element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
return (Size)transformToDevice.Transform((Vector)element.DesiredSize);
}
Note that in this code I call Measure only if no DesiredSize is present. This provides a convenient method to do everything but has several deficiencies:
It may be that the element's parent would have passed in a smaller availableSize
It is inefficient if the actual DesiredSize is zero (it is remeasured repeatedly)
It may mask bugs in a way that causes the application to fail due to unexpected timing (eg. the code being called at or above DispatchPriority.Render)
Because of these reasons, I would be inclined to omit the Measure call in GetElementPixelSize and just let the client do it.
Simple proportion between Screen.WorkingArea and SystemParameters.WorkArea:
private double PointsToPixels (double wpfPoints, LengthDirection direction)
{
if (direction == LengthDirection.Horizontal)
{
return wpfPoints * Screen.PrimaryScreen.WorkingArea.Width / SystemParameters.WorkArea.Width;
}
else
{
return wpfPoints * Screen.PrimaryScreen.WorkingArea.Height / SystemParameters.WorkArea.Height;
}
}
private double PixelsToPoints(int pixels, LengthDirection direction)
{
if (direction == LengthDirection.Horizontal)
{
return pixels * SystemParameters.WorkArea.Width / Screen.PrimaryScreen.WorkingArea.Width;
}
else
{
return pixels * SystemParameters.WorkArea.Height / Screen.PrimaryScreen.WorkingArea.Height;
}
}
public enum LengthDirection
{
Vertical, // |
Horizontal // ——
}
This works fine with multiple monitors as well.
I found a way to do it, but I don't like it much:
using (var graphics = Graphics.FromHwnd(IntPtr.Zero))
{
var pixelWidth = (int) (element.DesiredSize.Width * graphics.DpiX / 96.0);
var pixelHeight = (int) (element.DesiredSize.Height * graphics.DpiY / 96.0);
// ...
}
I don't like it because (a) it requires a reference to System.Drawing, rather than using WPF APIs; and (b) I have to do the math myself, which means I'm duplicating WPF's implementation details. In .NET 3.5, I have to truncate the result of the calculation to match what ElementHost does with AutoSize=true, but I don't know whether this will still be accurate in future versions of .NET.
This does seem to work, so I'm posting it in case it helps others. But if anyone has a better answer, please, post away.
Just did a quick lookup in the ObjectBrowser and found something quite interesting, you might want to check it out.
System.Windows.Form.AutoScaleMode, it has a property called DPI. Here's the docs, it might be what you are looking for :
public const
System.Windows.Forms.AutoScaleMode Dpi
= 2
Member of System.Windows.Forms.AutoScaleMode
Summary: Controls scale relative to
the display resolution. Common
resolutions are 96 and 120 DPI.
Apply that to your form, it should do the trick.
{enjoy}

WPF 3d ImageBrush material help

Im trying to apply a material to my GeometryModel3D at runtime like so:
var model3D = ShardModelVisual.Content as GeometryModel3D;
var materialGroup = model3D.Material as MaterialGroup;
BitmapImage image;
ResourceLoader.TryLoadImage("pack://application:,,,/AnzSurface;component/path file/img.png", out image, ".png");
var iceBrush = new ImageBrush(image);
var grp = new TransformGroup();
grp.Children.Add(new ScaleTransform(0.25, 0.65, 0.5, 0.5));
grp.Children.Add(new TranslateTransform(0.0, 0.0));
iceBrush.Transform = grp;
var iceMat = new DiffuseMaterial(iceBrush);
materialGroup.Children.Add(iceMat);
Which all works fine, and the material gets added.
What I dont understand is how I can map the users click on the screen to the offsets that need to be applied to the TranslateTransform.
I.e. at the moment, x: -0.25 moves the material backwards along the X axis, but I have NO IDEA how to get that type of a coordinate from the users mouse click...
when I do:
e.MouseDevice.GetPosition(ShardsViewPort3D);
that gives me normal X/Y corrds of the mouse click...
Thanks for any help you can give!
It sounds like you want to slide the material around on your geometry when you click on it. Here's how:
Use hit testing to translate your X/Y coordinates from the mouse click into a RayMeshGeometry3DHitTestResult as described in my earlier answer. This will give you the MeshGeometry3D that was hit, the vertices of the triangle that was hit, and the relative position on that triangle.
Look up each vertex index (VertexIndex1, VertexIndex2, VertexIndex2) in the MeshGeometry3D.TextureCoordinates to get the texture coordinates. This will give you three (u,v) pairs as Point objects. Multiply each the (u,v) pairs by the corresponding weight from the hit test result (VertexWeight1, VertexWeight2, VertexWeight3) and add the pairs together, ie:
uMouse = u1 * VertexWeight1 + u2 * VertexWeight2 + u3 * VertexWeight3
vMouse = v1 * VertexWeight1 + v2 * VertexWeight2 + v3 * VertexWeight3
Now you have a point (uMouse, vMouse) that indicates where on your material your mouse was clicked.
If you want a particular point on your texture to move to exactly where the mouse was clicked, just subtract the (uMouse, vMouse) where the mouse was clicked from the (u,v) coordinate of the location in the material you want to appear under the mouse, and set this as your TranslateTransform. If you want to handle dragging, store the computed (uMouse,vMouse) where the drag started and the transform as of the drag start, then as dragging progresses compute the new transform as:
translate = (uMouse,vMouse) - (uMouseDragStart, vMouseDragStart) + origTranslate
In code you'll write this as Point additions. I spelled it out as (u,v) in this explanation because I thought it was easier to understand if I did so. In actuality the code to compute (uMouse, vMouse) will look more like this:
var uv1 = hit.MeshHit.TextureCoordinates[hit.VertexIndex1];
var uv2 = hit.MeshHit.TextureCoordinates[hit.VertexIndex2];
var uv3 = hit.MeshHit.TextureCoordinates[hit.VertexIndex3];
var uvMouse = new Vector(
uv1.X * hit.VertexWeight1 + uv2.X * hit.VertexWeight2 + uv3.X * hit.VertexWeight3)
uv1.Y * hit.VertexWeight1 + uv2.Y * hit.VertexWeight2 + uv3.Y * hit.VertexWeight3);
and the code to update the transform during a drag will look something like this:
...
var translate = translateAtDragStart + uvMouse - uvMouseAtDragStart;
... = new TranslateTransform(translate.X, translate.Y);
You'll have to adapt this to the exact situation.
Note that your HitTest callback may be called multiple times, starting at the closest mesh and moving back. It may even be called with 2D hits, for example if a 2D object is in front of your Viewport3D. So you'll want to check each hit to see if it is really what you want, for example during dragging you want to keep checking the position on the mesh being dragged even if it is no longer foremost. Return HitTestResultBehavior.Stop from you callback once you have acted on the mesh being dragged.

Resources