1#![allow(unused_imports, unused_doc_comments, clippy::tabs_in_doc_comments)]
2use log::debug;
3use crate::{void, String};
4
5use objc2::{
6 rc::{Retained, Allocated},
7 runtime::ProtocolObject,
8 define_class,
9 msg_send,
10 DefinedClass,
11 MainThreadOnly,
12 Message,
13 ClassType,
14 sel
15};
16
17use objc2_app_kit::{
18 NSApplication, NSApplicationActivationPolicy, NSApplicationDelegate,
19 NSBackingStoreType, NSColor, NSFont, NSTextAlignment, NSTextField, NSWindow, NSWindowDelegate,
20 NSWindowStyleMask, NSView, NSWindowTitleVisibility, NSVisualEffectBlendingMode,
21 NSVisualEffectView, NSVisualEffectMaterial, NSVisualEffectState, NSAutoresizingMaskOptions,
22 NSMenu, NSMenuItem
23};
24
25use objc2_foundation::{
26 MainThreadMarker, NSNotification, NSObject, NSObjectProtocol, NSPoint, NSRect,
27 NSSize, NSString,
28};
29
30use crate::{DecorationMode, Decoration, WResponse, Color, ThemeDefault, NativeDecoration};
31
32#[derive(PartialEq, Debug, Clone)]
34pub struct Wrapper {
35 pub ns_view: *mut void, pub rect: *const void, }
38
39impl NativeDecoration for Decoration
40{
41 fn new(title: String, width: f64, height: f64, theme: ThemeDefault) -> Result<Self, WResponse>
43 {
44 let Some(mtm) = MainThreadMarker::new() else { return Err(WResponse::UnexpectedError) };
45
46 let origin = NSPoint::new(10.0, -2.3);
47 let size = NSSize::new(width, height);
48 let rect = NSRect::new(origin, size);
49
50 let window = unsafe { NSWindow::initWithContentRect_styleMask_backing_defer(
51 NSWindow::alloc(mtm),
52 rect,
53 NSWindowStyleMask::Titled
54 | NSWindowStyleMask::Closable
55 | NSWindowStyleMask::Miniaturizable
56 | NSWindowStyleMask::Resizable
57 | NSWindowStyleMask::FullSizeContentView,
58 NSBackingStoreType::Buffered,
59 false,
60 )};
61
62 window.setTitle(&NSString::from_str(title.as_str()));
63
64 if !theme.has_title {
65 window.setTitlebarAppearsTransparent(true);
66 window.setTitleVisibility(NSWindowTitleVisibility(1));
67 }
68
69 let (r, g, b, a) = theme.background_color.to_default();
71 window.setBackgroundColor(
72 Some(&NSColor::colorWithSRGBRed_green_blue_alpha(r, g, b, a)
73 ));
74
75 window.makeKeyAndOrderFront(None);
76 unsafe { window.setReleasedWhenClosed(false) };
77
78 let Some(view) = window.contentView() else { return Err(WResponse::UnexpectedError) };
79
80 window.center();
81 window.setContentMinSize(NSSize::new(width, height));
82
83 let Some(delegate) =
84 Delegate::new(window.clone()) else { return Err(WResponse::UnexpectedError) };
85
86 window.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
87 window.makeKeyAndOrderFront(None);
88
89 let app = NSApplication::sharedApplication(mtm);
90 let _ = app.setActivationPolicy(NSApplicationActivationPolicy::Regular);
91 app.activate();
92
93 let backend = Wrapper {
94 ns_view: void::to_handle(Retained::<NSView>::as_ptr(&view).cast_mut()),
95 rect: void::to_handle(&rect),
96 };
97
98 debug!("Creating NativeDecoration object");
99
100 Ok(Decoration {
101 mode: DecorationMode::ServerSide,
102 frame: void::to_handle(Retained::<NSWindow>::as_ptr(&window).cast_mut()),
103 backend,
104 })
105 }
106
107 fn apply_blur(&mut self) -> Result<(), WResponse>
109 {
110 let Some(mtm) = MainThreadMarker::new() else { return Err(WResponse::UnexpectedError) };
111
112 let backend = self.backend.clone();
113 let rect: NSRect = void::from_handle(backend.rect);
114 let alloc: Allocated<NSVisualEffectView> = NSVisualEffectView::alloc(mtm);
120 let blur_view_ptr = NSVisualEffectView::initWithFrame(alloc, rect);
121 let window: &NSWindow = void::from_handle(self.frame);
122
123 let Some(content) = window.contentView() else {
124 log::warn!("couldn't set blur");
125 return Err(WResponse::UnexpectedError);
126 };
127
128 let blur_view = blur_view_ptr.retain();
129 content.addSubview(&blur_view);
130
131 blur_view.setBlendingMode(NSVisualEffectBlendingMode(0));
132 blur_view.setMaterial(NSVisualEffectMaterial::HUDWindow);
133 blur_view.setState(NSVisualEffectState::Active);
134 blur_view.setFrame(content.bounds());
135 blur_view.setTranslatesAutoresizingMaskIntoConstraints(false);
136 blur_view.setAutoresizingMask(
137 NSAutoresizingMaskOptions::ViewWidthSizable
138 | NSAutoresizingMaskOptions::ViewHeightSizable
139 );
140
141 debug!("applying blur on NativeDecoration");
142 Ok(())
143 }
144
145 fn create_app_menu(&self, app_name: String) -> Result<(), WResponse>
147 {
148 let Some(mtm) = MainThreadMarker::new() else { return Err(WResponse::UnexpectedError) };
149 let app = NSApplication::sharedApplication(mtm);
150
151 let item_menu = NSMenuItem::alloc(mtm);
152 let quit_item = unsafe { NSMenuItem::initWithTitle_action_keyEquivalent(
153 item_menu,
154 &NSString::from_str(dirty::format!("Quit {}", app_name).as_str()),
155 Some(sel!(terminate:)),
156 &NSString::from_str("q")
157 ) };
158 unsafe { quit_item.setTarget(Some(&app)) };
159
160 let app_menu = NSMenu::new(mtm);
161 app_menu.addItem(&quit_item);
162
163 let app_menu_item = NSMenuItem::new(mtm);
164 let menubar = NSMenu::new(mtm);
165 menubar.addItem(&app_menu_item);
166
167 app_menu_item.setSubmenu(Some(&app_menu));
168 app.setMainMenu(Some(&menubar));
169
170 debug!("creating app menu");
171 Ok(())
172 }
173
174 fn run(&self)
176 {
177 use objc2::{class, runtime::AnyObject};
178 let raw: *mut NSApplication = unsafe {
179 msg_send![class!(NSApplication), sharedApplication]
180 };
181
182 unsafe { let _: *mut AnyObject = msg_send![raw, retain]; }
183 let Some(app) = (unsafe { Retained::from_raw(raw) }) else {
184 log::error!("no NSApplication found");
185 return
186 };
187
188 unsafe { msg_send![&*app, run] }
189 }
190
191 #[inline]
192 fn exit(&self) -> Result<(), WResponse>
193 {
194 use objc2::{class, runtime::AnyObject};
195 let raw: *mut NSApplication = unsafe {
196 msg_send![class!(NSApplication), sharedApplication]
197 };
198
199 unsafe { let _: *mut AnyObject = msg_send![raw, retain]; }
200 let Some(app) = (unsafe { Retained::from_raw(raw) }) else {
201 return Err(WResponse::UnexpectedError);
202 };
203
204 app.terminate(None);
205 Ok(())
206 }
207
208 }
213
214#[derive(Debug)]
215#[allow(dead_code)]
216struct AppDelegateIvars {
217 window: Retained<NSWindow>,
218}
219
220define_class!(
221 #[unsafe(super = NSObject)]
222 #[thread_kind = MainThreadOnly]
223 #[ivars = AppDelegateIvars]
224 struct Delegate;
225
226 unsafe impl NSObjectProtocol for Delegate {}
227
228 unsafe impl NSApplicationDelegate for Delegate {
229 #[unsafe(method(applicationDidFinishLaunching:))]
230 fn did_finish_launching(&self, _notification: &NSNotification) {}
231 }
232
233 unsafe impl NSWindowDelegate for Delegate {
234 #[unsafe(method(windowWillClose:))]
235 fn window_will_close(&self, _notification: &NSNotification)
236 { NSApplication::sharedApplication(self.mtm()).terminate(None); }
237 }
238);
239
240impl Delegate {
241 fn new(window: Retained<NSWindow>) -> Option<Retained<Self>>
242 {
243 let mtm = MainThreadMarker::new()?;
244 let this = Self::alloc(mtm).set_ivars(AppDelegateIvars { window });
245 Some(unsafe { msg_send![super(this), init] })
246 }
247}