Skip to content
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

GTK shape support WIP #348

Merged
merged 25 commits into from
Jan 12, 2021
Merged

GTK shape support WIP #348

merged 25 commits into from
Jan 12, 2021

Conversation

mortenbekditlevsen
Copy link
Contributor

@mortenbekditlevsen mortenbekditlevsen commented Jan 6, 2021

SECOND UPDATE:

The final thing on my todo for this PR is to test the Path size changes in TokamakStaticHTML.

UPDATE:

I marked the PR as ready for review.
There are a few issues that I am uncertain of:

  • I am getting an empty environment in the _ShapeView, so I have hacked around that for now, which is of course not very good.
  • Are the GSignal changes sound? Do we just keep adding overloads of connect for each new signal handler shape?
  • Is my solution of removing _SubPath acceptable? (I think it makes sense).
  • Is my CGDK systemPackage definition ok? It appears to do the trick (on macOS at least).
  • I have only made brief tests on TokamakStaticHTML, but I could add an ellipse to a path and rotate it a bit (and in this case it is turned into a .path() Storage) - and this drew nicely, so something is working.

I hope to get time to install virtualbox and play around with everything on Ubuntu too.


Adding support for Shape in GTK using gtk_drawing_area.

Modified Path.swift from TokamakCore to support a few more path modifications.
I also had to remove the _SubPath type in the process since I could not find a way for this to work together with path modification.
The code proposed in this PR behaves (as far as I can tell) like SwiftUI in the following respects:
Adding path elements to an existing path will convert the storage to be the generic .path case.
Adding path storage building blocks to an empty path just sets the storage.
For instance:
adding a rectangle to an empty path will result in a path with .rectangle storage.

The current implementation of PathBox is just a class containing an array of Element... I wonder what the intention of the type is in SwiftUI - I'd expect that a CGPath or an array of Element directly as the associated value would suffice.

Still lacking is:
.trimmed and .stroked are incomplete.
In SwiftUI it's possible to get the stroked path and the trimmed path (in fact it appears that the .description returns a description of the 'rendered' path).
Adding stroking and trimming is very, very hard. Stroking requires the possibility of offsetting a quadratic and cubic curve, which there is not a single correct way to do.
Trimming requires measuring the length of curves, which also turns out to be difficult (but can be done by approximating a curve with line segments) as well as splitting curves.

So basically the implementation requires a lot of what is already built in to CGPath.
I guess that it can be added eventually by getting inspiration from something like Cairo or other open source drawing APIs.

Since the current 'backends' can stroke paths, that part isn't a huge issue, but stroking a path twice would require the functionality.

Another issue with the current implementation is that in SwiftUI, a Shape can draw outside of the frame of the shape, but that is not currently possible with the gtk version.
UPDATE: a shape can now draw out of bounds - implemented by resetting the clipping area of the widget in the drawing routine. This could probably mess up some graphics updates. I will look for a better solution once I get started on mimicking SwiftUI view sizing and layout.

Package.swift Outdated Show resolved Hide resolved
Makefile Outdated Show resolved Hide resolved
@mortenbekditlevsen mortenbekditlevsen marked this pull request as ready for review January 7, 2021 12:50
import CGDK
import TokamakCore

func createPath(from elements: [Path.Element], in cr: OpaquePointer) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps this can be reused for clipping. I have an idea for a fun clipping hack:
It appears that if you connect a 'draw' signal handler, it will be called before the widget code gets a chance to draw.
This means that is should be possible to implement shape clipping by modifying the cairo context to add extra clipping for any kind of widget.
I assume that the context is 'restored' after drawing a child widget, so I expect the extra clipping to be automatically be cleaned up... Just guessing by reading docs though.
Also I think that some widgets may be drawing into their own context and not just reusing the shared window context... Will have to read up and experiment...

