From cc021c98d7181a67b148b9ce001f9411260accd5 Mon Sep 17 00:00:00 2001 From: clover caruso Date: Tue, 29 Sep 2026 04:24:02 -0700 Subject: [PATCH] feat(snow-leopard): textured window with the toolbar in the title gradient On 10.6 the window is textured with a top content border as tall as the toolbar row, so AppKit's unified gradient runs through it. The GL backend presents premultiplied sRGB for the transparent surface, empty toolbar space drags the window through the 10.6 shim, and the bottom corners are cut at 10.6's radius. Assisted-by: claude-opus-5.5 --- crates/snowbound/src/aqua.rs | 20 +++++----------- crates/snowbound/src/linux.rs | 3 --- crates/snowbound/src/macos.rs | 4 ++-- crates/snowbound/src/main.rs | 1 - platform/snow-leopard/rt/appkit.c | 38 ++++++++++++++++++++++++++++--- 5 files changed, 43 insertions(+), 23 deletions(-) diff --git a/crates/snowbound/src/aqua.rs b/crates/snowbound/src/aqua.rs index 5f475e2829308318102b94322eff2981da57fb98..4ad03391dfcbd105b25bfda25d62ba74fd2ac6ab 100644 --- a/crates/snowbound/src/aqua.rs +++ b/crates/snowbound/src/aqua.rs @@ -12,7 +12,7 @@ use objc2::{ sel, }; use objc2_foundation::{MainThreadMarker, NSPoint, NSRect, NSSize}; -use std::{cell::Cell, collections::HashMap}; +use std::collections::HashMap; use ui::{Axis, PaintedScroller, Scroller, ScrollerPart}; use winit::{ raw_window_handle::{HasWindowHandle, RawWindowHandle}, @@ -57,19 +57,11 @@ pub fn textured(window: &Window, row: f32) -> bool { true } -thread_local! { - static DRAGS: Cell = const { Cell::new(false) }; -} - -/// Whether a press where the pointer is drags the window, which a textured window asks -/// its content of: presses on empty toolbar space do, and nowhere else. -pub fn set_window_drags(drags: bool) { - DRAGS.set(drags); -} - -/// `-mouseDownCanMoveWindow` for the content view of a textured window. -pub extern "C" fn window_drags(_: &AnyObject, _: Sel) -> Bool { - Bool::new(DRAGS.get()) +/// `-mouseDownCanMoveWindow` for the content view: a textured window drags from any press +/// on a view that allows it, asking once. The app drags from its toolbar's empty space +/// itself, through `-performWindowDragWithEvent:`. +pub extern "C" fn no_window_drags(_: &AnyObject, _: Sel) -> Bool { + Bool::NO } /// The cursor for moving something: winit loads its move cursor from a folder of cursors diff --git a/crates/snowbound/src/linux.rs b/crates/snowbound/src/linux.rs index 6c5861024be22516003aeeb68c6cab8890768291..8870b50becd4f298ea228c184b4a190a75231c27 100644 --- a/crates/snowbound/src/linux.rs +++ b/crates/snowbound/src/linux.rs @@ -579,9 +579,6 @@ pub fn move_cursor() -> winit::window::CursorIcon { winit::window::CursorIcon::Move } -/// Window drags are the app's own. -pub fn set_window_drags(_: bool) {} - /// Scrollbars overlay the content. pub fn scrollers() -> Option { None diff --git a/crates/snowbound/src/macos.rs b/crates/snowbound/src/macos.rs index 49256adcf8793f0adba8a36afa4120fb8fa1e819..1858d5e573b063d325b4219bfbb761225be71977 100644 --- a/crates/snowbound/src/macos.rs +++ b/crates/snowbound/src/macos.rs @@ -29,7 +29,7 @@ use winit::{ window::{Window, WindowAttributes}, }; -pub use crate::aqua::{move_cursor, resize_grip, scrollers, set_window_drags}; +pub use crate::aqua::{move_cursor, resize_grip, scrollers}; static QUIT: OnceLock> = OnceLock::new(); static INPUT_CLASS: OnceLock<&'static AnyClass> = OnceLock::new(); @@ -533,7 +533,7 @@ pub fn install_text_input(window: &Window) { if crate::aqua::before_lion() { class.add_method( sel!(mouseDownCanMoveWindow), - crate::aqua::window_drags as extern "C" fn(_, _) -> _, + crate::aqua::no_window_drags as extern "C" fn(_, _) -> _, ); } class.register() diff --git a/crates/snowbound/src/main.rs b/crates/snowbound/src/main.rs index 2e59deec87ebae9d394676ce4d13591df3f83f01..885971bbd4c69485bab4e505f88227c9e733611b 100644 --- a/crates/snowbound/src/main.rs +++ b/crates/snowbound/src/main.rs @@ -3072,7 +3072,6 @@ impl State { fn input(&mut self, event: ui::Event) { if let ui::Event::PointerMoved(point) = event { self.pointer = point; - platform::set_window_drags(self.ui.box_at(point) == Some(strip())); if std::mem::take(&mut self.strip_held) { let _ = self.window.drag_window(); } diff --git a/platform/snow-leopard/rt/appkit.c b/platform/snow-leopard/rt/appkit.c index b47e88a1c18c259d0442fbfa30dd3030097ca20b..4b7275f83d59006bf1be2c80210a03fa5b6a29f3 100644 --- a/platform/snow-leopard/rt/appkit.c +++ b/platform/snow-leopard/rt/appkit.c @@ -1,7 +1,8 @@ // AppKit that winit, AccessKit and the app call and 10.6 lacks, added at load time with // the neutral answer: scale 1, no precise scrolling, no appearances, no tabbing, no full -// screen. `remote.sh selectors` lists what a binary names that 10.6 lacks. Linked from -// the archive by the appearance name winit imports, so only AppKit binaries carry it. +// screen; window drags are done here. `remote.sh selectors` lists what a binary names +// that 10.6 lacks. Linked from the archive by the appearance name winit imports, so only +// AppKit binaries carry it. // class_addMethod adds nothing where the system already has the method. #include #include @@ -53,6 +54,37 @@ static CGPoint point_from_screen(id self, SEL _cmd, CGPoint point) { return CGPointMake(point.x - frame.origin.x, point.y - frame.origin.y); } +// 10.11: the window follows the pointer until the button comes up, as a press on its +// title does. +static void window_drag(id self, SEL _cmd, id event) { + typedef CGPoint (*SendPoint)(id, SEL); + typedef CGRect (*SendRect)(id, SEL); + id event_class = (id)objc_getClass("NSEvent"); + CGPoint start = ((SendPoint)objc_msgSend)(event_class, sel_registerName("mouseLocation")); + CGRect frame = ((SendRect)objc_msgSend_stret)(self, sel_registerName("frame")); + id app = SEND(objc_getClass("NSApplication"), "sharedApplication"); + id forever = SEND(objc_getClass("NSDate"), "distantFuture"); + for (;;) { + // NSLeftMouseDraggedMask | NSLeftMouseUpMask. + id next = ((id (*)(id, SEL, unsigned long long, id, CFStringRef, BOOL))objc_msgSend)( + app, sel_registerName("nextEventMatchingMask:untilDate:inMode:dequeue:"), + 1 << 6 | 1 << 2, forever, CFSTR("NSEventTrackingRunLoopMode"), YES); + // NSLeftMouseUp. + if (((unsigned long (*)(id, SEL))objc_msgSend)(next, sel_registerName("type")) == 2) { + // The button's release stays in the queue for whoever asked for the drag. + ((void (*)(id, SEL, id, BOOL))objc_msgSend)(app, sel_registerName("postEvent:atStart:"), next, YES); + return; + } + CGPoint now = ((SendPoint)objc_msgSend)(event_class, sel_registerName("mouseLocation")); + CGRect moved = frame; + moved.origin.x += now.x - start.x; + moved.origin.y += now.y - start.y; + moved = ((CGRect (*)(id, SEL, CGRect, id))objc_msgSend_stret)( + self, sel_registerName("constrainFrameRect:toScreen:"), moved, SEND(self, "screen")); + ((void (*)(id, SEL, CGPoint))objc_msgSend)(self, sel_registerName("setFrameOrigin:"), moved.origin); + } +} + static id srgb_color(id self, SEL _cmd, CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha) { CGFloat components[] = {red, green, blue, alpha}; return ((id (*)(id, SEL, id, CGFloat *, long))objc_msgSend)( @@ -106,7 +138,7 @@ __attribute__((constructor)) static void polyfill(void) { add("NSWindow", 0, "backingScaleFactor", one, "d@:"); add("NSWindow", 0, "convertRectToScreen:", rect_to_screen, RECT "@:" RECT); add("NSWindow", 0, "convertPointFromScreen:", point_from_screen, "{CGPoint=dd}@:{CGPoint=dd}"); - add("NSWindow", 0, "performWindowDragWithEvent:", ignore, "v@:@"); + add("NSWindow", 0, "performWindowDragWithEvent:", window_drag, "v@:@"); add("NSWindow", 0, "toggleFullScreen:", ignore, "v@:@"); add("NSWindow", 0, "setTabbingMode:", ignore, "v@:q"); add("NSWindow", 0, "setTabbingIdentifier:", ignore, "v@:@"); -- 2.54.0