From 62cebab69113943bfaa92fff2076aff256524a7f Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Tue, 19 Jan 2021 00:11:52 -0800 Subject: [PATCH] Some more updates for macOS. - Added support for basic Cursor management. - Added support for NSWindow cancelOperation: callbacks. It's not... perfect, but it works as a discrete hook. - Added support for NSProgressIndicator. - Properly forward Error types from QuickLook generation calls, and future-proof the ThumbnailQuality enum. - Add support for configuring Label line break mode. --- src/filesystem/enums.rs | 4 + src/lib.rs | 1 + src/macos/cursor.rs | 85 +++++++++++++++ src/macos/mod.rs | 3 + src/macos/window/class.rs | 10 ++ src/macos/window/controller/mod.rs | 4 +- src/macos/window/mod.rs | 49 ++++++++- src/macos/window/traits.rs | 6 +- src/progress/enums.rs | 20 ++++ src/progress/ios.rs | 45 ++++++++ src/progress/macos.rs | 21 ++++ src/progress/mod.rs | 166 +++++++++++++++++++++++++++++ src/quicklook/config.rs | 33 +++++- src/quicklook/mod.rs | 15 ++- src/text/enums.rs | 36 +++++++ src/text/label/mod.rs | 13 ++- src/text/mod.rs | 2 +- 17 files changed, 497 insertions(+), 16 deletions(-) create mode 100644 src/macos/cursor.rs create mode 100644 src/progress/enums.rs create mode 100644 src/progress/ios.rs create mode 100644 src/progress/macos.rs create mode 100644 src/progress/mod.rs diff --git a/src/filesystem/enums.rs b/src/filesystem/enums.rs index 71c051b..08d915d 100644 --- a/src/filesystem/enums.rs +++ b/src/filesystem/enums.rs @@ -24,6 +24,10 @@ impl From for ModalResponse { -1000 => ModalResponse::Stopped, -1001 => ModalResponse::Aborted, -1002 => ModalResponse::Continue, + + // @TODO: Definitely don't panic here, wtf was I thinking? + // Probably make this a ModalResponse::Unknown or something so a user can + // gracefully handle. e => { panic!("Unknown NSModalResponse sent back! {}", e); } } } diff --git a/src/lib.rs b/src/lib.rs index 2d1a9be..672efb0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -106,6 +106,7 @@ pub mod listview; pub mod networking; pub mod notification_center; pub mod pasteboard; +pub mod progress; pub mod scrollview; pub mod text; diff --git a/src/macos/cursor.rs b/src/macos/cursor.rs new file mode 100644 index 0000000..ee5ab86 --- /dev/null +++ b/src/macos/cursor.rs @@ -0,0 +1,85 @@ + +use objc::{class, msg_send, sel, sel_impl}; +use crate::foundation::id; + +#[derive(Debug)] +pub enum CursorType { + Arrow, + Crosshair, + ClosedHand, + OpenHand, + PointingHand, + + ResizeLeft, + ResizeRight, + ResizeLeftRight, + + ResizeUp, + ResizeDown, + ResizeUpDown, + + DisappearingItem, + + IBeam, + IBeamVertical, + + OperationNotAllowed, + DragLink, + DragCopy, + ContextMenu +} + +#[derive(Debug)] +pub struct Cursor; + +impl Cursor { + /// Given a cursor type, will make it the system cursor. + /// The inverse of this call, which you should call when ready, is `pop()`. + pub fn push(cursor_type: CursorType) { + unsafe { + let cursor: id = match cursor_type { + CursorType::Arrow => msg_send![class!(NSCursor), arrowCursor], + CursorType::Crosshair => msg_send![class!(NSCursor), crosshairCursor], + CursorType::ClosedHand => msg_send![class!(NSCursor), closedHandCursor], + CursorType::OpenHand => msg_send![class!(NSCursor), openHandCursor], + CursorType::PointingHand => msg_send![class!(NSCursor), pointingHandCursor], + CursorType::ResizeLeft => msg_send![class!(NSCursor), resizeLeftCursor], + CursorType::ResizeRight => msg_send![class!(NSCursor), resizeRightCursor], + CursorType::ResizeLeftRight => msg_send![class!(NSCursor), resizeLeftRightCursor], + CursorType::ResizeUp => msg_send![class!(NSCursor), resizeUpCursor], + CursorType::ResizeDown => msg_send![class!(NSCursor), resizeDownCursor], + CursorType::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor], + CursorType::DisappearingItem => msg_send![class!(NSCursor), disappearingItemCursor], + CursorType::IBeam => msg_send![class!(NSCursor), IBeamCursor], + CursorType::IBeamVertical => msg_send![class!(NSCursor), IBeamCursorForVerticalLayout], + CursorType::OperationNotAllowed => msg_send![class!(NSCursor), operationNotAllowedCursor], + CursorType::DragLink => msg_send![class!(NSCursor), dragLinkCursor], + CursorType::DragCopy => msg_send![class!(NSCursor), dragCopyCursor], + CursorType::ContextMenu => msg_send![class!(NSCursor), contextualMenuCursor] + }; + + let _: () = msg_send![cursor, push]; + } + } + + /// Pops the current cursor off the cursor-stack. The inverse of push. + pub fn pop() { + unsafe { + let _: () = msg_send![class!(NSCursor), pop]; + } + } + + /// Hides the cursor. Part of a balanced call stack. + pub fn hide() { + unsafe { + let _: () = msg_send![class!(NSCursor), hide]; + } + } + + /// Un-hides the cursor. Part of a balanced call stack. + pub fn unhide() { + unsafe { + let _: () = msg_send![class!(NSCursor), unhide]; + } + } +} diff --git a/src/macos/mod.rs b/src/macos/mod.rs index c715a0a..4f87a6e 100644 --- a/src/macos/mod.rs +++ b/src/macos/mod.rs @@ -21,6 +21,9 @@ pub use alert::Alert; mod app; pub use app::*; +mod cursor; +pub use cursor::{Cursor, CursorType}; + pub mod menu; pub mod printing; pub mod toolbar; diff --git a/src/macos/window/class.rs b/src/macos/window/class.rs index 054f992..0182cda 100644 --- a/src/macos/window/class.rs +++ b/src/macos/window/class.rs @@ -219,6 +219,15 @@ extern fn did_expose(this: &Object, _: Sel, _: id) { window.did_expose(); } +/// Called as part of the responder chain, when, say, the ESC key is hit. If your +/// delegate returns `true` in `should_cancel_on_esc`, then this will allow your +/// window to close when the Esc key is hit. This is mostly useful for Sheet-presented +/// windows, and so the default response from delegates is `false` and must be opted in to. +extern fn cancel(this: &Object, _: Sel, _: id) { + let window = load::(this, WINDOW_DELEGATE_PTR); + window.cancel(); +} + /// Injects an `NSWindow` subclass, with some callback and pointer ivars for what we /// need to do. pub(crate) fn register_window_class() -> *const Class { @@ -292,6 +301,7 @@ pub(crate) fn register_window_class_with_delegate() -> *const decl.add_method(sel!(windowDidChangeOcclusionState:), did_change_occlusion_state:: as extern fn(&Object, _, _)); decl.add_method(sel!(windowDidExpose:), did_expose:: as extern fn(&Object, _, _)); decl.add_method(sel!(windowDidUpdate:), did_update:: as extern fn(&Object, _, _)); + decl.add_method(sel!(cancelOperation:), cancel:: as extern fn (&Object, _, _)); DELEGATE_CLASS = decl.register(); }); diff --git a/src/macos/window/controller/mod.rs b/src/macos/window/controller/mod.rs index 2ac3e7c..2f9e8f0 100644 --- a/src/macos/window/controller/mod.rs +++ b/src/macos/window/controller/mod.rs @@ -52,7 +52,7 @@ impl WindowController where T: WindowDelegate + 'static { /// Allocates and configures an `NSWindowController` in the Objective-C/Cocoa runtime that maps over /// to your supplied delegate. pub fn with(config: WindowConfig, delegate: T) -> Self { - let window = Window::with(config, delegate); + let mut window = Window::with(config, delegate); let objc = unsafe { let window_controller_class = register_window_controller_class::(); @@ -67,7 +67,7 @@ impl WindowController where T: WindowDelegate + 'static { ShareId::from_ptr(controller) }; - if let Some(delegate) = &window.delegate { + if let Some(delegate) = &mut window.delegate { (*delegate).did_load(Window { delegate: None, objc: window.objc.clone() diff --git a/src/macos/window/mod.rs b/src/macos/window/mod.rs index e1bed1b..1b3539c 100644 --- a/src/macos/window/mod.rs +++ b/src/macos/window/mod.rs @@ -10,10 +10,12 @@ use std::unreachable; +use block::ConcreteBlock; + use core_graphics::base::CGFloat; use core_graphics::geometry::{CGRect, CGSize}; -use objc::{msg_send, sel, sel_impl}; +use objc::{msg_send, sel, sel_impl, class}; use objc::runtime::Object; use objc_id::ShareId; @@ -66,6 +68,11 @@ impl Window { /// after we initialize the backing `NSWindow`. pub fn new(config: WindowConfig) -> Window { let objc = unsafe { + // This behavior might make sense to keep as default (YES), but I think the majority of + // apps that would use this toolkit wouldn't be tab-oriented... + let _: () = msg_send![class!(NSWindow), setAllowsAutomaticWindowTabbing:NO]; + + let alloc: id = msg_send![register_window_class(), alloc]; // Other types of backing (Retained/NonRetained) are archaic, dating back to the @@ -105,9 +112,14 @@ impl Window where T: WindowDelegate + 'static { /// enables easier structure of your codebase, and in a way simulates traditional class based /// architectures... just without the subclassing. pub fn with(config: WindowConfig, delegate: T) -> Self { - let delegate = Box::new(delegate); + let mut delegate = Box::new(delegate); let objc = unsafe { + // This behavior might make sense to keep as default (YES), but I think the majority of + // apps that would use this toolkit wouldn't be tab-oriented... + let _: () = msg_send![class!(NSWindow), setAllowsAutomaticWindowTabbing:NO]; + + let alloc: id = msg_send![register_window_class_with_delegate::(), alloc]; // Other types of backing (Retained/NonRetained) are archaic, dating back to the @@ -143,7 +155,7 @@ impl Window where T: WindowDelegate + 'static { }; { - &delegate.did_load(Window { + (&mut delegate).did_load(Window { delegate: None, objc: objc.clone() }); @@ -447,6 +459,37 @@ impl Window { scale as f64 } } + + /// Given a window and callback handler, will run it as a "sheet" (model-ish) and then run the + /// handler once the sheet is dismissed. + /// + /// This is a bit awkward due to Rust semantics; you have to use the same type of Window as the + /// one you're presenting on, but in practice this isn't too bad since you rarely want a Window + /// without a WindowDelegate. + pub fn begin_sheet(&self, window: &Window, completion: F) + where + F: Fn() + Send + Sync + 'static, + W: WindowDelegate + 'static + { + let block = ConcreteBlock::new(move |response: NSInteger| { + completion(); + }); + let block = block.copy(); + + unsafe { + let _: () = msg_send![&*self.objc, beginSheet:&*window.objc completionHandler:block]; + } + } + + /// Closes a sheet. + pub fn end_sheet(&self, window: &Window) + where + W: WindowDelegate + 'static + { + unsafe { + let _: () = msg_send![&*self.objc, endSheet:&*window.objc]; + } + } } impl Drop for Window { diff --git a/src/macos/window/traits.rs b/src/macos/window/traits.rs index fc787c3..775495a 100644 --- a/src/macos/window/traits.rs +++ b/src/macos/window/traits.rs @@ -13,7 +13,7 @@ pub trait WindowDelegate { /// to set up your views and what not. /// /// If you're coming from the web, you can think of this as `DOMContentLoaded`. - fn did_load(&self, _window: Window) {} + fn did_load(&mut self, _window: Window) {} /// Called when the user has attempted to close the window. NOT called when a user quits the /// application. Return false here if you need to handle the edge case. @@ -118,4 +118,8 @@ pub trait WindowDelegate { /// Fired when the Window receives an `update` message from higher up in the chain. fn did_update(&self) {} + + /// If you want your window to close when the `ESC` key is hit, implement this. + /// This is mostly useful for windows that present as modal sheets. + fn cancel(&self) {} } diff --git a/src/progress/enums.rs b/src/progress/enums.rs new file mode 100644 index 0000000..e62278c --- /dev/null +++ b/src/progress/enums.rs @@ -0,0 +1,20 @@ +use crate::foundation::{NSUInteger}; + +/// The type of spinner style you're after. +#[derive(Debug)] +pub enum ProgressIndicatorStyle { + /// A loading bar. + Bar, + + /// A spinning circle. + Spinner +} + +impl From for NSUInteger { + fn from(style: ProgressIndicatorStyle) -> Self { + match style { + ProgressIndicatorStyle::Bar => 0, + ProgressIndicatorStyle::Spinner => 1 + } + } +} diff --git a/src/progress/ios.rs b/src/progress/ios.rs new file mode 100644 index 0000000..1e835ed --- /dev/null +++ b/src/progress/ios.rs @@ -0,0 +1,45 @@ +use std::sync::Once; + +use objc::declare::ClassDecl; +use objc::runtime::{Class, Object, Sel, BOOL}; +use objc::{class, sel, sel_impl}; +use objc_id::Id; + +use crate::foundation::{id, YES, NO, NSUInteger}; +use crate::dragdrop::DragInfo; +use crate::view::{VIEW_DELEGATE_PTR, ViewDelegate}; +use crate::utils::load; + +/// Injects an `NSView` subclass. This is used for the default views that don't use delegates - we +/// have separate classes here since we don't want to waste cycles on methods that will never be +/// used if there's no delegates. +pub(crate) fn register_view_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!(UIView); + let mut decl = ClassDecl::new("RSTView", superclass).unwrap(); + VIEW_CLASS = decl.register(); + }); + + unsafe { VIEW_CLASS } +} + +/// Injects an `NSView` subclass, with some callback and pointer ivars for what we +/// need to do. +pub(crate) fn register_view_class_with_delegate() -> *const Class { + static mut VIEW_CLASS: *const Class = 0 as *const Class; + static INIT: Once = Once::new(); + + INIT.call_once(|| unsafe { + let superclass = class!(UIView); + let mut decl = ClassDecl::new("RSTViewWithDelegate", superclass).unwrap(); + decl.add_ivar::(VIEW_DELEGATE_PTR); + VIEW_CLASS = decl.register(); + }); + + unsafe { + VIEW_CLASS + } +} diff --git a/src/progress/macos.rs b/src/progress/macos.rs new file mode 100644 index 0000000..8c5dec3 --- /dev/null +++ b/src/progress/macos.rs @@ -0,0 +1,21 @@ +use std::sync::Once; + +use objc::declare::ClassDecl; +use objc::runtime::{Class, Object, Sel, BOOL}; +use objc::{class, sel, sel_impl}; + +/// Injects an `NSView` subclass. This is used for the default views that don't use delegates - we +/// have separate classes here since we don't want to waste cycles on methods that will never be +/// used if there's no delegates. +pub(crate) fn register_progress_indicator_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!(NSProgressIndicator); + let decl = ClassDecl::new("RSTProgressIndicator", superclass).unwrap(); + VIEW_CLASS = decl.register(); + }); + + unsafe { VIEW_CLASS } +} diff --git a/src/progress/mod.rs b/src/progress/mod.rs new file mode 100644 index 0000000..a5dfd79 --- /dev/null +++ b/src/progress/mod.rs @@ -0,0 +1,166 @@ +use objc_id::ShareId; +use objc::runtime::{Class, Object}; +use objc::{msg_send, sel, sel_impl}; + +use crate::foundation::{id, nil, YES, NO, NSUInteger}; +use crate::color::Color; +use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension}; + +#[cfg(target_os = "macos")] +mod macos; + +#[cfg(target_os = "macos")] +use macos::{register_progress_indicator_class}; + +#[cfg(target_os = "ios")] +mod ios; + +#[cfg(target_os = "ios")] +use ios::{register_progress_indicator_class}; + +mod enums; +pub use enums::ProgressIndicatorStyle; + +#[derive(Debug)] +pub struct ProgressIndicator { + /// A pointer to the Objective-C runtime view controller. + pub objc: ShareId, + + /// A pointer to the Objective-C runtime top layout constraint. + pub top: LayoutAnchorY, + + /// A pointer to the Objective-C runtime leading layout constraint. + pub leading: LayoutAnchorX, + + /// A pointer to the Objective-C runtime trailing layout constraint. + pub trailing: LayoutAnchorX, + + /// A pointer to the Objective-C runtime bottom layout constraint. + pub bottom: LayoutAnchorY, + + /// A pointer to the Objective-C runtime width layout constraint. + pub width: LayoutAnchorDimension, + + /// A pointer to the Objective-C runtime height layout constraint. + pub height: LayoutAnchorDimension, + + /// A pointer to the Objective-C runtime center X layout constraint. + pub center_x: LayoutAnchorX, + + /// A pointer to the Objective-C runtime center Y layout constraint. + pub center_y: LayoutAnchorY +} + +impl Default for ProgressIndicator { + fn default() -> Self { + ProgressIndicator::new() + } +} + +impl ProgressIndicator { + /// Returns a default `ProgressIndicator`, suitable for + pub fn new() -> Self { + let view = unsafe { + let view: id = msg_send![register_progress_indicator_class(), new]; + let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO]; + + #[cfg(target_os = "macos")] + let _: () = msg_send![view, setWantsLayer:YES]; + + view + }; + + ProgressIndicator { + top: LayoutAnchorY::new(unsafe { msg_send![view, topAnchor] }), + leading: LayoutAnchorX::new(unsafe { msg_send![view, leadingAnchor] }), + trailing: LayoutAnchorX::new(unsafe { msg_send![view, trailingAnchor] }), + bottom: LayoutAnchorY::new(unsafe { msg_send![view, bottomAnchor] }), + width: LayoutAnchorDimension::new(unsafe { msg_send![view, widthAnchor] }), + height: LayoutAnchorDimension::new(unsafe { msg_send![view, heightAnchor] }), + center_x: LayoutAnchorX::new(unsafe { msg_send![view, centerXAnchor] }), + center_y: LayoutAnchorY::new(unsafe { msg_send![view, centerYAnchor] }), + objc: unsafe { ShareId::from_ptr(view) }, + } + } +} + +impl ProgressIndicator { + /// Call this to set the background color for the backing layer. + pub fn set_background_color(&self, color: Color) { + let bg = color.into_platform_specific_color(); + + unsafe { + let cg: id = msg_send![bg, CGColor]; + let layer: id = msg_send![&*self.objc, layer]; + let _: () = msg_send![layer, setBackgroundColor:cg]; + } + } + + /// Starts the animation for an indeterminate indicator. + pub fn start_animation(&self) { + unsafe { + let _: () = msg_send![&*self.objc, startAnimation:nil]; + } + } + + pub fn stop_animation(&self) { + unsafe { + let _: () = msg_send![&*self.objc, stopAnimation:nil]; + } + } + + pub fn increment(&self, by: f64) { + unsafe { + let _: () = msg_send![&*self.objc, incrementBy:by]; + } + } + + pub fn set_style(&self, style: ProgressIndicatorStyle) { + unsafe { + let style = style as NSUInteger; + let _: () = msg_send![&*self.objc, setStyle:style]; + } + } + + pub fn set_indeterminate(&self, is_indeterminate: bool) { + unsafe { + let _: () = msg_send![&*self.objc, setIndeterminate:match is_indeterminate { + true => YES, + false => NO + }]; + } + } +} + +impl Layout for ProgressIndicator { + fn get_backing_node(&self) -> ShareId { + self.objc.clone() + } + + fn add_subview(&self, view: &V) { + let backing_node = view.get_backing_node(); + + unsafe { + let _: () = msg_send![&*self.objc, addSubview:backing_node]; + } + } +} + +impl Drop for ProgressIndicator { + /// A bit of extra cleanup for delegate callback pointers. If the originating `ProgressIndicator` is being + /// dropped, we do some logic to clean it all up (e.g, we go ahead and check to see if + /// this has a superview (i.e, it's in the heirarchy) on the AppKit side. If it does, we go + /// ahead and remove it - this is intended to match the semantics of how Rust handles things). + /// + /// There are, thankfully, no delegates we need to break here. + fn drop(&mut self) { + /*if self.delegate.is_some() { + unsafe { + let superview: id = msg_send![&*self.objc, superview]; + if superview != nil { + let _: () = msg_send![&*self.objc, removeFromSuperview]; + } + } + }*/ + } +} diff --git a/src/quicklook/config.rs b/src/quicklook/config.rs index f91d807..cf1c981 100644 --- a/src/quicklook/config.rs +++ b/src/quicklook/config.rs @@ -23,7 +23,12 @@ pub enum ThumbnailQuality { /// Ask for them all, and pick which one you /// use via your provided callback. - All + All, + + /// Provided in case this is ever expanded by the OS, and the system + /// returns a thumbnail quality type that can't be matched here. Users + /// could then handle the edge case themselves. + Unknown(NSUInteger) } impl From<&ThumbnailQuality> for NSUInteger { @@ -32,11 +37,35 @@ impl From<&ThumbnailQuality> for NSUInteger { ThumbnailQuality::Icon => 1 << 0, ThumbnailQuality::Low => 1 << 1, ThumbnailQuality::High => 1 << 2, - ThumbnailQuality::All => NSUInteger::MAX + ThumbnailQuality::All => NSUInteger::MAX, + ThumbnailQuality::Unknown(x) => *x } } } +impl From for NSUInteger { + fn from(quality: ThumbnailQuality) -> Self { + match quality { + ThumbnailQuality::Icon => 1 << 0, + ThumbnailQuality::Low => 1 << 1, + ThumbnailQuality::High => 1 << 2, + ThumbnailQuality::All => NSUInteger::MAX, + ThumbnailQuality::Unknown(x) => x + } + } +} + +impl From for ThumbnailQuality { + fn from(i: NSUInteger) -> Self { + match i { + 0 => ThumbnailQuality::Icon, + 2 => ThumbnailQuality::Low, + 4 => ThumbnailQuality::High, + NSUInteger::MAX => ThumbnailQuality::All, + i => ThumbnailQuality::Unknown(i) + } + } +} #[derive(Clone, Debug)] pub struct ThumbnailConfig { diff --git a/src/quicklook/mod.rs b/src/quicklook/mod.rs index bbca1aa..beb1a38 100644 --- a/src/quicklook/mod.rs +++ b/src/quicklook/mod.rs @@ -7,7 +7,7 @@ use block::ConcreteBlock; use url::Url; use crate::error::Error; -use crate::foundation::{id, NSUInteger}; +use crate::foundation::{id, nil, NSUInteger}; use crate::image::Image; mod config; @@ -25,13 +25,18 @@ impl ThumbnailGenerator { pub fn generate(&self, url: &Url, config: ThumbnailConfig, callback: F) where - //F: Fn(Result<(Image, ThumbnailQuality), Error>) + Send + Sync + 'static F: Fn(Result<(Image, ThumbnailQuality), Error>) + Send + Sync + 'static { let block = ConcreteBlock::new(move |thumbnail: id, thumbnail_type: NSUInteger, error: id| { - unsafe { - let image = Image::with(msg_send![thumbnail, NSImage]); - callback(Ok((image, ThumbnailQuality::Low))); + if error == nil { + unsafe { + let image = Image::with(msg_send![thumbnail, NSImage]); + let quality = ThumbnailQuality::from(thumbnail_type); + callback(Ok((image, ThumbnailQuality::Low))); + } + } else { + let error = Error::new(error); + callback(Err(error)); } }); diff --git a/src/text/enums.rs b/src/text/enums.rs index 20cb938..c73ebbf 100644 --- a/src/text/enums.rs +++ b/src/text/enums.rs @@ -20,3 +20,39 @@ impl From for NSInteger { } } } + +/// Instructs text controls how to optimize line breaks. +pub enum LineBreakMode { + /// Wrap at word boundaries (the default) + WrapWords, + + /// Wrap at character boundaries + WrapChars, + + /// Clip with no regard + Clip, + + /// Truncate the start, e.g, ...my sentence + TruncateHead, + + /// Truncate the end, e.g, my sentenc... + TruncateTail, + + /// Truncate the middle, e.g, my se...ce + TruncateMiddle +} + +impl Into for LineBreakMode { + fn into(self) -> NSUInteger { + match self { + LineBreakMode::WrapWords => 0, + LineBreakMode::WrapChars => 1, + LineBreakMode::Clip => 2, + LineBreakMode::TruncateHead => 3, + LineBreakMode::TruncateTail => 4, + LineBreakMode::TruncateMiddle => 5 + } + } +} + + diff --git a/src/text/label/mod.rs b/src/text/label/mod.rs index d443b3c..9d6c257 100644 --- a/src/text/label/mod.rs +++ b/src/text/label/mod.rs @@ -44,10 +44,10 @@ use objc_id::ShareId; use objc::runtime::{Class, Object}; use objc::{msg_send, sel, sel_impl}; -use crate::foundation::{id, nil, YES, NO, NSArray, NSInteger, NSString}; +use crate::foundation::{id, nil, YES, NO, NSArray, NSInteger, NSUInteger, NSString}; use crate::color::Color; use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension}; -use crate::text::{Font, TextAlign}; +use crate::text::{Font, TextAlign, LineBreakMode}; #[cfg(target_os = "macos")] mod macos; @@ -237,6 +237,15 @@ impl Label { let _: () = msg_send![&*self.objc, setFont:&*font.objc]; } } + + pub fn set_line_break_mode(&self, mode: LineBreakMode) { + #[cfg(target_os = "macos")] + unsafe { + let cell: id = msg_send![&*self.objc, cell]; + let mode = mode as NSUInteger; + let _: () = msg_send![cell, setLineBreakMode:mode]; + } + } } impl Layout for Label { diff --git a/src/text/mod.rs b/src/text/mod.rs index 3d342bb..7fa5e85 100644 --- a/src/text/mod.rs +++ b/src/text/mod.rs @@ -5,7 +5,7 @@ pub mod label; pub use label::Label; pub mod enums; -pub use enums::TextAlign; +pub use enums::{LineBreakMode, TextAlign}; pub mod font; pub use font::Font;