Multiple changes I had to make for Ebou (#89)

* Add segmented control, icons, toolbar

* add to demos

* doc tests

* format code

* Additional SFSymbol definitions
This commit is contained in:
Benedikt Terhechte 2023-08-01 08:51:53 +02:00 committed by GitHub
parent 1b577506a7
commit e6696eaa3e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 449 additions and 30 deletions

View file

@ -16,7 +16,7 @@ pub fn button(text: &str, msg: Msg) -> Button {
button.set_bordered(false); button.set_bordered(false);
button.set_bezel_style(BezelStyle::SmallSquare); button.set_bezel_style(BezelStyle::SmallSquare);
button.set_focus_ring_type(FocusRingType::None); button.set_focus_ring_type(FocusRingType::None);
button.set_action(move || dispatch(msg.clone())); button.set_action(move |_| dispatch(msg.clone()));
button.set_key_equivalent(&*text.to_lowercase()); button.set_key_equivalent(&*text.to_lowercase());
let font = Font::system(22.); let font = Font::system(22.);

View file

@ -5,13 +5,15 @@
//! - Another Controller / View //! - Another Controller / View
use cacao::appkit::menu::{Menu, MenuItem}; use cacao::appkit::menu::{Menu, MenuItem};
use cacao::appkit::segmentedcontrol::SegmentedControl;
use cacao::appkit::window::{Window, WindowConfig, WindowController, WindowDelegate}; use cacao::appkit::window::{Window, WindowConfig, WindowController, WindowDelegate};
use cacao::appkit::{App, AppDelegate}; use cacao::appkit::{App, AppDelegate};
use cacao::button::Button; use cacao::button::Button;
use cacao::foundation::NSArray;
use cacao::geometry::{Edge, Rect}; use cacao::geometry::{Edge, Rect};
use cacao::image::Image;
use cacao::layout::{Layout, LayoutConstraint}; use cacao::layout::{Layout, LayoutConstraint};
use cacao::notification_center::Dispatcher; use cacao::notification_center::Dispatcher;
use cacao::text::{Font, Label};
use cacao::view::{Popover, PopoverConfig, View, ViewController, ViewDelegate}; use cacao::view::{Popover, PopoverConfig, View, ViewController, ViewDelegate};
struct BasicApp { struct BasicApp {
@ -124,7 +126,7 @@ impl ViewDelegate for PopoverExampleContentView {
fn did_load(&mut self, view: cacao::view::View) { fn did_load(&mut self, view: cacao::view::View) {
let mut button = Button::new("Show"); let mut button = Button::new("Show");
button.set_action(|| dispatch_ui(Msg::Click)); button.set_action(|_| dispatch_ui(Msg::Click));
let controller = PopoverExampleContentViewController::new(); let controller = PopoverExampleContentViewController::new();
let config = PopoverConfig { let config = PopoverConfig {
@ -164,16 +166,21 @@ impl Dispatcher for BasicApp {
#[derive(Debug)] #[derive(Debug)]
struct PopoverExampleContentViewController { struct PopoverExampleContentViewController {
pub label: Label pub control: SegmentedControl
} }
impl PopoverExampleContentViewController { impl PopoverExampleContentViewController {
fn new() -> Self { fn new() -> Self {
let label = Label::new(); let images = NSArray::from(vec![
let font = Font::system(20.); &*Image::symbol(cacao::image::SFSymbol::AtSymbol, "Hello").0,
label.set_font(&font); &*Image::symbol(cacao::image::SFSymbol::PaperPlane, "Hello").0,
label.set_text("Hello"); &*Image::symbol(cacao::image::SFSymbol::PaperPlaneFilled, "Hello").0,
Self { label } ]);
let mut control = SegmentedControl::new(images, cacao::appkit::segmentedcontrol::TrackingMode::SelectOne);
control.set_action(|index| {
println!("Selected Index {index}");
});
Self { control }
} }
} }
@ -181,6 +188,6 @@ impl ViewDelegate for PopoverExampleContentViewController {
const NAME: &'static str = "PopoverExampleContentViewController"; const NAME: &'static str = "PopoverExampleContentViewController";
fn did_load(&mut self, view: View) { fn did_load(&mut self, view: View) {
view.add_subview(&self.label); view.add_subview(&self.control);
} }
} }

View file

@ -50,7 +50,7 @@ impl ViewDelegate for AddNewTodoContentView {
let mut button = Button::new("Add"); let mut button = Button::new("Add");
button.set_key_equivalent("\r"); button.set_key_equivalent("\r");
button.set_action(|| dispatch_ui(Message::ProcessNewTodo)); button.set_action(|_| dispatch_ui(Message::ProcessNewTodo));
view.add_subview(&instructions); view.add_subview(&instructions);
view.add_subview(&input); view.add_subview(&input);

View file

@ -2,6 +2,7 @@ use cacao::layout::{Layout, LayoutConstraint};
use cacao::switch::Switch; use cacao::switch::Switch;
use cacao::text::Label; use cacao::text::Label;
use cacao::view::View; use cacao::view::View;
use objc::runtime::Object;
/// A reusable widget for a toggle; this is effectively a standard checkbox/label combination for /// A reusable widget for a toggle; this is effectively a standard checkbox/label combination for
/// toggling a boolean value. /// toggling a boolean value.
@ -55,7 +56,7 @@ impl ToggleOptionView {
/// can toggle your settings and such there. /// can toggle your settings and such there.
pub fn configure<F>(&mut self, text: &str, subtitle: &str, state: bool, handler: F) pub fn configure<F>(&mut self, text: &str, subtitle: &str, state: bool, handler: F)
where where
F: Fn() + Send + Sync + 'static F: Fn(*const Object) + Send + Sync + 'static
{ {
self.title.set_text(text); self.title.set_text(text);
self.subtitle.set_text(subtitle); self.subtitle.set_text(subtitle);

View file

@ -19,7 +19,7 @@ impl Default for PreferencesToolbar {
let icon = Image::toolbar_icon(MacSystemIcon::PreferencesGeneral, "General"); let icon = Image::toolbar_icon(MacSystemIcon::PreferencesGeneral, "General");
item.set_image(icon); item.set_image(icon);
item.set_action(|| { item.set_action(|_| {
dispatch_ui(Message::SwitchPreferencesToGeneralPane); dispatch_ui(Message::SwitchPreferencesToGeneralPane);
}); });
@ -32,7 +32,7 @@ impl Default for PreferencesToolbar {
let icon = Image::toolbar_icon(MacSystemIcon::PreferencesAdvanced, "Advanced"); let icon = Image::toolbar_icon(MacSystemIcon::PreferencesAdvanced, "Advanced");
item.set_image(icon); item.set_image(icon);
item.set_action(|| { item.set_action(|_| {
dispatch_ui(Message::SwitchPreferencesToAdvancedPane); dispatch_ui(Message::SwitchPreferencesToAdvancedPane);
}); });

View file

@ -1,6 +1,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use cacao::defaults::{UserDefaults, Value}; use cacao::defaults::{UserDefaults, Value};
use objc::runtime::Object;
const EXAMPLE: &str = "exampleSetting"; const EXAMPLE: &str = "exampleSetting";
@ -25,7 +26,7 @@ impl Defaults {
} }
/// Toggles the example setting. /// Toggles the example setting.
pub fn toggle_should_whatever() { pub fn toggle_should_whatever(_object: *const Object) {
toggle_bool(EXAMPLE); toggle_bool(EXAMPLE);
} }

View file

@ -15,7 +15,7 @@ impl Default for TodosToolbar {
item.set_title("Add Todo"); item.set_title("Add Todo");
item.set_button(Button::new("+ New")); item.set_button(Button::new("+ New"));
item.set_action(|| { item.set_action(|_| {
dispatch_ui(Message::OpenNewTodoSheet); dispatch_ui(Message::OpenNewTodoSheet);
}); });

View file

@ -29,3 +29,4 @@ pub mod toolbar;
pub mod window; pub mod window;
pub mod haptics; pub mod haptics;
pub mod segmentedcontrol;

View file

@ -0,0 +1,346 @@
//! Wraps `NSSegmentedControl` on appkit
use std::fmt;
use std::sync::Once;
use std::cell::RefCell;
use std::rc::Rc;
use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel};
use objc::{class, msg_send, sel, sel_impl};
use objc_id::ShareId;
use crate::color::Color;
use crate::control::Control;
use crate::foundation::{id, nil, NSArray, NSString, NSUInteger, BOOL, NO, YES};
use crate::image::Image;
use crate::invoker::TargetActionHandler;
use crate::keys::Key;
use crate::layout::Layout;
use crate::objc_access::ObjcAccess;
use crate::text::{AttributedString, Font};
use crate::utils::{load, properties::ObjcProperty};
#[cfg(feature = "autolayout")]
use crate::layout::{LayoutAnchorDimension, LayoutAnchorX, LayoutAnchorY};
#[cfg(feature = "appkit")]
use crate::appkit::FocusRingType;
/// Wraps `NSButton` on appkit, and `UIButton` on iOS and tvOS.
///
/// You'd use this type to create a button that a user can interact with. Buttons can be configured
/// a number of ways, and support setting a callback to fire when they're clicked or tapped.
///
/// Some properties are platform-specific; see the documentation for further information.
///
/// ```rust,no_run
/// use cacao::button::Button;
/// use cacao::view::View;
/// use crate::cacao::layout::Layout;
/// let mut button = Button::new("My button title");
/// button.set_key_equivalent("c");
///
/// button.set_action(|_| {
/// println!("My button was clicked.");
/// });
/// let my_view : View<()> = todo!();
///
/// // Make sure you don't let your Button drop for as long as you need it.
/// my_view.add_subview(&button);
/// ```
#[derive(Debug)]
pub struct SegmentedControl {
/// A handle for the underlying Objective-C object.
pub objc: ObjcProperty,
/// Hold on to the images
images: NSArray,
handler: Option<TargetActionHandler>,
/// A pointer to the Objective-C runtime top layout constraint.
#[cfg(feature = "autolayout")]
pub top: LayoutAnchorY,
/// A pointer to the Objective-C runtime leading layout constraint.
#[cfg(feature = "autolayout")]
pub leading: LayoutAnchorX,
/// A pointer to the Objective-C runtime left layout constraint.
#[cfg(feature = "autolayout")]
pub left: LayoutAnchorX,
/// A pointer to the Objective-C runtime trailing layout constraint.
#[cfg(feature = "autolayout")]
pub trailing: LayoutAnchorX,
/// A pointer to the Objective-C runtime right layout constraint.
#[cfg(feature = "autolayout")]
pub right: LayoutAnchorX,
/// A pointer to the Objective-C runtime bottom layout constraint.
#[cfg(feature = "autolayout")]
pub bottom: LayoutAnchorY,
/// A pointer to the Objective-C runtime width layout constraint.
#[cfg(feature = "autolayout")]
pub width: LayoutAnchorDimension,
/// A pointer to the Objective-C runtime height layout constraint.
#[cfg(feature = "autolayout")]
pub height: LayoutAnchorDimension,
/// A pointer to the Objective-C runtime center X layout constraint.
#[cfg(feature = "autolayout")]
pub center_x: LayoutAnchorX,
/// A pointer to the Objective-C runtime center Y layout constraint.
#[cfg(feature = "autolayout")]
pub center_y: LayoutAnchorY
}
#[derive(Debug)]
#[repr(u8)]
pub enum TrackingMode {
SelectOne = 0,
SelectMany = 1,
SelectMomentary = 2
}
impl SegmentedControl {
/// Creates a new `NSSegmentedControl` instance, configures it appropriately,
/// and retains the necessary Objective-C runtime pointer.
pub fn new(images: NSArray, tracking_mode: TrackingMode) -> Self {
let view: id = unsafe {
let tracking_mode = tracking_mode as u8 as i32;
let control: id = msg_send![register_class(), segmentedControlWithImages:&*images trackingMode:tracking_mode
target:nil
action:nil
];
let _: () = msg_send![control, setWantsLayer: YES];
#[cfg(feature = "autolayout")]
let _: () = msg_send![control, setTranslatesAutoresizingMaskIntoConstraints: NO];
control
};
SegmentedControl {
handler: None,
images,
#[cfg(feature = "autolayout")]
top: LayoutAnchorY::top(view),
#[cfg(feature = "autolayout")]
left: LayoutAnchorX::left(view),
#[cfg(feature = "autolayout")]
leading: LayoutAnchorX::leading(view),
#[cfg(feature = "autolayout")]
right: LayoutAnchorX::right(view),
#[cfg(feature = "autolayout")]
trailing: LayoutAnchorX::trailing(view),
#[cfg(feature = "autolayout")]
bottom: LayoutAnchorY::bottom(view),
#[cfg(feature = "autolayout")]
width: LayoutAnchorDimension::width(view),
#[cfg(feature = "autolayout")]
height: LayoutAnchorDimension::height(view),
#[cfg(feature = "autolayout")]
center_x: LayoutAnchorX::center(view),
#[cfg(feature = "autolayout")]
center_y: LayoutAnchorY::center(view),
objc: ObjcProperty::retain(view)
}
}
/// Select the segment at index
pub fn set_tooltip_segment(&mut self, index: NSUInteger, tooltip: &str) {
self.objc.with_mut(|obj| unsafe {
let converted = NSString::new(tooltip);
let _: () = msg_send![obj, setToolTip: converted forSegment: index];
})
}
/// Select the segment at index
pub fn select_segment(&mut self, index: NSUInteger) {
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setSelectedSegment: index];
})
}
/// Sets an image on the underlying button.
pub fn set_image_segment(&mut self, image: Image, segment: NSUInteger) {
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setImage:&*image.0 forSegment: segment];
});
}
/// Attaches a callback for button press events. Don't get too creative now...
/// best just to message pass or something.
pub fn set_action<F: Fn(i32) + Send + Sync + 'static>(&mut self, action: F) {
// @TODO: This probably isn't ideal but gets the job done for now; needs revisiting.
let this = self.objc.get(|obj| unsafe { ShareId::from_ptr(msg_send![obj, self]) });
let handler = TargetActionHandler::new(&*this, move |obj: *const Object| unsafe {
let selected: i32 = msg_send![obj, selectedSegment];
action(selected)
});
self.handler = Some(handler);
}
/// Call this to set the background color for the backing layer.
pub fn set_background_color<C: AsRef<Color>>(&self, color: C) {
let color: id = color.as_ref().into();
#[cfg(feature = "appkit")]
self.objc.with_mut(|obj| unsafe {
let cell: id = msg_send![obj, cell];
let _: () = msg_send![cell, setBackgroundColor: color];
});
}
/// Set a key to be bound to this button. When the key is pressed, the action coupled to this
/// button will fire.
pub fn set_key_equivalent<'a, K>(&self, key: K)
where
K: Into<Key<'a>>
{
let key: Key<'a> = key.into();
self.objc.with_mut(|obj| {
let keychar = match key {
Key::Char(s) => NSString::new(s),
Key::Delete => NSString::new("\u{08}")
};
unsafe {
let _: () = msg_send![obj, setKeyEquivalent:&*keychar];
}
});
}
/// Sets the text color for this button.
///
/// On appkit, this is done by way of an `AttributedString` under the hood.
pub fn set_text_color<C: AsRef<Color>>(&self, color: C) {
#[cfg(feature = "appkit")]
self.objc.with_mut(move |obj| unsafe {
let text: id = msg_send![obj, attributedTitle];
let len: isize = msg_send![text, length];
let mut attr_str = AttributedString::wrap(text);
attr_str.set_text_color(color.as_ref(), 0..len);
let _: () = msg_send![obj, setAttributedTitle:&*attr_str];
});
}
// @TODO: Figure out how to handle oddities like this.
/// For buttons on appkit, one might need to disable the border. This does that.
#[cfg(feature = "appkit")]
pub fn set_bordered(&self, is_bordered: bool) {
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setBordered:match is_bordered {
true => YES,
false => NO
}];
});
}
/// Sets the font for this button.
pub fn set_font<F: AsRef<Font>>(&self, font: F) {
let font = font.as_ref().clone();
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setFont:&*font];
});
}
/// Sets how the control should draw a focus ring when a user is focused on it.
///
/// This is an appkit-only method.
#[cfg(feature = "appkit")]
pub fn set_focus_ring_type(&self, focus_ring_type: FocusRingType) {
let ring_type: NSUInteger = focus_ring_type.into();
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setFocusRingType: ring_type];
});
}
/// Toggles the highlighted status of the button.
pub fn set_highlighted(&self, highlight: bool) {
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, highlight:match highlight {
true => YES,
false => NO
}];
});
}
}
impl ObjcAccess for SegmentedControl {
fn with_backing_obj_mut<F: Fn(id)>(&self, handler: F) {
self.objc.with_mut(handler);
}
fn get_from_backing_obj<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
self.objc.get(handler)
}
}
impl Layout for SegmentedControl {}
impl Control for SegmentedControl {}
impl ObjcAccess for &SegmentedControl {
fn with_backing_obj_mut<F: Fn(id)>(&self, handler: F) {
self.objc.with_mut(handler);
}
fn get_from_backing_obj<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
self.objc.get(handler)
}
}
impl Layout for &SegmentedControl {}
impl Control for &SegmentedControl {}
impl Drop for SegmentedControl {
/// Nils out references on the Objective-C side and removes this from the backing view.
// Just to be sure, let's... nil these out. They should be weak references,
// but I'd rather be paranoid and remove them later.
fn drop(&mut self) {
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setTarget: nil];
let _: () = msg_send![obj, setAction: nil];
});
}
}
/// Registers an `NSButton` subclass, and configures it to hold some ivars
/// for various things we need to store.
fn register_class() -> *const Class {
static mut VIEW_CLASS: *const Class = 0 as *const Class;
static INIT: Once = Once::new();
INIT.call_once(|| unsafe {
let superclass = class!(NSSegmentedControl);
let decl = ClassDecl::new("RSTSegmentedControl", superclass).unwrap();
VIEW_CLASS = decl.register();
});
unsafe { VIEW_CLASS }
}

