-
Notifications
You must be signed in to change notification settings - Fork 161
Rework new Image constructors accepting a ImageGcDrawer #1985
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Rework new Image constructors accepting a ImageGcDrawer #1985
Conversation
Test Results104 files - 435 104 suites - 435 5s ⏱️ - 31m 51s Results for commit 49d2fcc. ± Comparison against base commit 3670670. This pull request removes 4267 tests.
♻️ This comment has been updated with latest results. |
When implementing the ImageGCDrawer as lambda, the resulting code is better to read if the ImageGCDrawer is passed as last argument: ''' new Image(device, 16, 16, (gc, w, h) -> { gc.draw ... }) ''' Furthermore this way the argument order also better reflects the order in which the arguments are applied: 1. an image with the given size is created. 2. the GC draws on that image Follow-up on - eclipse-platform#1734
412ffc4
to
5756e3d
Compare
I don't have a strong opinion on this. I agree that the proposed order would have been better from the beginning and I also agree on the "semantics" of the argument order (first specifying image creation and then image contents). I only disagree with the other argument of producing more verbose code because of the current API preventing easily inlining a multi-line lambda, as that conflicts with my understanding of clean coding practices: If there is a multi-line lambda, that should be assigned to a variable that condensely expresses its meaning. When I inline a multi-line lambda into the call of another method (or a constructor, like here), the code becomes incomprehensible anyway, no matter whether that lambda is the last argument of the call or not. That's why I would encourage to never do that anyway So in summary, I am not sure whether the change is worth the API extension + deprecation, but at the same time I don't have a strong argument against it, in particular since the API has just been introduced and is not consumed that much yet. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@HeikoKlare, @fedejeanne or anybody else, what do you think?
Same as Heiko: I have no strong opinion about this.
Your proposal is reasonable and I see the slight benefits. If no one objects, you may go for it @HannesWell :-) ✔️
I have to admit that I have the exact opposite opinion about it. If a lambda is only used once, most of the times I find it much cleaner to line it, because a lambda usually specifies the behavior of something and I find it more clear if it's then written in the context of the 'thing' whose behavior it defines then somewhere else. One prominent example for me is the But to stick with this case, with a inlined lambda I can define the drawer and therefore the content of the image, literally within the image-constructor and can say I create a new Image and draw this content XYZ : Image rightCaretBitmap = new Image(display, caretWidth, lineHeight, (gc, width, height) -> {
gc.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
gc.fillRectangle(0, 0, width, height);
gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
gc.drawLine(width-1,0,width-1,height);
gc.drawLine(0,0,width-1,0);
gc.drawLine(width-1,1,1,1);
}); Previously it read like: I create an ImageGcDrawer rightCaretDrawer = (gc, width, height) -> {
gc.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
gc.fillRectangle(0, 0, width, height);
gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
gc.drawLine(width-1,0,width-1,height);
gc.drawLine(0,0,width-1,0);
gc.drawLine(width-1,1,1,1);
};
Image rightCaretBitmap = new Image(display, rightCaretDrawer, caretWidth, lineHeight); In the end it's of course not a game-changer, but I personally found the former more clear than the latter. |
In a second commit I have now also added the suggestion from #1948 (comment), about how a Image(GC)Drawer could be extended in an alternative way. Please consider this just as a starting point for a discussion. This doesn't have to be the final result. E.g. to create a drawer with post-processing one could then use: Image image = new Image(display, width, height, ImageDrawer.create((gc, w, h) -> {
gc.fillRectangle(0, 0, w, h);
...
}, imageData -> {
// do some post-processing
}); to create an Image with a drawer with a certain style one could use: Image image = new Image(display, width, height, ImageDrawer.withGCStyle(SWT.LEFT_TO_RIGHT, (gc, w, h) -> {
gc.fillRectangle(0, 0, w, h);
...
}); or to create a drawer with a style and post-processing one could use: Image image = new Image(display, width, height, ImageDrawer.create(withGCStyle(SWT.LEFT_TO_RIGHT, (gc, w, h) -> {
gc.fillRectangle(0, 0, w, h);
...
}), imageData -> {
// do some post-processing
}); And since multiple things are then already change in this area I also wanted to suggest to replace |
I generally like the proposed way of creating the drawer instances. My only two concerns/comments are:
So in short: the changes are basically fine for me. Just be aware that this requires adaptations in at least Platform UI as well, as the deprecated APIs are already used there. |
+1 for having lamda as last argument and Beside that I personally more like BuilderPatterns over constructors with many optional and/or varying variants. Something like Image image = Image.from(device)
.withSize(width, height)
.drawing((gc, w, h)-> {
//draw on me
}).process(imageData -> {
// do some post-processing
})
.create(); that way its much easier to adjust/change without the hassle to find the "right" position for parameter X and its easier to evolve and better to read. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- -1 on having to explicitly know about
ExtendedImageDrawer
(just as Heiko pointed out) - +1 on the usage of builders if you want to hide such details
- +0 (neutral) on renaming
ImageGcDrawer
toImageDrawer
but please adapt the names of the parameters and variables accordingly - +0 on changing the order of the parameters in the constructor
All in all: I like factories/builders but I don't like having to know the classes explicitly in order for them to work. Having checks saying if (drawer instanceof ExtendedImageDrawer extendedDrawer)
is fine inside a factory method (or a builder) but only there and only if that factory method (or builder) is the only one that needs to know about (and instantiate) ExtendedImageDrawer
.
HTH
GC gc = new GC(image, ExtendedImageDrawer.getGCStyle(imageGcDrawer)); | ||
try { | ||
imageGcDrawer.drawOn(gc, width, height); | ||
ImageData imageData = image.getImageData(zoom); | ||
imageGcDrawer.postProcess(imageData); | ||
ExtendedImageDrawer.postProcess(imageGcDrawer, imageData); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather not have to use static methods in here since one needs to know about the existence of ExtendedImageDrawer
(just like Heiko pointed out). A more natural approach to me would be:
GC gc = new GC(image, imageGcDrawer.getGCStyle());
try {
imageGcDrawer.drawOn(gc, width, height);
ImageData imageData = image.getImageData(zoom);
imageGcDrawer.postProcess(imageData);
return imageData;
Which means that you need to move getGcStyle()
back to ImageDrawer
.
When implementing the
ImageGCDrawer
as lambda, the resulting code is better to read if theImageGCDrawer
is passed as last argument:For example at eclipse-platform/eclipse.platform.ui#2869 or in the adapted parts within SWT one can see that the current order encourages to code in way that is more verbose.
Furthermore this way the argument order also better reflects the order in which the arguments are applied:
I know that changing an API after it was released is not ideal, but since it's very new I assume there are not yet many users and existing users are very likely able to adapt especially as the change is trivial. Consequently we have a real chance to remove the first shot eventually. On the other hand if we stick with the existing API we might be annoyed by its imperfection forever.
If we have this, I can take care of adopting the existing callers in the SDK.
Follow-up on
@HeikoKlare, @fedejeanne or anybody else, what do you think?