// Rubrail Example // // Demonstrates using Rubrail to layout and interact with a persistent Touchbar // on Macbooks that support it. // // Usage notes: // // To access the touchbar, Rubrail *must* execute inside an OS X app bundle. // Since Cargo doesn't do that itself, this example uses the Trampoline feature // of the fruitbasket crate to relaunch itself in an app bundle. // // Simply run: // // $ cargo test && cargo run --example example // // extern crate rubrail; extern crate fruitbasket; use rubrail::Touchbar; use rubrail::TTouchbar; use rubrail::TScrubberData; use rubrail::ItemId; use rubrail::SwipeState; #[macro_use] extern crate log; use std::rc::Rc; use std::cell::RefCell; struct TouchbarHandler { devices: RefCell>, } impl TScrubberData for TouchbarHandler { fn count(&self, _item: ItemId) -> u32 { self.devices.borrow().len() as u32 } fn text(&self, _item: ItemId, idx: u32) -> String { self.devices.borrow()[idx as usize].to_string() } fn width(&self, _item: ItemId, idx: u32) -> u32 { // 10px per character + some padding seems to work nicely for the default // font. no idea what it's like on other machines. does the touchbar // font change? ¯\_(ツ)_/¯ let len = self.devices.borrow()[idx as usize].len() as u32; let width = len * 8 + 20; width } fn touch(&self, _item: ItemId, idx: u32) { info!("scrub touch: {}", idx); } } fn populate(bar_rc: Rc>, count: u32, stopper: fruitbasket::FruitStopper) { // Get touchbar from the refcell. It's wrapped in a cell so // it can be passed around in the button callbacks. let mut tb = (bar_rc).borrow_mut(); // Create the lowest level "root" touchbar let mut barid = tb.create_bar(); // Create a quit button for root bar let quit_stopper = stopper.clone(); let quit_id = tb.create_button(None, Some("Quit"), Box::new(move |_| {quit_stopper.stop();})); // Create an action button for the root bar. When clicked, it will // close the bar and re-create itself. let bar_copy = bar_rc.clone(); let text = format!("button{}", count); let button1_id = tb.create_button(None, Some(&text), Box::new(move |_| { populate(bar_copy.clone(), count+1, stopper.clone()) })); // Create a text label for the root bar 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); // Support double-clicking the label with one finger tb.add_item_tap_gesture(&label1_id, 2, 1, Box::new(move |_item| { info!("Label double-clicked!"); })); // 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![ "one".to_string(), "two".to_string(), "a little bit longer one".to_string(), "three".to_string(), "this one is really quite a bit longer than the others".to_string()]), }); // Create a scrubber for the root bar let scrubber1_id = tb.create_text_scrubber(scrubber.clone()); tb.select_scrubber_item(&scrubber1_id, 1); // Create a 'popbar', a second level deep bar let mut popbar1_id = tb.create_bar(); let popbutton1_id = tb.create_popover_item(None, Some("Popbar1"), &popbar1_id); // Create another scrubber with the same data, for the popbar. // Note that the data and callbacks are shared, but this is a different // instance and can store a different active selection. let scrubber2_id = tb.create_text_scrubber(scrubber.clone()); tb.select_scrubber_item(&scrubber2_id, 3); // Create a slider for the popbar. 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. let mut popbar2_id = tb.create_bar(); let popbutton2_id = tb.create_popover_item(None, Some("Popbar2"), &popbar2_id); // Create buttons to display on the popbars let popbar_button_id = tb.create_button(None, Some("1 level deep"), Box::new(move |_| {})); let deep_button_id = tb.create_button(None, Some("2 levels deep"), Box::new(move |_| {})); // Layout the deepest (2-level) popbar tb.add_items_to_bar(&mut popbar2_id, vec![deep_button_id]); // Layout the middle (1-level) popbar tb.add_items_to_bar(&mut popbar1_id, vec![popbar_button_id, popbutton2_id, slider1_id, scrubber2_id]); // Layout the root bar tb.add_items_to_bar(&mut barid, vec![quit_id, button1_id, popbutton1_id, label1_id, scrubber1_id]); // Register the root bar and display it. tb.set_bar_as_root(barid); } fn main() { // Write log to home directory fruitbasket::create_logger(".rubrail.log", fruitbasket::LogDir::Home, 5, 2).unwrap(); // Initialize OS X application. let icon = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("examples").join("icon.png"); let mut nsapp = fruitbasket::Trampoline::new( "rubrail_example", "rubrail_example", "com.trevorbentley.rubrail_example") .icon("icon.png") .version(env!("CARGO_PKG_VERSION")) .plist_key("LSBackgroundOnly", "1") .resource(icon.to_str().unwrap()) .build(fruitbasket::InstallDir::Custom("target/".to_string())).unwrap(); nsapp.set_activation_policy(fruitbasket::ActivationPolicy::Prohibited); // Initialize the touchbar let bar_rc = Rc::new(RefCell::new(Touchbar::alloc("bar"))); let stopper = nsapp.stopper(); // Populate the touchbar with UI elements populate(bar_rc.clone(), 1, stopper); // Enter OS X application loop. nsapp.run(fruitbasket::RunPeriod::Forever).expect("Failed to launch app"); }