Compare commits

...

9 commits

Author SHA1 Message Date
Alex Janka f800b57b26 image: custom symbol 2024-07-07 09:48:57 +10:00
Alex Janka c8c85c2580 content view pointer 2024-07-07 09:48:57 +10:00
Alex Janka 6952cc2429 add checkmark for menu items 2024-07-07 09:48:57 +10:00
Alex Janka 5db4785825 event: get current modifier flags 2024-07-07 09:48:57 +10:00
Alex Janka ac2cc95c64 add set enabled to textfield 2024-07-07 09:48:57 +10:00
Alex Janka 22b322cdfb more stepper bits 2024-07-07 09:48:57 +10:00
Alex Janka 1655ece898 some more input bits 2024-07-07 09:48:57 +10:00
Alex Janka 631f53214c add stepper 2024-07-07 09:48:57 +10:00
Alex Janka 671f29e56d bump objc beta 2024-07-07 09:48:57 +10:00
8 changed files with 347 additions and 41 deletions

View file

@ -20,7 +20,7 @@ rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
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" }
# Temporary: Patched versions that implement `Encode` for common types
# Branch: `objc2`

View file

@ -48,7 +48,7 @@ pub enum EventMask {
Pressure = 1 << 34,
DirectTouch = 1 << 37,
ChangeMode = 1 << 38
ChangeMode = 1 << 38,
}
/// A wrapper over an `NSEvent`.
@ -103,18 +103,22 @@ impl Event {
unsafe { msg_send![&*self.0, clickCount] }
}
/*pub fn contains_modifier_flags(&self, flags: &[EventModifierFlag]) -> bool {
let modifier_flags: NSUInteger = unsafe {
msg_send![&*self.0, modifierFlags]
};
pub fn current_modifier_flags(&self) -> Vec<EventModifierFlag> {
let pressed_modifier_flags: NSUInteger = unsafe { msg_send![&*self.0, modifierFlags] };
for flag in flags {
let f: NSUInteger = flag.into();
let all_modifier_flags = vec![
EventModifierFlag::CapsLock,
EventModifierFlag::Control,
EventModifierFlag::Option,
EventModifierFlag::Command,
EventModifierFlag::DeviceIndependentFlagsMask,
];
}
false
}*/
all_modifier_flags
.into_iter()
.filter(|modifier| (Into::<NSUInteger>::into(modifier) & pressed_modifier_flags) != 0)
.collect()
}
/// Register an event handler with the local system event stream. This method
/// watches for events that occur _within the application_. Events outside
@ -124,14 +128,14 @@ impl Event {
/// monitors are required - the streams don't mix.
pub fn local_monitor<F>(mask: EventMask, handler: F) -> EventMonitor
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 evt = Event::new(event);
match handler(evt) {
Some(mut evt) => &mut *evt.0,
None => nil
None => nil,
}
});
let block = block.copy();
@ -153,14 +157,14 @@ impl Event {
/// monitors are required - the streams don't mix.
pub fn global_monitor<F>(mask: EventMask, handler: F) -> EventMonitor
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 evt = Event::new(event);
match handler(evt) {
Some(mut evt) => &mut *evt.0,
None => nil
None => nil,
}
});
let block = block.copy();
@ -183,7 +187,7 @@ pub enum EventModifierFlag {
Control,
Option,
Command,
DeviceIndependentFlagsMask
DeviceIndependentFlagsMask,
}
impl From<EventModifierFlag> for NSUInteger {
@ -193,7 +197,7 @@ impl From<EventModifierFlag> for NSUInteger {
EventModifierFlag::Control => 1 << 18,
EventModifierFlag::Option => 1 << 19,
EventModifierFlag::Command => 1 << 20,
EventModifierFlag::DeviceIndependentFlagsMask => 0xffff0000
EventModifierFlag::DeviceIndependentFlagsMask => 0xffff0000,
}
}
}
@ -205,7 +209,7 @@ impl From<&EventModifierFlag> for NSUInteger {
EventModifierFlag::Control => 1 << 18,
EventModifierFlag::Option => 1 << 19,
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,
key: Option<&str>,
action: Option<Sel>,
modifiers: Option<&[EventModifierFlag]>
modifiers: Option<&[EventModifierFlag]>,
) -> Id<Object, Owned> {
unsafe {
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.
let key = NSString::new(match key {
Some(s) => s,
None => ""
None => "",
});
// 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,
action: sel!(fireBlockAction:),
keyEquivalent: &*key,
]
],
};
if let Some(modifiers) = modifiers {
@ -151,7 +151,7 @@ pub enum MenuItem {
/// Represents a Separator. It's useful nonetheless for
/// separating out pieces of the `NSMenu` structure.
Separator
Separator,
}
impl MenuItem {
@ -186,7 +186,7 @@ impl MenuItem {
"Hide Others",
Some("h"),
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),
@ -203,7 +203,7 @@ impl MenuItem {
"Enter Full Screen",
Some("f"),
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),
@ -213,13 +213,13 @@ impl MenuItem {
"Toggle Sidebar",
Some("s"),
Some(sel!(toggleSidebar:)),
Some(&[EventModifierFlag::Command, EventModifierFlag::Option])
Some(&[EventModifierFlag::Command, EventModifierFlag::Option]),
),
Self::Separator => {
let cls = class!(NSMenuItem);
msg_send_id![cls, separatorItem]
}
},
}
}
@ -287,6 +287,19 @@ impl MenuItem {
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

View file

@ -50,7 +50,7 @@ pub struct Window<T = ()> {
pub objc: Id<Object, Shared>,
/// A delegate for this window.
pub delegate: Option<Box<T>>
pub delegate: Option<Box<T>>,
}
impl Default for Window {
@ -108,21 +108,21 @@ impl Window {
Window {
objc: objc,
delegate: None
delegate: None,
}
}
pub(crate) unsafe fn existing(window: *mut Object) -> Window {
Window {
objc: Id::retain(window).unwrap(),
delegate: None
delegate: None,
}
}
}
impl<T> Window<T>
where
T: WindowDelegate + 'static
T: WindowDelegate + 'static,
{
/// 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
@ -180,13 +180,13 @@ where
{
(&mut delegate).did_load(Window {
delegate: None,
objc: objc.clone()
objc: objc.clone(),
});
}
Window {
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
pub(crate) unsafe fn content_view(&self) -> id {
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)
where
F: Fn() + Send + Sync + 'static,
W: WindowDelegate + 'static
W: WindowDelegate + 'static,
{
let block = ConcreteBlock::new(move |_response: NSInteger| {
completion();
@ -529,7 +533,7 @@ impl<T> Window<T> {
/// Closes a sheet.
pub fn end_sheet<W>(&self, window: &Window<W>)
where
W: WindowDelegate + 'static
W: WindowDelegate + 'static,
{
unsafe {
let _: () = msg_send![&*self.objc, endSheet:&*window.objc];

View file

@ -218,6 +218,12 @@ impl Image {
/// ever exposes a compatible API, this can be tweaked in a PR.
#[cfg(any(target_os = "macos", target_os = "ios"))]
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+
#[cfg(feature = "appkit")]
let min_version = 11;
@ -229,7 +235,7 @@ impl Image {
Image(unsafe {
match os::is_minimum_version(min_version) {
true => {
let icon = NSString::new(symbol.to_str());
let icon = NSString::new(symbol);
let desc = NSString::new(accessibility_description);
msg_send_id![
Self::class(),

View file

@ -140,7 +140,7 @@ pub struct TextField<T = ()> {
/// A pointer to the Objective-C runtime center Y layout constraint.
#[cfg(feature = "autolayout")]
pub center_y: LayoutAnchorY
pub center_y: LayoutAnchorY,
}
impl Default for TextField {
@ -187,14 +187,14 @@ impl TextField {
center_x: LayoutAnchorX::center(view),
#[cfg(feature = "autolayout")]
center_y: LayoutAnchorY::center(view)
center_y: LayoutAnchorY::center(view),
}
}
}
impl<T> TextField<T>
where
T: TextFieldDelegate + 'static
T: TextFieldDelegate + 'static,
{
/// 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.
@ -242,7 +242,7 @@ where
center_x: LayoutAnchorX::center(input),
#[cfg(feature = "autolayout")]
center_y: LayoutAnchorY::center(input)
center_y: LayoutAnchorY::center(input),
};
(&mut delegate).did_load(input.clone_as_handle());
@ -289,7 +289,7 @@ impl<T> TextField<T> {
center_x: self.center_x.clone(),
#[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.
pub fn set_wraps(&self, uses_single_line: bool) {
self.objc.with_mut(|obj| unsafe {
@ -386,6 +415,16 @@ impl<T> TextField<T> {
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> {

View file

@ -172,6 +172,9 @@ pub mod switch;
#[cfg(feature = "appkit")]
pub mod select;
#[cfg(feature = "appkit")]
pub mod stepper;
pub mod text;
#[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 {})
}