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
4 changes: 3 additions & 1 deletion docs/host-ios.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ backend separately as `wd.dpi = 96 * screen.scale`.
present). *Left, right and bottom are reported but not subtracted — do that yourself.*
- **Dynamic Type** — `metrics->ui_scale` and every `NEUI_METRIC_*` are already scaled, and an
explicit `NEUI_ATTR_FONT_SIZE` is routed through `UIFontMetrics`. **Do not scale again.**
- **`NEUI_EVENT_METRICS_CHANGED`** on Dynamic Type, rotation and safe-area change.
- **`NEUI_EVENT_METRICS_CHANGED`** on Dynamic Type, rotation and safe-area change — a change
confined to the left, right or bottom inset moves no bounds and raises no `RESIZE`, so this
is the only notice a client laying out inside the safe area gets.
- **Dark mode** via `NEUI_ATTR_FOLLOW_SYSTEM_THEME`; `@2x`/`@3x` asset selection.

### Not available here
Expand Down
65 changes: 51 additions & 14 deletions hosts/crossplatform/platform_ios.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1750,6 +1750,13 @@ @interface NEUIViewController : UIViewController
// still re-fires RESIZE - the usable client rect changed even though the
// frame size didn't, so the client must re-lay-out below the new inset.
int _last_top_inset;
// The other three safe-area edges. They are not folded into the client rect,
// so they raise METRICS_CHANGED on their own rather than RESIZE - but they do
// move, and a client laying out inside the safe area has no other way to hear
// about it.
int _last_left;
int _last_right;
int _last_bottom;
}
@end

Expand Down Expand Up @@ -1836,25 +1843,54 @@ - (void)reportResizeIfChanged
// The effective top inset (status-bar/notch safe area + hamburger band when
// a menubar exists) feeds widget_client_rect; track it alongside the bounds.
int top_inset = session->frame_top_inset(widget_index);
if (w == _last_w && h == _last_h && top_inset == _last_top_inset) return;
// And the other three edges, read off the same view metrics_safe_area_xpl_ios
// reads so the gate and the answer cannot drift apart. Gating on the top alone
// swallowed a side- or bottom-only resolve entirely: the client was never
// told, and went on laying out to insets that had moved. In landscape those
// are the edges that matter - the rounded corners and the camera housing down
// the sides, the home indicator along the bottom.
int left = 0, right = 0, bottom = 0;
if (@available(iOS 11.0, *)) {
NEUIView* view = [self neuiView];
if (view) {
UIEdgeInsets ins = view.safeAreaInsets;
left = (int)(ins.left + 0.5);
right = (int)(ins.right + 0.5);
bottom = (int)(ins.bottom + 0.5);
}
}
// The top inset is folded into the rect get_client_rect reports, so a change
// to it is a resize as far as a client is concerned even when the bounds held
// still. The other three are not, so they raise METRICS_CHANGED alone.
const bool client_rect_changed =
(w != _last_w || h != _last_h || top_inset != _last_top_inset);
const bool edges_changed =
(left != _last_left || right != _last_right || bottom != _last_bottom);
if (!client_rect_changed && !edges_changed) return;
_last_w = w;
_last_h = h;
_last_top_inset = top_inset;
_last_left = left;
_last_right = right;
_last_bottom = bottom;

