General cleanup, implementing an NSPasteboard wrapper, support for setting background colors on views

This commit is contained in:
Ryan McGrath 2020-03-06 19:35:18 -08:00
parent 0446227a8d
commit 6c12a8fa29
No known key found for this signature in database
GPG key ID: 811674B62B666830
18 changed files with 375 additions and 111 deletions

View file

@ -11,12 +11,12 @@ use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel};
use objc::{class, msg_send, sel, sel_impl};
use crate::constants::APP_PTR;
use crate::menu::Menu;
mod events;
use events::register_app_class;
static APP_PTR: &str = "rstAppPtr";
pub trait AppDelegate {
type Message: Send + Sync;

128
appkit/src/color.rs Normal file
View file

@ -0,0 +1,128 @@
//! Implements `Color`. Heavily based on the `Color` module in Servo's CSS parser, but tweaked
//! for (what I believe) is a friendlier API, and to separate out the parsing into a separate
//! module.
use cocoa::base::id;
use core_graphics::base::CGFloat;
use objc::{class, msg_send, sel, sel_impl};
/// A color with red, green, blue, and alpha components, in a byte each.
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct Color {
/// The red component.
pub red: u8,
/// The green component.
pub green: u8,
/// The blue component.
pub blue: u8,
/// The alpha component.
pub alpha: u8,
}
impl Default for Color {
fn default() -> Color {
Color { red: 0, green: 0, blue: 0, alpha: 0 }
}
}
impl Color {
/// Constructs a new Color value from float components. It expects the red,
/// green, blue and alpha channels in that order, and all values will be
/// clamped to the 0.0 ... 1.0 range.
#[inline]
pub fn new<T: Into<f32>>(red: T, green: T, blue: T, alpha: T) -> Self {
Self::from_u8s(
clamp_unit_f32(red.into()),
clamp_unit_f32(green.into()),
clamp_unit_f32(blue.into()),
clamp_unit_f32(alpha.into()),
)
}
/// Maps to NS/UIColor.
pub fn into_platform_specific_color(&self) -> id {
let red = self.red as CGFloat / 255.0;
let green = self.green as CGFloat / 255.0;
let blue = self.blue as CGFloat / 255.0;
let alpha = self.alpha as CGFloat / 255.0;
unsafe {
msg_send![class!(NSColor), colorWithRed:red green:green blue:blue alpha:alpha]
}
}
/// Returns a transparent color.
#[inline]
pub fn transparent() -> Self {
Self::new(0., 0., 0., 0.)
}
/// Same thing, but with `u8` values instead of floats in the 0 to 1 range.
#[inline]
pub fn from_u8s(red: u8, green: u8, blue: u8, alpha: u8) -> Self {
Color {
red: red,
green: green,
blue: blue,
alpha: alpha,
}
}
/// Returns the red channel in a floating point number form, from 0 to 1.
#[inline]
pub fn red_f32(&self) -> f32 {
self.red as f32 / 255.0
}
/// Returns the green channel in a floating point number form, from 0 to 1.
#[inline]
pub fn green_f32(&self) -> f32 {
self.green as f32 / 255.0
}
/// Returns the blue channel in a floating point number form, from 0 to 1.
#[inline]
pub fn blue_f32(&self) -> f32 {
self.blue as f32 / 255.0
}
/// Returns the alpha channel in a floating point number form, from 0 to 1.
#[inline]
pub fn alpha_f32(&self) -> f32 {
self.alpha as f32 / 255.0
}
}
/// A less-verbose way of specifying a generic color, without alpha.
#[inline]
pub fn rgb<T: Into<f32>>(red: T, green: T, blue: T) -> Color {
rgba(red.into(), green.into(), blue.into(), 1.)
}
/// A less-verbose way of specifying a generic color, with alpha.
#[inline]
pub fn rgba<T: Into<f32>>(red: T, green: T, blue: T, alpha: T) -> Color {
Color::new(red.into(), green.into(), blue.into(), alpha.into())
}
fn clamp_unit_f32(val: f32) -> u8 {
// Whilst scaling by 256 and flooring would provide
// an equal distribution of integers to percentage inputs,
// this is not what Gecko does so we instead multiply by 255
// and round (adding 0.5 and flooring is equivalent to rounding)
//
// Chrome does something similar for the alpha value, but not
// the rgb values.
//
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1340484
//
// Clamping to 256 and rounding after would let 1.0 map to 256, and
// `256.0_f32 as u8` is undefined behavior:
//
// https://github.com/rust-lang/rust/issues/10184
clamp_floor_256_f32(val * 255.)
}
fn clamp_floor_256_f32(val: f32) -> u8 {
val.round().max(0.).min(255.) as u8
}

10
appkit/src/constants.rs Normal file
View file

@ -0,0 +1,10 @@
//! Constants typically used for referencing around in the Objective-C runtime.
//! Specific to this crate.
pub(crate) static APP_PTR: &str = "rstAppPtr";
pub(crate) static BACKGROUND_COLOR: &str = "rstBackgroundColor";
pub(crate) static TOOLBAR_PTR: &str = "rstToolbarPtr";
pub(crate) static VIEW_CONTROLLER_PTR: &str = "rstViewControllerPtr";
pub(crate) static WEBVIEW_CONFIG_VAR: &str = "rstWebViewConfig";
pub(crate) static WEBVIEW_VAR: &str = "rstWebView";
pub(crate) static WEBVIEW_CONTROLLER_PTR: &str = "rstWebViewControllerPtr";

View file

@ -4,6 +4,9 @@
use cocoa::foundation::NSUInteger;
use objc::runtime::Object;
use objc_id::Id;
/// Represents operations that can happen for a given drag/drop scenario.
pub enum DragOperation {
/// No drag operations are allowed.
@ -45,3 +48,13 @@ impl From<DragOperation> for NSUInteger {
}
}
}
/// A wrapper for `NSDraggingInfo`. As this is a protocol/type you should never create yourself,
/// this only provides getters - merely a Rust-y way to grab what you need.
pub struct DragInfo {
pub info: Id<Object>
}
impl DragInfo {
}

View file

@ -5,7 +5,7 @@ use std::rc::Rc;
use std::cell::RefCell;
use cocoa::base::{id, nil, NO};
use cocoa::foundation::{NSInteger, NSUInteger};
use cocoa::foundation::NSUInteger;
use objc_id::Id;
use objc::runtime::Object;

View file

@ -23,7 +23,9 @@ pub use cocoa::base::id;
pub mod alert;
pub mod app;
pub mod button;
pub mod color;
pub mod collection_view;
pub mod constants;
pub mod dragdrop;
pub mod events;
pub mod filesystem;

View file

@ -1,79 +0,0 @@
//! This module provides some basic wrappers for PasteBoard functionality. It's currently not an
//! exhaustive clone, but feel free to pull request accordingly!
use cocoa::base::{id, nil};
use cocoa::foundation::NSString;
/// Represents different PasteBoard types that can be referred to.
#[derive(Debug, Copy, Clone)]
pub enum PasteBoardType {
/// URL data for one file or resource.
URL,
/// Color data.
Color,
/// A file URL.
FileURL,
/// Font and character information.
Font,
/// Type for HTML content.
HTML,
/// Multiple text selection.
MultipleTextSelection,
/// PDF data.
PDF,
/// PNG image data.
PNG,
/// Rich Text Format (RTF) data.
RTF,
/// RTFD formatted file contents.
RTFD,
/// Paragraph formatting information.
Ruler,
/// Sound data.
Sound,
/// String data.
String,
/// Tab-separated fields of text.
TabularText,
/// Tag Image File Format (TIFF) data.
TIFF
}
impl PasteBoardType {
/// Creates an `NSString` out of the underlying type.
pub fn to_nsstring(&self) -> id {
unsafe {
NSString::alloc(nil).init_str(match self {
PasteBoardType::URL => "public.url",
PasteBoardType::Color => "com.apple.cocoa.pasteboard.color",
PasteBoardType::FileURL => "public.file-url",
PasteBoardType::Font => "com.apple.cocoa.pasteboard.character-formatting",
PasteBoardType::HTML => "public.html",
PasteBoardType::MultipleTextSelection => "com.apple.cocoa.pasteboard.multiple-text-selection",
PasteBoardType::PDF => "com.adobe.pdf",
PasteBoardType::PNG => "public.png",
PasteBoardType::RTF => "public.rtf",
PasteBoardType::RTFD => "com.apple.flat-rtfd",
PasteBoardType::Ruler => "com.apple.cocoa.pasteboard.paragraph-formatting",
PasteBoardType::Sound => "com.apple.cocoa.pasteboard.sound",
PasteBoardType::String => "public.utf8-plain-text",
PasteBoardType::TabularText => "public.utf8-tab-separated-values-text",
PasteBoardType::TIFF => "public.tiff",
})
}
}
}

View file

@ -0,0 +1,6 @@
pub mod types;
pub use types::*;
pub mod pasteboard;
pub use pasteboard::*;

View file

