Compare commits
11 commits
f800b57b26
...
f756a14e8a
Author | SHA1 | Date | |
---|---|---|---|
Alex Janka | f756a14e8a | ||
Alex Janka | ee5988ff53 | ||
Alex Janka | da22feaebb | ||
Alex Janka | fedc3cebb6 | ||
Alex Janka | 0ab38ad14f | ||
Alex Janka | abbf078482 | ||
Alex Janka | 1d6e666335 | ||
Alex Janka | e7d057b193 | ||
Alex Janka | 52064424ec | ||
Alex Janka | 366c5e7138 | ||
7ffe39891c |
18
Cargo.toml
18
Cargo.toml
|
@ -19,23 +19,23 @@ default-target = "x86_64-apple-darwin"
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bitmask-enum = "2.2.1"
|
bitmask-enum = "2.2.4"
|
||||||
objc = { version = "=0.3.0-beta.2", package = "objc2" }
|
objc = { version = "=0.3.0-beta.3", package = "objc2" }
|
||||||
block = { version = "=0.2.0-alpha.6", package = "block2" }
|
block = { version = "=0.2.0-alpha.6", package = "block2" }
|
||||||
# Temporary: Patched versions that implement `Encode` for common types
|
# Temporary: Patched versions that implement `Encode` for common types
|
||||||
# Branch: `objc2`
|
# Branch: `objc2`
|
||||||
core-foundation = { git = "https://github.com/madsmtm/core-foundation-rs.git", rev = "7d593d016175755e492a92ef89edca68ac3bd5cd" }
|
core-foundation = { git = "https://github.com/madsmtm/core-foundation-rs.git", rev = "7d593d016175755e492a92ef89edca68ac3bd5cd" }
|
||||||
core-graphics = { git = "https://github.com/madsmtm/core-foundation-rs.git", rev = "7d593d016175755e492a92ef89edca68ac3bd5cd" }
|
core-graphics = { git = "https://github.com/madsmtm/core-foundation-rs.git", rev = "7d593d016175755e492a92ef89edca68ac3bd5cd" }
|
||||||
dispatch = "0.2.0"
|
dispatch = "0.2.0"
|
||||||
infer = { version = "0.15", optional = true }
|
infer = { version = "0.16.0", optional = true }
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.5.0"
|
||||||
libc = "0.2"
|
libc = "0.2.157"
|
||||||
os_info = "3.0.1"
|
os_info = "3.8.2"
|
||||||
url = "2.1.1"
|
url = "2.5.2"
|
||||||
uuid = { version = "1.1", features = ["v4"], optional = true }
|
uuid = { version = "1.10.0", features = ["v4"], optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
eval = "0.4"
|
eval = "0.4.3"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
appkit = ["core-foundation/mac_os_10_8_features"]
|
appkit = ["core-foundation/mac_os_10_8_features"]
|
||||||
|
|
|
@ -48,7 +48,7 @@ pub enum EventMask {
|
||||||
Pressure = 1 << 34,
|
Pressure = 1 << 34,
|
||||||
DirectTouch = 1 << 37,
|
DirectTouch = 1 << 37,
|
||||||
|
|
||||||
ChangeMode = 1 << 38
|
ChangeMode = 1 << 38,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A wrapper over an `NSEvent`.
|
/// A wrapper over an `NSEvent`.
|
||||||
|
@ -103,18 +103,22 @@ impl Event {
|
||||||
unsafe { msg_send![&*self.0, clickCount] }
|
unsafe { msg_send![&*self.0, clickCount] }
|
||||||
}
|
}
|
||||||
|
|
||||||
/*pub fn contains_modifier_flags(&self, flags: &[EventModifierFlag]) -> bool {
|
pub fn current_modifier_flags(&self) -> Vec<EventModifierFlag> {
|
||||||
let modifier_flags: NSUInteger = unsafe {
|
let pressed_modifier_flags: NSUInteger = unsafe { msg_send![&*self.0, modifierFlags] };
|
||||||
msg_send![&*self.0, modifierFlags]
|
|
||||||
};
|
|
||||||
|
|
||||||
for flag in flags {
|
let all_modifier_flags = vec![
|
||||||
let f: NSUInteger = flag.into();
|
EventModifierFlag::CapsLock,
|
||||||
|
EventModifierFlag::Control,
|
||||||
|
EventModifierFlag::Option,
|
||||||
|
EventModifierFlag::Command,
|
||||||
|
EventModifierFlag::DeviceIndependentFlagsMask,
|
||||||
|
];
|
||||||
|
|
||||||
}
|
all_modifier_flags
|
||||||
|
.into_iter()
|
||||||
false
|
.filter(|modifier| (Into::<NSUInteger>::into(modifier) & pressed_modifier_flags) != 0)
|
||||||
}*/
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
/// Register an event handler with the local system event stream. This method
|
/// Register an event handler with the local system event stream. This method
|
||||||
/// watches for events that occur _within the application_. Events outside
|
/// watches for events that occur _within the application_. Events outside
|
||||||
|
@ -124,14 +128,14 @@ impl Event {
|
||||||
/// monitors are required - the streams don't mix.
|
/// monitors are required - the streams don't mix.
|
||||||
pub fn local_monitor<F>(mask: EventMask, handler: F) -> EventMonitor
|
pub fn local_monitor<F>(mask: EventMask, handler: F) -> EventMonitor
|
||||||
where
|
where
|
||||||
F: Fn(Event) -> Option<Event> + Send + Sync + 'static
|
F: Fn(Event) -> Option<Event> + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
let block = ConcreteBlock::new(move |event: id| {
|
let block = ConcreteBlock::new(move |event: id| {
|
||||||
let evt = Event::new(event);
|
let evt = Event::new(event);
|
||||||
|
|
||||||
match handler(evt) {
|
match handler(evt) {
|
||||||
Some(mut evt) => &mut *evt.0,
|
Some(mut evt) => &mut *evt.0,
|
||||||
None => nil
|
None => nil,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let block = block.copy();
|
let block = block.copy();
|
||||||
|
@ -153,14 +157,14 @@ impl Event {
|
||||||
/// monitors are required - the streams don't mix.
|
/// monitors are required - the streams don't mix.
|
||||||
pub fn global_monitor<F>(mask: EventMask, handler: F) -> EventMonitor
|
pub fn global_monitor<F>(mask: EventMask, handler: F) -> EventMonitor
|
||||||
where
|
where
|
||||||
F: Fn(Event) -> Option<Event> + Send + Sync + 'static
|
F: Fn(Event) -> Option<Event> + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
let block = ConcreteBlock::new(move |event: id| {
|
let block = ConcreteBlock::new(move |event: id| {
|
||||||
let evt = Event::new(event);
|
let evt = Event::new(event);
|
||||||
|
|
||||||
match handler(evt) {
|
match handler(evt) {
|
||||||
Some(mut evt) => &mut *evt.0,
|
Some(mut evt) => &mut *evt.0,
|
||||||
None => nil
|
None => nil,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let block = block.copy();
|
let block = block.copy();
|
||||||
|
@ -183,7 +187,7 @@ pub enum EventModifierFlag {
|
||||||
Control,
|
Control,
|
||||||
Option,
|
Option,
|
||||||
Command,
|
Command,
|
||||||
DeviceIndependentFlagsMask
|
DeviceIndependentFlagsMask,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<EventModifierFlag> for NSUInteger {
|
impl From<EventModifierFlag> for NSUInteger {
|
||||||
|
@ -193,7 +197,7 @@ impl From<EventModifierFlag> for NSUInteger {
|
||||||
EventModifierFlag::Control => 1 << 18,
|
EventModifierFlag::Control => 1 << 18,
|
||||||
EventModifierFlag::Option => 1 << 19,
|
EventModifierFlag::Option => 1 << 19,
|
||||||
EventModifierFlag::Command => 1 << 20,
|
EventModifierFlag::Command => 1 << 20,
|
||||||
EventModifierFlag::DeviceIndependentFlagsMask => 0xffff0000
|
EventModifierFlag::DeviceIndependentFlagsMask => 0xffff0000,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -205,7 +209,7 @@ impl From<&EventModifierFlag> for NSUInteger {
|
||||||
EventModifierFlag::Control => 1 << 18,
|
EventModifierFlag::Control => 1 << 18,
|
||||||
EventModifierFlag::Option => 1 << 19,
|
EventModifierFlag::Option => 1 << 19,
|
||||||
EventModifierFlag::Command => 1 << 20,
|
EventModifierFlag::Command => 1 << 20,
|
||||||
EventModifierFlag::DeviceIndependentFlagsMask => 0xffff0000
|
EventModifierFlag::DeviceIndependentFlagsMask => 0xffff0000,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ fn make_menu_item<S: AsRef<str>>(
|
||||||
title: S,
|
title: S,
|
||||||
key: Option<&str>,
|
key: Option<&str>,
|
||||||
action: Option<Sel>,
|
action: Option<Sel>,
|
||||||
modifiers: Option<&[EventModifierFlag]>
|
modifiers: Option<&[EventModifierFlag]>,
|
||||||
) -> Id<Object, Owned> {
|
) -> Id<Object, Owned> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let title = NSString::new(title.as_ref());
|
let title = NSString::new(title.as_ref());
|
||||||
|
@ -44,7 +44,7 @@ fn make_menu_item<S: AsRef<str>>(
|
||||||
// Note that AppKit requires a blank string if nil, not nil.
|
// Note that AppKit requires a blank string if nil, not nil.
|
||||||
let key = NSString::new(match key {
|
let key = NSString::new(match key {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => ""
|
None => "",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Stock menu items that use selectors targeted at system pieces are just standard
|
// Stock menu items that use selectors targeted at system pieces are just standard
|
||||||
|
@ -63,7 +63,7 @@ fn make_menu_item<S: AsRef<str>>(
|
||||||
initWithTitle: &*title,
|
initWithTitle: &*title,
|
||||||
action: sel!(fireBlockAction:),
|
action: sel!(fireBlockAction:),
|
||||||
keyEquivalent: &*key,
|
keyEquivalent: &*key,
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(modifiers) = modifiers {
|
if let Some(modifiers) = modifiers {
|
||||||
|
@ -151,7 +151,7 @@ pub enum MenuItem {
|
||||||
|
|
||||||
/// Represents a Separator. It's useful nonetheless for
|
/// Represents a Separator. It's useful nonetheless for
|
||||||
/// separating out pieces of the `NSMenu` structure.
|
/// separating out pieces of the `NSMenu` structure.
|
||||||
Separator
|
Separator,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MenuItem {
|
impl MenuItem {
|
||||||
|
@ -186,7 +186,7 @@ impl MenuItem {
|
||||||
"Hide Others",
|
"Hide Others",
|
||||||
Some("h"),
|
Some("h"),
|
||||||
Some(sel!(hide:)),
|
Some(sel!(hide:)),
|
||||||
Some(&[EventModifierFlag::Command, EventModifierFlag::Option])
|
Some(&[EventModifierFlag::Command, EventModifierFlag::Option]),
|
||||||
),
|
),
|
||||||
|
|
||||||
Self::ShowAll => make_menu_item("Show All", None, Some(sel!(unhideAllApplications:)), None),
|
Self::ShowAll => make_menu_item("Show All", None, Some(sel!(unhideAllApplications:)), None),
|
||||||
|
@ -203,7 +203,7 @@ impl MenuItem {
|
||||||
"Enter Full Screen",
|
"Enter Full Screen",
|
||||||
Some("f"),
|
Some("f"),
|
||||||
Some(sel!(toggleFullScreen:)),
|
Some(sel!(toggleFullScreen:)),
|
||||||
Some(&[EventModifierFlag::Command, EventModifierFlag::Control])
|
Some(&[EventModifierFlag::Command, EventModifierFlag::Control]),
|
||||||
),
|
),
|
||||||
|
|
||||||
Self::Minimize => make_menu_item("Minimize", Some("m"), Some(sel!(performMiniaturize:)), None),
|
Self::Minimize => make_menu_item("Minimize", Some("m"), Some(sel!(performMiniaturize:)), None),
|
||||||
|
@ -213,13 +213,13 @@ impl MenuItem {
|
||||||
"Toggle Sidebar",
|
"Toggle Sidebar",
|
||||||
Some("s"),
|
Some("s"),
|
||||||
Some(sel!(toggleSidebar:)),
|
Some(sel!(toggleSidebar:)),
|
||||||
Some(&[EventModifierFlag::Command, EventModifierFlag::Option])
|
Some(&[EventModifierFlag::Command, EventModifierFlag::Option]),
|
||||||
),
|
),
|
||||||
|
|
||||||
Self::Separator => {
|
Self::Separator => {
|
||||||
let cls = class!(NSMenuItem);
|
let cls = class!(NSMenuItem);
|
||||||
msg_send_id![cls, separatorItem]
|
msg_send_id![cls, separatorItem]
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,6 +287,19 @@ impl MenuItem {
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn checkmark(self, enabled: bool) -> Self {
|
||||||
|
if let MenuItem::Custom(objc) = self {
|
||||||
|
unsafe {
|
||||||
|
let enabled: NSUInteger = if enabled { 1 } else { 0 };
|
||||||
|
let _: () = msg_send![&*objc, setState: enabled];
|
||||||
|
}
|
||||||
|
|
||||||
|
return MenuItem::Custom(objc);
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// On the Objective-C side, we need to ensure our handler is dropped when this subclass
|
/// On the Objective-C side, we need to ensure our handler is dropped when this subclass
|
||||||
|
|
|
@ -50,7 +50,7 @@ pub struct Window<T = ()> {
|
||||||
pub objc: Id<Object, Shared>,
|
pub objc: Id<Object, Shared>,
|
||||||
|
|
||||||
/// A delegate for this window.
|
/// A delegate for this window.
|
||||||
pub delegate: Option<Box<T>>
|
pub delegate: Option<Box<T>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Window {
|
impl Default for Window {
|
||||||
|
@ -108,21 +108,21 @@ impl Window {
|
||||||
|
|
||||||
Window {
|
Window {
|
||||||
objc: objc,
|
objc: objc,
|
||||||
delegate: None
|
delegate: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) unsafe fn existing(window: *mut Object) -> Window {
|
pub(crate) unsafe fn existing(window: *mut Object) -> Window {
|
||||||
Window {
|
Window {
|
||||||
objc: Id::retain(window).unwrap(),
|
objc: Id::retain(window).unwrap(),
|
||||||
delegate: None
|
delegate: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Window<T>
|
impl<T> Window<T>
|
||||||
where
|
where
|
||||||
T: WindowDelegate + 'static
|
T: WindowDelegate + 'static,
|
||||||
{
|
{
|
||||||
/// Constructs a new Window with a `config` and `delegate`. Using a `WindowDelegate` enables
|
/// Constructs a new Window with a `config` and `delegate`. Using a `WindowDelegate` enables
|
||||||
/// you to respond to window lifecycle events - visibility, movement, and so on. It also
|
/// you to respond to window lifecycle events - visibility, movement, and so on. It also
|
||||||
|
@ -180,13 +180,13 @@ where
|
||||||
{
|
{
|
||||||
(&mut delegate).did_load(Window {
|
(&mut delegate).did_load(Window {
|
||||||
delegate: None,
|
delegate: None,
|
||||||
objc: objc.clone()
|
objc: objc.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Window {
|
Window {
|
||||||
objc: objc,
|
objc: objc,
|
||||||
delegate: Some(delegate)
|
delegate: Some(delegate),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -205,7 +205,7 @@ impl<T> Window<T> {
|
||||||
/// underlying window. When this property is an empty string, the system removes the subtitle
|
/// underlying window. When this property is an empty string, the system removes the subtitle
|
||||||
/// from the window layout. Allocates and passes an `NSString` over to the Objective C runtime.
|
/// from the window layout. Allocates and passes an `NSString` over to the Objective C runtime.
|
||||||
/// Does nothing when less than version 11.
|
/// Does nothing when less than version 11.
|
||||||
pub fn set_subtittle(&self, subtitle: &str) {
|
pub fn set_subtitle(&self, subtitle: &str) {
|
||||||
if !os::is_minimum_version(11) {
|
if !os::is_minimum_version(11) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -338,6 +338,10 @@ impl<T> Window<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub unsafe fn content_view_ptr(&self) -> Option<std::ptr::NonNull<std::ffi::c_void>> {
|
||||||
|
std::ptr::NonNull::new(self.content_view() as *mut std::ffi::c_void)
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the objc ContentView from the window
|
/// Return the objc ContentView from the window
|
||||||
pub(crate) unsafe fn content_view(&self) -> id {
|
pub(crate) unsafe fn content_view(&self) -> id {
|
||||||
let id: *mut Object = msg_send![&*self.objc, contentView];
|
let id: *mut Object = msg_send![&*self.objc, contentView];
|
||||||
|
@ -514,7 +518,7 @@ impl<T> Window<T> {
|
||||||
pub fn begin_sheet<F, W>(&self, window: &Window<W>, completion: F)
|
pub fn begin_sheet<F, W>(&self, window: &Window<W>, completion: F)
|
||||||
where
|
where
|
||||||
F: Fn() + Send + Sync + 'static,
|
F: Fn() + Send + Sync + 'static,
|
||||||
W: WindowDelegate + 'static
|
W: WindowDelegate + 'static,
|
||||||
{
|
{
|
||||||
let block = ConcreteBlock::new(move |_response: NSInteger| {
|
let block = ConcreteBlock::new(move |_response: NSInteger| {
|
||||||
completion();
|
completion();
|
||||||
|
@ -529,7 +533,7 @@ impl<T> Window<T> {
|
||||||
/// Closes a sheet.
|
/// Closes a sheet.
|
||||||
pub fn end_sheet<W>(&self, window: &Window<W>)
|
pub fn end_sheet<W>(&self, window: &Window<W>)
|
||||||
where
|
where
|
||||||
W: WindowDelegate + 'static
|
W: WindowDelegate + 'static,
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
let _: () = msg_send![&*self.objc, endSheet:&*window.objc];
|
let _: () = msg_send![&*self.objc, endSheet:&*window.objc];
|
||||||
|
|
|
@ -218,6 +218,12 @@ impl Image {
|
||||||
/// ever exposes a compatible API, this can be tweaked in a PR.
|
/// ever exposes a compatible API, this can be tweaked in a PR.
|
||||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||||
pub fn symbol(symbol: SFSymbol, accessibility_description: &str) -> Self {
|
pub fn symbol(symbol: SFSymbol, accessibility_description: &str) -> Self {
|
||||||
|
Self::custom_symbol(symbol.to_str(), accessibility_description)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates and returns an Image with an arbitrary `SFSymbol`
|
||||||
|
pub fn custom_symbol(symbol: &str, accessibility_description: &str) -> Self {
|
||||||
|
println!("custom symbol name: {symbol}");
|
||||||
// SFSymbols is macOS 11.0+
|
// SFSymbols is macOS 11.0+
|
||||||
#[cfg(feature = "appkit")]
|
#[cfg(feature = "appkit")]
|
||||||
let min_version = 11;
|
let min_version = 11;
|
||||||
|
@ -229,7 +235,7 @@ impl Image {
|
||||||
Image(unsafe {
|
Image(unsafe {
|
||||||
match os::is_minimum_version(min_version) {
|
match os::is_minimum_version(min_version) {
|
||||||
true => {
|
true => {
|
||||||
let icon = NSString::new(symbol.to_str());
|
let icon = NSString::new(symbol);
|
||||||
let desc = NSString::new(accessibility_description);
|
let desc = NSString::new(accessibility_description);
|
||||||
msg_send_id![
|
msg_send_id![
|
||||||
Self::class(),
|
Self::class(),
|
||||||
|
|
|
@ -140,7 +140,7 @@ pub struct TextField<T = ()> {
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime center Y layout constraint.
|
/// A pointer to the Objective-C runtime center Y layout constraint.
|
||||||
#[cfg(feature = "autolayout")]
|
#[cfg(feature = "autolayout")]
|
||||||
pub center_y: LayoutAnchorY
|
pub center_y: LayoutAnchorY,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TextField {
|
impl Default for TextField {
|
||||||
|
@ -187,14 +187,14 @@ impl TextField {
|
||||||
center_x: LayoutAnchorX::center(view),
|
center_x: LayoutAnchorX::center(view),
|
||||||
|
|
||||||
#[cfg(feature = "autolayout")]
|
#[cfg(feature = "autolayout")]
|
||||||
center_y: LayoutAnchorY::center(view)
|
center_y: LayoutAnchorY::center(view),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> TextField<T>
|
impl<T> TextField<T>
|
||||||
where
|
where
|
||||||
T: TextFieldDelegate + 'static
|
T: TextFieldDelegate + 'static,
|
||||||
{
|
{
|
||||||
/// Initializes a new TextField with a given `TextFieldDelegate`. This enables you to respond to events
|
/// Initializes a new TextField with a given `TextFieldDelegate`. This enables you to respond to events
|
||||||
/// and customize the view as a module, similar to class-based systems.
|
/// and customize the view as a module, similar to class-based systems.
|
||||||
|
@ -242,7 +242,7 @@ where
|
||||||
center_x: LayoutAnchorX::center(input),
|
center_x: LayoutAnchorX::center(input),
|
||||||
|
|
||||||
#[cfg(feature = "autolayout")]
|
#[cfg(feature = "autolayout")]
|
||||||
center_y: LayoutAnchorY::center(input)
|
center_y: LayoutAnchorY::center(input),
|
||||||
};
|
};
|
||||||
|
|
||||||
(&mut delegate).did_load(input.clone_as_handle());
|
(&mut delegate).did_load(input.clone_as_handle());
|
||||||
|
@ -289,7 +289,7 @@ impl<T> TextField<T> {
|
||||||
center_x: self.center_x.clone(),
|
center_x: self.center_x.clone(),
|
||||||
|
|
||||||
#[cfg(feature = "autolayout")]
|
#[cfg(feature = "autolayout")]
|
||||||
center_y: self.center_y.clone()
|
center_y: self.center_y.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -360,6 +360,35 @@ impl<T> TextField<T> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set whether this field should truncate the last visible line.
|
||||||
|
pub fn set_truncates_last_visible_line(&self, truncates_last_visible_line: bool) {
|
||||||
|
self.objc.with_mut(|obj| unsafe {
|
||||||
|
let cell: id = msg_send![obj, cell];
|
||||||
|
let _: () = msg_send![cell, setTruncatesLastVisibleLine:match truncates_last_visible_line {
|
||||||
|
true => YES,
|
||||||
|
false => NO
|
||||||
|
}];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the line break mode.
|
||||||
|
pub fn set_line_break_mode(&self, mode: crate::text::LineBreakMode) {
|
||||||
|
self.objc.with_mut(|obj| unsafe {
|
||||||
|
let _: () = msg_send![obj, setLineBreakMode:mode as crate::foundation::NSUInteger];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set whether this field is editable.
|
||||||
|
pub fn set_editable(&self, editable: bool) {
|
||||||
|
self.objc.with_mut(|obj| unsafe {
|
||||||
|
let cell: id = msg_send![obj, cell];
|
||||||
|
let _: () = msg_send![cell, setEditable:match editable {
|
||||||
|
true => YES,
|
||||||
|
false => NO
|
||||||
|
}];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Set whether this field operates in single-line mode.
|
/// Set whether this field operates in single-line mode.
|
||||||
pub fn set_wraps(&self, uses_single_line: bool) {
|
pub fn set_wraps(&self, uses_single_line: bool) {
|
||||||
self.objc.with_mut(|obj| unsafe {
|
self.objc.with_mut(|obj| unsafe {
|
||||||
|
@ -386,6 +415,16 @@ impl<T> TextField<T> {
|
||||||
let _: () = msg_send![obj, setFont:&*font];
|
let _: () = msg_send![obj, setFont:&*font];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Enable/disable this field.
|
||||||
|
pub fn set_enabled(&self, enabled: bool) {
|
||||||
|
self.objc.with_mut(|obj| unsafe {
|
||||||
|
let _: () = msg_send![obj, setEnabled:match enabled {
|
||||||
|
true => YES,
|
||||||
|
false => NO,
|
||||||
|
}];
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> ObjcAccess for TextField<T> {
|
impl<T> ObjcAccess for TextField<T> {
|
||||||
|
|
|
@ -172,6 +172,9 @@ pub mod switch;
|
||||||
#[cfg(feature = "appkit")]
|
#[cfg(feature = "appkit")]
|
||||||
pub mod select;
|
pub mod select;
|
||||||
|
|
||||||
|
#[cfg(feature = "appkit")]
|
||||||
|
pub mod stepper;
|
||||||
|
|
||||||
pub mod text;
|
pub mod text;
|
||||||
|
|
||||||
#[cfg(feature = "quicklook")]
|
#[cfg(feature = "quicklook")]
|
||||||
|
|
237
src/stepper/mod.rs
Normal file
237
src/stepper/mod.rs
Normal file
|
@ -0,0 +1,237 @@
|
||||||
|
//! Implements a number stepper. By default this uses NSStepper on macOS.
|
||||||
|
|
||||||
|
use core_graphics::base::CGFloat;
|
||||||
|
use core_graphics::geometry::CGRect;
|
||||||
|
use objc::rc::{Id, Shared};
|
||||||
|
use objc::runtime::{Class, Object};
|
||||||
|
use objc::{msg_send, msg_send_id, sel};
|
||||||
|
|
||||||
|
use crate::control::Control;
|
||||||
|
use crate::foundation::{id, load_or_register_class, nil, NSInteger, NSNumber, NSString, NO, YES};
|
||||||
|
use crate::geometry::Rect;
|
||||||
|
use crate::invoker::TargetActionHandler;
|
||||||
|
use crate::layout::Layout;
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
use crate::layout::{LayoutAnchorDimension, LayoutAnchorX, LayoutAnchorY};
|
||||||
|
use crate::objc_access::ObjcAccess;
|
||||||
|
use crate::utils::properties::ObjcProperty;
|
||||||
|
|
||||||
|
/// Wraps `NSStepper` on AppKit. Not currently implemented for iOS.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Stepper {
|
||||||
|
/// A handle for the underlying Objective-C object.
|
||||||
|
pub objc: ObjcProperty,
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stepper {
|
||||||
|
/// Creates a new `Stepper` instance, configures it appropriately,
|
||||||
|
/// and retains the necessary Objective-C runtime pointer.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let zero: CGRect = Rect::zero().into();
|
||||||
|
|
||||||
|
let view: id = unsafe {
|
||||||
|
let alloc: id = msg_send![register_class(), alloc];
|
||||||
|
let stepper: id = msg_send![alloc, initWithFrame:zero];
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
let _: () = msg_send![stepper, setTranslatesAutoresizingMaskIntoConstraints: NO];
|
||||||
|
|
||||||
|
stepper
|
||||||
|
};
|
||||||
|
|
||||||
|
Stepper {
|
||||||
|
handler: None,
|
||||||
|
|
||||||
|
#[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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attaches a callback
|
||||||
|
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.
|
||||||
|
let this: Id<Object, Shared> = self.objc.get(|obj| unsafe { msg_send_id![obj, self] });
|
||||||
|
let handler = TargetActionHandler::new(&this, action);
|
||||||
|
self.handler = Some(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets maximum value
|
||||||
|
pub fn set_max_value(&self, value: f64) {
|
||||||
|
self.objc.with_mut(|obj| unsafe {
|
||||||
|
let _: () = msg_send![obj, setMaxValue: value];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets minimum value
|
||||||
|
pub fn set_min_value(&self, value: f64) {
|
||||||
|
self.objc.with_mut(|obj| unsafe {
|
||||||
|
let _: () = msg_send![obj, setMinValue: value];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets increment
|
||||||
|
pub fn set_increment(&self, value: f64) {
|
||||||
|
self.objc.with_mut(|obj| unsafe {
|
||||||
|
let _: () = msg_send![obj, setIncrement: value];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets whether this wraps
|
||||||
|
pub fn set_wraps(&self, wraps: bool) {
|
||||||
|
self.objc.with_mut(|obj| unsafe {
|
||||||
|
let _: () = msg_send![obj, setValueWraps:match wraps {
|
||||||
|
true => YES,
|
||||||
|
false => NO
|
||||||
|
}];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the selected value.
|
||||||
|
pub fn set_value(&self, value: f64) {
|
||||||
|
let value = value as CGFloat;
|
||||||
|
|
||||||
|
self.objc.with_mut(|obj| unsafe {
|
||||||
|
let _: () = msg_send![obj, setDoubleValue: value];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the selected value.
|
||||||
|
pub fn get_value(&self) -> f64 {
|
||||||
|
self.objc.get(|obj| unsafe { msg_send![obj, doubleValue] })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjcAccess for Stepper {
|
||||||
|
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 Stepper {
|
||||||
|
fn add_subview<V: Layout>(&self, _view: &V) {
|
||||||
|
panic!(
|
||||||
|
r#"
|
||||||
|
Tried to add a subview to a Stepper. This is not allowed in Cacao. If you think this should be supported,
|
||||||
|
open a discussion on the GitHub repo.
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Control for Stepper {}
|
||||||
|
|
||||||
|
impl ObjcAccess for &Stepper {
|
||||||
|
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 &Stepper {
|
||||||
|
fn add_subview<V: Layout>(&self, _view: &V) {
|
||||||
|
panic!(
|
||||||
|
r#"
|
||||||
|
Tried to add a subview to a Stepper. This is not allowed in Cacao. If you think this should be supported,
|
||||||
|
open a discussion on the GitHub repo.
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Control for &Stepper {}
|
||||||
|
|
||||||
|
impl Drop for Stepper {
|
||||||
|
/// 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 `NSStepper` subclass, and configures it to hold some ivars
|
||||||
|
/// for various things we need to store.
|
||||||
|
fn register_class() -> &'static Class {
|
||||||
|
load_or_register_class("NSStepper", "CacaoStepper", |decl| unsafe {})
|
||||||
|
}
|
Loading…
Reference in a new issue