auto& wd = session->_widgets[widget_index];
wd.width = w;
wd.height = h;
session->resize_render_ctx(widget_index, (uint32_t)w, (uint32_t)h);
if (client_rect_changed) {
wd.width = w;
wd.height = h;
session->resize_render_ctx(widget_index, (uint32_t)w, (uint32_t)h);

neui_event_t ev = {};
ev.type = NEUI_EVENT_RESIZE;
ev.data.resize.widget = { wd.widget_id };
ev.data.resize.width = w;
ev.data.resize.height = h;
session->dispatch_event(&ev);
neui_event_t ev = {};
ev.type = NEUI_EVENT_RESIZE;
ev.data.resize.widget = { wd.widget_id };
ev.data.resize.width = w;
ev.data.resize.height = h;
session->dispatch_event(&ev);
}
// The safe-area insets (and thus safe_area_insets / get_client_rect) change on
// rotation + when the notch/status-bar inset first resolves, so notify the
// client that the metrics changed too (alongside RESIZE).
// rotation, when the notch/status-bar inset first resolves, and when any of
// the other three edges moves. This fires for all of them - alongside RESIZE
// where the client rect moved too, on its own where only an edge did.
dispatch_metrics_changed_xpl_ios(session, widget_index);
// A rotation settles here too. Told from the layout pass rather than from
// UIDevice orientation notifications: no accelerometer, and this is the
Expand Down Expand Up @@ -1904,8 +1940,9 @@ - (void)viewDidLayoutSubviews
// A safe-area change (status bar appearing, the first inset resolve after the
// window binds to its scene, or rotation revealing a notch on a new edge) does
// not change the view bounds, so reportResizeIfChanged would otherwise stay
// silent. The inset feeds widget_client_rect + the hamburger band position, so
// re-fire RESIZE (now inset-aware) and re-layout the button.
// silent. The top inset feeds widget_client_rect + the hamburger band position,
// so re-fire RESIZE for that one; a move on any of the other three edges raises
// METRICS_CHANGED instead. Either way the button is re-laid-out.
- (void)viewSafeAreaInsetsDidChange
{
[super viewSafeAreaInsetsDidChange];
Expand Down
55 changes: 43 additions & 12 deletions hosts/ios/window.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1546,6 +1546,11 @@ @interface NEUINativeIOSViewController : UIViewController
uint32_t widget_index;
@private
int _last_w, _last_h, _last_inset;
// The other three safe-area edges. They do not move the client rect the way
// the top inset does, so they drive METRICS_CHANGED on their own rather than
// RESIZE - but they do move, and a client laying out inside the safe area has
// no other way to hear about it.
int _last_left, _last_right, _last_bottom;
}
@end

Expand Down Expand Up @@ -1628,21 +1633,47 @@ - (void)reportResizeIfChanged
int w = (int)sz.width, h = (int)sz.height;
if (w <= 0 || h <= 0) return;
int inset = ios_host::frame_top_inset_ios(session, widget_index);
if (w == _last_w && h == _last_h && inset == _last_inset) return;
// And the other three edges, read off the same view metrics_safe_area_ios
// reads so the gate and the answer cannot drift apart. Gating on the top
// alone swallowed a side- or bottom-only resolve entirely: the client was
// never told, and went on laying out to insets that had moved. In landscape
// those are the edges that matter - the rounded corners and the camera
// housing down the sides, the home indicator along the bottom.
int left = 0, right = 0, bottom = 0;
if (@available(iOS 11.0, *)) {
NEUINativeIOSContentView* cv = [self contentView];
if (cv) {
UIEdgeInsets ins = cv.safeAreaInsets;
left = (int)(ins.left + 0.5);
right = (int)(ins.right + 0.5);
bottom = (int)(ins.bottom + 0.5);
}
}
// The top inset is folded into the rect get_client_rect reports, so a change
// to it is a resize as far as a client is concerned even when the bounds held
// still. The other three are not, so they raise METRICS_CHANGED alone.
const bool client_rect_changed = (w != _last_w || h != _last_h || inset != _last_inset);
const bool edges_changed =
(left != _last_left || right != _last_right || bottom != _last_bottom);
if (!client_rect_changed && !edges_changed) return;
_last_w = w; _last_h = h; _last_inset = inset;
_last_left = left; _last_right = right; _last_bottom = bottom;
auto& wd = session->_widgets[widget_index];
wd.width = w; wd.height = h;
auto* backend = neui_cg_backend::get_backend();
if (backend && backend->resize && wd.render_ctx)
backend->resize(wd.render_ctx, (uint32_t)w, (uint32_t)h);
neui_event_t ev = {};
ev.type = NEUI_EVENT_RESIZE;
ev.data.resize.widget = { wd.widget_id };
ev.data.resize.width = w; ev.data.resize.height = h;
session->dispatch_event(&ev);
if (client_rect_changed) {
wd.width = w; wd.height = h;
auto* backend = neui_cg_backend::get_backend();
if (backend && backend->resize && wd.render_ctx)
backend->resize(wd.render_ctx, (uint32_t)w, (uint32_t)h);
neui_event_t ev = {};
ev.type = NEUI_EVENT_RESIZE;
ev.data.resize.widget = { wd.widget_id };
ev.data.resize.width = w; ev.data.resize.height = h;
session->dispatch_event(&ev);
}
// The safe-area insets (and thus safe_area_insets / get_client_rect) change on
// rotation + when the notch/status-bar inset first resolves, so notify the
// client that the metrics changed too (alongside RESIZE).
// rotation, when the notch/status-bar inset first resolves, and when any of
// the other three edges moves. This fires for all of them - alongside RESIZE
// where the client rect moved too, on its own where only an edge did.
ios_host::dispatch_metrics_changed_ios(session, widget_index);
// A rotation settles here too. Told from the layout pass rather than from
// UIDevice orientation notifications: no accelerometer, and this is the
Expand Down
Loading