summary history branches tags files
commit:adbecf37d44645e104c611fe6fbe2a04f9de1917
author:Trevor Bentley
committer:GitHub
date:Tue Jul 25 13:52:29 2017 +0200
parents:a4e618e2057db945bd83b838f23d10f6ae9abf7c, c0bbafe64d47a0578b2191558435e1183412ed79
Merge pull request #7 from mrmekon/0.4.0

Features and fixes for 0.4.0
diff --git a/CHANGELOG.md b/CHANGELOG.md
line changes: +16/-0
index 67b3645..db623ee
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,19 @@
+0.4.0
+-----
+ * Fix crash when setting main touchbar icon
+ * Fix crash when refreshing scrubber with no selected item
+ * Allocate image items instead of using paths
+ * Support for Apple template images
+ * Support for spacer elements
+ * Convenience function for finding bundled images
+ * Support setting label and button widths
+ * 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
 -----
  * Redesigned to track and release allocated memory.  Most leaks fixed.

diff --git a/Cargo.toml b/Cargo.toml
line changes: +1/-2
index 3d05754..78dde6a
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -24,9 +24,8 @@ objc_wrapper = []
 log = "0.3.7"
 log4rs = "0.6.3"
 
-libc = {version = "0.2", optional = true}
-
 [target."cfg(target_os = \"macos\")".dependencies]
+libc = "0.2"
 cocoa = "0.9.2"
 objc-foundation = "0.1.1"
 objc_id = "0.1"

diff --git a/docs/release.txt b/docs/release.txt
line changes: +2/-2
index 667e451..b7f99d0
--- a/docs/release.txt
+++ b/docs/release.txt
@@ -1,4 +1,4 @@
 Release with cargo-release and custom prefix:
 
-$ cargo release --tag-prefix="rubrail-" --dry-run
-$ cargo release --tag-prefix="rubrail-"
+$ cargo release --tag-prefix="rubrail-" -l minor --dry-run
+$ cargo release --tag-prefix="rubrail-" -l minor

diff --git a/examples/example.rs b/examples/example.rs
line changes: +27/-2
index 56278ef..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,7 +72,30 @@ 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 {
@@ -97,7 +121,8 @@ 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, Box::new(move |_s,v| {info!("Slid to: {}", v);}));
+    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);
 
     // Create a another popbar.  This will make a 2-level deep UI.

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: +243/-15
index a8bbc01..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,130 @@ 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
+///
+/// A `TouchbarImage` can be created from a path to a file or from a standard
+/// Apple template image, and then registered with Touch Bar items that support
+/// 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 {
+    AddDetailTemplate,
+    AddTemplate,
+    AlarmTemplate,
+    AudioInputMuteTemplate,
+    AudioInputTemplate,
+    AudioOutputMuteTemplate,
+    AudioOutputVolumeHighTemplate,
+    AudioOutputVolumeLowTemplate,
+    AudioOutputVolumeMediumTemplate,
+    AudioOutputVolumeOffTemplate,
+    BookmarksTemplate,
+    ColorPickerFill,
+    ColorPickerFont,
+    ColorPickerStroke,
+    CommunicationAudioTemplate,
+    CommunicationVideoTemplate,
+    ComposeTemplate,
+    DeleteTemplate,
+    DownloadTemplate,
+    EnterFullScreenTemplate,
+    ExitFullScreenTemplate,
+    FastForwardTemplate,
+    FolderCopyToTemplate,
+    FolderMoveToTemplate,
+    FolderTemplate,
+    GetInfoTemplate,
+    GoBackTemplate,
+    GoDownTemplate,
+    GoForwardTemplate,
+    GoUpTemplate,
+    HistoryTemplate,
+    IconViewTemplate,
+    ListViewTemplate,
+    MailTemplate,
+    NewFolderTemplate,
+    NewMessageTemplate,
+    OpenInBrowserTemplate,
+    PauseTemplate,
+    PlayheadTemplate,
+    PlayPauseTemplate,
+    PlayTemplate,
+    QuickLookTemplate,
+    RecordStartTemplate,
+    RecordStopTemplate,
+    RefreshTemplate,
+    RewindTemplate,
+    RotateLeftTemplate,
+    RotateRightTemplate,
+    SearchTemplate,
+    ShareTemplate,
+    SidebarTemplate,
+    SkipAhead15SecondsTemplate,
+    SkipAhead30SecondsTemplate,
+    SkipAheadTemplate,
+    SkipBack15SecondsTemplate,
+    SkipBack30SecondsTemplate,
+    SkipBackTemplate,
+    SkipToEndTemplate,
+    SkipToStartTemplate,
+    SlideshowTemplate,
+    TagIconTemplate,
+    TextBoldTemplate,
+    TextBoxTemplate,
+    TextCenterAlignTemplate,
+    TextItalicTemplate,
+    TextJustifiedAlignTemplate,
+    TextLeftAlignTemplate,
+    TextListTemplate,
+    TextRightAlignTemplate,
+    TextStrikethroughTemplate,
+    TextUnderlineTemplate,
+    UserAddTemplate,
+    UserGroupTemplate,
+    UserTemplate
+}
+
+/// Identifiers for the type of spacing available between items
+pub enum SpacerType {
+    /// "Small" space, defined by Apple
+    Small,
+    /// "Large" space, defined by Apple
+    Large,
+    /// Flexible space, grows and shrinks as it can/needs
+    Flexible
+}
 
 /// The callback API for managing data in a Scrubber
 ///
