Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- **win32: a canvas grows with its window, and `on_resize` fires.**
`canvas_create`'s width and height are the canvas's natural size on
every backend (GTK4 expands the drawing area past its content size,
AppKit holds the size at priority 150); win32 stored them as the pin
`width()`/`height()`/`canvas_size` set, so a canvas created 80x80 stayed
80x80 in a 700px window and `canvas_on_resize` never fired
(`tests/resizecb_demo` failed on Windows for that reason). The natural
size is the measure's answer now; a pin is still a pin.
- **win32: a picker's text is its selection, and a programmatic selection
fires `on_change`.** The driver's `text` for a picker was always empty
(a combo box has no window text to cache), so `tests/picker` failed on
Windows; the selected item is cached after every change. `CB_SETCURSEL`
sends no `CBN_SELCHANGE`, so `picker_set_selected` invokes the closure
itself when the index changed, as GTK4, AppKit and UIKit do.
- **A selected list row is visible on every backend.** `.aui-row-selected`
was painted by GTK4's stylesheet and, on AppKit, Win32 and UIKit, only
reported to the driver: a listbox's selection could be read by a spec
and not seen by a user. Each backend now paints its system's selection
tint over the row's own ground -- Win32 the accent colour as a tint the
labels stay legible on (`w32_selection_ground`, a quarter over white or
a good third over the dark ground), AppKit
`unemphasizedSelectedContentBackgroundColor`, UIKit
`tertiarySystemFillColor` -- and takes it off when the class leaves.
- **win32 lays the containers above a changed stack out again.** A child
added to a nested stack (a listbox's rows go into the `each` container
inside the app's column) changes that stack's natural size, and the
column has to place what follows it lower; the layout request stopped at
the nested stack, the column kept the container at the height it had
when empty, and 200 rows were drawn over the buttons under it.
`w32_request_layout` now climbs to every container whose size can
change, stopping after a scrollview or a stack pinned in height, the
bounds `set_hidden`'s synchronous climb already used. rebuild_bench is
unchanged (0.88-0.95s on screen, 0.63-0.67s headless).
- **win32 dark mode: fields and text areas wear a dark edge, the accent
colour while focused.** The dark theme class darkens an EDIT and what it
draws inside, but the sunken client edge around it is the system's and
Expand Down
38 changes: 37 additions & 1 deletion backend/aether_ui_macos.m
Original file line number Diff line number Diff line change
Expand Up @@ -2980,8 +2980,26 @@ static void aeui_apply_state_bg(NSView* v) {
style = objc_getAssociatedObject(v, "aeui-active-style");
if (!style && objc_getAssociatedObject(v, "aeui-hovered"))
style = objc_getAssociatedObject(v, "aeui-hover-style");
// A selected list row (.aui-row-selected) paints the system's selection
// ground over whatever ground it has, the one the other backends paint
// too: the unemphasized one, which keeps the labels' own colours legible
// in both appearances (an emphasized selection wants white text, and
// the row's labels are the app's).
if (!style && objc_getAssociatedObject(v, "aeui-row-selected")) {
[v setWantsLayer:YES];
v.layer.backgroundColor = [[NSColor unemphasizedSelectedContentBackgroundColor] CGColor];
return;
}
if (!style) style = objc_getAssociatedObject(v, "aeui_styled_bg");
if (!style) return;
if (!style) {
// Nothing to paint: a row that just lost its selection goes back to
// no ground of its own.
if (objc_getAssociatedObject(v, "aeui-row-selection-painted")) {
objc_setAssociatedObject(v, "aeui-row-selection-painted", nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (v.layer) v.layer.backgroundColor = NULL;
}
return;
}
int packed = [style intValue];
if (!(packed & 0x1000000)) return;
[v setWantsLayer:YES];
Expand Down Expand Up @@ -4308,19 +4326,36 @@ static int class_list_has(const char* list, const char* cls) {
return 0;
}

// A class is a name the driver reads back, and on GTK4 a stylesheet's hook.
// One has a look here as well: .aui-row-selected, the listbox's selection,
// which every list on every backend has to show (see aeui_apply_state_bg).
static void aeui_class_visual(int handle, const char* cls, int on) {
if (strcmp(cls, "aui-row-selected") != 0) return;
NSView* v = (__bridge NSView*)aether_ui_get_widget(handle);
if (!v) return;
objc_setAssociatedObject(v, "aeui-row-selected", on ? @(1) : nil,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (on) objc_setAssociatedObject(v, "aeui-row-selection-painted", @(1),
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
aeui_apply_state_bg(v);
[v setNeedsDisplay:YES];
}

void aether_ui_widget_add_css_class_impl(int handle, const char* cls) {
if (handle < 1 || handle > widget_count || !cls || !*cls) return;
char* cur = widget_classes[handle - 1];
if (class_list_has(cur, cls)) return; // idempotent, like GTK's
if (!cur) {
widget_classes[handle - 1] = strdup(cls);
aeui_class_visual(handle, cls, 1);
return;
}
char* joined = (char*)malloc(strlen(cur) + strlen(cls) + 2);
if (!joined) return;
sprintf(joined, "%s %s", cur, cls);
free(cur);
widget_classes[handle - 1] = joined;
aeui_class_visual(handle, cls, 1);
}

void aether_ui_widget_remove_css_class_impl(int handle, const char* cls) {
Expand All @@ -4341,6 +4376,7 @@ void aether_ui_widget_remove_css_class_impl(int handle, const char* cls) {
free(work);
free(cur);
widget_classes[handle - 1] = out; // "" when the last class was removed
aeui_class_visual(handle, cls, 0);
}
// ---------------------------------------------------------------------------
// The drawn tooltip — a vg-drawn shape's tooltip, rendered as an overlay
Expand Down
27 changes: 26 additions & 1 deletion backend/aether_ui_uikit.m
Original file line number Diff line number Diff line change
Expand Up @@ -2934,10 +2934,33 @@ void aether_ui_widget_weight_impl(int handle, int n) {
}

// --- CSS classes (per-widget, space-separated; drives selectors + the driver)
// A class is a name the driver reads back, and on GTK4 a stylesheet's hook.
// One has a look here as well: .aui-row-selected, the listbox's selection,
// which every list on every backend has to show. The system fill is the
// tint an iOS list draws under a selected cell; the row's own colour is
// kept and comes back when the selection leaves.
static void aeui_class_visual(int handle, const char* cls, int on) {
if (strcmp(cls, "aui-row-selected") != 0) return;
UIView* v = (__bridge UIView*)aether_ui_get_widget(handle);
if (!v) return;
if (on) {
if (!objc_getAssociatedObject(v, "aeui-row-orig"))
objc_setAssociatedObject(v, "aeui-row-orig", v.backgroundColor ?: [NSNull null],
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
v.backgroundColor = [UIColor tertiarySystemFillColor];
} else {
id orig = objc_getAssociatedObject(v, "aeui-row-orig");
if (orig) {
v.backgroundColor = (orig == [NSNull null]) ? nil : orig;
objc_setAssociatedObject(v, "aeui-row-orig", nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
}
}

void aether_ui_widget_add_css_class_impl(int handle, const char* cls) {
if (handle < 1 || handle > widget_count || !cls || !cls[0]) return;
char* cur = widget_classes[handle - 1];
if (!cur) { widget_classes[handle - 1] = strdup(cls); return; }
if (!cur) { widget_classes[handle - 1] = strdup(cls); aeui_class_visual(handle, cls, 1); return; }
// Already present? (whole-token match)
size_t clen = strlen(cls);
const char* p = cur;
Expand All @@ -2952,6 +2975,7 @@ void aether_ui_widget_add_css_class_impl(int handle, const char* cls) {
snprintf(joined, n, "%s %s", cur, cls);
free(cur);
widget_classes[handle - 1] = joined;
aeui_class_visual(handle, cls, 1);
}
void aether_ui_widget_remove_css_class_impl(int handle, const char* cls) {
if (handle < 1 || handle > widget_count || !cls || !cls[0]) return;
Expand All @@ -2973,6 +2997,7 @@ void aether_ui_widget_remove_css_class_impl(int handle, const char* cls) {
}
free(cur);
widget_classes[handle - 1] = out[0] ? out : (free(out), (char*)NULL);
aeui_class_visual(handle, cls, 0);
}
const char* aether_ui_widget_classes_impl(int handle) {
if (handle < 1 || handle > widget_count) return "";
Expand Down
Loading
Loading