View file

@ -10,6 +10,7 @@ use objc::runtime::Object;
use objc::{class, msg_send, sel, sel_impl}; use objc::{class, msg_send, sel, sel_impl};
use objc_id::{Id, ShareId}; use objc_id::{Id, ShareId};
use crate::appkit::segmentedcontrol::SegmentedControl;
use crate::button::{BezelStyle, Button}; use crate::button::{BezelStyle, Button};
use crate::foundation::{id, NSString, NO, YES}; use crate::foundation::{id, NSString, NO, YES};
use crate::image::Image; use crate::image::Image;
@ -21,6 +22,7 @@ pub struct ToolbarItem {
pub identifier: String, pub identifier: String,
pub objc: Id<Object>, pub objc: Id<Object>,
pub button: Option<Button>, pub button: Option<Button>,
pub segmented_control: Option<SegmentedControl>,
pub image: Option<Image>, pub image: Option<Image>,
handler: Option<TargetActionHandler> handler: Option<TargetActionHandler>
} }
@ -42,6 +44,7 @@ impl ToolbarItem {
identifier, identifier,
objc, objc,
button: None, button: None,
segmented_control: None,
image: None, image: None,
handler: None handler: None
} }
@ -52,6 +55,7 @@ impl ToolbarItem {
identifier: "".to_string(), identifier: "".to_string(),
objc: unsafe { Id::from_retained_ptr(item) }, objc: unsafe { Id::from_retained_ptr(item) },
button: None, button: None,
segmented_control: None,
image: None, image: None,
handler: None handler: None
} }
@ -76,6 +80,15 @@ impl ToolbarItem {
self.button = Some(button); self.button = Some(button);
} }
/// Sets and takes ownership of the segmented control for this item.
pub fn set_segmented_control(&mut self, control: SegmentedControl) {
control.objc.with_mut(|obj| unsafe {
let _: () = msg_send![&*self.objc, setView: obj];
});
self.segmented_control = Some(control);
}
/// Sets and takes ownership of the image for this toolbar item. /// Sets and takes ownership of the image for this toolbar item.
pub fn set_image(&mut self, image: Image) { pub fn set_image(&mut self, image: Image) {
unsafe { unsafe {
@ -102,7 +115,7 @@ impl ToolbarItem {
} }
/// Sets an action on this item. /// Sets an action on this item.
pub fn set_action<F: Fn() + Send + Sync + 'static>(&mut self, action: F) { pub fn set_action<F: Fn(*const Object) + Send + Sync + 'static>(&mut self, action: F) {
let handler = TargetActionHandler::new(&*self.objc, action); let handler = TargetActionHandler::new(&*self.objc, action);
self.handler = Some(handler); self.handler = Some(handler);
} }

View file

@ -295,6 +295,14 @@ impl<T> Window<T> {
} }
} }
/// Used for setting a toolbar on this window.
pub fn toolbar(&self) -> ShareId<Object> {
unsafe {
let o: *mut Object = msg_send![&*self.objc, toolbar];
ShareId::from_ptr(o)
}
}
/// Toggles whether the toolbar is shown for this window. Has no effect if no toolbar exists on /// Toggles whether the toolbar is shown for this window. Has no effect if no toolbar exists on
/// this window. /// this window.
pub fn toggle_toolbar_shown(&self) { pub fn toggle_toolbar_shown(&self) {
@ -303,6 +311,14 @@ impl<T> Window<T> {
} }
} }
/// Set the toolbar style
pub fn set_toolbar_style(&self, style: WindowToolbarStyle) {
let style: NSUInteger = style.into();
unsafe {
let _: () = msg_send![&*self.objc, setToolbarStyle: style];
}
}
/// Set whether the toolbar toggle button is shown. Has no effect if no toolbar exists on this /// Set whether the toolbar toggle button is shown. Has no effect if no toolbar exists on this
/// window. /// window.
pub fn set_shows_toolbar_button(&self, shows: bool) { pub fn set_shows_toolbar_button(&self, shows: bool) {

View file

@ -12,7 +12,7 @@
//! let mut button = Button::new("My button title"); //! let mut button = Button::new("My button title");
//! button.set_key_equivalent("c"); //! button.set_key_equivalent("c");
//! //!
//! button.set_action(|| { //! button.set_action(|_| {
//! println!("My button was clicked."); //! println!("My button was clicked.");
//! }); //! });
//! let my_view : View<()> = todo!(); //! let my_view : View<()> = todo!();
@ -58,7 +58,7 @@ mod enums;
/// let mut button = Button::new("My button title"); /// let mut button = Button::new("My button title");
/// button.set_key_equivalent("c"); /// button.set_key_equivalent("c");
/// ///
/// button.set_action(|| { /// button.set_action(|_| {
/// println!("My button was clicked."); /// println!("My button was clicked.");
/// }); /// });
/// let my_view : View<()> = todo!(); /// let my_view : View<()> = todo!();
@ -212,7 +212,7 @@ impl Button {
/// Attaches a callback for button press events. Don't get too creative now... /// Attaches a callback for button press events. Don't get too creative now...
/// best just to message pass or something. /// best just to message pass or something.
pub fn set_action<F: Fn() + Send + Sync + 'static>(&mut self, action: F) { pub fn set_action<F: Fn(*const Object) + Send + Sync + 'static>(&mut self, action: F) {
// @TODO: This probably isn't ideal but gets the job done for now; needs revisiting. // @TODO: This probably isn't ideal but gets the job done for now; needs revisiting.
let this = self.objc.get(|obj| unsafe { ShareId::from_ptr(msg_send![obj, self]) }); let this = self.objc.get(|obj| unsafe { ShareId::from_ptr(msg_send![obj, self]) });
let handler = TargetActionHandler::new(&*this, action); let handler = TargetActionHandler::new(&*this, action);

View file

@ -74,36 +74,62 @@ impl MacSystemIcon {
#[derive(Debug)] #[derive(Debug)]
pub enum SFSymbol { pub enum SFSymbol {
AtSymbol, AtSymbol,
ArrowClockwise,
Bell,
BellFill,
BellBadge,
BellBadgeFill,
GearShape, GearShape,
FolderFilled, FolderFilled,
ListAndFilm,
PaperPlane, PaperPlane,
PaperPlaneFilled, PaperPlaneFilled,
Plus, Plus,
Minus, Minus,
Message,
MessageFill,
MessageBadge,
MessageBadgeFill,
MessageBadgeFilledFill,
PersonCropCircle,
SliderVertical3, SliderVertical3,
SquareAndArrowUpOnSquare, SquareAndArrowUpOnSquare,
SquareAndArrowUpOnSquareFill, SquareAndArrowUpOnSquareFill,
SquareAndArrowDownOnSquare, SquareAndArrowDownOnSquare,
SquareAndArrowDownOnSquareFill, SquareAndArrowDownOnSquareFill,
SquareDashed SquareDashed,
SquareAndPencil
} }
impl SFSymbol { impl SFSymbol {
pub fn to_str(&self) -> &'static str { pub fn to_str(&self) -> &'static str {
match self { match self {
Self::AtSymbol => "at", Self::AtSymbol => "at",
Self::ArrowClockwise => "arrow.clockwise",
Self::GearShape => "gearshape", Self::GearShape => "gearshape",
Self::FolderFilled => "folder.fill", Self::FolderFilled => "folder.fill",
Self::ListAndFilm => "list.and.film",
Self::Bell => "bell",
Self::BellFill => "bell.fill",
Self::BellBadge => "bell.badge",
Self::BellBadgeFill => "bell.badge.fill",
Self::PaperPlane => "paperplane", Self::PaperPlane => "paperplane",
Self::PaperPlaneFilled => "paperplane.fill", Self::PaperPlaneFilled => "paperplane.fill",
Self::Plus => "plus", Self::Plus => "plus",
Self::Minus => "minus", Self::Minus => "minus",
Self::Message => "message",
Self::MessageFill => "message.fill",
Self::MessageBadge => "message.badge",
Self::MessageBadgeFill => "message.badge.fill",
Self::MessageBadgeFilledFill => "message.badge.filled.fill",
Self::PersonCropCircle => "person.crop.circle",
Self::SliderVertical3 => "slider.vertical.3", Self::SliderVertical3 => "slider.vertical.3",
Self::SquareAndArrowUpOnSquare => "square.and.arrow.up.on.square", Self::SquareAndArrowUpOnSquare => "square.and.arrow.up.on.square",
Self::SquareAndArrowUpOnSquareFill => "square.and.arrow.up.on.square.fill", Self::SquareAndArrowUpOnSquareFill => "square.and.arrow.up.on.square.fill",
Self::SquareAndArrowDownOnSquare => "square.and.arrow.down.on.square", Self::SquareAndArrowDownOnSquare => "square.and.arrow.down.on.square",
Self::SquareAndArrowDownOnSquareFill => "square.and.arrow.down.on.square.fill", Self::SquareAndArrowDownOnSquareFill => "square.and.arrow.down.on.square.fill",
Self::SquareDashed => "square.dashed" Self::SquareDashed => "square.dashed",
Self::SquareAndPencil => "square.and.pencil"
} }
} }
} }

View file

@ -12,7 +12,7 @@ use core_graphics::{
}; };
use super::icons::*; use super::icons::*;
use crate::foundation::{id, NSData, NSString, NO, YES}; use crate::foundation::{id, NSData, NSString, NO, NSURL, YES};
use crate::utils::os; use crate::utils::os;
/// Specifies resizing behavior for image drawing. /// Specifies resizing behavior for image drawing.
@ -146,6 +146,14 @@ impl Image {
}) })
} }
#[cfg(target_os = "macos")]
pub fn with_contents_of_url(url: NSURL) -> Self {
Image(unsafe {
let alloc: id = msg_send![Self::class(), alloc];
ShareId::from_ptr(msg_send![alloc, initWithContentsOfURL: url.objc])
})
}
/// Given a Vec of data, will transform it into an Image by passing it through NSData. /// Given a Vec of data, will transform it into an Image by passing it through NSData.
/// This can be useful for when you need to include_bytes!() something into your binary. /// This can be useful for when you need to include_bytes!() something into your binary.
pub fn with_data(data: &[u8]) -> Self { pub fn with_data(data: &[u8]) -> Self {

View file

@ -27,7 +27,7 @@ pub static ACTION_CALLBACK_PTR: &str = "rstTargetActionPtr";
/// Point is, Button aren't created that much in the grand scheme of things, /// Point is, Button aren't created that much in the grand scheme of things,
/// and the heap isn't our enemy in a GUI framework anyway. If someone knows /// and the heap isn't our enemy in a GUI framework anyway. If someone knows
/// a better way to do this that doesn't require double-boxing, I'm all ears. /// a better way to do this that doesn't require double-boxing, I'm all ears.
pub struct Action(Box<dyn Fn() + Send + Sync + 'static>); pub struct Action(Box<dyn Fn(*const Object) + Send + Sync + 'static>);
impl fmt::Debug for Action { impl fmt::Debug for Action {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@ -51,7 +51,7 @@ pub struct TargetActionHandler {
impl TargetActionHandler { impl TargetActionHandler {
/// Returns a new TargetEventHandler. /// Returns a new TargetEventHandler.
pub fn new<F: Fn() + Send + Sync + 'static>(control: &Object, action: F) -> Self { pub fn new<F: Fn(*const Object) + Send + Sync + 'static>(control: &Object, action: F) -> Self {
let block = Box::new(Action(Box::new(action))); let block = Box::new(Action(Box::new(action)));
let ptr = Box::into_raw(block); let ptr = Box::into_raw(block);
@ -74,9 +74,9 @@ impl TargetActionHandler {
} }
/// This will fire for an NSButton callback. /// This will fire for an NSButton callback.
extern "C" fn perform<F: Fn() + 'static>(this: &mut Object, _: Sel, _sender: id) { extern "C" fn perform<F: Fn(*const Object) + 'static>(this: &mut Object, _: Sel, sender: id) {
let action = load::<Action>(this, ACTION_CALLBACK_PTR); let action = load::<Action>(this, ACTION_CALLBACK_PTR);
(action.0)(); (action.0)(sender.cast_const());
} }
/// Due to the way that Rust and Objective-C live... very different lifestyles, /// Due to the way that Rust and Objective-C live... very different lifestyles,
@ -91,7 +91,7 @@ extern "C" fn perform<F: Fn() + 'static>(this: &mut Object, _: Sel, _sender: id)
/// The `NSButton` owns this object on instantiation, and will release it /// The `NSButton` owns this object on instantiation, and will release it
/// on drop. We handle the heap copy on the Rust side, so setting the block /// on drop. We handle the heap copy on the Rust side, so setting the block
/// is just an ivar. /// is just an ivar.
pub(crate) fn register_invoker_class<F: Fn() + 'static>() -> *const Class { pub(crate) fn register_invoker_class<F: Fn(*const Object) + 'static>() -> *const Class {
load_or_register_class("NSObject", "RSTTargetActionHandler", |decl| unsafe { load_or_register_class("NSObject", "RSTTargetActionHandler", |decl| unsafe {
decl.add_ivar::<usize>(ACTION_CALLBACK_PTR); decl.add_ivar::<usize>(ACTION_CALLBACK_PTR);
decl.add_method(sel!(perform:), perform::<F> as extern "C" fn(&mut Object, _, id)); decl.add_method(sel!(perform:), perform::<F> as extern "C" fn(&mut Object, _, id));

View file

@ -140,7 +140,7 @@ impl Select {
/// Really, this is not ideal. /// Really, this is not ideal.
/// ///
/// I cannot stress this enough. /// I cannot stress this enough.
pub fn set_action<F: Fn() + Send + Sync + 'static>(&mut self, action: F) { pub fn set_action<F: Fn(*const Object) + Send + Sync + 'static>(&mut self, action: F) {
// @TODO: This probably isn't ideal but gets the job done for now; needs revisiting. // @TODO: This probably isn't ideal but gets the job done for now; needs revisiting.
let this = self.objc.get(|obj| unsafe { ShareId::from_ptr(msg_send![obj, self]) }); let this = self.objc.get(|obj| unsafe { ShareId::from_ptr(msg_send![obj, self]) });
let handler = TargetActionHandler::new(&*this, action); let handler = TargetActionHandler::new(&*this, action);

View file

@ -130,7 +130,7 @@ impl Switch {
/// Attaches a callback for button press events. Don't get too creative now... /// Attaches a callback for button press events. Don't get too creative now...
/// best just to message pass or something. /// best just to message pass or something.
pub fn set_action<F: Fn() + Send + Sync + 'static>(&mut self, action: F) { pub fn set_action<F: Fn(*const Object) + Send + Sync + 'static>(&mut self, action: F) {
// @TODO: This probably isn't ideal but gets the job done for now; needs revisiting. // @TODO: This probably isn't ideal but gets the job done for now; needs revisiting.
let this = self.objc.get(|obj| unsafe { ShareId::from_ptr(msg_send![obj, self]) }); let this = self.objc.get(|obj| unsafe { ShareId::from_ptr(msg_send![obj, self]) });
let handler = TargetActionHandler::new(&*this, action); let handler = TargetActionHandler::new(&*this, action);