@ -0,0 +1,67 @@
//! A wrapper for NSPasteBoard, which is the interface for copy/paste and general transferring
//! (think: drag and drop between applications). It exposes a Rust interface that tries to be
//! complete, but might not cover everything 100% right now - feel free to pull request.
use cocoa::base::id;
use objc::runtime::Object;
use objc::{class, msg_send, sel, sel_impl};
use objc_id::Id;
use crate::pasteboard::types::PasteboardName;
/// Represents an `NSPasteboard`, enabling you to handle copy/paste/drag and drop.
pub struct Pasteboard {
/// The internal pointer to the Objective-C side.
pub inner: Id<Object>
}
impl Default for Pasteboard {
fn default() -> Self {
Pasteboard {
inner: unsafe { Id::from_ptr(msg_send![class!(NSPasteboard), generalPasteboard]) }
}
}
}
impl Pasteboard {
/// Used internally for wrapping a Pasteboard returned from operations (say, drag and drop).
pub(crate) fn with(existing: id) -> Self {
Pasteboard {
inner: unsafe { Id::from_ptr(existing) }
}
}
/// Should be pasteboardname enum!
pub fn named(name: PasteboardName) -> Self {
Pasteboard {
inner: unsafe {
let name = name.to_nsstring();
Id::from_ptr(msg_send![class!(NSPasteboard), pasteboardWithName:name])
}
}
}
/// Creates and returns a new pasteboard with a name that is guaranteed to be unique with
/// respect to other pasteboards in the system.
pub fn unique() -> Self {
Pasteboard {
inner: unsafe { Id::from_ptr(msg_send![class!(NSPasteboard), pasteboardWithUniqueName]) }
}
}
/// Releases the receivers resources in the pasteboard server. It's rare-ish to need to use
/// this, but considering this stuff happens on the Objective-C side you may need it.
pub fn release_globally(&self) {
unsafe {
let _: () = msg_send![&*self.inner, releaseGlobally];
}
}
/// Clears the existing contents of the pasteboard.
pub fn clear_contents(&self) {
unsafe {
let _: () = msg_send![&*self.inner, clearContents];
}
}
}

View file

@ -0,0 +1,104 @@
//! This module provides some basic wrappers for Pasteboard functionality. It's currently not an
//! exhaustive clone, but feel free to pull request accordingly!
use cocoa::base::{id, nil};
use cocoa::foundation::NSString;
/// Constants for the standard system pasteboard names.
#[derive(Debug, Copy, Clone)]
pub enum PasteboardName {
Drag,
Find,
Font,
General,
Ruler
}
impl PasteboardName {
/// Creates an `NSString` out of the underlying type.
pub fn to_nsstring(&self) -> id {
unsafe {
NSString::alloc(nil).init_str(match self {
PasteboardName::Drag => "",
PasteboardName::Find => "",
PasteboardName::Font => "",
PasteboardName::General => "",
PasteboardName::Ruler => ""
})
}
}
}
/// Represents different Pasteboard types that can be referred to.
#[derive(Debug, Copy, Clone)]
pub enum PasteboardType {
/// URL data for one file or resource.
URL,
/// Color data.
Color,
/// A file URL.
FileURL,
/// Font and character information.
Font,
/// Type for HTML content.
HTML,
/// Multiple text selection.
MultipleTextSelection,
/// PDF data.
PDF,
/// PNG image data.
PNG,
/// Rich Text Format (RTF) data.
RTF,
/// RTFD formatted file contents.
RTFD,
/// Paragraph formatting information.
Ruler,
/// Sound data.
Sound,
/// String data.
String,
/// Tab-separated fields of text.
TabularText,
/// Tag Image File Format (TIFF) data.
TIFF
}
impl PasteboardType {
/// Creates an `NSString` out of the underlying type.
pub fn to_nsstring(&self) -> id {
unsafe {
NSString::alloc(nil).init_str(match self {
PasteboardType::URL => "public.url",
PasteboardType::Color => "com.apple.cocoa.pasteboard.color",
PasteboardType::FileURL => "public.file-url",
PasteboardType::Font => "com.apple.cocoa.pasteboard.character-formatting",
PasteboardType::HTML => "public.html",
PasteboardType::MultipleTextSelection => "com.apple.cocoa.pasteboard.multiple-text-selection",
PasteboardType::PDF => "com.adobe.pdf",
PasteboardType::PNG => "public.png",
PasteboardType::RTF => "public.rtf",
PasteboardType::RTFD => "com.apple.flat-rtfd",
PasteboardType::Ruler => "com.apple.cocoa.pasteboard.paragraph-formatting",
PasteboardType::Sound => "com.apple.cocoa.pasteboard.sound",
PasteboardType::String => "public.utf8-plain-text",
PasteboardType::TabularText => "public.utf8-tab-separated-values-text",
PasteboardType::TIFF => "public.tiff",
})
}
}
}

