darlinghq / darling

Darwin/macOS emulation layer for Linux
http://www.darlinghq.org
GNU General Public License v3.0
11.27k stars 428 forks source link

Theming support #369

Open bugaevc opened 6 years ago

bugaevc commented 6 years ago

We want Darling GUI to be themable. After careful consideration, this I what I came up with.

Non-goals:

Goals:

We want to ship a few (think 1 or 2) built-in Aqua-like themes with Darling, alongside the GTK and Qt themes. It's expected that most users will be using the GTK/Qt ones, because those integrate best into native Linux desktop environments.

I initially thought we could use Cocotron's existing NSGrpahicsStyle/NSInterfacePart APIs, but those turned out to be unfit for our needs. So, let's make our own.

The "theme" class is NSAppearance. The theming engine is implemented inside AppKit (and not as a separate private framework), in cocotron/AppKit/NSAppearance.subproj, and replaces NSGrpahicsStyle/NSInterfacePart.

Because in a typical case most (but not all) of the drawing will be done by GTK/Qt using their native cairo/QPainter graphics stacks, it is crucial that we don't perform a costy conversion between O2Surface and cairo_surface_t/QPaintDevice on each drawRect: call. This means the NSWindow's CG/O2 context should be implemented on top of the native graphics stack the current theme prefers.

It should look something like this:

@interface NSAppearance
+ (NSAppearance *) currentAppearance;
- (Class) preferredContextType;

// actual rendering methods

// this renders a button onto *any* kind of O2Context, not just the kind it prefers
// but it can have an optimized implementation for that one
- (void) renderButtonOfSize: (CGSize) size withContext: (O2Context *) context miscArgs: (MiscArgs) args;
@end

@implementation NSGTKAppearance
- (Class) preferredContextType {
    return [O2CairoContext class];
}
- (void) renderButtonOfSize: (CGSize) size withContext: (O2Context *) context miscArgs: (MiscArgs) args {
    [context performCairoOperation: ^(cairo_t *cairo_context) {
        gtk_render_button_somehow(cairo_context, args);
    }];
}
@end

@interface O2Context (CairoCompatibility)
- (void) performCairoOperation: (void (^)(cairo_t *)) operation;
@end

@implementation O2CairoContext (CairoCompatibility)
- (void) performCairoOperation: (void (^)(cairo_t *)) operation {
    operation(_cairo_context);
}
@end

@implementation O2Context (CairoCompatibility)
- (void) performCairoOperation: (void (^)(cairo_t *)) operation {
    char *rawData = [[self surface] pixelBytes];
    cairo_surface_t *cairo_surface = cairo_image_surface_create_for_data(rawData, ...);
    cairo_t *cairo_context = cairo_create(cairo_surface);
    operation(cairo_context);
    cairo_destroy(cairo_context);
    cairo_surface_destroy(cairo_surface);
}
@end

Next, we should un-implement Cocotron's support for backing store types other than NSBackingStoreBuffered, thus getting rid of all the "window context" vs "backing context" distinctions and context creating difficulties. A CGWindow will then just use [[[NSAppearance currentAppearance] preferredContextType] initWithSize: _size] or something similar to create its context.

We then implement the built-in theme(s) directly on top of CoreGraphics/Onyx2D inside AppKit, but GTK and Qt themes (and their O2Surface/Context impls) go into separate bundles/frameworks. This is because we don't want AppKit to link to Qt and GTK (and preferably cairo too). This also means we do need to make themes loadable and switchable in runtime, but that should be just as easy as doing [NSAppearance setCurrentAppearance: newAppearance], which should automatically make all the windows recreate their contexts using the new O2Context subclass and redraw their contents.

There are some aspects of look'n'feel (e.g. whether to use server-side or client-side window decorations or whether to draw menu in-window, as a pop-up from a headerbar button or forward it over D-Bus to the DE) that should not be defined by the current theme and tweakable independently from it.

Maybe we should also implement NSDynamicSystemColor and somehow integrate it with NSAppearance?

Feedback & ideas welcome.

LubosD commented 4 years ago

How styling is done on macOS:

This issue now became more important, because I don't intend to port color provisioning from X11Display over to CGS.