feat: finalizations for M0-M2

This commit is contained in:
2026-04-05 07:41:33 +02:00
parent e46294a022
commit ee61ad56ea
48 changed files with 1296 additions and 498 deletions

View File

@@ -3,11 +3,11 @@ use iced::Task;
use crate::app::Message;
/// Pick a folder using the native file dialog.
pub fn pick_folder() -> Task<Message> {
pub fn pick_folder(title: String) -> Task<Message> {
Task::perform(
async {
async move {
rfd::AsyncFileDialog::new()
.set_title("Select Folder")
.set_title(&title)
.pick_folder()
.await
.map(|h| h.path().to_path_buf())
@@ -17,13 +17,13 @@ pub fn pick_folder() -> Task<Message> {
}
/// Pick one or more image/media files using the native file dialog.
pub fn pick_media_files() -> Task<Message> {
pub fn pick_media_files(title: String, filter_label: String) -> Task<Message> {
Task::perform(
async {
async move {
rfd::AsyncFileDialog::new()
.set_title("Import Media")
.set_title(&title)
.add_filter(
"Images",
&filter_label,
&["jpg", "jpeg", "png", "gif", "webp", "svg", "tiff", "bmp"],
)
.pick_files()

View File

@@ -1,6 +1,12 @@
use std::path::PathBuf;
use std::sync::mpsc;
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2::{define_class, msg_send, DefinedClass, MainThreadMarker, MainThreadOnly};
use objc2_app_kit::{NSApplication, NSApplicationDelegate};
use objc2_foundation::{NSArray, NSNotification, NSObject, NSObjectProtocol, NSString, NSURL};
use crate::app::Message;
/// Events that arrive from macOS lifecycle hooks (openFile, openURLs).
@@ -10,11 +16,75 @@ pub enum LifecycleEvent {
UrlOpen(String),
}
/// Create the macOS lifecycle channel (sender goes to objc hooks, receiver polled by subscription).
/// Ivars for the delegate — holds the sender side of the lifecycle channel.
#[derive(Debug)]
pub struct DelegateIvars {
tx: mpsc::Sender<LifecycleEvent>,
}
define_class!(
// SAFETY: NSObject has no subclassing requirements. BdsAppDelegate does not impl Drop.
#[unsafe(super(NSObject))]
#[thread_kind = MainThreadOnly]
#[name = "BdsAppDelegate"]
#[ivars = DelegateIvars]
pub struct BdsAppDelegate;
unsafe impl NSObjectProtocol for BdsAppDelegate {}
unsafe impl NSApplicationDelegate for BdsAppDelegate {
#[unsafe(method(applicationDidFinishLaunching:))]
fn did_finish_launching(&self, _notification: &NSNotification) {
// App is already running via Iced; nothing to do here.
}
#[unsafe(method(application:openFile:))]
fn application_open_file(&self, _sender: &NSApplication, filename: &NSString) -> bool {
let path = PathBuf::from(filename.to_string());
let _ = self.ivars().tx.send(LifecycleEvent::FileOpen(path));
true
}
#[unsafe(method(application:openURLs:))]
fn application_open_urls(&self, _sender: &NSApplication, urls: &NSArray<NSURL>) {
for i in 0..urls.len() {
if let Some(url) = urls.objectAtIndex(i).absoluteString() {
let _ = self.ivars().tx.send(LifecycleEvent::UrlOpen(url.to_string()));
}
}
}
}
);
impl BdsAppDelegate {
fn new(mtm: MainThreadMarker, tx: mpsc::Sender<LifecycleEvent>) -> Retained<Self> {
let this = Self::alloc(mtm);
let this = this.set_ivars(DelegateIvars { tx });
unsafe { msg_send![super(this), init] }
}
}
/// Create the macOS lifecycle channel and wire the native delegate.
///
/// Returns `(Retained<BdsAppDelegate>, Receiver)`. The delegate must be kept alive
/// for the lifetime of the application (drop it and the callbacks stop).
pub fn lifecycle_channel() -> (mpsc::Sender<LifecycleEvent>, mpsc::Receiver<LifecycleEvent>) {
mpsc::channel()
}
/// Install the native Objective-C delegate on NSApplication, wiring it to the given sender.
///
/// Must be called from the main thread after the Iced application is running.
/// Returns the retained delegate (caller must keep it alive).
pub fn install_delegate(tx: mpsc::Sender<LifecycleEvent>) -> Option<Retained<BdsAppDelegate>> {
let mtm = MainThreadMarker::new()?;
let delegate = BdsAppDelegate::new(mtm, tx);
let app = NSApplication::sharedApplication(mtm);
let object = ProtocolObject::from_ref(&*delegate);
app.setDelegate(Some(object));
Some(delegate)
}
/// Poll the macOS lifecycle receiver for the next event and map to a Message.
pub fn poll_lifecycle(
receiver: &mpsc::Receiver<LifecycleEvent>,