Tweaks

A tweak configures the native widget behind a built-in piece. For example, you can apply AppKit’s toolbar bezel to a button or enable tick marks on a XAML slider. Day continues to manage the widget’s layout and lifecycle. A Tweaked Piece uses the same widget and handle with additional configuration.

The showcase’s Tweaks page (in the gallery) demonstrates everything on this page.

Applying a tweak

The portable entry point is a modifier that runs once at mount, after the native widget exists:

button("Save").tweak(|node| {
    // `node` is the realized node; per-toolkit accessors turn it into a native handle.
})

Each toolkit crate adds a typed extension trait over that, and that trait is the normal entry point. The closure gets the native widget and its concrete class name, so a tweak knows exactly what it is configuring:

use day_appkit::AppKitExt;   // exists only in the appkit build

button("Save").appkit(|view, class, _mtm| {   // class == "NSButton"
    if let Some(btn) = view.downcast_ref::<objc2_app_kit::NSButton>() {
        unsafe { btn.setBezelStyle(objc2_app_kit::NSBezelStyle::Toolbar) };
    }
})

.gtk(|widget, class| …), .uikit(|view, class, mtm| …), and .android(|view, class, jni_env| …) follow the same shape with each platform’s types. Qt, XAML, and ArkUI sit behind C shims, so their accessors hand out the raw native pointer (plus the class) instead, with a short bring-your-own-C++ recipe (each tier is spelled out in the tweaks reference).

The class name keeps a tweak from breaking silently. On the typed tiers it’s the live widget’s runtime class, so if a piece ever has more than one native backing (a plain label as UILabel, a link-bearing one as UITextView), the tweak can match on the class instead of guessing a downcast. On the raw tiers, where Rust can’t introspect an opaque pointer, it’s the metadata your C++ needs: pass it across the shim and guard the cast so the pointer is never reinterpreted as the wrong control.

Day re-applies the properties it manages (a button’s title, a slider’s value) on its next update, so tweak the properties Day doesn’t touch (bezels, tick marks, selectability) and they’re stable. And if a native call changes the widget’s intrinsic size, tell layout with day::invalidate_size(node), because Day can’t see mutations it didn’t make.

Reaching a widget later

A mount-time hook covers configuration; for imperative access afterward (from an event handler, say), capture a NativeRef:

let save_ref = NativeRef::new();

column((
    button("Save").native_ref(&save_ref),
    button("Flash the save button").action(move || {
        save_ref.with(|node| { /* per-toolkit accessor on `node` */ });
    }),
))

The ref clears automatically when the piece unmounts, so a late timer or async completion is a safe None, never a dangling widget. Reads are reactive, too: a label whose closure calls save_ref.node() re-renders when the referenced piece mounts or disappears.

Packaged tweaks

To reuse a tweak across apps, package it: a day-tweak-* crate wraps the per-toolkit calls in one modifier and no-ops on toolkits it doesn’t cover, so the app using it stays free of #[cfg]. Three examples in the repository show different platform configurations:

use day_tweak_button_bezel::{Bezel, ButtonBezelTweak};
use day_tweak_tooltip::TooltipTweak;
use day_tweak_slider_tickmarks::{SliderTickmarksTweak, Tickmarks};

button("Save").bezel(Bezel::Toolbar);          // AppKit only; stock elsewhere
button("Save").tooltip("Save your changes (⌘S)");  // AppKit, GTK, Android; no-op elsewhere
slider(v).tickmarks(Tickmarks::count(11).snap(true));  // six toolkits, incl. its own C++

The tick-marks crate demonstrates configuring a native feature on six toolkits through objc2, gtk4-rs, JNI, and compiled Qt C++, WinRT C++, and ArkUI NDK code. It documents each platform’s behavior (Material sliders always snap when stepped; UIKit has no native tick API, so there it’s a no-op). Publish the crate as a Cargo package. Consumers add it as a dependency, and day build enables the corresponding toolkit features.

The tweaks reference has the full per-toolkit matrix, the native-code recipes, and the mechanics underneath. To add a new widget, write a native piece.