@@ -119,6 +242,7 @@ pub trait TScrubberData {
 /// Apple channels which don't permit private API usage.
 ///
 pub trait TTouchbar {
+    /// A concrete implementation of TTouchbar
     type T: TTouchbar;
 
     /// Allocate a new Touch Bar interface
@@ -207,21 +331,18 @@ pub trait TTouchbar {
     /// can themselves contain more popover items.
     ///
     /// All buttons accept an image, text, or both.  If both are provided, they
-    /// will both be displayed at the same time.  When specifying an image,
-    /// follow Apple's guidelines for icons on Retina displays (40px x 40px @
-    /// 150dpi PNG recommended), and remember to handle paths into the app
-    /// bundle correctly.
+    /// will both be displayed at the same time.
     ///
     /// # Arguments
     ///
-    /// * `image` - Full path to an image to display on the button
+    /// * `image` - An image allocated with a `create_image_*` function
     /// * `text` - Text to display on the button
     /// * `bar_id` - Bar to present when this button is pressed
     ///
     /// # Returns
     ///
     /// A newly allocated item which can be added to a bar.
-    fn create_popover_item(&mut self, image: Option<&str>,
+    fn create_popover_item(&mut self, image: Option<&TouchbarImage>,
                            text: Option<&str>, bar_id: &BarId) -> ItemId {0}
 
     /// Create a new label
@@ -251,6 +372,17 @@ pub trait TTouchbar {
     ///
     fn update_label(&mut self, label_id: &ItemId, text: &str) {}
 
+    /// Changes the width of an existing label
+    ///
+    /// Set a fixed width for a label, in pixels.
+    ///
+    /// # Arguments
+    ///
+    /// * `label_id` - Label item to change
+    /// * `width` - New width of label, in pixels
+    ///
+    fn update_label_width(&mut self, label_id: &ItemId, width: u32) {}
+
     /// Create a horizontally scrolling 'scrubber' of text
     ///
     /// Creates a Scrubber, which is a  horizontally scrolling widget filled
@@ -298,24 +430,115 @@ 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
+    ///
+    /// * `space` - The type of spacer to create
+    ///
+    /// # Returns
+    ///
+    /// A newly allocated spacer item that can be added to a bar
+    ///
+    fn create_spacer(&mut self, space: SpacerType) -> ItemId {0}
+
+    /// Create an image from a file path
+    ///
+    /// Creates an image that can be assigned to UI items that display them,
+    /// like buttons and popovers.
+    ///
+    /// Specify a relative or absolute path to the image.
+    ///
+    /// When specifying an image, follow Apple's guidelines for icons on Retina
+    /// displays (40px x 40px @ 150dpi PNG recommended), and remember to handle
+    /// paths into the app bundle correctly.
+    ///
+    /// **WARNING** image memory is _deallocated_ after it is assigned to an
+    /// item.  Do **not** use the same image twice.  If two buttons will have
+    /// the same image, you must allocate the image twice.
+    ///
+    /// # Arguments
+    ///
+    /// * `path` - Path to an image file
+    ///
+    /// # Returns
+    ///
+    /// A newly allocated image that can be added to an item
+    ///
+    fn create_image_from_path(&mut self, path: &str) -> TouchbarImage {0}
+
+    /// Create an image from a template
+    ///
+    /// Creates an image that can be assigned to UI items that display them,
+    /// like buttons and popovers.
+    ///
+    /// See `ImageTemplate` for the supported options.  These templates are
+    /// provided by Apple.
+    ///
+    /// **WARNING** image memory is _deallocated_ after it is assigned to an
+    /// item.  Do **not** use the same image twice.  If two buttons will have
+    /// the same image, you must allocate the image twice.
+    ///
+    /// # Arguments
+    ///
+    /// * `template` - Identifier of the image template to use
+    ///
+    /// # Returns
+    ///
+    /// A newly allocated image that can be added to an item
+    ///
+    fn create_image_from_template(&mut self, template: ImageTemplate) -> TouchbarImage {0}
+
     /// Create a button that triggers a callback when pressed
     ///
     /// All buttons accept an image, text, or both.  If both are provided, they
-    /// will both be displayed at the same time.  When specifying an image,
-    /// follow Apple's guidelines for icons on Retina displays (40px x 40px @
-    /// 150dpi PNG recommended), and remember to handle paths into the app
-    /// bundle correctly.
+    /// will both be displayed at the same time.
     ///
     /// # Arguments
     ///
-    /// * `image` - Full path to an image to display on the button
+    /// * `image` - An image allocated with a `create_image_*` function
     /// * `text` - Text to display on the button
     /// * `cb` - Callback to call when the button is pressed
     ///
     /// # Returns
     ///
     /// A newly allocated item which can be added to a bar.
-    fn create_button(&mut self, image: Option<&str>, text: Option<&str>, cb: ButtonCb) -> ItemId {0}
+    fn create_button(&mut self, image: Option<&TouchbarImage>, text: Option<&str>, cb: ButtonCb) -> ItemId {0}
+
+    /// Changes the image and/or text of a button
+    ///
+    /// # Arguments
+    ///
+    /// * `item` - Button item to change
+    /// * `image` - New image to draw on button (optional)
+    /// * `text` - New text to draw on button (optional)
+    ///
+    fn update_button(&mut self, item: &ItemId, image: Option<&TouchbarImage>, text: Option<&str>) {}
+
+    /// Changes the width of an existing button
+    ///
+    /// Set a fixed width for a button, in pixels.
+    ///
+    /// # Arguments
+    ///
+    /// * `button_id` - Button item to change
+    /// * `width` - New width of button, in pixels
+    ///
+    fn update_button_width(&mut self, button_id: &ItemId, width: u32) {}
 
     /// Create a slider item
     ///
@@ -334,12 +557,17 @@ pub trait TTouchbar {
     ///
     /// * `min` - Minimum value (slider all the way left)
     /// * `max` - Maximum value (slider all the way right)
+    /// * `label` - Text label displayed on left of slider (optional)
+    /// * `continuous` - Whether callback is called while sliding, or only
     /// * `cb` - Callback called when the slider value is changed
+    ///   after it is released.
     ///
     /// # Returns
     ///
     /// A newly allocated slider item
-    fn create_slider(&mut self, min: f64, max: f64, cb: SliderCb) -> ItemId {0}
+    fn create_slider(&mut self, min: f64, max: f64,
+                     label: Option<&str>,
+                     continuous: bool, cb: SliderCb) -> ItemId {0}
 
     /// Update the current position of a slider
     ///

diff --git a/src/lib.rs b/src/lib.rs
line changes: +14/-1
index f17c1dd..34b5f0c
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -51,6 +51,7 @@
 //! Any objects created with a _create*()_ function that are never added to a
 //! bar that is set as the system bar will be leaked.
 //!
+#![deny(missing_docs)]
 
 #[allow(dead_code)]
 #[allow(unused_variables)]
@@ -92,6 +93,13 @@ mod dummy;
 #[cfg(not(feature = "private_api"))]
 pub use dummy::DummyTouchbar as Touchbar;
 
+#[cfg(target_os = "macos")]
+#[cfg(feature = "private_api")]
+pub use touchbar::util;
+
+#[cfg(not(feature = "private_api"))]
+pub use dummy::util;
+
 /// Module for creating and running a simple Mac application
 ///
 /// The `app` module contains helper functions for creating and running a very
@@ -105,6 +113,8 @@ pub mod app {
     extern crate objc;
     extern crate log4rs;
     use std::env;
+    #[cfg(not(feature = "private_api"))]
+    use std::process;
 
     #[cfg(target_os = "macos")]
     #[cfg(feature = "private_api")]
@@ -126,6 +136,7 @@ pub mod app {
         }
     }
     #[cfg(not(feature = "private_api"))]
+    ///
     pub fn init_app() {}
 
     #[cfg(target_os = "macos")]
@@ -144,6 +155,7 @@ pub mod app {
         }
     }
     #[cfg(not(feature = "private_api"))]
+    ///
     pub fn run_forever() { loop {} }
 
     #[cfg(target_os = "macos")]
@@ -162,8 +174,9 @@ pub mod app {
         }
     }
     #[cfg(not(feature = "private_api"))]
+    ///
     pub fn quit() {
-        std::process::exit(0);
+        process::exit(0);
     }
 
     /// Enable logging to a file in the user's home directory

diff --git a/src/touchbar.rs b/src/touchbar.rs
line changes: +414/-25
index 27aafae..366b637
--- a/src/touchbar.rs
+++ b/src/touchbar.rs
@@ -68,12 +68,23 @@ extern {
     pub fn DFRElementSetControlStripPresenceForIdentifier(n: *mut Object, x: i8);
 }
 
-#[cfg(feature = "libc")]
 pub mod util {
+    //! Utility functions for working with the Apple/TouchBar environment
+    //!
+    //! Contains convenience functions that help with some common actions in
+    //! Mac environments.
+
     extern crate libc;
-    use objc::runtime::Object;
+    extern crate cocoa;
+    use super::ItemId;
+    use std::ptr;
     use std::ffi::CStr;
+    use objc::runtime::Object;
+    use objc::runtime::Class;
+    use self::cocoa::foundation::NSString;
+    use self::cocoa::base::nil;
     #[allow(dead_code)]
+    /// Print an NSString object to the global logger
     pub fn print_nsstring(str: *mut Object) {
         unsafe {
             let cstr: *const libc::c_char = msg_send![str, UTF8String];
@@ -81,6 +92,8 @@ pub mod util {
             info!("{}", rstr);
         }
     }
+
+    /// Convert an NSString object into a Rust String
     pub fn nsstring_decode(str: *mut Object) -> String {
         unsafe {
             let cstr: *const libc::c_char = msg_send![str, UTF8String];
@@ -88,13 +101,175 @@ pub mod util {
             rstr
         }
     }
+
+    /// Locate a resource in the executing Mac App bundle
+    ///
+    /// If the program is executing from an App bundle (.app), which it must be
+    /// to use Rubrail correctly, this looks for a resource by name and
+    /// extension in the bundled Resources directory.
+    pub fn bundled_resource_path(name: &str, extension: &str) -> Option<String> {
+        unsafe {
+            let cls = Class::get("NSBundle").unwrap();
+            let bundle: *mut Object = msg_send![cls, mainBundle];
+            let res = NSString::alloc(nil).init_str(name);
+            let ext = NSString::alloc(nil).init_str(extension);
+            let ini: *mut Object = msg_send![bundle, pathForResource:res ofType:ext];
+            let _ = msg_send![res, release];
+            let _ = msg_send![ext, release];
+            let cstr: *const libc::c_char = msg_send![ini, UTF8String];
+            if cstr != ptr::null() {
+                let rstr = CStr::from_ptr(cstr).to_string_lossy().into_owned();
+                return Some(rstr);
+            }
+            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];
+    }
 }
-#[cfg(not(feature = "libc"))]
-pub mod util {
-    use objc::runtime::Object;
-    #[allow(dead_code)]
-    pub fn print_nsstring(_str: *mut Object) {}
-    pub fn nsstring_decode(_str: *mut Object) -> String { String::new() }
+
+macro_rules! image_template {
+    ( $var:ident, $($template:ident),* ) => {
+        match $var {
+            $(
+                ImageTemplate::$template => format!("NSTouchBar{}", stringify!($template)),
+            )*
+        }
+    }
+}
+
+impl ImageTemplate {
+    fn objc(template: ImageTemplate) -> *mut Object {
+        unsafe {
+            let s = image_template!(
+                template,
+                AddDetailTemplate,
+                AddTemplate,
+                AlarmTemplate,
+                AudioInputMuteTemplate,
+                AudioInputTemplate,
+                AudioOutputMuteTemplate,
+                AudioOutputVolumeHighTemplate,
+                AudioOutputVolumeLowTemplate,
+                AudioOutputVolumeMediumTemplate,
+                AudioOutputVolumeOffTemplate,
+                BookmarksTemplate,
+                ColorPickerFill,
+                ColorPickerFont,
+                ColorPickerStroke,
+                CommunicationAudioTemplate,
+                CommunicationVideoTemplate,
+                ComposeTemplate,
+                DeleteTemplate,
+                DownloadTemplate,
+                EnterFullScreenTemplate,
+                ExitFullScreenTemplate,
+                FastForwardTemplate,
+                FolderCopyToTemplate,
+                FolderMoveToTemplate,
+                FolderTemplate,
+                GetInfoTemplate,
+                GoBackTemplate,
+                GoDownTemplate,
+                GoForwardTemplate,
+                GoUpTemplate,
+                HistoryTemplate,
+                IconViewTemplate,
+                ListViewTemplate,
+                MailTemplate,
+                NewFolderTemplate,
+                NewMessageTemplate,
+                OpenInBrowserTemplate,
+                PauseTemplate,
+                PlayheadTemplate,
+                PlayPauseTemplate,
+                PlayTemplate,
+                QuickLookTemplate,
+                RecordStartTemplate,
+                RecordStopTemplate,
+                RefreshTemplate,
+                RewindTemplate,
+                RotateLeftTemplate,
+                RotateRightTemplate,
+                SearchTemplate,
+                ShareTemplate,
+                SidebarTemplate,
+                SkipAhead15SecondsTemplate,
+                SkipAhead30SecondsTemplate,
+                SkipAheadTemplate,
+                SkipBack15SecondsTemplate,
+                SkipBack30SecondsTemplate,
+                SkipBackTemplate,
+                SkipToEndTemplate,
+                SkipToStartTemplate,
+                SlideshowTemplate,
+                TagIconTemplate,
+                TextBoldTemplate,
+                TextBoxTemplate,
+                TextCenterAlignTemplate,
+                TextItalicTemplate,
+                TextJustifiedAlignTemplate,
+                TextLeftAlignTemplate,
+                TextListTemplate,
+                TextRightAlignTemplate,
+                TextStrikethroughTemplate,
+                TextUnderlineTemplate,
+                UserAddTemplate,
+                UserGroupTemplate,
+                UserTemplate
+            );
+            let name = NSString::alloc(nil).init_str(&s);
+            let _ = msg_send![name, autorelease];
+            name
+        }
+    }
 }
 
 #[derive(PartialEq, Debug)]
@@ -104,6 +279,7 @@ enum ItemType {
     Slider,
     Scrubber,
     Popover,
+    Spacer,
 }
 
 struct InternalBar {
@@ -129,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>,
 }
 
@@ -157,6 +334,7 @@ impl InternalItem {
             self.control = None;
             self.scrubber = None;
             self.button_cb = None;
+            self.swipe_cb = None;
             self.slider_cb = None;
         }
     }
@@ -180,7 +358,7 @@ impl RustTouchbarDelegateWrapper {
             objc_ident as u64
         }
     }
-    fn alloc_button(&mut self, image: Option<&str>, text: Option<&str>,
+    fn alloc_button(&mut self, image: Option<&TouchbarImage>, text: Option<&str>,
                     target: *mut Object, sel: SEL) -> *mut Object {
         unsafe {
             let text = match text {
@@ -188,12 +366,7 @@ impl RustTouchbarDelegateWrapper {
                 None => nil,
             };
             let image = match image {
-                Some(i) => {
-                    let filename = NSString::alloc(nil).init_str(i);
-                    let objc_image = NSImage::alloc(nil).initWithContentsOfFile_(filename);
-                    let _ = msg_send![filename, release];
-                    objc_image
-                },
+                Some(i) => *i as *mut Object,
                 None => nil,
             };
             let cls = Class::get("NSButton").unwrap();
@@ -244,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
@@ -276,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
@@ -324,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 {
@@ -351,7 +568,6 @@ impl TTouchbar for Touchbar {
             let objc_image = NSImage::alloc(nil).initWithContentsOfFile_(filename);
             let _:() = msg_send![self.objc, setIcon: objc_image];
             let _ = msg_send![filename, release];
-            let _ = msg_send![objc_image, release];
         }
     }
 
@@ -372,7 +588,7 @@ impl TTouchbar for Touchbar {
             bar as u64
         }
     }
-    fn create_popover_item(&mut self, image: Option<&str>,
+    fn create_popover_item(&mut self, image: Option<&TouchbarImage>,
                            text: Option<&str>, bar_id: &BarId) -> ItemId {
         unsafe {
             let bar = *bar_id as *mut Object;
@@ -400,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);
@@ -441,11 +658,14 @@ impl TTouchbar for Touchbar {
     }
     fn create_label(&mut self, text: &str) -> ItemId {
         unsafe {
-            let frame = NSRect::new(NSPoint::new(0., 0.), NSSize::new(300., 44.));
+            let frame = NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 40.));
             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];
             let text = NSString::alloc(nil).init_str(text);
             let _:() = msg_send![label, setStringValue: text];
             let _ = msg_send![text, release];
@@ -464,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);
@@ -474,11 +695,30 @@ 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];
         }
     }
+    fn update_label_width(&mut self, label_id: &ItemId, width: u32) {
+        unsafe {
+            //let _ = msg_send![label, setAutoresizingMask: 0];
+            //let _ = msg_send![label, setFrameSize: NSSize::new(600., 10.)];
+            //let _ = msg_send![label, setBoundsSize: NSSize::new(600., 10.)];
+            //let _ = msg_send![item, setFrameSize: NSSize::new(600., 30.)];
+            //let _ = msg_send![label, setPreferredMaxLayoutWidth: 500.];
+            //let constraints: *mut Object = msg_send![label, constraints];
+            //let count: u32 = msg_send![constraints, count];
+            //info!("CONSTRAINTS: {}", count);
+            let item: *mut Object = *label_id as *mut Object;
+            let label: *mut Object = msg_send![item, view];
+            let anchor: *mut Object = msg_send![label, widthAnchor];
+            let constraint: *mut Object = msg_send![anchor, constraintEqualToConstant: width as f64];
+            let _ = msg_send![constraint, setActive: YES];
+        }
+    }
+
     fn create_text_scrubber(&mut self, data: Rc<TScrubberData>) -> ItemId {
         unsafe {
             let ident = self.generate_ident();
@@ -511,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);
@@ -528,13 +769,89 @@ impl TTouchbar for Touchbar {
         unsafe {
             let item = *scrub_id as *mut Object;
             let scrubber: *mut Object = msg_send![item, view];
-            let sel_idx: u32 = msg_send![scrubber, selectedIndex];
+            let sel_idx: i32 = msg_send![scrubber, selectedIndex];
             let _:() = msg_send![scrubber, reloadData];
             // reload clears the selected item.  re-select it.
-            let _:() = msg_send![scrubber, setSelectedIndex: sel_idx];
+            if sel_idx >= 0 {
+                let _:() = msg_send![scrubber, setSelectedIndex: sel_idx];
+            }
         }
     }
-    fn create_button(&mut self, image: Option<&str>, text: Option<&str>, cb: ButtonCb) -> ItemId {
+
+    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 {
+                SpacerType::Small =>
+                    NSString::alloc(nil).init_str("NSTouchBarItemIdentifierFixedSpaceSmall"),
+                SpacerType::Large =>
+                    NSString::alloc(nil).init_str("NSTouchBarItemIdentifierFixedSpaceLarge"),
+                SpacerType::Flexible =>
+                    NSString::alloc(nil).init_str("NSTouchBarItemIdentifierFlexibleSpace"),
+            };
+            // This is really stupid, since it's just a string, but to fit into the
+            // rest of the system we go ahead and allocate a whole internal item for it.
+
+            // And since it doesn't have an ident, just use it as its own ident.  Both
+            // the view and ident will be sent a release at shutdown, so retain it an
+            // extra time here to keep the references balanced.
+            let _ = msg_send![s, retain];
+
+            let internal = InternalItem {
+                _type: ItemType::Spacer,
+                view: s,
+                ident: s as u64,
+                control: None,
+                scrubber: None,
+                button_cb: None,
+                slider_cb: None,
+                swipe_cb: None,
+                child_bar: None,
+            };
+            self.item_map.insert(s as u64, internal);
+            s as ItemId
+        }
+    }
+
+    fn create_image_from_path(&mut self, path: &str) -> TouchbarImage {
+        unsafe {
+            let filename = NSString::alloc(nil).init_str(path);
+            let objc_image = NSImage::alloc(nil).initWithContentsOfFile_(filename);
+            let _ = msg_send![filename, release];
+            objc_image as TouchbarImage
+        }
+    }
+
+    fn create_image_from_template(&mut self, template: ImageTemplate) -> TouchbarImage {
+        unsafe {
+            let cls = Class::get("NSImage").unwrap();
+            let image: *mut Object = msg_send![cls, imageNamed: ImageTemplate::objc(template)];
+            let _ = msg_send![image, retain];
+            image as TouchbarImage
+        }
+    }
+
+    fn create_button(&mut self, image: Option<&TouchbarImage>, text: Option<&str>, cb: ButtonCb) -> ItemId {
         unsafe {
             let ident = self.generate_ident();
             let target = (&*self.objc.clone()) as *const ObjcAppDelegate as *mut Object;
@@ -554,22 +871,58 @@ 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);
             item as u64
         }
     }
-    fn create_slider(&mut self, min: f64, max: f64, cb: SliderCb) -> ItemId {
+
+    fn update_button(&mut self, item: &ItemId, image: Option<&TouchbarImage>, text: Option<&str>) {
+        unsafe {
+            let item = *item as *mut Object;
+            let btn: *mut Object = msg_send![item, view];
+            if let Some(image) = image {
+                let image = *image as *mut Object;
+                let _ = msg_send![btn, setImage: image];
+                let _ = msg_send![image, release];
+            }
+            if let Some(text) = text {
+                let objc_text = NSString::alloc(nil).init_str(text);
+                let _ = msg_send![btn, setTitle: objc_text];
+                let _ = msg_send![objc_text, release];
+            }
+        }
+    }
+
+    fn update_button_width(&mut self, button_id: &ItemId, width: u32) {
+        unsafe {
+            let item: *mut Object = *button_id as *mut Object;
+            let control: *mut Object = msg_send![item, view];
+            let anchor: *mut Object = msg_send![control, widthAnchor];
+            let constraint: *mut Object = msg_send![anchor, constraintEqualToConstant: width as f64];
+            let _ = msg_send![constraint, setActive: YES];
+        }
+    }
+
+    fn create_slider(&mut self, min: f64, max: f64,
+                     label: Option<&str>,
+                     continuous: bool, cb: SliderCb) -> ItemId {
         unsafe {
             let ident = self.generate_ident();
             let cls = RRSliderTouchBarItem::class();
             let item: *mut Object = msg_send![cls, alloc];
             let item: *mut Object = msg_send![item, initWithIdentifier: ident];
             let slider: *mut Object = msg_send![item, slider];
+            if let Some(label) = label {
+                let objc_text: *mut Object = NSString::alloc(nil).init_str(label);
+                msg_send![item, setLabel: objc_text];
+                msg_send![objc_text, release];
+            }
             msg_send![slider, setMinValue: min];
             msg_send![slider, setMaxValue: max];
-            msg_send![slider, setContinuous: YES];
+            msg_send![slider, setContinuous: continuous];
             msg_send![item, setTarget: self.objc.clone()];
             msg_send![item, setAction: sel!(slider:)];
 
@@ -581,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);
@@ -732,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);
+                        }
                     }
                 }
             }
@@ -744,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);
                     }
                 }
             }
@@ -833,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);