diff --git a/druid-shell/Cargo.toml b/druid-shell/Cargo.toml index 79a795b60d..9584a3e1cb 100755 --- a/druid-shell/Cargo.toml +++ b/druid-shell/Cargo.toml @@ -41,7 +41,7 @@ serde = ["kurbo/serde"] [dependencies] # NOTE: When changing the piet or kurbo versions, ensure that # the kurbo version included in piet is compatible with the kurbo version specified here. -piet-common = "=0.5.0" +piet-common = { git = "https://github.com/linebender/piet", rev = "57cc49a8a88703b1c754a8c2c7f6f96007a24d4d" } kurbo = "0.8.2" tracing = "0.1.22" @@ -67,7 +67,7 @@ features = ["d2d1_1", "dwrite", "winbase", "libloaderapi", "errhandlingapi", "wi "d3d11", "dwmapi", "wincon", "fileapi", "processenv", "winbase", "handleapi", "shellapi", "winnls"] -[target.'cfg(target_os="macos")'.dependencies] +[target.'cfg(any(target_os="macos", target_os="ios"))'.dependencies] block = "0.1.6" cocoa = "0.24.0" objc = "0.2.7" @@ -96,7 +96,7 @@ version = "0.3.44" features = ["Window", "MouseEvent", "CssStyleDeclaration", "WheelEvent", "KeyEvent", "KeyboardEvent", "Navigator"] [dev-dependencies] -piet-common = { version = "=0.5.0", features = ["png"] } +piet-common = { git = "https://github.com/linebender/piet", rev = "57cc49a8a88703b1c754a8c2c7f6f96007a24d4d", features = ["png"] } static_assertions = "1.1.0" test-log = { version = "0.2.5", features = ["trace"], default-features = false } tracing-subscriber = { version = "0.3.2", features = ["env-filter"] } diff --git a/druid-shell/src/backend/ios/application.rs b/druid-shell/src/backend/ios/application.rs new file mode 100644 index 0000000000..8767f37297 --- /dev/null +++ b/druid-shell/src/backend/ios/application.rs @@ -0,0 +1,42 @@ +// Copyright 2022 The Druid Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! iOS implementation of features at the application scope. + +use crate::application::AppHandler; + +use super::clipboard::Clipboard; +use super::error::Error; + +#[derive(Clone)] +pub(crate) struct Application; + +impl Application { + pub fn new() -> Result { + Ok(Application) + } + + pub fn run(self, _handler: Option>) {} + + pub fn quit(&self) {} + + pub fn clipboard(&self) -> Clipboard { + Clipboard + } + + pub fn get_locale() -> String { + tracing::warn!("unimplemented"); + "".to_string() + } +} diff --git a/druid-shell/src/backend/ios/clipboard.rs b/druid-shell/src/backend/ios/clipboard.rs new file mode 100644 index 0000000000..edef010a76 --- /dev/null +++ b/druid-shell/src/backend/ios/clipboard.rs @@ -0,0 +1,60 @@ +// Copyright 2022 The Druid Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Interactions with the system pasteboard on iOS. + +use crate::clipboard::{ClipboardFormat, FormatId}; + +/// The browser clipboard. +#[derive(Debug, Clone, Default)] +pub struct Clipboard; + +impl Clipboard { + /// Put a string onto the system clipboard. + pub fn put_string(&mut self, _s: impl AsRef) { + tracing::warn!("unimplemented"); + } + + /// Put multi-format data on the system clipboard. + pub fn put_formats(&mut self, _formats: &[ClipboardFormat]) { + tracing::warn!("unimplemented"); + } + + /// Get a string from the system clipboard, if one is available. + pub fn get_string(&self) -> Option { + tracing::warn!("unimplemented"); + None + } + + /// Given a list of supported clipboard types, returns the supported type which has + /// highest priority on the system clipboard, or `None` if no types are supported. + pub fn preferred_format(&self, _formats: &[FormatId]) -> Option { + tracing::warn!("unimplemented"); + None + } + + /// Return data in a given format, if available. + /// + /// It is recommended that the `fmt` argument be a format returned by + /// [`Clipboard::preferred_format`] + pub fn get_format(&self, _format: FormatId) -> Option> { + tracing::warn!("unimplemented"); + None + } + + pub fn available_type_names(&self) -> Vec { + tracing::warn!("unimplemented"); + Vec::new() + } +} diff --git a/druid-shell/src/backend/ios/error.rs b/druid-shell/src/backend/ios/error.rs new file mode 100644 index 0000000000..537c201d12 --- /dev/null +++ b/druid-shell/src/backend/ios/error.rs @@ -0,0 +1,26 @@ +// Copyright 2022 The Druid Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! iOS backend errors. + +#[derive(Debug, Clone)] +pub struct Error; + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "iOS Platform Error") + } +} + +impl std::error::Error for Error {} diff --git a/druid-shell/src/backend/ios/menu.rs b/druid-shell/src/backend/ios/menu.rs new file mode 100644 index 0000000000..2feefec06c --- /dev/null +++ b/druid-shell/src/backend/ios/menu.rs @@ -0,0 +1,15 @@ +// Copyright 2022 The Druid Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub use crate::backend::shared::noop_menu::*; diff --git a/druid-shell/src/backend/ios/mod.rs b/druid-shell/src/backend/ios/mod.rs new file mode 100644 index 0000000000..f942592a91 --- /dev/null +++ b/druid-shell/src/backend/ios/mod.rs @@ -0,0 +1,24 @@ +// Copyright 2022 The Druid Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! macOS druid-shell backend. + +#![allow(clippy::let_unit_value)] + +pub mod application; +pub mod clipboard; +pub mod error; +pub mod menu; +pub mod screen; +pub mod window; diff --git a/druid-shell/src/backend/ios/screen.rs b/druid-shell/src/backend/ios/screen.rs new file mode 100644 index 0000000000..6b40cedb8e --- /dev/null +++ b/druid-shell/src/backend/ios/screen.rs @@ -0,0 +1,26 @@ +// Copyright 2022 The Druid Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! macOS Monitors and Screen information. + +use crate::kurbo::Rect; +use crate::screen::Monitor; +use cocoa::base::id; +use cocoa::foundation::NSArray; +use objc::{class, msg_send, sel, sel_impl}; + +pub(crate) fn get_monitors() -> Vec { + tracing::warn!("unimplemented"); + vec![] +} diff --git a/druid-shell/src/backend/ios/window.rs b/druid-shell/src/backend/ios/window.rs new file mode 100644 index 0000000000..0f89116233 --- /dev/null +++ b/druid-shell/src/backend/ios/window.rs @@ -0,0 +1,1474 @@ +// Copyright 2022 The Druid Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! macOS implementation of window creation. + +#![allow(non_snake_case)] + +use std::ffi::c_void; +use std::mem; +use std::sync::{Arc, Mutex, Weak}; +use std::time::Instant; + +use block::ConcreteBlock; +use cocoa::base::{id, nil, BOOL, NO, YES}; +use cocoa::foundation::{NSArray, NSAutoreleasePool, NSInteger, NSString, NSUInteger}; +use core_graphics::context::CGContextRef; +use foreign_types::ForeignTypeRef; +use lazy_static::lazy_static; +use objc::declare::ClassDecl; +use objc::rc::WeakPtr; +use objc::runtime::{Class, Object, Protocol, Sel}; +use objc::{class, msg_send, sel, sel_impl}; +use tracing::{debug, error, info}; + +#[cfg(feature = "raw-win-handle")] +use raw_window_handle::{macos::MacOSHandle, HasRawWindowHandle, RawWindowHandle}; + +use crate::kurbo::{Insets, Point, Rect, Size, Vec2}; +use crate::piet::{Piet, PietText, RenderContext}; + +use super::application::Application; +use super::menu::Menu; +use crate::common_util::IdleCallback; +use crate::dialog::{FileDialogOptions, FileDialogType}; +use crate::keyboard_types::KeyState; +use crate::mouse::{Cursor, CursorDesc, MouseButton, MouseButtons, MouseEvent}; +use crate::region::Region; +use crate::scale::Scale; +use crate::text::{Event, InputHandler}; +use crate::window::{ + FileDialogToken, IdleToken, TextFieldToken, TimerToken, WinHandler, WindowLevel, WindowState, +}; +use crate::Error; + +// #[allow(non_upper_case_globals)] +// const NSWindowDidBecomeKeyNotification: &str = "NSWindowDidBecomeKeyNotification"; + +// #[allow(dead_code)] +// #[allow(non_upper_case_globals)] +// mod levels { +// use crate::window::WindowLevel; + +// // These are the levels that AppKit seems to have. +// pub const NSModalPanelLevel: i32 = 24; +// pub const NSNormalWindowLevel: i32 = 0; +// pub const NSFloatingWindowLevel: i32 = 3; +// pub const NSTornOffMenuWindowLevel: i32 = NSFloatingWindowLevel; +// pub const NSSubmenuWindowLevel: i32 = NSFloatingWindowLevel; +// pub const NSModalPanelWindowLevel: i32 = 8; +// pub const NSStatusWindowLevel: i32 = 25; +// pub const NSPopUpMenuWindowLevel: i32 = 101; +// pub const NSScreenSaverWindowLevel: i32 = 1000; + +// pub fn as_raw_window_level(window_level: WindowLevel) -> i32 { +// use WindowLevel::*; +// match window_level { +// AppWindow => NSNormalWindowLevel, +// Tooltip(_) => NSFloatingWindowLevel, +// DropDown(_) => NSFloatingWindowLevel, +// Modal(_) => NSModalPanelWindowLevel, +// } +// } +// } + +#[derive(Clone)] +pub(crate) struct WindowHandle { + // TODO rlord: should this be a uiwindow? uiwindowscene? + uiview: WeakPtr, + idle_queue: Weak>>, +} +impl PartialEq for WindowHandle { + fn eq(&self, other: &Self) -> bool { + match (self.idle_queue.upgrade(), other.idle_queue.upgrade()) { + (None, None) => true, + (Some(s), Some(o)) => std::sync::Arc::ptr_eq(&s, &o), + (_, _) => false, + } + } +} +impl Eq for WindowHandle {} + +impl std::fmt::Debug for WindowHandle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + f.write_str("WindowHandle{\n")?; + f.write_str("}")?; + Ok(()) + } +} + +impl Default for WindowHandle { + fn default() -> Self { + WindowHandle { + uiview: unsafe { WeakPtr::new(nil) }, + idle_queue: Default::default(), + } + } +} + +/// Builder abstraction for creating new windows. +pub(crate) struct WindowBuilder { + handler: Option>, + title: String, + menu: Option, + size: Size, + min_size: Option, + position: Option, + level: Option, + window_state: Option, + resizable: bool, + show_titlebar: bool, + transparent: bool, +} + +#[derive(Clone)] +pub(crate) struct IdleHandle { + nsview: WeakPtr, + idle_queue: Weak>>, +} + +#[derive(Debug)] +enum DeferredOp { + // SetSize(Size), +// SetPosition(Point), +} + +/// This represents different Idle Callback Mechanism +enum IdleKind { + Callback(Box), + Token(IdleToken), + // DeferredOp(DeferredOp), +} + +/// This is the state associated with our custom UIView. +struct ViewState { + uiview: WeakPtr, + handler: Box, + idle_queue: Arc>>, + /// Tracks window focusing left clicks + // focus_click: bool, + // Tracks whether we have already received the mouseExited event + // mouse_left: bool, + // keyboard_state: KeyboardState, + text: PietText, + // active_text_input: Option, + // parent: Option, +} + +#[derive(Clone, PartialEq)] +// TODO: support custom cursors on iPadOS +pub struct CustomCursor; + +impl WindowBuilder { + pub fn new(_app: Application) -> WindowBuilder { + WindowBuilder { + handler: None, + title: String::new(), + menu: None, + size: Size::new(500., 400.), + min_size: None, + position: None, + level: None, + window_state: None, + resizable: true, + show_titlebar: true, + transparent: false, + } + } + + pub fn set_handler(&mut self, handler: Box) { + self.handler = Some(handler); + } + + pub fn set_size(&mut self, size: Size) { + self.size = size; + } + + pub fn set_min_size(&mut self, size: Size) { + self.min_size = Some(size); + } + + pub fn resizable(&mut self, resizable: bool) { + self.resizable = resizable; + } + + pub fn show_titlebar(&mut self, show_titlebar: bool) { + self.show_titlebar = show_titlebar; + } + + pub fn set_transparent(&mut self, transparent: bool) { + self.transparent = transparent; + } + + pub fn set_level(&mut self, level: WindowLevel) { + self.level = Some(level); + } + + pub fn set_position(&mut self, position: Point) { + self.position = Some(position) + } + + pub fn set_window_state(&mut self, state: WindowState) { + self.window_state = Some(state); + } + + pub fn set_title(&mut self, title: impl Into) { + self.title = title.into(); + } + + pub fn set_menu(&mut self, menu: Menu) { + self.menu = Some(menu); + } + + pub fn build(self) -> Result { + // assert_main_thread(); + unsafe { + // let mut style_mask = NSWindowStyleMask::NSClosableWindowMask + // | NSWindowStyleMask::NSMiniaturizableWindowMask; + + // if self.show_titlebar { + // style_mask |= NSWindowStyleMask::NSTitledWindowMask; + // } + + // if self.resizable { + // style_mask |= NSWindowStyleMask::NSResizableWindowMask; + // } + + // let screen_height = crate::Screen::get_display_rect().height(); + // let position = self.position.unwrap_or_else(|| Point::new(20., 20.)); + // let origin = NSPoint::new(position.x, screen_height - position.y - self.size.height); // Flip back + + // let rect = NSRect::new(origin, NSSize::new(self.size.width, self.size.height)); + + // let window: id = msg_send![WINDOW_CLASS.0, alloc]; + // let window = window.initWithContentRect_styleMask_backing_defer_( + // rect, + // style_mask, + // NSBackingStoreBuffered, + // NO, + // ); + + // if let Some(min_size) = self.min_size { + // let size = NSSize::new(min_size.width, min_size.height); + // window.setContentMinSize_(size); + // } + + // if self.transparent { + // window.setOpaque_(NO); + // window.setBackgroundColor_(NSColor::clearColor(nil)); + // } + + // window.setTitle_(make_nsstring(&self.title)); + + // let (view, idle_queue) = make_view(self.handler.expect("view")); + // let content_view = window.contentView(); + // let frame = NSView::frame(content_view); + // view.initWithFrame_(frame); + + // let () = msg_send![window, setDelegate: view]; + + // if let Some(menu) = self.menu { + // NSApp().setMainMenu_(menu.menu); + // } + + // content_view.addSubview_(view); + // let view_state: *mut c_void = *(*view).get_ivar("viewState"); + // let view_state = &mut *(view_state as *mut ViewState); + // let mut handle = WindowHandle { + // nsview: view_state.nsview.clone(), + // idle_queue, + // }; + + // if let Some(window_state) = self.window_state { + // handle.set_window_state(window_state); + // } + + // if let Some(level) = self.level { + // match &level { + // WindowLevel::Tooltip(parent) => (*view_state).parent = Some(parent.clone()), + // WindowLevel::DropDown(parent) => (*view_state).parent = Some(parent.clone()), + // WindowLevel::Modal(parent) => (*view_state).parent = Some(parent.clone()), + // _ => {} + // } + // handle.set_level(level); + // } + + // // set_window_state above could have invalidated the frame size + // let frame = NSView::frame(content_view); + + // (*view_state).handler.connect(&handle.clone().into()); + // (*view_state).handler.scale(Scale::default()); + // (*view_state) + // .handler + // .size(Size::new(frame.size.width, frame.size.height)); + + // Ok(handle) + } + unimplemented!() + } +} + +// Wrap pointer because lazy_static requires Sync. +// struct ViewClass(*const Class); +// unsafe impl Sync for ViewClass {} + +// lazy_static! { +// static ref VIEW_CLASS: ViewClass = unsafe { +// let mut decl = ClassDecl::new("DruidView", class!(NSView)).expect("View class defined"); +// decl.add_ivar::<*mut c_void>("viewState"); + +// decl.add_method( +// sel!(isFlipped), +// isFlipped as extern "C" fn(&Object, Sel) -> BOOL, +// ); +// extern "C" fn isFlipped(_this: &Object, _sel: Sel) -> BOOL { +// YES +// } +// decl.add_method( +// sel!(acceptsFirstResponder), +// acceptsFirstResponder as extern "C" fn(&Object, Sel) -> BOOL, +// ); +// extern "C" fn acceptsFirstResponder(_this: &Object, _sel: Sel) -> BOOL { +// YES +// } +// // acceptsFirstMouse is called when a left mouse click would focus the window +// decl.add_method( +// sel!(acceptsFirstMouse:), +// acceptsFirstMouse as extern "C" fn(&Object, Sel, id) -> BOOL, +// ); +// extern "C" fn acceptsFirstMouse(this: &Object, _sel: Sel, _nsevent: id) -> BOOL { +// unsafe { +// let view_state: *mut c_void = *this.get_ivar("viewState"); +// let view_state = &mut *(view_state as *mut ViewState); +// view_state.focus_click = true; +// } +// YES +// } +// decl.add_method(sel!(dealloc), dealloc as extern "C" fn(&Object, Sel)); +// extern "C" fn dealloc(this: &Object, _sel: Sel) { +// info!("view is dealloc'ed"); +// unsafe { +// let view_state: *mut c_void = *this.get_ivar("viewState"); +// Box::from_raw(view_state as *mut ViewState); +// } +// } + +// decl.add_method( +// sel!(windowDidBecomeKey:), +// window_did_become_key as extern "C" fn(&mut Object, Sel, id), +// ); +// decl.add_method( +// sel!(windowDidResignKey:), +// window_did_resign_key as extern "C" fn(&mut Object, Sel, id), +// ); +// decl.add_method( +// sel!(setFrameSize:), +// set_frame_size as extern "C" fn(&mut Object, Sel, NSSize), +// ); +// decl.add_method( +// sel!(mouseDown:), +// mouse_down_left as extern "C" fn(&mut Object, Sel, id), +// ); +// decl.add_method( +// sel!(rightMouseDown:), +// mouse_down_right as extern "C" fn(&mut Object, Sel, id), +// ); +// decl.add_method( +// sel!(otherMouseDown:), +// mouse_down_other as extern "C" fn(&mut Object, Sel, id), +// ); +// decl.add_method( +// sel!(mouseUp:), +// mouse_up_left as extern "C" fn(&mut Object, Sel, id), +// ); +// decl.add_method( +// sel!(rightMouseUp:), +// mouse_up_right as extern "C" fn(&mut Object, Sel, id), +// ); +// decl.add_method( +// sel!(otherMouseUp:), +// mouse_up_other as extern "C" fn(&mut Object, Sel, id), +// ); +// decl.add_method( +// sel!(mouseMoved:), +// mouse_move as extern "C" fn(&mut Object, Sel, id), +// ); +// decl.add_method( +// sel!(mouseDragged:), +// mouse_move as extern "C" fn(&mut Object, Sel, id), +// ); +// decl.add_method( +// sel!(otherMouseDragged:), +// mouse_move as extern "C" fn(&mut Object, Sel, id), +// ); +// decl.add_method( +// sel!(mouseEntered:), +// mouse_enter as extern "C" fn(&mut Object, Sel, id), +// ); +// decl.add_method( +// sel!(mouseExited:), +// mouse_leave as extern "C" fn(&mut Object, Sel, id), +// ); +// decl.add_method( +// sel!(scrollWheel:), +// scroll_wheel as extern "C" fn(&mut Object, Sel, id), +// ); +// decl.add_method( +// sel!(magnifyWithEvent:), +// pinch_event as extern "C" fn(&mut Object, Sel, id), +// ); +// decl.add_method( +// sel!(keyDown:), +// key_down as extern "C" fn(&mut Object, Sel, id), +// ); +// decl.add_method(sel!(keyUp:), key_up as extern "C" fn(&mut Object, Sel, id)); +// decl.add_method( +// sel!(flagsChanged:), +// mods_changed as extern "C" fn(&mut Object, Sel, id), +// ); +// decl.add_method( +// sel!(drawRect:), +// draw_rect as extern "C" fn(&mut Object, Sel, NSRect), +// ); +// decl.add_method(sel!(runIdle), run_idle as extern "C" fn(&mut Object, Sel)); +// decl.add_method(sel!(viewWillDraw), view_will_draw as extern "C" fn(&mut Object, Sel)); +// decl.add_method(sel!(redraw), redraw as extern "C" fn(&mut Object, Sel)); +// decl.add_method( +// sel!(handleTimer:), +// handle_timer as extern "C" fn(&mut Object, Sel, id), +// ); +// decl.add_method( +// sel!(handleMenuItem:), +// handle_menu_item as extern "C" fn(&mut Object, Sel, id), +// ); +// decl.add_method( +// sel!(showContextMenu:), +// show_context_menu as extern "C" fn(&mut Object, Sel, id), +// ); +// decl.add_method( +// sel!(windowShouldClose:), +// window_should_close as extern "C" fn(&mut Object, Sel, id)->BOOL, +// ); +// decl.add_method( +// sel!(windowWillClose:), +// window_will_close as extern "C" fn(&mut Object, Sel, id), +// ); + +// // methods for NSTextInputClient +// decl.add_method(sel!(hasMarkedText), super::text_input::has_marked_text as extern fn(&mut Object, Sel) -> BOOL); +// decl.add_method( +// sel!(markedRange), +// super::text_input::marked_range as extern fn(&mut Object, Sel) -> NSRange, +// ); +// decl.add_method(sel!(selectedRange), super::text_input::selected_range as extern fn(&mut Object, Sel) -> NSRange); +// decl.add_method( +// sel!(setMarkedText:selectedRange:replacementRange:), +// super::text_input::set_marked_text as extern fn(&mut Object, Sel, id, NSRange, NSRange), +// ); +// decl.add_method(sel!(unmarkText), super::text_input::unmark_text as extern fn(&mut Object, Sel)); +// decl.add_method( +// sel!(validAttributesForMarkedText), +// super::text_input::valid_attributes_for_marked_text as extern fn(&mut Object, Sel) -> id, +// ); +// decl.add_method( +// sel!(attributedSubstringForProposedRange:actualRange:), +// super::text_input::attributed_substring_for_proposed_range +// as extern fn(&mut Object, Sel, NSRange, *mut c_void) -> id, +// ); +// decl.add_method( +// sel!(insertText:replacementRange:), +// super::text_input::insert_text as extern fn(&mut Object, Sel, id, NSRange), +// ); +// decl.add_method( +// sel!(characterIndexForPoint:), +// super::text_input::character_index_for_point as extern fn(&mut Object, Sel, NSPoint) -> NSUInteger, +// ); +// decl.add_method( +// sel!(firstRectForCharacterRange:actualRange:), +// super::text_input::first_rect_for_character_range +// as extern fn(&mut Object, Sel, NSRange, *mut c_void) -> NSRect, +// ); +// decl.add_method( +// sel!(doCommandBySelector:), +// super::text_input::do_command_by_selector as extern fn(&mut Object, Sel, Sel), +// ); + +// let protocol = Protocol::get("NSTextInputClient").unwrap(); +// decl.add_protocol(protocol); + +// ViewClass(decl.register()) +// }; +// } + +// /// Acquires a lock to an `InputHandler`, passes it to a closure, and releases the lock. +// pub(super) fn with_edit_lock_from_window( +// this: &mut Object, +// mutable: bool, +// f: impl FnOnce(Box) -> R, +// ) -> Option { +// let view_state = unsafe { +// let view_state: *mut c_void = *this.get_ivar("viewState"); +// let view_state = &mut *(view_state as *mut ViewState); +// &mut (*view_state) +// }; +// let input_token = view_state.active_text_input?; +// let handler = view_state.handler.acquire_input_lock(input_token, mutable); +// let r = f(handler); +// view_state.handler.release_input_lock(input_token); +// Some(r) +// } + +// fn make_view(handler: Box) -> (id, Weak>>) { +// let idle_queue = Arc::new(Mutex::new(Vec::new())); +// let queue_handle = Arc::downgrade(&idle_queue); +// unsafe { +// let view: id = msg_send![VIEW_CLASS.0, new]; +// let nsview = WeakPtr::new(view); +// let keyboard_state = KeyboardState::new(); +// let state = ViewState { +// nsview, +// handler, +// idle_queue, +// focus_click: false, +// mouse_left: true, +// keyboard_state, +// text: PietText::new_with_unique_state(), +// active_text_input: None, +// parent: None, +// }; +// let state_ptr = Box::into_raw(Box::new(state)); +// (*view).set_ivar("viewState", state_ptr as *mut c_void); +// let options: NSAutoresizingMaskOptions = NSViewWidthSizable | NSViewHeightSizable; +// view.setAutoresizingMask_(options); + +// // The rect of the tracking area doesn't matter, because +// // we use the InVisibleRect option where the OS syncs the size automatically. +// let rect = NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.)); +// let opts = NSTrackingAreaOptions::MouseEnteredAndExited +// | NSTrackingAreaOptions::MouseMoved +// | NSTrackingAreaOptions::ActiveAlways +// | NSTrackingAreaOptions::InVisibleRect; +// let tracking_area = NSTrackingArea::alloc(nil) +// .initWithRect_options_owner_userInfo(rect, opts, view, nil) +// .autorelease(); +// view.addTrackingArea(tracking_area); + +// (view.autorelease(), queue_handle) +// } +// } + +// struct WindowClass(*const Class); +// unsafe impl Sync for WindowClass {} + +// lazy_static! { +// static ref WINDOW_CLASS: WindowClass = unsafe { +// let mut decl = +// ClassDecl::new("DruidWindow", class!(NSWindow)).expect("Window class defined"); +// decl.add_method( +// sel!(canBecomeKeyWindow), +// canBecomeKeyWindow as extern "C" fn(&Object, Sel) -> BOOL, +// ); +// extern "C" fn canBecomeKeyWindow(_this: &Object, _sel: Sel) -> BOOL { +// YES +// } +// WindowClass(decl.register()) +// }; +// } + +// extern "C" fn set_frame_size(this: &mut Object, _: Sel, size: NSSize) { +// unsafe { +// let view_state: *mut c_void = *this.get_ivar("viewState"); +// let view_state = &mut *(view_state as *mut ViewState); +// (*view_state) +// .handler +// .size(Size::new(size.width, size.height)); +// let superclass = msg_send![this, superclass]; +// let () = msg_send![super(this, superclass), setFrameSize: size]; +// } +// } + +// fn mouse_event( +// nsevent: id, +// view: id, +// count: u8, +// focus: bool, +// button: MouseButton, +// wheel_delta: Vec2, +// ) -> MouseEvent { +// unsafe { +// let point = nsevent.locationInWindow(); +// let view_point = view.convertPoint_fromView_(point, nil); +// let pos = Point::new(view_point.x as f64, view_point.y as f64); +// let buttons = get_mouse_buttons(NSEvent::pressedMouseButtons(nsevent)); +// let modifiers = make_modifiers(nsevent.modifierFlags()); +// MouseEvent { +// pos, +// buttons, +// mods: modifiers, +// count, +// focus, +// button, +// wheel_delta, +// } +// } +// } + +// fn get_mouse_button(button: NSInteger) -> Option { +// match button { +// 0 => Some(MouseButton::Left), +// 1 => Some(MouseButton::Right), +// 2 => Some(MouseButton::Middle), +// 3 => Some(MouseButton::X1), +// 4 => Some(MouseButton::X2), +// _ => None, +// } +// } + +// fn get_mouse_buttons(mask: NSUInteger) -> MouseButtons { +// let mut buttons = MouseButtons::new(); +// if mask & 1 != 0 { +// buttons.insert(MouseButton::Left); +// } +// if mask & 1 << 1 != 0 { +// buttons.insert(MouseButton::Right); +// } +// if mask & 1 << 2 != 0 { +// buttons.insert(MouseButton::Middle); +// } +// if mask & 1 << 3 != 0 { +// buttons.insert(MouseButton::X1); +// } +// if mask & 1 << 4 != 0 { +// buttons.insert(MouseButton::X2); +// } +// buttons +// } + +// extern "C" fn mouse_down_left(this: &mut Object, _: Sel, nsevent: id) { +// mouse_down(this, nsevent, MouseButton::Left); +// } + +// extern "C" fn mouse_down_right(this: &mut Object, _: Sel, nsevent: id) { +// mouse_down(this, nsevent, MouseButton::Right); +// } + +// extern "C" fn mouse_down_other(this: &mut Object, _: Sel, nsevent: id) { +// unsafe { +// if let Some(button) = get_mouse_button(nsevent.buttonNumber()) { +// mouse_down(this, nsevent, button); +// } +// } +// } + +// fn mouse_down(this: &mut Object, nsevent: id, button: MouseButton) { +// unsafe { +// let view_state: *mut c_void = *this.get_ivar("viewState"); +// let view_state = &mut *(view_state as *mut ViewState); +// let count = nsevent.clickCount() as u8; +// let focus = view_state.focus_click && button == MouseButton::Left; +// let event = mouse_event(nsevent, this as id, count, focus, button, Vec2::ZERO); +// (*view_state).handler.mouse_down(&event); +// } +// } + +// extern "C" fn mouse_up_left(this: &mut Object, _: Sel, nsevent: id) { +// mouse_up(this, nsevent, MouseButton::Left); +// } + +// extern "C" fn mouse_up_right(this: &mut Object, _: Sel, nsevent: id) { +// mouse_up(this, nsevent, MouseButton::Right); +// } + +// extern "C" fn mouse_up_other(this: &mut Object, _: Sel, nsevent: id) { +// unsafe { +// if let Some(button) = get_mouse_button(nsevent.buttonNumber()) { +// mouse_up(this, nsevent, button); +// } +// } +// } + +// fn mouse_up(this: &mut Object, nsevent: id, button: MouseButton) { +// unsafe { +// let view_state: *mut c_void = *this.get_ivar("viewState"); +// let view_state = &mut *(view_state as *mut ViewState); +// let focus = if view_state.focus_click && button == MouseButton::Left { +// view_state.focus_click = false; +// true +// } else { +// false +// }; +// let event = mouse_event(nsevent, this as id, 0, focus, button, Vec2::ZERO); +// (*view_state).handler.mouse_up(&event); +// // If we have already received a mouseExited event then that means +// // we're still receiving mouse events because some buttons are being held down. +// // When the last held button is released and we haven't received a mouseEntered event, +// // then we will no longer receive mouse events until the next mouseEntered event +// // and need to inform the handler of the mouse leaving. +// if view_state.mouse_left && event.buttons.is_empty() { +// (*view_state).handler.mouse_leave(); +// } +// } +// } + +// extern "C" fn mouse_move(this: &mut Object, _: Sel, nsevent: id) { +// unsafe { +// let view_state: *mut c_void = *this.get_ivar("viewState"); +// let view_state = &mut *(view_state as *mut ViewState); +// let event = mouse_event(nsevent, this as id, 0, false, MouseButton::None, Vec2::ZERO); +// (*view_state).handler.mouse_move(&event); +// } +// } + +// extern "C" fn mouse_enter(this: &mut Object, _sel: Sel, nsevent: id) { +// unsafe { +// let view_state: *mut c_void = *this.get_ivar("viewState"); +// let view_state = &mut *(view_state as *mut ViewState); +// view_state.mouse_left = false; +// let event = mouse_event(nsevent, this, 0, false, MouseButton::None, Vec2::ZERO); +// (*view_state).handler.mouse_move(&event); +// } +// } + +// extern "C" fn mouse_leave(this: &mut Object, _: Sel, _nsevent: id) { +// unsafe { +// let view_state: *mut c_void = *this.get_ivar("viewState"); +// let view_state = &mut *(view_state as *mut ViewState); +// view_state.mouse_left = true; +// (*view_state).handler.mouse_leave(); +// } +// } + +// extern "C" fn scroll_wheel(this: &mut Object, _: Sel, nsevent: id) { +// unsafe { +// let view_state: *mut c_void = *this.get_ivar("viewState"); +// let view_state = &mut *(view_state as *mut ViewState); +// let (dx, dy) = { +// let dx = -nsevent.scrollingDeltaX() as f64; +// let dy = -nsevent.scrollingDeltaY() as f64; +// if nsevent.hasPreciseScrollingDeltas() == cocoa::base::YES { +// (dx, dy) +// } else { +// (dx * 32.0, dy * 32.0) +// } +// }; + +// let event = mouse_event( +// nsevent, +// this as id, +// 0, +// false, +// MouseButton::None, +// Vec2::new(dx, dy), +// ); +// (*view_state).handler.wheel(&event); +// } +// } + +// extern "C" fn pinch_event(this: &mut Object, _: Sel, nsevent: id) { +// unsafe { +// let view_state: *mut c_void = *this.get_ivar("viewState"); +// let view_state = &mut *(view_state as *mut ViewState); + +// let delta: CGFloat = msg_send![nsevent, magnification]; +// (*view_state).handler.zoom(delta as f64); +// } +// } + +// extern "C" fn key_down(this: &mut Object, _: Sel, nsevent: id) { +// let view_state = unsafe { +// let view_state: *mut c_void = *this.get_ivar("viewState"); +// &mut *(view_state as *mut ViewState) +// }; +// if let Some(event) = (*view_state).keyboard_state.process_native_event(nsevent) { +// if !(*view_state).handler.key_down(event) { +// // key down not handled; foward to text input system +// unsafe { +// let events = NSArray::arrayWithObjects(nil, &[nsevent]); +// let _: () = msg_send![*(*view_state).nsview.load(), interpretKeyEvents: events]; +// } +// } +// } +// } + +// extern "C" fn key_up(this: &mut Object, _: Sel, nsevent: id) { +// let view_state = unsafe { +// let view_state: *mut c_void = *this.get_ivar("viewState"); +// &mut *(view_state as *mut ViewState) +// }; +// if let Some(event) = (*view_state).keyboard_state.process_native_event(nsevent) { +// (*view_state).handler.key_up(event); +// } +// } + +// extern "C" fn mods_changed(this: &mut Object, _: Sel, nsevent: id) { +// let view_state = unsafe { +// let view_state: *mut c_void = *this.get_ivar("viewState"); +// &mut *(view_state as *mut ViewState) +// }; +// if let Some(event) = (*view_state).keyboard_state.process_native_event(nsevent) { +// if event.state == KeyState::Down { +// (*view_state).handler.key_down(event); +// } else { +// (*view_state).handler.key_up(event); +// } +// } +// } + +// extern "C" fn view_will_draw(this: &mut Object, _: Sel) { +// unsafe { +// let view_state: *mut c_void = *this.get_ivar("viewState"); +// let view_state = &mut *(view_state as *mut ViewState); +// (*view_state).handler.prepare_paint(); +// } +// } + +// extern "C" fn draw_rect(this: &mut Object, _: Sel, dirtyRect: NSRect) { +// unsafe { +// let context: id = msg_send![class![NSGraphicsContext], currentContext]; +// //FIXME: when core_graphics is at 0.20, we should be able to use +// //core_graphics::sys::CGContextRef as our pointer type. +// let cgcontext_ptr: *mut ::CType = +// msg_send![context, CGContext]; +// let cgcontext_ref = CGContextRef::from_ptr_mut(cgcontext_ptr); + +// // FIXME: use the actual invalid region instead of just this bounding box. +// // https://developer.apple.com/documentation/appkit/nsview/1483772-getrectsbeingdrawn?language=objc +// let rect = Rect::from_origin_size( +// (dirtyRect.origin.x, dirtyRect.origin.y), +// (dirtyRect.size.width, dirtyRect.size.height), +// ); +// let invalid = Region::from(rect); + +// let view_state: *mut c_void = *this.get_ivar("viewState"); +// let view_state = &mut *(view_state as *mut ViewState); +// let mut piet_ctx = Piet::new_y_down(cgcontext_ref, Some(view_state.text.clone())); + +// (*view_state).handler.paint(&mut piet_ctx, &invalid); +// if let Err(e) = piet_ctx.finish() { +// error!("{}", e) +// } + +// let superclass = msg_send![this, superclass]; +// let () = msg_send![super(this, superclass), drawRect: dirtyRect]; +// } +// } + +// fn run_deferred(this: &mut Object, view_state: &mut ViewState, op: DeferredOp) { +// match op { +// DeferredOp::SetSize(size) => set_size_deferred(this, view_state, size), +// DeferredOp::SetPosition(pos) => set_position_deferred(this, view_state, pos), +// } +// } + +// fn set_size_deferred(this: &mut Object, _view_state: &mut ViewState, size: Size) { +// unsafe { +// let window: id = msg_send![this, window]; +// let current_frame: NSRect = msg_send![window, frame]; +// let mut new_frame = current_frame; + +// // maintain druid origin (as mac origin is bottom left) +// new_frame.origin.y -= size.height - current_frame.size.height; +// new_frame.size.width = size.width; +// new_frame.size.height = size.height; +// let () = msg_send![window, setFrame: new_frame display: YES]; +// } +// } + +// fn set_position_deferred(this: &mut Object, _view_state: &mut ViewState, position: Point) { +// unsafe { +// let window: id = msg_send![this, window]; +// let frame: NSRect = msg_send![window, frame]; + +// let mut new_frame = frame; +// new_frame.origin.x = position.x; +// // TODO Everywhere we use the height for flipping around y it should be the max y in orig mac coords. +// // Need to set up a 3 screen config to test in this arrangement. +// // 3 +// // 1 +// // 2 + +// let screen_height = crate::Screen::get_display_rect().height(); +// new_frame.origin.y = screen_height - position.y - frame.size.height; // Flip back +// let () = msg_send![window, setFrame: new_frame display: YES]; +// debug!("set_position_deferred {:?}", position); +// } +// } + +// extern "C" fn run_idle(this: &mut Object, _: Sel) { +// let view_state = unsafe { +// let view_state: *mut c_void = *this.get_ivar("viewState"); +// &mut *(view_state as *mut ViewState) +// }; +// let queue: Vec<_> = mem::take(&mut view_state.idle_queue.lock().expect("queue")); +// for item in queue { +// match item { +// IdleKind::Callback(it) => it.call(&mut *view_state.handler), +// IdleKind::Token(it) => { +// view_state.handler.as_mut().idle(it); +// } +// IdleKind::DeferredOp(op) => run_deferred(this, view_state, op), +// } +// } +// } + +// extern "C" fn redraw(this: &mut Object, _: Sel) { +// unsafe { +// let () = msg_send![this as *const _, setNeedsDisplay: YES]; +// } +// } + +// extern "C" fn handle_timer(this: &mut Object, _: Sel, timer: id) { +// let view_state = unsafe { +// let view_state: *mut c_void = *this.get_ivar("viewState"); +// &mut *(view_state as *mut ViewState) +// }; +// let token = unsafe { +// let user_info: id = msg_send![timer, userInfo]; +// msg_send![user_info, unsignedIntValue] +// }; + +// (*view_state).handler.timer(TimerToken::from_raw(token)); +// } + +// extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) { +// unsafe { +// let tag: isize = msg_send![item, tag]; +// let view_state: *mut c_void = *this.get_ivar("viewState"); +// let view_state = &mut *(view_state as *mut ViewState); +// (*view_state).handler.command(tag as u32); +// } +// } + +// extern "C" fn show_context_menu(this: &mut Object, _: Sel, item: id) { +// unsafe { +// let window: id = msg_send![this as *const _, window]; +// let mut location: NSPoint = msg_send![window, mouseLocationOutsideOfEventStream]; +// let bounds: NSRect = msg_send![this as *const _, bounds]; +// location.y = bounds.size.height - location.y; +// let _: BOOL = msg_send![item, popUpMenuPositioningItem: nil atLocation: location inView: this as *const _]; +// } +// } + +// extern "C" fn window_did_become_key(this: &mut Object, _: Sel, _notification: id) { +// unsafe { +// let view_state: *mut c_void = *this.get_ivar("viewState"); +// let view_state = &mut *(view_state as *mut ViewState); +// (*view_state).handler.got_focus(); +// } +// } + +// extern "C" fn window_did_resign_key(this: &mut Object, _: Sel, _notification: id) { +// unsafe { +// let view_state: *mut c_void = *this.get_ivar("viewState"); +// let view_state = &mut *(view_state as *mut ViewState); +// (*view_state).handler.lost_focus(); +// } +// } + +// extern "C" fn window_should_close(this: &mut Object, _: Sel, _window: id) -> BOOL { +// unsafe { +// let view_state: *mut c_void = *this.get_ivar("viewState"); +// let view_state = &mut *(view_state as *mut ViewState); +// (*view_state).handler.request_close(); +// NO +// } +// } + +// extern "C" fn window_will_close(this: &mut Object, _: Sel, _notification: id) { +// unsafe { +// let view_state: *mut c_void = *this.get_ivar("viewState"); +// let view_state = &mut *(view_state as *mut ViewState); +// (*view_state).handler.destroy(); +// } +// } + +impl WindowHandle { + pub fn show(&self) { + unimplemented!() + // unsafe { + // let window: id = msg_send![*self.nsview.load(), window]; + // // register our view class to be alerted when it becomes the key view. + // let notif_center_class = class!(NSNotificationCenter); + // let notif_string = NSString::alloc(nil) + // .init_str(NSWindowDidBecomeKeyNotification) + // .autorelease(); + // let notif_center: id = msg_send![notif_center_class, defaultCenter]; + // let () = msg_send![notif_center, addObserver:*self.nsview.load() selector: sel!(windowDidBecomeKey:) name: notif_string object: window]; + // window.makeKeyAndOrderFront_(nil) + // } + } + + /// Close the window. + pub fn close(&self) { + unimplemented!() + // unsafe { + // let window: id = msg_send![*self.nsview.load(), window]; + // let () = msg_send![window, performSelectorOnMainThread: sel!(close) withObject: nil waitUntilDone: NO]; + // } + } + + /// Bring this window to the front of the window stack and give it focus. + pub fn bring_to_front_and_focus(&self) { + unimplemented!() + // unsafe { + // let window: id = msg_send![*self.nsview.load(), window]; + // let () = msg_send![window, performSelectorOnMainThread: sel!(makeKeyAndOrderFront:) withObject: nil waitUntilDone: NO]; + // } + } + + /// Bring this window to the front of the window stack and give it focus. + pub fn request_anim_frame(&self) { + unimplemented!() + // unsafe { + // // TODO: synchronize with screen refresh rate using CVDisplayLink instead. + // let () = msg_send![*self.nsview.load(), performSelectorOnMainThread: sel!(redraw) + // withObject: nil waitUntilDone: NO]; + // } + } + + // // Request invalidation of the entire window contents. + pub fn invalidate(&self) { + unimplemented!() + // unsafe { + // // We could share impl with redraw, but we'd need to deal with nil. + // let () = msg_send![*self.nsview.load(), setNeedsDisplay: YES]; + // } + } + + // /// Request invalidation of one rectangle. + pub fn invalidate_rect(&self, rect: Rect) { + unimplemented!() + // let rect = NSRect::new( + // NSPoint::new(rect.x0, rect.y0), + // NSSize::new(rect.width(), rect.height()), + // ); + // unsafe { + // // We could share impl with redraw, but we'd need to deal with nil. + // let () = msg_send![*self.nsview.load(), setNeedsDisplayInRect: rect]; + // } + } + + pub fn set_cursor(&mut self, cursor: &Cursor) { + unimplemented!() + // unsafe { + // let nscursor = class!(NSCursor); + // #[allow(deprecated)] + // let cursor: id = match cursor { + // Cursor::Arrow => msg_send![nscursor, arrowCursor], + // Cursor::IBeam => msg_send![nscursor, IBeamCursor], + // Cursor::Pointer => msg_send![nscursor, pointingHandCursor], + // Cursor::Crosshair => msg_send![nscursor, crosshairCursor], + // Cursor::OpenHand => msg_send![nscursor, openHandCursor], + // Cursor::NotAllowed => msg_send![nscursor, operationNotAllowedCursor], + // Cursor::ResizeLeftRight => msg_send![nscursor, resizeLeftRightCursor], + // Cursor::ResizeUpDown => msg_send![nscursor, resizeUpDownCursor], + // // TODO: support custom cursors + // Cursor::Custom(_) => msg_send![nscursor, arrowCursor], + // }; + // let () = msg_send![cursor, set]; + // } + } + + pub fn make_cursor(&self, _cursor_desc: &CursorDesc) -> Option { + // we should probably support this though?? + tracing::warn!("Custom cursors are not yet supported in the iOS backend"); + None + } + + pub fn request_timer(&self, deadline: std::time::Instant) -> TimerToken { + unimplemented!() + // let ti = time_interval_from_deadline(deadline); + // let token = TimerToken::next(); + // unsafe { + // let nstimer = class!(NSTimer); + // let nsnumber = class!(NSNumber); + // let user_info: id = msg_send![nsnumber, numberWithUnsignedInteger: token.into_raw()]; + // let selector = sel!(handleTimer:); + // let view = self.nsview.load(); + // let timer: id = msg_send![nstimer, timerWithTimeInterval: ti target: view selector: selector userInfo: user_info repeats: NO]; + // let runloop: id = msg_send![class!(NSRunLoop), currentRunLoop]; + // let () = msg_send![runloop, addTimer: timer forMode: NSRunLoopCommonModes]; + // } + // token + } + + pub fn text(&self) -> PietText { + unimplemented!() + // let view = self.nsview.load(); + // unsafe { + // if let Some(view) = (*view).as_ref() { + // let state: *mut c_void = *view.get_ivar("viewState"); + // (*(state as *mut ViewState)).text.clone() + // } else { + // // this codepath should only happen during tests in druid, when view is nil + // PietText::new_with_unique_state() + // } + // } + } + + pub fn add_text_field(&self) -> TextFieldToken { + unimplemented!() + // TextFieldToken::next() + } + + pub fn remove_text_field(&self, token: TextFieldToken) { + unimplemented!() + // let view = self.nsview.load(); + // unsafe { + // if let Some(view) = (*view).as_ref() { + // let state: *mut c_void = *view.get_ivar("viewState"); + // let state = &mut (*(state as *mut ViewState)); + // if state.active_text_input == Some(token) { + // state.active_text_input = None; + // } + // } + // } + } + + pub fn set_focused_text_field(&self, active_field: Option) { + unimplemented!() + // unsafe { + // if let Some(view) = self.nsview.load().as_ref() { + // let state: *mut c_void = *view.get_ivar("viewState"); + // let state = &mut (*(state as *mut ViewState)); + + // if let Some(old_field) = state.active_text_input { + // self.update_text_field(old_field, Event::Reset); + // } + // state.active_text_input = active_field; + // if let Some(new_field) = active_field { + // self.update_text_field(new_field, Event::Reset); + // } + // } + // } + } + + pub fn update_text_field(&self, token: TextFieldToken, update: Event) { + unimplemented!() + // unsafe { + // if let Some(view) = self.nsview.load().as_ref() { + // let state: *mut c_void = *view.get_ivar("viewState"); + // let state = &mut (*(state as *mut ViewState)); + + // if state.active_text_input != Some(token) { + // return; + // } + // match update { + // Event::LayoutChanged => { + // let input_context: id = msg_send![*self.nsview.load(), inputContext]; + // let _: () = msg_send![input_context, invalidateCharacterCoordinates]; + // } + // Event::Reset | Event::SelectionChanged => { + // let input_context: id = msg_send![*self.nsview.load(), inputContext]; + // let _: () = msg_send![input_context, discardMarkedText]; + // let mut edit_lock = state.handler.acquire_input_lock(token, true); + // edit_lock.set_composition_range(None); + // state.handler.release_input_lock(token); + // } + // } + // } + // } + } + + pub fn open_file(&mut self, options: FileDialogOptions) -> Option { + unimplemented!() + // Some(self.open_save_impl(FileDialogType::Open, options)) + } + + pub fn save_as(&mut self, options: FileDialogOptions) -> Option { + unimplemented!() + // Some(self.open_save_impl(FileDialogType::Save, options)) + } + + // fn open_save_impl(&mut self, ty: FileDialogType, opts: FileDialogOptions) -> FileDialogToken { + // let token = FileDialogToken::next(); + // let self_clone = self.clone(); + // unsafe { + // let panel = dialog::build_panel(ty, opts.clone()); + // let block = ConcreteBlock::new(move |response: dialog::NSModalResponse| { + // let url = dialog::get_file_info(panel, opts.clone(), response); + // let view = self_clone.nsview.load(); + // if let Some(view) = (*view).as_ref() { + // let view_state: *mut c_void = *view.get_ivar("viewState"); + // let view_state = &mut *(view_state as *mut ViewState); + // if ty == FileDialogType::Open { + // (*view_state).handler.open_file(token, url); + // } else if ty == FileDialogType::Save { + // (*view_state).handler.save_as(token, url); + // } + // } + // }); + // let block = block.copy(); + // let () = msg_send![panel, beginWithCompletionHandler: block]; + // } + // token + // } + + /// Set the title for this menu. + pub fn set_title(&self, title: &str) { + unimplemented!() + // unsafe { + // let window: id = msg_send![*self.nsview.load(), window]; + // let title = make_nsstring(title); + // window.setTitle_(title); + // } + } + + // TODO: Implement this + pub fn show_titlebar(&self, _show_titlebar: bool) { + unimplemented!() + } + + // Need to translate mac y coords, as they start from bottom left + pub fn set_position(&self, mut position: Point) { + unimplemented!() + // unsafe { + // if let Some(view) = self.nsview.load().as_ref() { + // let state: *mut c_void = *view.get_ivar("viewState"); + // let state = &mut (*(state as *mut ViewState)); + // if let Some(parent_state) = &state.parent { + // let pos = (*parent_state).get_position(); + // position += (pos.x, pos.y) + // } + // } + // } + + // self.defer(DeferredOp::SetPosition(position)) + } + + pub fn get_position(&self) -> Point { + unimplemented!() + // unsafe { + // // TODO this should be the max y in orig mac coords + // let screen_height = crate::Screen::get_display_rect().height(); + + // let window: id = msg_send![*self.nsview.load(), window]; + // let current_frame: NSRect = msg_send![window, frame]; + + // let mut position = Point::new( + // current_frame.origin.x, + // screen_height - current_frame.origin.y - current_frame.size.height, + // ); + // if let Some(view) = self.nsview.load().as_ref() { + // let state: *mut c_void = *view.get_ivar("viewState"); + // let state = &mut (*(state as *mut ViewState)); + // if let Some(parent_state) = &state.parent { + // let pos = (*parent_state).get_position(); + // position -= (pos.x, pos.y) + // } + // } + // position + // } + } + + pub fn content_insets(&self) -> Insets { + unimplemented!() + // unsafe { + // let screen_height = crate::Screen::get_display_rect().height(); + + // let window: id = msg_send![*self.nsview.load(), window]; + // let clr: NSRect = msg_send![window, contentLayoutRect]; + + // let window_frame_r: NSRect = NSWindow::frame(window); + // let content_frame_r: NSRect = NSWindow::convertRectToScreen_(window, clr); + + // let window_frame_rk = Rect::from_origin_size( + // ( + // window_frame_r.origin.x, + // screen_height - window_frame_r.origin.y - window_frame_r.size.height, + // ), + // (window_frame_r.size.width, window_frame_r.size.height), + // ); + // let content_frame_rk = Rect::from_origin_size( + // ( + // content_frame_r.origin.x, + // screen_height - content_frame_r.origin.y - content_frame_r.size.height, + // ), + // (content_frame_r.size.width, content_frame_r.size.height), + // ); + // window_frame_rk - content_frame_rk + // } + } + + pub fn set_size(&self, size: Size) { + unimplemented!() + // self.defer(DeferredOp::SetSize(size)); + } + + pub fn get_size(&self) -> Size { + unimplemented!() + // unsafe { + // let window: id = msg_send![*self.nsview.load(), window]; + // let current_frame: NSRect = msg_send![window, frame]; + // Size::new(current_frame.size.width, current_frame.size.height) + // } + } + + pub fn get_window_state(&self) -> WindowState { + unimplemented!() + // unsafe { + // let window: id = msg_send![*self.nsview.load(), window]; + // let isMin: BOOL = msg_send![window, isMiniaturized]; + // if isMin != NO { + // return WindowState::Minimized; + // } + // let isZoomed: BOOL = msg_send![window, isZoomed]; + // if isZoomed != NO { + // return WindowState::Maximized; + // } + // } + // WindowState::Restored + } + + pub fn set_window_state(&mut self, state: WindowState) { + unimplemented!() + // let cur_state = self.get_window_state(); + // unsafe { + // let window: id = msg_send![*self.nsview.load(), window]; + // match (state, cur_state) { + // (s1, s2) if s1 == s2 => (), + // (WindowState::Minimized, _) => { + // let () = msg_send![window, performMiniaturize: self]; + // } + // (WindowState::Maximized, _) => { + // let () = msg_send![window, performZoom: self]; + // } + // (WindowState::Restored, WindowState::Maximized) => { + // let () = msg_send![window, performZoom: self]; + // } + // (WindowState::Restored, WindowState::Minimized) => { + // let () = msg_send![window, deminiaturize: self]; + // } + // (WindowState::Restored, WindowState::Restored) => {} // Can't be reached + // } + // } + } + + pub fn handle_titlebar(&self, _val: bool) { + unimplemented!() + // tracing::warn!("WindowHandle::handle_titlebar is currently unimplemented for Mac."); + } + + pub fn resizable(&self, resizable: bool) { + unimplemented!() + // unsafe { + // let window: id = msg_send![*self.nsview.load(), window]; + // let mut style_mask: NSWindowStyleMask = window.styleMask(); + + // if resizable { + // style_mask |= NSWindowStyleMask::NSResizableWindowMask; + // } else { + // style_mask &= !NSWindowStyleMask::NSResizableWindowMask; + // } + + // window.setStyleMask_(style_mask); + // } + } + + pub fn set_menu(&self, menu: Menu) { + unimplemented!() + // unsafe { + // NSApp().setMainMenu_(menu.menu); + // } + } + + //FIXME: we should be using the x, y values passed by the caller, but then + //we have to figure out some way to pass them along with this performSelector: + //call. This isn't super hard, I'm just not up for it right now. + pub fn show_context_menu(&self, menu: Menu, _pos: Point) { + unimplemented!() + // unsafe { + // let () = msg_send![*self.nsview.load(), performSelectorOnMainThread: sel!(showContextMenu:) withObject: menu.menu waitUntilDone: NO]; + // } + } + + fn defer(&self, op: DeferredOp) { + // if let Some(i) = self.get_idle_handle() { + // i.add_idle(IdleKind::DeferredOp(op)) + // } + } + + /// Get a handle that can be used to schedule an idle task. + pub fn get_idle_handle(&self) -> Option { + unimplemented!() + // if self.nsview.load().is_null() { + // None + // } else { + // Some(IdleHandle { + // nsview: self.nsview.clone(), + // idle_queue: self.idle_queue.clone(), + // }) + // } + } + + /// Get the `Scale` of the window. + pub fn get_scale(&self) -> Result { + unimplemented!() + // TODO: Get actual Scale + // Ok(Scale::new(1.0, 1.0)) + } +} + +unsafe impl Send for IdleHandle {} + +impl IdleHandle { + fn add_idle(&self, idle: IdleKind) { + if let Some(queue) = self.idle_queue.upgrade() { + let mut queue = queue.lock().expect("queue lock"); + if queue.is_empty() { + unsafe { + unimplemented!() + // let nsview = self.nsview.load(); + // // Note: the nsview might be nil here if the window has been dropped, but that's ok. + // let () = msg_send!(*nsview, performSelectorOnMainThread: sel!(runIdle) + // withObject: nil waitUntilDone: NO); + } + } + queue.push(idle); + } + } + + /// Add an idle handler, which is called (once) when the message loop + /// is empty. The idle handler will be run from the main UI thread, and + /// won't be scheduled if the associated view has been dropped. + /// + /// Note: the name "idle" suggests that it will be scheduled with a lower + /// priority than other UI events, but that's not necessarily the case. + pub fn add_idle_callback(&self, callback: F) + where + F: FnOnce(&mut dyn WinHandler) + Send + 'static, + { + self.add_idle(IdleKind::Callback(Box::new(callback))); + } + + pub fn add_idle_token(&self, token: IdleToken) { + self.add_idle(IdleKind::Token(token)); + } +} + +// /// Convert an `Instant` into an NSTimeInterval, i.e. a fractional number +// /// of seconds from now. +// /// +// /// This may lose some precision for multi-month durations. +// fn time_interval_from_deadline(deadline: std::time::Instant) -> f64 { +// let now = Instant::now(); +// if now >= deadline { +// 0.0 +// } else { +// let t = deadline - now; +// let secs = t.as_secs() as f64; +// let subsecs = f64::from(t.subsec_micros()) * 0.000_001; +// secs + subsecs +// } +// } diff --git a/druid-shell/src/backend/mac/mod.rs b/druid-shell/src/backend/mac/mod.rs index c84d879cf8..08df0d6406 100644 --- a/druid-shell/src/backend/mac/mod.rs +++ b/druid-shell/src/backend/mac/mod.rs @@ -25,5 +25,7 @@ mod keyboard; pub mod menu; pub mod screen; pub mod text_input; -pub mod util; pub mod window; +pub mod util { + pub(crate) use crate::backend::shared::cocoa_util::*; +} diff --git a/druid-shell/src/backend/mod.rs b/druid-shell/src/backend/mod.rs index f07150398d..a127c51b28 100644 --- a/druid-shell/src/backend/mod.rs +++ b/druid-shell/src/backend/mod.rs @@ -28,6 +28,13 @@ pub use mac::*; #[cfg(target_os = "macos")] pub(crate) mod shared; +#[cfg(target_os = "ios")] +mod ios; +#[cfg(target_os = "ios")] +pub use ios::*; +#[cfg(target_os = "ios")] +pub(crate) mod shared; + #[cfg(all(feature = "x11", any(target_os = "linux", target_os = "openbsd")))] mod x11; #[cfg(all(feature = "x11", any(target_os = "linux", target_os = "openbsd")))] @@ -46,3 +53,5 @@ pub(crate) mod shared; mod web; #[cfg(target_arch = "wasm32")] pub use web::*; +#[cfg(target_arch = "wasm32")] +pub(crate) mod shared; diff --git a/druid-shell/src/backend/mac/util.rs b/druid-shell/src/backend/shared/cocoa_util.rs similarity index 100% rename from druid-shell/src/backend/mac/util.rs rename to druid-shell/src/backend/shared/cocoa_util.rs diff --git a/druid-shell/src/backend/shared/mod.rs b/druid-shell/src/backend/shared/mod.rs index da805c19ab..b59f3dc62f 100644 --- a/druid-shell/src/backend/shared/mod.rs +++ b/druid-shell/src/backend/shared/mod.rs @@ -20,3 +20,10 @@ cfg_if::cfg_if! { pub use keyboard::*; } } +cfg_if::cfg_if! { + if #[cfg(any(target_os = "macos", target_os = "ios"))] { + pub(crate) mod cocoa_util; + } +} + +pub(crate) mod noop_menu; diff --git a/druid-shell/src/backend/shared/noop_menu.rs b/druid-shell/src/backend/shared/noop_menu.rs new file mode 100644 index 0000000000..87aaf823c1 --- /dev/null +++ b/druid-shell/src/backend/shared/noop_menu.rs @@ -0,0 +1,52 @@ +// Copyright 2020 The Druid Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Implementation of menu that does nothing, for platforms that either don't have a menu available, or +//! just don't have an implementation in druid-shell yet. + +#![allow(unused)] + +use crate::hotkey::HotKey; +pub struct Menu; + +impl Menu { + pub fn new() -> Menu { + tracing::warn!("Menu::new is currently unimplemented for this backend."); + Menu {} + } + + pub fn new_for_popup() -> Menu { + tracing::warn!("Menu::new_for_popup is currently unimplemented for this backend."); + Menu {} + } + + pub fn add_dropdown(&mut self, mut _menu: Menu, _text: &str, _enabled: bool) { + tracing::warn!("Menu::add_dropdown is currently unimplemented for this backend."); + } + + pub fn add_item( + &mut self, + _id: u32, + _text: &str, + _key: Option<&HotKey>, + _enabled: bool, + _selected: bool, + ) { + tracing::warn!("Menu::add_item is currently unimplemented for this backend."); + } + + pub fn add_separator(&mut self) { + tracing::warn!("Menu::add_separator is currently unimplemented for this backend."); + } +} diff --git a/druid-shell/src/backend/web/menu.rs b/druid-shell/src/backend/web/menu.rs index 4d37b85c6e..a9594cb88c 100644 --- a/druid-shell/src/backend/web/menu.rs +++ b/druid-shell/src/backend/web/menu.rs @@ -12,45 +12,4 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Safe wrapper for menus. - -use crate::hotkey::HotKey; - -/// A menu object, which can be either a top-level menubar or a -/// submenu. -pub struct Menu; - -impl Drop for Menu { - fn drop(&mut self) { - // TODO - } -} - -impl Menu { - pub fn new() -> Menu { - Menu - } - - pub fn new_for_popup() -> Menu { - Menu - } - - pub fn add_dropdown(&mut self, _menu: Menu, _text: &str, _enabled: bool) { - tracing::warn!("unimplemented"); - } - - pub fn add_item( - &mut self, - _id: u32, - _text: &str, - _key: Option<&HotKey>, - _enabled: bool, - _selected: bool, - ) { - tracing::warn!("unimplemented"); - } - - pub fn add_separator(&mut self) { - tracing::warn!("unimplemented"); - } -} +pub use crate::backend::shared::noop_menu::*; diff --git a/druid-shell/src/backend/x11/menu.rs b/druid-shell/src/backend/x11/menu.rs index 70e570cbb7..87b4e563a4 100644 --- a/druid-shell/src/backend/x11/menu.rs +++ b/druid-shell/src/backend/x11/menu.rs @@ -12,44 +12,5 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! X11 menus implementation. - -use crate::hotkey::HotKey; - -pub struct Menu; - -impl Menu { - pub fn new() -> Menu { - // TODO(x11/menus): implement Menu::new (currently a no-op) - tracing::warn!("Menu::new is currently unimplemented for X11 backend."); - Menu {} - } - - pub fn new_for_popup() -> Menu { - // TODO(x11/menus): implement Menu::new_for_popup (currently a no-op) - tracing::warn!("Menu::new_for_popup is currently unimplemented for X11 backend."); - Menu {} - } - - pub fn add_dropdown(&mut self, mut _menu: Menu, _text: &str, _enabled: bool) { - // TODO(x11/menus): implement Menu::add_dropdown (currently a no-op) - tracing::warn!("Menu::add_dropdown is currently unimplemented for X11 backend."); - } - - pub fn add_item( - &mut self, - _id: u32, - _text: &str, - _key: Option<&HotKey>, - _enabled: bool, - _selected: bool, - ) { - // TODO(x11/menus): implement Menu::add_item (currently a no-op) - tracing::warn!("Menu::add_item is currently unimplemented for X11 backend."); - } - - pub fn add_separator(&mut self) { - // TODO(x11/menus): implement Menu::add_separator (currently a no-op) - tracing::warn!("Menu::add_separator is currently unimplemented for X11 backend."); - } -} +// TODO(x11/menus): implement menus (currently a no-op) +pub use crate::backend::shared::noop_menu::*; diff --git a/druid/Cargo.toml b/druid/Cargo.toml index 3b0b7c276c..78d0a5d354 100644 --- a/druid/Cargo.toml +++ b/druid/Cargo.toml @@ -80,7 +80,7 @@ console_error_panic_hook = { version = "0.1.6" } float-cmp = { version = "0.8.0", features = ["std"], default-features = false } # tempfile 3.2.0 broke wasm; I assume it will be yanked (Jan 12, 2021) tempfile = "=3.1.0" -piet-common = { version = "=0.5.0", features = ["png"] } +piet-common = { git = "https://github.com/linebender/piet", rev = "57cc49a8a88703b1c754a8c2c7f6f96007a24d4d", features = ["png"] } pulldown-cmark = { version = "0.8", default-features = false } test-log = { version = "0.2.5", features = ["trace"], default-features = false } # test-env-log needs it