View file

@ -11,13 +11,12 @@ use cocoa::foundation::{NSArray, NSString};
use objc_id::Id;
use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel};
use objc::{msg_send, sel, sel_impl};
use objc::{class, msg_send, sel, sel_impl};
use crate::constants::TOOLBAR_PTR;
use crate::toolbar::item::ToolbarItem;
use crate::utils::str_from;
static TOOLBAR_PTR: &str = "rstToolbarPtr";
/// A trait that you can implement to have your struct/etc act as an `NSToolbarDelegate`.
pub trait ToolbarDelegate {
/// What items are allowed in this toolbar.
@ -30,8 +29,8 @@ pub trait ToolbarDelegate {
fn item_for(&self, _identifier: &str) -> ToolbarItem;
}
/// A wrapper for `NSWindow`. Holds (retains) pointers for the Objective-C runtime
/// where our `NSWindow` and associated delegate live.
/// A wrapper for `NSToolbar`. Holds (retains) pointers for the Objective-C runtime
/// where our `NSToolbar` and associated delegate live.
pub struct Toolbar {
pub inner: Id<Object>,
pub objc_delegate: Id<Object>,
@ -44,7 +43,7 @@ impl Toolbar {
pub fn new<D: ToolbarDelegate + 'static>(identifier: &str, delegate: D) -> Self {
let inner = unsafe {
let identifier = NSString::alloc(nil).init_str(identifier);
let alloc: id = msg_send![Class::get("NSToolbar").unwrap(), alloc];
let alloc: id = msg_send![class!(NSToolbar), alloc];
let toolbar: id = msg_send![alloc, initWithIdentifier:identifier];
Id::from_ptr(toolbar)
};
@ -112,7 +111,7 @@ fn register_delegate_class<D: ToolbarDelegate>() -> *const Class {
static INIT: Once = Once::new();
INIT.call_once(|| unsafe {
let superclass = Class::get("NSObject").unwrap();
let superclass = class!(NSObject);
let mut decl = ClassDecl::new("RSTToolbarDelegate", superclass).unwrap();
// For callbacks

View file

@ -14,9 +14,9 @@ use cocoa::foundation::{NSUInteger};
use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel, BOOL};
use objc::{class, msg_send, sel, sel_impl};
use objc::{msg_send, sel, sel_impl};
use crate::view::VIEW_CONTROLLER_PTR;
use crate::constants::{BACKGROUND_COLOR, VIEW_CONTROLLER_PTR};
use crate::view::traits::ViewController;
/// Enforces normalcy, or: a needlessly cruel method in terms of the name. You get the idea though.
@ -24,9 +24,10 @@ extern fn enforce_normalcy(_: &Object, _: Sel) -> BOOL {
return YES;
}
/// Used for handling background colors in layer backed views (which is the default here).
extern fn update_layer(this: &Object, _: Sel) {
unsafe {
let background_color: id = msg_send![class!(NSColor), redColor];
let background_color: id = *this.get_ivar(BACKGROUND_COLOR);
if background_color != nil {
let layer: id = msg_send![this, layer];
let cg: id = msg_send![background_color, CGColor];
@ -92,6 +93,7 @@ pub(crate) fn register_view_class<T: ViewController>() -> *const Class {
// A pointer to the "view controller" on the Rust side. It's expected that this doesn't
// move.
decl.add_ivar::<usize>(VIEW_CONTROLLER_PTR);
decl.add_ivar::<id>(BACKGROUND_COLOR);
decl.add_method(sel!(isFlipped), enforce_normalcy as extern fn(&Object, _) -> BOOL);
decl.add_method(sel!(wantsUpdateLayer), enforce_normalcy as extern fn(&Object, _) -> BOOL);

View file

@ -4,14 +4,15 @@
use std::sync::Once;
use cocoa::base::{id, nil, YES, NO};
use cocoa::foundation::{NSRect, NSUInteger};
use cocoa::foundation::{NSRect};
use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel};
use objc::{class, msg_send, sel, sel_impl};
use crate::constants::VIEW_CONTROLLER_PTR;
use crate::geometry::Rect;
use crate::view::{VIEW_CONTROLLER_PTR, ViewController};
use crate::view::ViewController;
use crate::view::class::register_view_class;
/// Loads and configures ye old NSView for this controller.
@ -31,7 +32,7 @@ pub fn register_controller_class<T: ViewController + 'static>() -> *const Class
static INIT: Once = Once::new();
INIT.call_once(|| unsafe {
let superclass = Class::get("NSViewController").unwrap();
let superclass = class!(NSViewController);
let mut decl = ClassDecl::new("RSTViewController", superclass).unwrap();
decl.add_ivar::<usize>(VIEW_CONTROLLER_PTR);

View file

@ -1,6 +1,4 @@
pub(crate) static VIEW_CONTROLLER_PTR: &str = "rstViewControllerPtr";
pub(crate) mod class;
pub(crate) mod controller;

View file

@ -4,14 +4,16 @@ use std::rc::Rc;
use std::cell::RefCell;
use cocoa::base::{id, nil, YES, NO};
use cocoa::foundation::{NSString, NSArray};
use cocoa::foundation::NSArray;
use objc_id::ShareId;
use objc::runtime::Object;
use objc::{class, msg_send, sel, sel_impl};
use objc::{msg_send, sel, sel_impl};
use crate::pasteboard::PasteBoardType;
use crate::view::{VIEW_CONTROLLER_PTR, ViewController};
use crate::color::Color;
use crate::constants::{BACKGROUND_COLOR, VIEW_CONTROLLER_PTR};
use crate::pasteboard::PasteboardType;
use crate::view::traits::ViewController;
use crate::view::controller::register_controller_class;
#[derive(Default)]
@ -32,7 +34,7 @@ impl ViewInner {
});
}
pub fn register_for_dragged_types(&self, types: &[PasteBoardType]) {
pub fn register_for_dragged_types(&self, types: &[PasteboardType]) {
if let Some(controller) = &self.controller {
unsafe {
let types = NSArray::arrayWithObjects(nil, &types.iter().map(|t| {
@ -44,6 +46,16 @@ impl ViewInner {
}
}
}
pub fn set_background_color(&self, color: Color) {
if let Some(controller) = &self.controller {
unsafe {
let view: id = msg_send![*controller, view];
(*view).set_ivar(BACKGROUND_COLOR, color.into_platform_specific_color());
let _: () = msg_send![view, setNeedsDisplay:YES];
}
}
}
}
#[derive(Default)]
@ -64,8 +76,13 @@ impl View {
view.controller.clone()
}
pub fn register_for_dragged_types(&self, types: &[PasteBoardType]) {
pub fn register_for_dragged_types(&self, types: &[PasteboardType]) {
let view = self.0.borrow();
view.register_for_dragged_types(types);
}
pub fn set_background_color(&self, color: Color) {
let view = self.0.borrow();
view.set_background_color(color);
}
}

View file

@ -14,10 +14,9 @@ use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel};
use objc::{class, msg_send, sel, sel_impl};
use crate::view::ViewController;
use crate::view::class::register_view_class;
use crate::constants::{WEBVIEW_VAR, WEBVIEW_CONFIG_VAR, WEBVIEW_CONTROLLER_PTR};
use crate::view::traits::ViewController;
use crate::webview::action::{NavigationAction, NavigationResponse};
use crate::webview::{WEBVIEW_VAR, WEBVIEW_CONFIG_VAR, WEBVIEW_CONTROLLER_PTR};
use crate::webview::traits::WebViewController;
use crate::utils::str_from;

View file

@ -1,9 +1,5 @@
//! A wrapper for WKWebview and associated configurations and properties.
pub(crate) static WEBVIEW_CONFIG_VAR: &str = "rstWebViewConfig";
pub(crate) static WEBVIEW_VAR: &str = "rstWebView";
pub(crate) static WEBVIEW_CONTROLLER_PTR: &str = "rstWebViewControllerPtr";
pub mod action;
pub(crate) mod controller;

View file

@ -18,10 +18,11 @@ use objc_id::ShareId;
use objc::runtime::Object;
use objc::{class, msg_send, sel, sel_impl};
use crate::constants::{WEBVIEW_VAR, WEBVIEW_CONFIG_VAR, WEBVIEW_CONTROLLER_PTR};
use crate::view::ViewController;
use crate::webview::{WEBVIEW_VAR, WEBVIEW_CONFIG_VAR, WEBVIEW_CONTROLLER_PTR, WebViewController};
use crate::webview::controller::register_controller_class;
use crate::webview::config::{WebViewConfig, InjectAt};
use crate::webview::controller::register_controller_class;
use crate::webview::traits::WebViewController;
#[derive(Default)]
pub struct WebViewInner {