summary history branches tags files
commit:c0bbafe64d47a0578b2191558435e1183412ed79
author:Trevor Bentley
committer:Trevor Bentley
date:Tue Jul 25 08:16:47 2017 +0200
parents:583bad01e9b4661bb93008df012ac60889d1acc1
Support adding swipe gestures to touch bar items

Fix font in multi-line labels.
Fix invalid items sent to button callbacks.
diff --git a/CHANGELOG.md b/CHANGELOG.md
line changes: +3/-0
index 076ea19..db623ee
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,9 @@
  * Support non-continous sliders
  * Support changing button contents
  * Support label for sliders
+ * Support adding swipe gestures to touch bar items
+ * Fix font in multi-line labels.
+ * Fix invalid items sent to button callbacks.
 
 0.3.0
 -----

diff --git a/examples/example.rs b/examples/example.rs
line changes: +25/-2
index 0851ba6..e19455d
--- a/examples/example.rs
+++ b/examples/example.rs
@@ -21,6 +21,7 @@ use rubrail::Touchbar;
 use rubrail::TTouchbar;
 use rubrail::TScrubberData;
 use rubrail::ItemId;
+use rubrail::SwipeState;
 
 #[macro_use]
 extern crate log;
@@ -71,9 +72,31 @@ fn populate(bar_rc: Rc<RefCell<Touchbar>>, count: u32) {
     }));
 
     // Create a text label for the root bar
-    let label1_id = tb.create_label("This is a label\nWith two rows");
+    let label1_id = tb.create_label("This is a label");
+    tb.update_label(&label1_id, "This is a label\nWith two rows");
     tb.update_label_width(&label1_id, 100);
 
