I took a stab at implementing "ScalePane", a container that would
automatically scale its contents to the container's actual size (
https://issues.apache.org/jira/browse/PIVOT-710). I've come to the
conclusion that it's not possible to implement it as a straightforward user
add-on, at least partly because the notion of a coordinate-space transform
does not exist at the Component level. I'd love to be proved wrong, so let
me describe what I've tried so far.
I created ScalePane as a subclass of Container, and its skin ScalePaneSkin
as a subclass of ContainerSkin. I modeled both of them on the implementation
of Border, another single-component container. I gave the skin horizontal
and vertical alignment properties (to tell where to place the contents in
the parent when aspect ratio is to be maintained).
The first pass of ScalePane#paint looks like this:
@Override
public void simplepaint(Graphics2D graphics) {
ScalePaneSkin skin = (ScalePaneSkin) this.getSkin();
skin.adjustGraphics(graphics);
super.paint(graphics);
}
where adjustGraphics performs a translate and scale on the graphics context
according to the ratio of the child's preferred size and the container's
actual size. This works great, though to get the container's background
properly painted I also have to define ScalePaneSkin#paint to undo the
transformation that ScalePane#paint did.
That quick start was encouraging, but now the hard part is to get it to work
when the child has interactive components, and this is where I fall down
completely. I was hoping it would suffice to map the parent's mouse
coordinates to the child's space, so in ScalePane I override mouseMove,
mouseDown, mouseUp, mouseClick, and mouseWheel, having them transform the
mouse coordinates before passing to the super, e.g.,
@Override
protected boolean mouseDown(Button button, int x, int y) {
Point transformed = translateMouseToChild(x, y);
return super.mouseDown(button, transformed.x, transformed.y);
}
I can tell the transformation is correct, because the cursor changes to a
hand in the appropriate places, and clicking the mouse hits a component in
the right place, but that's about it. Everything else is wrong:
Tooltips -- hovering over a component raises a tooltip, but it does it in
the wrong location. That's because the code that raises the tip does it by
creating a Label on the component's Window, totally bypassing ScalePane.
ListButton -- the popup is unscaled and in the wrong place. Same as the
tooltip issue, I believe -- it opens the list directly on the component's
window.
Checkbox and similar components that display a changed state -- the new
state is not visible until something causes a repaint of the relevant part
of the window. I'm guessing this has something to do with clipping regions
being wrong.
TextArea -- selecting a region of text has no visible effect (even if the
window is forced to repaint), and the blinking insertion point does not
appear, though I can tell it's trying, judging by the regular calls to my
paint function with the graphics' clip region set to a 1-pixel-wide strip in
the appropriate child coordinates.
Slider -- fails in multiple ways, including a NullPointerException in
ThumbSkin.mouseMove.
Expander and friends -- these only work if the scale factor in effect is
less than 1. Otherwise, the entire animation takes place only in the
untransformed coordinate space of upper left of the ScalePane, and the rest
of the pane is never repainted.
Several of the failures are tied to the clipping region being wrong. Is
there any way to get this right? Logs of the clipping regions I see in
ScalePane#paint are usually in the child's coordinates, but if I transform
the clipping region to the parent, it doesn't help, and in fact makes it
worse. I also noticed that some of the time the clipping region appears to
be in the parent's coordinates, so I don't even know how I'd figure it out.
Is it time to give up, or do any of the implementors have insights?