@MaxDesiatov MaxDesiatov added GTK renderer Extending GTK support SwiftUI compatibility Tokamak API differences with SwiftUI labels Jan 7, 2021
@@ -139,6 +143,27 @@ public struct Path: Equatable, LosslessStringConvertible {
case let .roundedRect(fixedRoundedRect): return fixedRoundedRect.rect
case let .stroked(strokedPath): return strokedPath.path.boundingRect
case let .trimmed(trimmedPath): return trimmedPath.path.boundingRect
case let .path(pathBox):
// Note: Copied from TokamakStaticHTML/Shapes/Path.swift
Copy link
Collaborator

@MaxDesiatov MaxDesiatov Jan 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this copied from that file or actually moved? Is the overall duplication of code reduced then?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made a single change. I'll take a look and see if I can get a better understanding of the existing logic and try to reuse the same logic both places.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that there was an error in the previous sizing and that it's fixed in the Path.boundingRect function, but I am not 100% certain.

The previous elementsSize in StaticHTML/Shapes/Path.swift had:

    return CGSize(width: abs(maxX - min(0, minX)), height: abs(maxY - min(0, minY)))

but I replaced that with

    return CGSize(width: maxX - minX, height: maxY - minY)

because otherwise the size depends on the origin, which I guess it shouldn't - unless the usage of the size needs this special behavior.
But the similar special casing was previously not applied for .rect storage type - here the size was just the rect's size (no matter the origin) - so the change seems more consistent.

@carson-katri Do you remember a use case for the special case for the previous element path sizing?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, all our renderer-independent code in TokamakCore is easy to test and there's already a test target for it, which runs only on macOS for now. I'll make it run on Ubuntu too in a future PR. Thus if you think you've found a bug in something like this, please provide a test case. Then we can test the new code against it, if that makes sense 🙂

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't remember, sorry 😞

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another comment about the _Sizing concept:
Right now built-in shapes like Rectangle are .flexible in size while custom Shapes are .fixed.
A custom shape may as well be flexible - it all depends on whether or not the func path(in rect:) returns a path that is relative to the rect or not...

I don't know if the _Sizing concept could be made obsolete somehow by doing something else entirely...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I consider bringing the layout stuff in a separate PR from https://github.com/objcio/S01E235-swiftui-layout-explained-layout-priorities, would that be related in any way?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appear to get ok results by removing the Path._Sizing enum entirely and choosing the code paths that corresponded to the previous 'flexible' sizing...

That removes the need for the fixed size entirely - and I get quite similar results with SwiftUI and Tokamak(DOM) drawing shapes with fixed sizes - both when I create them using a 'Shape' or a 'Path' directly.

Skærmbillede 2021-01-12 kl  10 52 19

Skærmbillede 2021-01-12 kl  10 52 07

I am still uncertain if I may be lacking some context, but at least I suspect that this could be a way to go about it...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If fixing this requires some substantial changes, I suggest doing it in a separate PR to make changes more granular and easier to track and review.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent. I’ll leave the sizing as it is for now. I don’t think it’s substantially more correct or incorrect than the previous version.

And regarding your question above which I just saw now:
Yes, my knowledge about the layout system indeed comes from the reverse engineering work of objc.io Swift Talks.

I really hope to get time to look into that for GTK.
I believe that the full layout system requires you to be able to measure view sizes before they are rendered - I think that can be done in gtk, but I am not familiar with how it might be done in HTML/JavaScript although there appears to be some hacks available.

Copy link
Member

@carson-katri carson-katri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great overall 👍

Sources/TokamakGTK/Shapes/Shape.swift Outdated Show resolved Hide resolved
final class DualParamClosureBox<T, U, V> {
let closure: (T, U) -> V

init(_ closure: @escaping (T, U) -> V) { self.closure = closure }
Copy link
Collaborator

@MaxDesiatov MaxDesiatov Jan 11, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a shame that Swift doesn't support variadic generics, or macros, or something of that nature that could simplify this boilerplate. I guess GYB is more hassle than it's worth, but we could try it later you want 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, there are quite a few good use cases for variadic generics. I hope that we'll see them one day.
I haven't looked too much into the various events/signals in gtk, but my guess is that they don't have callbacks with a whole lot of arguments... So until we see some more of them I guess this is acceptable.

Copy link
Collaborator

@MaxDesiatov MaxDesiatov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall LGTM, this just needs a SwiftFormat pass 🙂

Copy link
Collaborator

@MaxDesiatov MaxDesiatov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Splendid! 👏
Ready for merging 🙂

@mortenbekditlevsen mortenbekditlevsen merged commit 25e2191 into main Jan 12, 2021
@mortenbekditlevsen mortenbekditlevsen deleted the gtk-shapes branch January 12, 2021 11:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
GTK renderer Extending GTK support SwiftUI compatibility Tokamak API differences with SwiftUI
Development

Successfully merging this pull request may close these issues.

3 participants