+    // Add a swipe gesture to the label that changes the text color to
+    // increasingly green as you swipe right, or increasingly red as you swipe
+    // left, and resets to white when released.
+    tb.add_item_swipe_gesture(&label1_id, Box::new(move |item,state,translation| {
+        let color: f64 = match translation.abs().trunc() as u32 {
+            t if t < 10 => 1.0,
+            t if t > 100 => 0.0,
+            _ => (45. / translation.abs()),
+        };
+        let rgba = match state {
+            SwipeState::Ended => (1.0, 1.0, 1.0, 1.0),
+            _ => {
+                match translation.is_sign_positive() {
+                    true => (color, 1.0, color, 1.0),
+                    false => (1.0, color, color, 1.0),
+                }
+            }
+        };
+        unsafe { rubrail::util::set_text_color(item, rgba.0, rgba.1, rgba.2, rgba.3); }
+    }));
+
     // Create a data backend for scrolling text "scrubbers"
     let scrubber = Rc::new(TouchbarHandler {
         devices: RefCell::new(vec![
@@ -98,7 +121,7 @@ fn populate(bar_rc: Rc<RefCell<Touchbar>>, count: u32) {
     tb.select_scrubber_item(&scrubber2_id, 3);
 
     // Create a slider for the popbar.
-    let slider1_id = tb.create_slider(0.0, 50.0, true, "Slide",
+    let slider1_id = tb.create_slider(0.0, 50.0, Some("Slide"), true,
                                       Box::new(move |_s,v| {info!("Slid to: {}", v);}));
     tb.update_slider(&slider1_id, 15.0);
 

diff --git a/src/dummy.rs b/src/dummy.rs
line changes: +16/-0
index d04a897..48852cf
--- a/src/dummy.rs
+++ b/src/dummy.rs
@@ -1,6 +1,7 @@
 #[allow(dead_code)]
 use super::interface::*;
 
+///
 pub struct DummyTouchbar {}
 
 #[allow(dead_code)]
@@ -8,3 +9,18 @@ impl TTouchbar for DummyTouchbar {
     type T = DummyTouchbar;
     fn alloc(_title: &str) -> DummyTouchbar { DummyTouchbar {} }
 }
+
+///
+pub mod util {
+    use super::ItemId;
+    ///
+    pub fn print_nsstring(_str: *mut u64) {}
+    ///
+    pub fn nsstring_decode(_str: *mut u64) -> String { String::new() }
+    ///
+    pub fn bundled_resource_path(_name: &str, _extension: &str) -> Option<String> { None }
+    ///
+    pub unsafe fn set_bg_color(_item: &ItemId, _r: f64, _g: f64, _b: f64, _alpha: f64) { }
+    ///
+    pub unsafe fn set_text_color(_item: &ItemId, _r: f64, _g: f64, _b: f64, _alpha: f64) { }
+}

diff --git a/src/interface.rs b/src/interface.rs
line changes: +43/-2
index f1ef338..4771356
--- a/src/interface.rs
+++ b/src/interface.rs
@@ -45,7 +45,7 @@ pub type ItemId = u64;
 /// # Arguments
 ///
 /// * first - `ItemId` of the button that was pressed
-pub type ButtonCb = Box<Fn(ItemId)>;
+pub type ButtonCb = Box<Fn(&ItemId)>;
 
 /// A callback that is called when the value of a slide on a Touch Bar changes
 ///
@@ -56,7 +56,21 @@ pub type ButtonCb = Box<Fn(ItemId)>;
 ///
 /// * first - `ItemId` of the slider that was changed
 /// * second - Current value of the slider
-pub type SliderCb = Box<Fn(ItemId, f64)>;
+pub type SliderCb = Box<Fn(&ItemId, f64)>;
+
+/// A callback that is called when an item is swiped
+///
+/// `SwipeCb` is expected to be a Boxed closure, and it receives the
+/// `ItemId` of an item as it is being swiped if a swipe gesture recognizer
+/// has been added to the item.
+///
+/// # Arguments
+///
+/// * first - `ItemId` of the item that was swiped
+/// * second - `SwipeState` representing the current gesture's lifecycle
+/// * third - Horizontal translation of the swipe, in pixels (positive is right,
+///   negative is left).
+pub type SwipeCb = Box<Fn(&ItemId, SwipeState, f64)>;
 
 /// An allocated image that can be added to items
 ///
@@ -65,6 +79,19 @@ pub type SliderCb = Box<Fn(ItemId, f64)>;
 /// images, such as buttons and popovers.
 pub type TouchbarImage = u64;
 
+/// State of the current swipe gesture on an item
+#[derive(PartialEq, Debug)]
+pub enum SwipeState {
+    /// Swipe gesture is newly detected (finger touched)
+    Began,
+    /// Existing swipe gesture has continued/changed (finger swiped)
+    Changed,
+    /// Swipe gesture has ended (finger lifted)
+    Ended,
+    /// Invalid state.  Never sent to callbacks.
+    Unknown,
+}
+
 /// Identifiers for Apple's standard button image templates
 #[allow(missing_docs)]
 pub enum ImageTemplate {
@@ -403,6 +430,20 @@ pub trait TTouchbar {
     ///
     fn refresh_scrubber(&mut self, scrub_id: &ItemId) {}
 
+    /// Register a swipe gesture handler with a Touch Bar item
+    ///
+    /// Registers a callback to be called when the given item is swiped with a
+    /// horizontal motion.  This can be used to detect a finger sliding back and
+    /// forth on a Touch Bar item, even for items that are normally
+    /// non-interactive (like labels).
+    ///
+    /// # Arguments
+    ///
+    /// * `item` - Item to add the gesture detection to
+    /// * `cb` - Callback to call when a touch is detected
+    ///
+    fn add_item_swipe_gesture(&mut self, item: &ItemId, cb: SwipeCb) {}
+
     /// Create space between items in a bar
     ///
     /// # Arguments

diff --git a/src/lib.rs b/src/lib.rs
line changes: +4/-5
index 3df10f3..34b5f0c
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -98,11 +98,7 @@ pub use dummy::DummyTouchbar as Touchbar;
 pub use touchbar::util;
 
 #[cfg(not(feature = "private_api"))]
-pub mod util {
-    pub fn print_nsstring(_str: *mut u64) {}
-    pub fn nsstring_decode(_str: *mut u64) -> String { String::new() }
-    pub fn bundled_resource_path(_name: &str, _extension: &str) -> Option<String> { None }
-}
+pub use dummy::util;
 
 /// Module for creating and running a simple Mac application
 ///
@@ -140,6 +136,7 @@ pub mod app {
         }
     }
     #[cfg(not(feature = "private_api"))]
+    ///
     pub fn init_app() {}
 
     #[cfg(target_os = "macos")]
@@ -158,6 +155,7 @@ pub mod app {
         }
     }
     #[cfg(not(feature = "private_api"))]
+    ///
     pub fn run_forever() { loop {} }
 
     #[cfg(target_os = "macos")]
@@ -176,6 +174,7 @@ pub mod app {
         }
     }
     #[cfg(not(feature = "private_api"))]
+    ///
     pub fn quit() {
         process::exit(0);
     }

diff --git a/src/touchbar.rs b/src/touchbar.rs
line changes: +162/-2
index d031c2a..366b637
--- a/src/touchbar.rs
+++ b/src/touchbar.rs
@@ -76,6 +76,7 @@ pub mod util {
 
     extern crate libc;
     extern crate cocoa;
+    use super::ItemId;
     use std::ptr;
     use std::ffi::CStr;
     use objc::runtime::Object;
@@ -123,6 +124,55 @@ pub mod util {
             None
         }
     }
+
+    /// Sets the backgroundColor attribute on an item's view.
+    ///
+    /// This is an **unsafe** helper function to set the backgroundColor
+    /// attribute on the view of an item.  It does *not* verify that the item
+    /// actually has a view, nor that the view supports backgroundColor, hence
+    /// **unsafe**.
+    ///
+    /// Known compatible items: labels
+    ///
+    /// # Arguments
+    ///
+    /// * `item` - The `ItemId` to color
+    /// * `r` - Red value (0.0 - 1.0)
+    /// * `g` - Green value (0.0 - 1.0)
+    /// * `b` - Blue value (0.0 - 1.0)
+    /// * `alpha` - Alpha value (0.0 - 1.0)
+    pub unsafe fn set_bg_color(item: &ItemId, r: f64, g: f64, b: f64, alpha: f64) {
+        let item = *item as *mut Object;
+        let view: *mut Object = msg_send![item, view];
+        let cls = Class::get("NSColor").unwrap();
+        let color: *mut Object = msg_send![
+            cls, colorWithRed: r green: g blue: b alpha: alpha];
+        msg_send![view, setBackgroundColor: color];
+    }
+
+    /// Sets the textColor attribute on an item's view.
+    ///
+    /// This is an **unsafe** helper function to set the textColor attribute on
+    /// the view of an item.  It does *not* verify that the item actually has a
+    /// view, nor that the view supports textColor, hence **unsafe**.
+    ///
+    /// Known compatible items: labels
+    ///
+    /// # Arguments
+    ///
+    /// * `item` - The `ItemId` to color
+    /// * `r` - Red value (0.0 - 1.0)
+    /// * `g` - Green value (0.0 - 1.0)
+    /// * `b` - Blue value (0.0 - 1.0)
+    /// * `alpha` - Alpha value (0.0 - 1.0)
+    pub unsafe fn set_text_color(item: &ItemId, r: f64, g: f64, b: f64, alpha: f64) {
+        let item = *item as *mut Object;
+        let view: *mut Object = msg_send![item, view];
+        let cls = Class::get("NSColor").unwrap();
+        let color: *mut Object = msg_send![
+            cls, colorWithRed: r green: g blue: b alpha: alpha];
+        msg_send![view, setTextColor: color];
+    }
 }
 
 macro_rules! image_template {
@@ -255,6 +305,7 @@ struct InternalItem {
     scrubber: Option<Rc<TScrubberData>>,
     button_cb: Option<ButtonCb>,
     slider_cb: Option<SliderCb>,
+    swipe_cb: Option<SwipeCb>,
     child_bar: Option<ItemId>,
 }
 
@@ -283,6 +334,7 @@ impl InternalItem {
             self.control = None;
             self.scrubber = None;
             self.button_cb = None;
+            self.swipe_cb = None;
             self.slider_cb = None;
         }
     }
@@ -365,6 +417,14 @@ impl RustTouchbarDelegateWrapper {
             None => None,
         }
     }
+    fn find_view_from_control(&self, item: &ItemId) -> Option<*mut Object> {
+        match self.item_map.values().into_iter().filter(|x| {
+            x.control.is_some() && x.control.unwrap() as ItemId == *item
+        }).next() {
+            Some(item) => Some(item.view),
+            None => None,
+        }
+    }
     fn find_bar_ident(&self, bar: &ItemId) -> Option<Ident> {
         match self.bar_map.values().into_iter().filter(|x| {
             x.view as ItemId == *bar
@@ -397,6 +457,14 @@ impl RustTouchbarDelegateWrapper {
             None => None,
         }
     }
+    fn find_swipe_cb(&self, item: u64) -> Option<&SwipeCb> {
+        match self.item_map.values().into_iter().filter(|x| {
+            x.control.is_some() && x.control.unwrap() as u64 == item
+        }).next() {
+            Some(item) => item.swipe_cb.as_ref(),
+            None => None,
+        }
+    }
     fn find_slider_cb(&self, sldr: u64) -> Option<&SliderCb> {
         match self.item_map.values().into_iter().filter(|x| {
             x._type == ItemType::Slider && x.view as u64 == sldr
@@ -445,6 +513,34 @@ impl RustTouchbarDelegateWrapper {
             self.free_bar_allocations(subbar);
         }
     }
+    unsafe fn set_label_font_for_text(label: *mut Object, text: &str) {
+        //let constraints: *mut Object = msg_send![label, constraints];
+        //let height_constraint: *mut Object = msg_send![constraints, firstObject];
+        //if height_constraint != nil {
+        //    // TODO: if re-enabled, must use NSLayoutConstraint identifier to
+        //    // make sure the correct constraint is removed
+        //    msg_send![label, removeConstraint: height_constraint];
+        //}
+        if text.contains("\n") {
+            // Shrink font for multi-line labels.  This makes for quite small text.
+            let cls = Class::get("NSFont").unwrap();
+            let default_size:f64 = msg_send![cls, systemFontSize];
+            let custom_font: *mut Object = msg_send![cls, systemFontOfSize: default_size - 3.0];
+            msg_send![label, setFont: custom_font];
+
+            //let anchor: *mut Object = msg_send![label, heightAnchor];
+            //let constraint: *mut Object = msg_send![anchor, constraintEqualToConstant: 30. as f64];
+            //let _ = msg_send![constraint, setActive: YES];
+        }
+        else {
+            // Enlarge the font for single lines.  For some reason it defaults
+            // to smaller than other elements.
+            let cls = Class::get("NSFont").unwrap();
+            let default_size:f64 = msg_send![cls, systemFontSize];
+            let custom_font: *mut Object = msg_send![cls, systemFontOfSize: default_size + 3.0];
+            msg_send![label, setFont: custom_font];
+        }
+    }
 }
 
 impl TTouchbar for Touchbar {
@@ -520,6 +616,7 @@ impl TTouchbar for Touchbar {
                 scrubber: None,
                 button_cb: None,
                 slider_cb: None,
+                swipe_cb: None,
                 child_bar: Some(bar as ItemId),
             };
             self.item_map.insert(item as u64, internal);
@@ -565,6 +662,7 @@ impl TTouchbar for Touchbar {
             let cls = Class::get("NSTextField").unwrap();
             let label: *mut Object = msg_send![cls, alloc];
             let label: *mut Object = msg_send![label, initWithFrame: frame];
+            RustTouchbarDelegateWrapper::set_label_font_for_text(label, text);
             let _:() = msg_send![label, setEditable: NO];
             let cell: *mut Object = msg_send![label, cell];
             let _:() = msg_send![cell, setWraps: NO];
@@ -586,6 +684,7 @@ impl TTouchbar for Touchbar {
                 scrubber: None,
                 button_cb: None,
                 slider_cb: None,
+                swipe_cb: None,
                 child_bar: None
             };
             self.item_map.insert(item as u64, internal);
@@ -596,6 +695,7 @@ impl TTouchbar for Touchbar {
         unsafe {
             let item: *mut Object = *label_id as *mut Object;
             let label: *mut Object = msg_send![item, view];
+            RustTouchbarDelegateWrapper::set_label_font_for_text(label, text);
             let text = NSString::alloc(nil).init_str(text);
             let _:() = msg_send![label, setStringValue: text];
             let _ = msg_send![text, release];
@@ -651,6 +751,7 @@ impl TTouchbar for Touchbar {
                 scrubber: Some(data),
                 button_cb: None,
                 slider_cb: None,
+                swipe_cb: None,
                 child_bar: None,
             };
             self.item_map.insert(item as u64, internal);
@@ -677,6 +778,27 @@ impl TTouchbar for Touchbar {
         }
     }
 
+    fn add_item_swipe_gesture(&mut self, item_id: &ItemId, cb: SwipeCb) {
+        unsafe {
+            let item = *item_id as *mut Object;
+            let view: *mut Object = msg_send![item, view];
+            if view == nil {
+                return;
+            }
+            msg_send![view, setAllowedTouchTypes: 1]; // NSTouchTypeMaskDirect
+            let cls = Class::get("NSPanGestureRecognizer").unwrap();
+            let gesture: *mut Object = msg_send![cls, alloc];
+            let gesture: *mut Object = msg_send![gesture,
+                                                 initWithTarget: self.objc.clone()
+                                                 action: sel!(swipeGesture:)];
+            msg_send![gesture, setAllowedTouchTypes: 1]; // NSTouchTypeMaskDirect
+            msg_send![view, addGestureRecognizer: gesture];
+            let mut internal_item = self.item_map.remove(item_id).unwrap();
+            internal_item.swipe_cb = Some(cb);
+            self.item_map.insert(*item_id, internal_item);
+        }
+    }
+
     fn create_spacer(&mut self, space: SpacerType) -> ItemId {
         unsafe {
             let s = match space {
@@ -703,6 +825,7 @@ impl TTouchbar for Touchbar {
                 scrubber: None,
                 button_cb: None,
                 slider_cb: None,
+                swipe_cb: None,
                 child_bar: None,
             };
             self.item_map.insert(s as u64, internal);
@@ -748,6 +871,7 @@ impl TTouchbar for Touchbar {
                 scrubber: None,
                 button_cb: Some(cb),
                 slider_cb: None,
+                swipe_cb: None,
                 child_bar: None,
             };
             self.item_map.insert(item as u64, internal);
@@ -810,6 +934,7 @@ impl TTouchbar for Touchbar {
                 scrubber: None,
                 button_cb: None,
                 slider_cb: Some(cb),
+                swipe_cb: None,
                 child_bar: None,
             };
             self.item_map.insert(item as u64, internal);
@@ -961,7 +1086,39 @@ impl INSObject for ObjcAppDelegate {
                     let ptr: u64 = *this.get_ivar("_rust_wrapper");
                     let wrapper = &mut *(ptr as *mut RustTouchbarDelegateWrapper);
                     if let Some(ref cb) = wrapper.find_button_cb(sender) {
-                        cb(sender);
+                        // Sender is the button.  Find the owning touchbar item:
+                        let item = wrapper.find_view_from_control(&sender).unwrap();
+                        cb(&(item as u64));
+                    }
+                }
+            }
+            extern fn objc_swipe_gesture(this: &mut Object, _cmd: Sel, sender: u64) {
+                unsafe {
+                    let ptr: u64 = *this.get_ivar("_rust_wrapper");
+                    let wrapper = &mut *(ptr as *mut RustTouchbarDelegateWrapper);
+                    let gesture = sender as *mut Object;
+                    let view: *mut Object = msg_send![gesture, view];
+                    let translation: NSPoint = msg_send![gesture,
+                                                         translationInView: view];
+                    let gesture_state: u32 = msg_send![gesture, state];
+                    let state = match gesture_state {
+                        // NSGestureRecognizerStateBegan
+                        1 => SwipeState::Began,
+                        // NSGestureRecognizerStateChanged
+                        2 => SwipeState::Changed,
+                        // NSGestureRecognizerStateEnded
+                        3 => SwipeState::Ended,
+                        // NSGestureRecognizerStatePossible,
+                        // NSGestureRecognizerStateCancelled,
+                        // NSGestureRecognizerStateFailed
+                        _ => SwipeState::Unknown,
+                    };
+                    if state != SwipeState::Unknown {
+                        if let Some(ref cb) = wrapper.find_swipe_cb(view as u64) {
+                            // Sender is the view.  Find the owning touchbar item:
+                            let item = wrapper.find_view_from_control(&(view as u64)).unwrap();
+                            cb(&(item as u64), state, translation.x);
+                        }
                     }
                 }
             }
@@ -973,7 +1130,7 @@ impl INSObject for ObjcAppDelegate {
                         let item = sender as *mut Object;
                         let slider: *mut Object = msg_send![item, slider];
                         let value: f64 = msg_send![slider, doubleValue];
-                        cb(sender, value);
+                        cb(&sender, value);
                     }
                 }
             }
@@ -1062,6 +1219,9 @@ impl INSObject for ObjcAppDelegate {
                 let f: extern fn(&mut Object, Sel, u64) = objc_button;
                 decl.add_method(sel!(button:), f);
 
+                let f: extern fn(&mut Object, Sel, u64) = objc_swipe_gesture;
+                decl.add_method(sel!(swipeGesture:), f);
+
                 let f: extern fn(&mut Object, Sel, u64) = objc_slider;
                 decl.add_method(sel!(slider:), f);