Compare commits

...

9 commits

Author SHA1 Message Date
Alex Janka b19806af97 content view pointer 2024-02-08 15:16:43 +11:00
Alex Janka 65b32e79c0 add checkmark for menu items 2023-11-28 16:20:28 +11:00
Alex Janka 4fd93e3fae event: get current modifier flags 2023-11-27 12:23:41 +11:00
Alex Janka 0e3c2dc037
Merge branch 'ryanmcgrath:trunk' into trunk 2023-11-23 12:11:34 +11:00
Alex Janka 6144e9f244 add set enabled to textfield 2023-11-03 10:07:10 +11:00
Alex Janka 6a3ed259b2 more stepper bits 2023-11-02 21:32:29 +11:00
Alex Janka ddd0237347 some more input bits 2023-11-02 21:32:18 +11:00
Alex Janka 292390c05c add stepper 2023-11-02 10:59:31 +11:00
Alex Janka 196fade777 bump objc beta 2023-11-02 10:42:53 +11:00
7 changed files with 340 additions and 40 deletions

View file

@ -20,7 +20,7 @@ rustdoc-args = ["--cfg", "docsrs"]
[dependencies] [dependencies]
bitmask-enum = "2.2.1" bitmask-enum = "2.2.1"
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`

View file

@ -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,
} }
} }
} }

View file

@ -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

View file

@ -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),
} }
} }
} }
@ -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];

View file

@ -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> {

View file

@ -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
View 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 {})
}