authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-09-29 04:24:02-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-09-29 04:24:02-07:00
logcc021c98d7181a67b148b9ce001f9411260accd5
tree184bd924f32ec7dc30c7c20e34890d90bfa62c16
parent2d9ae5806189ab5ae2c042699773045856976c65
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU

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

5 files changed, 43 insertions(+), 23 deletions(-)

crates/snowbound/src/aqua.rs+6-14
......@@ -12,7 +12,7 @@ use objc2::{
1212 sel,
1313};
1414use objc2_foundation::{MainThreadMarker, NSPoint, NSRect, NSSize};
15use std::{cell::Cell, collections::HashMap};
15use std::collections::HashMap;
1616use ui::{Axis, PaintedScroller, Scroller, ScrollerPart};
1717use winit::{
1818 raw_window_handle::{HasWindowHandle, RawWindowHandle},
......@@ -57,19 +57,11 @@ pub fn textured(window: &Window, row: f32) -> bool {
5757 true
5858}
5959
60thread_local! {
61 static DRAGS: Cell<bool> = const { Cell::new(false) };
62}
63
64/// Whether a press where the pointer is drags the window, which a textured window asks
65/// its content of: presses on empty toolbar space do, and nowhere else.
66pub fn set_window_drags(drags: bool) {
67 DRAGS.set(drags);
68}
69
70/// `-mouseDownCanMoveWindow` for the content view of a textured window.
71pub extern "C" fn window_drags(_: &AnyObject, _: Sel) -> Bool {
72 Bool::new(DRAGS.get())
60/// `-mouseDownCanMoveWindow` for the content view: a textured window drags from any press
61/// on a view that allows it, asking once. The app drags from its toolbar's empty space
62/// itself, through `-performWindowDragWithEvent:`.
63pub extern "C" fn no_window_drags(_: &AnyObject, _: Sel) -> Bool {
64 Bool::NO
7365}
7466
7567/// The cursor for moving something: winit loads its move cursor from a folder of cursors
crates/snowbound/src/linux.rs-3
......@@ -579,9 +579,6 @@ pub fn move_cursor() -> winit::window::CursorIcon {
579579 winit::window::CursorIcon::Move
580580}
581581
582/// Window drags are the app's own.
583pub fn set_window_drags(_: bool) {}
584
585582/// Scrollbars overlay the content.
586583pub fn scrollers() -> Option<ui::Scrollers> {
587584 None
crates/snowbound/src/macos.rs+2-2
......@@ -29,7 +29,7 @@ use winit::{
2929 window::{Window, WindowAttributes},
3030};
3131
32pub use crate::aqua::{move_cursor, resize_grip, scrollers, set_window_drags};
32pub use crate::aqua::{move_cursor, resize_grip, scrollers};
3333
3434static QUIT: OnceLock<EventLoopProxy<crate::UserEvent>> = OnceLock::new();
3535static INPUT_CLASS: OnceLock<&'static AnyClass> = OnceLock::new();
......@@ -533,7 +533,7 @@ pub fn install_text_input(window: &Window) {
533533 if crate::aqua::before_lion() {
534534 class.add_method(
535535 sel!(mouseDownCanMoveWindow),
536 crate::aqua::window_drags as extern "C" fn(_, _) -> _,
536 crate::aqua::no_window_drags as extern "C" fn(_, _) -> _,
537537 );
538538 }
539539 class.register()
crates/snowbound/src/main.rs-1
......@@ -3072,7 +3072,6 @@ impl State {
30723072 fn input(&mut self, event: ui::Event) {
30733073 if let ui::Event::PointerMoved(point) = event {
30743074 self.pointer = point;
3075 platform::set_window_drags(self.ui.box_at(point) == Some(strip()));
30763075 if std::mem::take(&mut self.strip_held) {
30773076 let _ = self.window.drag_window();
30783077 }
platform/snow-leopard/rt/appkit.c+35-3
......@@ -1,7 +1,8 @@
11// AppKit that winit, AccessKit and the app call and 10.6 lacks, added at load time with
22// the neutral answer: scale 1, no precise scrolling, no appearances, no tabbing, no full
3// screen. `remote.sh selectors` lists what a binary names that 10.6 lacks. Linked from
4// the archive by the appearance name winit imports, so only AppKit binaries carry it.
3// screen; window drags are done here. `remote.sh selectors` lists what a binary names
4// that 10.6 lacks. Linked from the archive by the appearance name winit imports, so only
5// AppKit binaries carry it.
56// class_addMethod adds nothing where the system already has the method.
67#include <ApplicationServices/ApplicationServices.h>
78#include <objc/message.h>
......@@ -53,6 +54,37 @@ static CGPoint point_from_screen(id self, SEL _cmd, CGPoint point) {
5354 return CGPointMake(point.x - frame.origin.x, point.y - frame.origin.y);
5455}
5556
57// 10.11: the window follows the pointer until the button comes up, as a press on its
58// title does.
59static void window_drag(id self, SEL _cmd, id event) {
60 typedef CGPoint (*SendPoint)(id, SEL);
61 typedef CGRect (*SendRect)(id, SEL);
62 id event_class = (id)objc_getClass("NSEvent");
63 CGPoint start = ((SendPoint)objc_msgSend)(event_class, sel_registerName("mouseLocation"));
64 CGRect frame = ((SendRect)objc_msgSend_stret)(self, sel_registerName("frame"));
65 id app = SEND(objc_getClass("NSApplication"), "sharedApplication");
66 id forever = SEND(objc_getClass("NSDate"), "distantFuture");
67 for (;;) {
68 // NSLeftMouseDraggedMask | NSLeftMouseUpMask.
69 id next = ((id (*)(id, SEL, unsigned long long, id, CFStringRef, BOOL))objc_msgSend)(
70 app, sel_registerName("nextEventMatchingMask:untilDate:inMode:dequeue:"),
71 1 << 6 | 1 << 2, forever, CFSTR("NSEventTrackingRunLoopMode"), YES);
72 // NSLeftMouseUp.
73 if (((unsigned long (*)(id, SEL))objc_msgSend)(next, sel_registerName("type")) == 2) {
74 // The button's release stays in the queue for whoever asked for the drag.
75 ((void (*)(id, SEL, id, BOOL))objc_msgSend)(app, sel_registerName("postEvent:atStart:"), next, YES);
76 return;
77 }
78 CGPoint now = ((SendPoint)objc_msgSend)(event_class, sel_registerName("mouseLocation"));
79 CGRect moved = frame;
80 moved.origin.x += now.x - start.x;
81 moved.origin.y += now.y - start.y;
82 moved = ((CGRect (*)(id, SEL, CGRect, id))objc_msgSend_stret)(
83 self, sel_registerName("constrainFrameRect:toScreen:"), moved, SEND(self, "screen"));
84 ((void (*)(id, SEL, CGPoint))objc_msgSend)(self, sel_registerName("setFrameOrigin:"), moved.origin);
85 }
86}
87
5688static id srgb_color(id self, SEL _cmd, CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha) {
5789 CGFloat components[] = {red, green, blue, alpha};
5890 return ((id (*)(id, SEL, id, CGFloat *, long))objc_msgSend)(
......@@ -106,7 +138,7 @@ __attribute__((constructor)) static void polyfill(void) {
106138 add("NSWindow", 0, "backingScaleFactor", one, "d@:");
107139 add("NSWindow", 0, "convertRectToScreen:", rect_to_screen, RECT "@:" RECT);
108140 add("NSWindow", 0, "convertPointFromScreen:", point_from_screen, "{CGPoint=dd}@:{CGPoint=dd}");
109 add("NSWindow", 0, "performWindowDragWithEvent:", ignore, "v@:@");
141 add("NSWindow", 0, "performWindowDragWithEvent:", window_drag, "v@:@");
110142 add("NSWindow", 0, "toggleFullScreen:", ignore, "v@:@");
111143 add("NSWindow", 0, "setTabbingMode:", ignore, "v@:q");
112144 add("NSWindow", 0, "setTabbingIdentifier:", ignore, "v@:@");