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:
parent
1b577506a7
commit
e6696eaa3e
|
@ -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.);
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -29,3 +29,4 @@ pub mod toolbar;
|
||||||
pub mod window;
|
pub mod window;
|
||||||
|
|
||||||
pub mod haptics;
|
pub mod haptics;
|
||||||
|
pub mod segmentedcontrol;
|
||||||
|
|
346
src/appkit/segmentedcontrol.rs
Normal file
346
src/appkit/segmentedcontrol.rs
Normal 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 }
|
||||||
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue