From 83525d0cd33a93574b7c01902a6f342d6d8e1719 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Tue, 22 Sep 2026 15:03:49 +0200 Subject: [PATCH 1/2] feat(ui): allow macOS button font family selection --- crates/perry-codegen-arkts/src/mutations.rs | 2 +- crates/perry-codegen-js/src/emit/calls.rs | 1 + crates/perry-codegen-js/src/web_runtime.js | 6 ++ .../src/emit/ui_method_map.rs | 1 + crates/perry-dispatch/src/ui_table/part_a.rs | 6 ++ .../perry-ui-android/src/ffi/text_scroll.rs | 3 + crates/perry-ui-gtk4/src/ffi/text_button.rs | 3 + crates/perry-ui-ios/src/ffi/widgets_basic.rs | 3 + crates/perry-ui-macos/Cargo.toml | 6 ++ .../src/lib_ffi/core_widgets.rs | 6 ++ crates/perry-ui-macos/src/widgets/button.rs | 50 +++++++++++- .../tests/native_button_font_family.rs | 76 +++++++++++++++++++ .../perry-ui-test/src/features/interaction.rs | 13 +++- crates/perry-ui-tvos/src/ffi/styling.rs | 3 + crates/perry-ui-visionos/src/ffi_layout.rs | 3 + crates/perry-ui-watchos/src/lib.rs | 3 + .../perry-ui-windows/src/ffi/text_button.rs | 3 + crates/perry-ui/src/styling_matrix.rs | 6 ++ docs/src/ui/styling-matrix.md | 19 ++--- docs/src/ui/widgets.md | 2 + types/perry/ui/index.d.ts | 2 + 21 files changed, 204 insertions(+), 13 deletions(-) create mode 100644 crates/perry-ui-macos/tests/native_button_font_family.rs diff --git a/crates/perry-codegen-arkts/src/mutations.rs b/crates/perry-codegen-arkts/src/mutations.rs index 60d302ac41..53ad831b5d 100644 --- a/crates/perry-codegen-arkts/src/mutations.rs +++ b/crates/perry-codegen-arkts/src/mutations.rs @@ -466,7 +466,7 @@ pub(crate) fn collect_mutations_in_expr( cond, ); } - "textSetFontFamily" => { + "textSetFontFamily" | "buttonSetFontFamily" => { // Args: (widget, family). Family must resolve to a string // literal — most theme code passes a const-bound string. let mut cur = match args.get(1) { diff --git a/crates/perry-codegen-js/src/emit/calls.rs b/crates/perry-codegen-js/src/emit/calls.rs index 79bd1a9b10..5f0684ea62 100644 --- a/crates/perry-codegen-js/src/emit/calls.rs +++ b/crates/perry-codegen-js/src/emit/calls.rs @@ -294,6 +294,7 @@ impl JsEmitter { "textSetFontFamily" => "perry_ui_set_font_family", "textSetString" => "perry_ui_text_set_string", "buttonSetBordered" => "perry_ui_button_set_bordered", + "buttonSetFontFamily" => "perry_ui_button_set_font_family", "buttonSetTextColor" => "perry_ui_button_set_text_color", "buttonSetTitle" => "perry_ui_button_set_title", "buttonSetImage" => "perry_ui_button_set_image", diff --git a/crates/perry-codegen-js/src/web_runtime.js b/crates/perry-codegen-js/src/web_runtime.js index 7e79e3e0cd..cedda54927 100644 --- a/crates/perry-codegen-js/src/web_runtime.js +++ b/crates/perry-codegen-js/src/web_runtime.js @@ -1149,6 +1149,11 @@ function perry_ui_button_set_title(h, title) { if (el) el.textContent = title; } +function perry_ui_button_set_font_family(h, family) { + const el = getHandle(h); + if (el) el.style.fontFamily = family; +} + function perry_ui_button_set_text_color(h, r, g, b, a) { const el = getHandle(h); if (el) el.style.color = `rgba(${Math.round(r*255)},${Math.round(g*255)},${Math.round(b*255)},${a})`; @@ -3714,6 +3719,7 @@ window.__perry = { perry_ui_text_set_string, perry_ui_text_set_selectable, perry_ui_button_set_bordered, + perry_ui_button_set_font_family, perry_ui_button_set_title, perry_ui_button_set_text_color, perry_ui_button_set_image, diff --git a/crates/perry-codegen-wasm/src/emit/ui_method_map.rs b/crates/perry-codegen-wasm/src/emit/ui_method_map.rs index a437b44216..8c8191cf0d 100644 --- a/crates/perry-codegen-wasm/src/emit/ui_method_map.rs +++ b/crates/perry-codegen-wasm/src/emit/ui_method_map.rs @@ -140,6 +140,7 @@ pub(super) fn map_ui_method(method: &str, class_name: Option<&str>) -> &'static "textSetString" => "perry_ui_text_set_string", "textSetWraps" => "perry_ui_text_set_wraps", "buttonSetBordered" => "perry_ui_button_set_bordered", + "buttonSetFontFamily" => "perry_ui_button_set_font_family", "buttonSetTitle" => "perry_ui_button_set_title", "buttonSetTextColor" => "perry_ui_button_set_text_color", "buttonSetImage" => "perry_ui_button_set_image", diff --git a/crates/perry-dispatch/src/ui_table/part_a.rs b/crates/perry-dispatch/src/ui_table/part_a.rs index fd8f037743..ccdf3e3bde 100644 --- a/crates/perry-dispatch/src/ui_table/part_a.rs +++ b/crates/perry-dispatch/src/ui_table/part_a.rs @@ -442,6 +442,12 @@ pub(crate) const PERRY_UI_TABLE_PART_A: &[MethodRow] = &[ ret: ReturnKind::Void, }, // ---- Button setters ---- + MethodRow { + method: "buttonSetFontFamily", + runtime: "perry_ui_button_set_font_family", + args: &[ArgKind::Widget, ArgKind::Str], + ret: ReturnKind::Void, + }, MethodRow { method: "buttonSetBordered", runtime: "perry_ui_button_set_bordered", diff --git a/crates/perry-ui-android/src/ffi/text_scroll.rs b/crates/perry-ui-android/src/ffi/text_scroll.rs index 0b599b7e3a..3d895d23eb 100644 --- a/crates/perry-ui-android/src/ffi/text_scroll.rs +++ b/crates/perry-ui-android/src/ffi/text_scroll.rs @@ -204,6 +204,9 @@ pub extern "C" fn perry_ui_button_set_title(handle: i64, title_ptr: i64) { widgets::button::set_title(handle, title_ptr as *const u8); } +#[no_mangle] +pub extern "C" fn perry_ui_button_set_font_family(_handle: i64, _family_ptr: i64) {} + // ============================================================================= // Phase A.4: Focus & Scroll-To // ============================================================================= diff --git a/crates/perry-ui-gtk4/src/ffi/text_button.rs b/crates/perry-ui-gtk4/src/ffi/text_button.rs index ec4c18c49a..cb35911f45 100644 --- a/crates/perry-ui-gtk4/src/ffi/text_button.rs +++ b/crates/perry-ui-gtk4/src/ffi/text_button.rs @@ -88,6 +88,9 @@ pub extern "C" fn perry_ui_button_set_title(handle: i64, title_ptr: i64) { widgets::button::set_title(handle, title_ptr as *const u8); } +#[no_mangle] +pub extern "C" fn perry_ui_button_set_font_family(_handle: i64, _family_ptr: i64) {} + /// Set the text color of a button's label. #[no_mangle] pub extern "C" fn perry_ui_button_set_text_color(handle: i64, r: f64, g: f64, b: f64, a: f64) { diff --git a/crates/perry-ui-ios/src/ffi/widgets_basic.rs b/crates/perry-ui-ios/src/ffi/widgets_basic.rs index 58a977c39a..39d77bc59a 100644 --- a/crates/perry-ui-ios/src/ffi/widgets_basic.rs +++ b/crates/perry-ui-ios/src/ffi/widgets_basic.rs @@ -523,6 +523,9 @@ pub extern "C" fn perry_ui_button_set_title(handle: i64, title_ptr: i64) { widgets::button::set_title(handle, title_ptr as *const u8); } +#[no_mangle] +pub extern "C" fn perry_ui_button_set_font_family(_handle: i64, _family_ptr: i64) {} + #[no_mangle] pub extern "C" fn perry_ui_button_set_text_color(handle: i64, r: f64, g: f64, b: f64, a: f64) { widgets::button::set_text_color(handle, r, g, b, a); diff --git a/crates/perry-ui-macos/Cargo.toml b/crates/perry-ui-macos/Cargo.toml index d7528f407d..5ebbd1c64c 100644 --- a/crates/perry-ui-macos/Cargo.toml +++ b/crates/perry-ui-macos/Cargo.toml @@ -60,6 +60,7 @@ objc2-app-kit = { version = "0.3", features = [ "NSPasteboard", "NSColor", "NSFont", + "NSFontManager", "NSSavePanel", "NSOpenPanel", "NSSecureTextField", @@ -86,6 +87,11 @@ name = "native_widget_padding" path = "tests/native_widget_padding.rs" harness = false +[[test]] +name = "native_button_font_family" +path = "tests/native_button_font_family.rs" +harness = false + [[test]] name = "native_widget_max_width" path = "tests/native_widget_max_width.rs" diff --git a/crates/perry-ui-macos/src/lib_ffi/core_widgets.rs b/crates/perry-ui-macos/src/lib_ffi/core_widgets.rs index 504264b2d6..9557f13013 100644 --- a/crates/perry-ui-macos/src/lib_ffi/core_widgets.rs +++ b/crates/perry-ui-macos/src/lib_ffi/core_widgets.rs @@ -467,6 +467,12 @@ pub extern "C" fn perry_ui_button_set_text_color(handle: i64, r: f64, g: f64, b: widgets::button::set_text_color(handle, r, g, b, a); } +/// Set a Button's font family, retaining its current point size and weight. +#[no_mangle] +pub extern "C" fn perry_ui_button_set_font_family(handle: i64, family_ptr: i64) { + widgets::button::set_font_family(handle, family_ptr as *const u8); +} + /// Set a fixed width constraint on a widget. #[no_mangle] pub extern "C" fn perry_ui_widget_set_width(handle: i64, width: f64) { diff --git a/crates/perry-ui-macos/src/widgets/button.rs b/crates/perry-ui-macos/src/widgets/button.rs index b0dd0603d2..7692ee2c66 100644 --- a/crates/perry-ui-macos/src/widgets/button.rs +++ b/crates/perry-ui-macos/src/widgets/button.rs @@ -1,8 +1,8 @@ use objc2::rc::Retained; use objc2::runtime::{AnyClass, AnyObject, Sel}; use objc2::{define_class, msg_send, AnyThread, DefinedClass}; -use objc2_app_kit::{NSButton, NSView}; -use objc2_foundation::{MainThreadMarker, NSObject, NSString}; +use objc2_app_kit::{NSButton, NSFont, NSFontManager, NSView}; +use objc2_foundation::{MainThreadMarker, NSObject, NSRange, NSString}; use std::cell::RefCell; use std::collections::HashMap; @@ -173,6 +173,52 @@ pub fn set_text_color(handle: i64, r: f64, g: f64, b: f64, a: f64) { } } +/// Change a button's font family while retaining its current size and traits. +/// Keep the font on the attributed title too: an attributed font overrides the +/// button cell's font, and setting text color can create an attributed title. +pub fn set_font_family(handle: i64, family_ptr: *const u8) { + if family_ptr.is_null() { + return; + } + let family = unsafe { str_from_header(family_ptr) }; + if family.is_empty() { + return; + } + let Some(view) = super::get_widget(handle) else { + return; + }; + let mtm = MainThreadMarker::new().expect("perry/ui must run on the main thread"); + unsafe { + let is_button: bool = msg_send![&*view, isKindOfClass: AnyClass::get(c"NSButton").unwrap()]; + if !is_button { + return; + } + let button: &NSButton = &*(Retained::as_ptr(&view) as *const NSButton); + let current = button + .font() + .unwrap_or_else(|| NSFont::systemFontOfSize(13.0)); + let font = NSFontManager::sharedFontManager(mtm) + .convertFont_toFamily(¤t, &NSString::from_str(&family)); + button.setFont(Some(&font)); + + let title: *mut AnyObject = msg_send![button, attributedTitle]; + if !title.is_null() { + let length: usize = msg_send![title, length]; + if length > 0 { + let attributed: Retained = msg_send![title, mutableCopy]; + let key = NSString::from_str("NSFont"); + let _: () = msg_send![ + &*attributed, + addAttribute: &*key, + value: &*font, + range: NSRange::new(0, length) + ]; + let _: () = msg_send![button, setAttributedTitle: &*attributed]; + } + } + } +} + /// Set an SF Symbol image on a button with a large point size. pub fn set_image(handle: i64, name_ptr: *const u8) { let name = unsafe { str_from_header(name_ptr) }; diff --git a/crates/perry-ui-macos/tests/native_button_font_family.rs b/crates/perry-ui-macos/tests/native_button_font_family.rs new file mode 100644 index 0000000000..aeffa6d4e8 --- /dev/null +++ b/crates/perry-ui-macos/tests/native_button_font_family.rs @@ -0,0 +1,76 @@ +// AppKit UI objects must be created and inspected on the main thread. +#[cfg(target_os = "macos")] +fn main() { + use objc2::rc::Retained; + use objc2::runtime::AnyObject; + use objc2_app_kit::{NSApplication, NSButton, NSFont}; + use objc2_foundation::{MainThreadMarker, NSRange, NSString}; + use perry_ui_macos::{perry_ui_button_set_font_family, widgets}; + + if std::env::args().any(|arg| arg == "--list") { + println!("native_button_font_family: test"); + return; + } + + let mtm = MainThreadMarker::new().expect("native button test runs on the main thread"); + let _app = NSApplication::sharedApplication(mtm); + let label = perry_runtime::string::js_string_from_bytes(b"Copy".as_ptr(), 4); + let handle = widgets::button::create(label.cast(), 0.0); + let view = widgets::get_widget(handle).expect("button is registered"); + let button = unsafe { &*(Retained::as_ptr(&view) as *const NSButton) }; + button.setFont(Some(&NSFont::boldSystemFontOfSize(17.0))); + widgets::button::set_text_color(handle, 0.25, 0.5, 0.75, 1.0); + + let family = perry_runtime::string::js_string_from_bytes(b"Menlo".as_ptr(), 5); + perry_ui_button_set_font_family(handle, family as i64); + + let font = button.font().expect("button font"); + assert_eq!(font.familyName().expect("font family").to_string(), "Menlo"); + assert_eq!(font.pointSize(), 17.0); + assert!( + font.fontName().to_string().contains("Bold"), + "family conversion must preserve the button's weight" + ); + + unsafe { + let title: *mut AnyObject = objc2::msg_send![button, attributedTitle]; + let font_key = NSString::from_str("NSFont"); + let color_key = NSString::from_str("NSColor"); + let font_in_title: *mut NSFont = objc2::msg_send![ + title, + attribute: &*font_key, + atIndex: 0usize, + effectiveRange: std::ptr::null_mut::() + ]; + let color_in_title: *mut AnyObject = objc2::msg_send![ + title, + attribute: &*color_key, + atIndex: 0usize, + effectiveRange: std::ptr::null_mut::() + ]; + assert!(!font_in_title.is_null(), "attributed title keeps font"); + assert!( + !color_in_title.is_null(), + "attributed title keeps text color" + ); + assert_eq!( + (*font_in_title) + .familyName() + .expect("attributed font family") + .to_string(), + "Menlo" + ); + } + + widgets::button::set_bordered(handle, false); + assert_eq!( + button.font().expect("borderless button font").pointSize(), + 17.0 + ); + let text = widgets::text::create(label.cast()); + perry_ui_button_set_font_family(text, family as i64); + println!("PASS native button font family"); +} + +#[cfg(not(target_os = "macos"))] +fn main() {} diff --git a/crates/perry-ui-test/src/features/interaction.rs b/crates/perry-ui-test/src/features/interaction.rs index 5762631b6b..f65ddde174 100644 --- a/crates/perry-ui-test/src/features/interaction.rs +++ b/crates/perry-ui-test/src/features/interaction.rs @@ -2,7 +2,7 @@ //! Button/TextField/ScrollView state ops, continuous events, //! and view-driven animations. -use crate::{full, Category::*, Feature, S, U}; +use crate::{full, Category::*, Feature, Support::Stub, S, U}; pub(crate) const ROWS: &[Feature] = &[ // ── Button Ops ─────────────────────────────────────────────────────── @@ -28,6 +28,17 @@ pub(crate) const ROWS: &[Feature] = &[ web: S, web_name: None, }, + Feature { + name: "perry_ui_button_set_font_family", + category: ButtonOps, + macos: S, + ios: Stub, + android: Stub, + gtk4: Stub, + windows: Stub, + web: S, + web_name: None, + }, Feature { name: "perry_ui_button_set_content_tint_color", category: ButtonOps, diff --git a/crates/perry-ui-tvos/src/ffi/styling.rs b/crates/perry-ui-tvos/src/ffi/styling.rs index 17f52185ca..386a5781bd 100644 --- a/crates/perry-ui-tvos/src/ffi/styling.rs +++ b/crates/perry-ui-tvos/src/ffi/styling.rs @@ -68,6 +68,9 @@ pub extern "C" fn perry_ui_button_set_title(handle: i64, title_ptr: i64) { widgets::button::set_title(handle, title_ptr as *const u8); } +#[no_mangle] +pub extern "C" fn perry_ui_button_set_font_family(_handle: i64, _family_ptr: i64) {} + #[no_mangle] pub extern "C" fn perry_ui_button_set_text_color(handle: i64, r: f64, g: f64, b: f64, a: f64) { widgets::button::set_text_color(handle, r, g, b, a); diff --git a/crates/perry-ui-visionos/src/ffi_layout.rs b/crates/perry-ui-visionos/src/ffi_layout.rs index dd6c2b6506..ba973c0fc7 100644 --- a/crates/perry-ui-visionos/src/ffi_layout.rs +++ b/crates/perry-ui-visionos/src/ffi_layout.rs @@ -159,6 +159,9 @@ pub extern "C" fn perry_ui_button_set_title(handle: i64, title_ptr: i64) { widgets::button::set_title(handle, title_ptr as *const u8); } +#[no_mangle] +pub extern "C" fn perry_ui_button_set_font_family(_handle: i64, _family_ptr: i64) {} + #[no_mangle] pub extern "C" fn perry_ui_button_set_text_color(handle: i64, r: f64, g: f64, b: f64, a: f64) { widgets::button::set_text_color(handle, r, g, b, a); diff --git a/crates/perry-ui-watchos/src/lib.rs b/crates/perry-ui-watchos/src/lib.rs index da1cf6b2f2..9945091303 100644 --- a/crates/perry-ui-watchos/src/lib.rs +++ b/crates/perry-ui-watchos/src/lib.rs @@ -416,6 +416,9 @@ pub extern "C" fn perry_ui_button_set_title(handle: i64, title_ptr: i64) { }); } +#[no_mangle] +pub extern "C" fn perry_ui_button_set_font_family(_handle: i64, _family_ptr: i64) {} + #[no_mangle] pub extern "C" fn perry_ui_button_set_text_color(handle: i64, r: f64, g: f64, b: f64, a: f64) { tree::with_node_mut(handle, |node| { diff --git a/crates/perry-ui-windows/src/ffi/text_button.rs b/crates/perry-ui-windows/src/ffi/text_button.rs index 08764d7645..452d26b617 100644 --- a/crates/perry-ui-windows/src/ffi/text_button.rs +++ b/crates/perry-ui-windows/src/ffi/text_button.rs @@ -82,6 +82,9 @@ pub extern "C" fn perry_ui_button_set_title(handle: i64, title_ptr: i64) { widgets::button::set_title(handle, title_ptr as *const u8); } +#[no_mangle] +pub extern "C" fn perry_ui_button_set_font_family(_handle: i64, _family_ptr: i64) {} + /// Set button image (SF Symbol name). On Windows, maps known SF Symbol names to Unicode/text fallbacks. #[no_mangle] pub extern "C" fn perry_ui_button_set_image(handle: i64, name_ptr: i64) { diff --git a/crates/perry-ui/src/styling_matrix.rs b/crates/perry-ui/src/styling_matrix.rs index f4b5a5c45e..9fc02b2447 100644 --- a/crates/perry-ui/src/styling_matrix.rs +++ b/crates/perry-ui/src/styling_matrix.rs @@ -438,6 +438,12 @@ pub const MATRIX: &[MatrixRow] = &[ statuses: W_ALL_NATIVE_WEB_TODO, }, // ---- button widget styling ---------------------------------------- + MatrixRow { + widget: "button", + prop: "font_family", + ffi: "perry_ui_button_set_font_family", + statuses: [Wired, Stub, Stub, Stub, Stub, Stub, Stub, Stub, Wired], + }, MatrixRow { widget: "button", prop: "text_color", diff --git a/docs/src/ui/styling-matrix.md b/docs/src/ui/styling-matrix.md index ac8e62017b..1b5e8a9023 100644 --- a/docs/src/ui/styling-matrix.md +++ b/docs/src/ui/styling-matrix.md @@ -40,6 +40,7 @@ Legend: `✓` Wired (real native impl), `~` Stub (symbol exists, no-op), `✗` M | Prop | FFI symbol | macOS | iOS | tvOS | visionOS | watchOS | Android | GTK4 | Windows | Web | |---|---|---|---|---|---|---|---|---|---|---| +| `font_family` | `perry_ui_button_set_font_family` | ✓ | ~ | ~ | ~ | ~ | ~ | ~ | ~ | ✓ | | `text_color` | `perry_ui_button_set_text_color` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | `content_tint_color` | `perry_ui_button_set_content_tint_color` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | `bordered` | `perry_ui_button_set_bordered` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | @@ -86,13 +87,13 @@ Legend: `✓` Wired (real native impl), `~` Stub (symbol exists, no-op), `✗` M | Platform | Wired | Stub | Missing | Not applicable | |---|---|---|---|---| -| macOS | 48 | 0 | 0 | 0 | -| iOS | 47 | 1 | 0 | 0 | -| tvOS | 47 | 1 | 0 | 0 | -| visionOS | 47 | 1 | 0 | 0 | -| watchOS | 47 | 1 | 0 | 0 | -| Android | 47 | 1 | 0 | 0 | -| GTK4 | 47 | 1 | 0 | 0 | -| Windows | 47 | 1 | 0 | 0 | -| Web | 48 | 0 | 0 | 0 | +| macOS | 49 | 0 | 0 | 0 | +| iOS | 47 | 2 | 0 | 0 | +| tvOS | 47 | 2 | 0 | 0 | +| visionOS | 47 | 2 | 0 | 0 | +| watchOS | 47 | 2 | 0 | 0 | +| Android | 47 | 2 | 0 | 0 | +| GTK4 | 47 | 2 | 0 | 0 | +| Windows | 47 | 2 | 0 | 0 | +| Web | 49 | 0 | 0 | 0 | diff --git a/docs/src/ui/widgets.md b/docs/src/ui/widgets.md index 2fb32eabbd..11b126f167 100644 --- a/docs/src/ui/widgets.md +++ b/docs/src/ui/widgets.md @@ -41,6 +41,8 @@ A clickable button. **Helpers:** `buttonSetTitle`, `buttonSetBordered`, `buttonSetImage` (SF Symbol name on macOS/iOS), `buttonSetImagePosition`, `buttonSetContentTintColor`, `buttonSetTextColor`, `widgetSetEnabled`. +On macOS, `buttonSetFontFamily(button, "Menlo")` changes the title font while +retaining its size and weight; it also works after `buttonSetTextColor`. ## TextField diff --git a/types/perry/ui/index.d.ts b/types/perry/ui/index.d.ts index 7e7187da70..b3bb037df2 100644 --- a/types/perry/ui/index.d.ts +++ b/types/perry/ui/index.d.ts @@ -873,6 +873,8 @@ export function textSetDecoration(widget: Widget, decoration: number): void; // --------------------------------------------------------------------------- export function buttonSetBordered(widget: Widget, bordered: number): void; +/** Change a macOS button title's font family while retaining its size and weight. */ +export function buttonSetFontFamily(widget: Widget, family: string): void; export function buttonSetTitle(widget: Widget, title: string): void; export function buttonSetTextColor(widget: Widget, r: number, g: number, b: number, a: number): void; export function buttonSetImage(widget: Widget, symbolName: string): void; From 59a10081873ddd49554bc023f1da407875d7b6a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Tue, 22 Sep 2026 15:04:20 +0200 Subject: [PATCH 2/2] chore: add button font family changelog --- changelog.d/10976-button-font-family.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/10976-button-font-family.md diff --git a/changelog.d/10976-button-font-family.md b/changelog.d/10976-button-font-family.md new file mode 100644 index 0000000000..4456f874cd --- /dev/null +++ b/changelog.d/10976-button-font-family.md @@ -0,0 +1 @@ +- Add `buttonSetFontFamily` for macOS buttons, preserving the current font size, weight, and attributed title color. The setter also applies CSS font families on Web.