diff --git a/.github/workflows/nannou.yml b/.github/workflows/nannou.yml index 603f06aca..00b0840f9 100644 --- a/.github/workflows/nannou.yml +++ b/.github/workflows/nannou.yml @@ -177,14 +177,8 @@ jobs: - name: Cargo publish nannou_osc continue-on-error: true run: cargo publish --token $CRATESIO_TOKEN --manifest-path nannou_osc/Cargo.toml - - name: Cargo publish nannou_timeline - continue-on-error: true - run: cargo publish --token $CRATESIO_TOKEN --manifest-path nannou_timeline/Cargo.toml - name: Wait for crates.io run: sleep 15 - - name: Cargo publish nannou_conrod - continue-on-error: true - run: cargo publish --token $CRATESIO_TOKEN --manifest-path nannou_conrod/Cargo.toml - name: Cargo publish nannou_egui continue-on-error: true run: cargo publish --token $CRATESIO_TOKEN --manifest-path nannou_egui/Cargo.toml @@ -201,6 +195,7 @@ jobs: profile: minimal toolchain: stable override: true + - uses: Swatinem/rust-cache@v1 - name: Install mdbook uses: actions-rs/cargo@v1 with: @@ -225,6 +220,7 @@ jobs: profile: minimal toolchain: stable override: true + - uses: Swatinem/rust-cache@v1 - name: Run guide tests uses: actions-rs/cargo@v1 with: diff --git a/Cargo.toml b/Cargo.toml index d783a1de9..f22498c96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,6 @@ members = [ "guide/book_tests", "nannou", "nannou_audio", - "nannou_conrod", "nannou_core", "nannou_egui", "nannou_egui_demo_app", @@ -15,7 +14,6 @@ members = [ "nannou_new", "nannou_osc", "nannou_package", - "nannou_timeline", "nannou_wgpu", "nature_of_code", "scripts/run_all_examples", diff --git a/README.md b/README.md index 4db68b35f..d47e3df4c 100644 --- a/README.md +++ b/README.md @@ -72,14 +72,12 @@ The following nannou **libraries** are included within this repository. | --- | --- | --- | | [**`nannou`**](./nannou) | [![Crates.io](https://img.shields.io/crates/v/nannou.svg)](https://crates.io/crates/nannou) [![docs.rs](https://docs.rs/nannou/badge.svg)](https://docs.rs/nannou/) | App, sketching, graphics, windowing and UI. | | [**`nannou_audio`**](./nannou_audio) | [![Crates.io](https://img.shields.io/crates/v/nannou_audio.svg)](https://crates.io/crates/nannou_audio) [![docs.rs](https://docs.rs/nannou_audio/badge.svg)](https://docs.rs/nannou_audio/) | Audio hosts, devices and streams. | -| [**`nannou_conrod`**](./nannou_conrod) | [![Crates.io](https://img.shields.io/crates/v/nannou_conrod.svg)](https://crates.io/crates/nannou_conrod) [![docs.rs](https://docs.rs/nannou_conrod/badge.svg)](https://docs.rs/nannou_conrod/) | For creating conrod UIs in nannou apps. | | [**`nannou_core`**](./nannou_core) | [![Crates.io](https://img.shields.io/crates/v/nannou_core.svg)](https://crates.io/crates/nannou_core) [![docs.rs](https://docs.rs/nannou_core/badge.svg)](https://docs.rs/nannou_core/) | Just-the-core for headless, embedded and libraries. | | [**`nannou_egui`**](./nannou_egui) | [![Crates.io](https://img.shields.io/crates/v/nannou_egui.svg)](https://crates.io/crates/nannou_egui) [![docs.rs](https://docs.rs/nannou_egui/badge.svg)](https://docs.rs/nannou_egui/) | For creating egui UIs in nannou apps. | | [**`nannou_isf`**](./nannou_isf) | [![Crates.io](https://img.shields.io/crates/v/nannou_isf.svg)](https://crates.io/crates/nannou_isf) [![docs.rs](https://docs.rs/nannou_isf/badge.svg)](https://docs.rs/nannou_isf/) | An Interactive Shader Format pipeline. | | [**`nannou_laser`**](./nannou_laser) | [![Crates.io](https://img.shields.io/crates/v/nannou_laser.svg)](https://crates.io/crates/nannou_laser) [![docs.rs](https://docs.rs/nannou_laser/badge.svg)](https://docs.rs/nannou_laser/) | LASER devices, streams and path optimisation. | | [**`nannou_mesh`**](./nannou_mesh) | [![Crates.io](https://img.shields.io/crates/v/nannou_mesh.svg)](https://crates.io/crates/nannou_mesh) [![docs.rs](https://docs.rs/nannou_mesh/badge.svg)](https://docs.rs/nannou_mesh/) | API for composing meshes from channels. | | [**`nannou_osc`**](./nannou_osc) | [![Crates.io](https://img.shields.io/crates/v/nannou_osc.svg)](https://crates.io/crates/nannou_osc) [![docs.rs](https://docs.rs/nannou_osc/badge.svg)](https://docs.rs/nannou_osc/) | Simple OSC sender and receiver. | -| [**`nannou_timeline`**](./nannou_timeline) | [![Crates.io](https://img.shields.io/crates/v/nannou_timeline.svg)](https://crates.io/crates/nannou_timeline) [![docs.rs](https://docs.rs/nannou_timeline/badge.svg)](https://docs.rs/nannou_timeline/) | A timeline widget for nannou GUIs. | | [**`nannou_wgpu`**](./nannou_wgpu) | [![Crates.io](https://img.shields.io/crates/v/nannou_wgpu.svg)](https://crates.io/crates/nannou_wgpu) [![docs.rs](https://docs.rs/nannou_wgpu/badge.svg)](https://docs.rs/nannou_wgpu/) | WGPU helpers and extensions. | ## Tools diff --git a/examples/Cargo.toml b/examples/Cargo.toml index c84930225..826272b29 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -20,12 +20,10 @@ hotglsl = { git = "https://github.com/nannou-org/hotglsl", branch = "master" } hrtf = "0.2" nannou = { version ="0.18.0", path = "../nannou" } nannou_audio = { version ="0.18.0", path = "../nannou_audio" } -nannou_conrod = { version ="0.18.0", path = "../nannou_conrod" } nannou_egui = { version = "0.5.0", path = "../nannou_egui" } nannou_isf = { version = "0.1.0", path = "../nannou_isf" } nannou_laser = { version ="0.18.0", features = ["ffi", "ilda-idtf"], path = "../nannou_laser" } nannou_osc = { version ="0.18.0", path = "../nannou_osc" } -nannou_timeline = { version ="0.18.0", features = ["serde1"], path = "../nannou_timeline" } pitch_calc = { version = "0.12", features = ["serde"] } time_calc = { version= "0.13", features = ["serde"] } walkdir = "2" @@ -190,15 +188,6 @@ path = "templates/template_sketch.rs" # User Interface [[example]] -name = "named_color_reference" -path = "ui/conrod/named_color_reference.rs" -[[example]] -name = "simple_ui" -path = "ui/conrod/simple_ui.rs" -[[example]] -name = "timeline_demo" -path = "ui/conrod/timeline_demo.rs" -[[example]] name = "circle_packing" path = "ui/egui/circle_packing.rs" [[example]] diff --git a/examples/ui/conrod/named_color_reference.rs b/examples/ui/conrod/named_color_reference.rs deleted file mode 100644 index d76b20172..000000000 --- a/examples/ui/conrod/named_color_reference.rs +++ /dev/null @@ -1,426 +0,0 @@ -//! A simple example presenting all of the named colors in alphabetical order. -//! -//! This is also used as a test for nannou developers to test that colors specified via the `Ui` -//! and `Draw` API behave as expected, and easily compare them to the online css reference: -//! https://www.w3schools.com/cssref/css_colors.asp - -use nannou::prelude::*; -use nannou_conrod as ui; -use nannou_conrod::position::{Place, Relative}; -use nannou_conrod::prelude::*; - -fn main() { - nannou::app(model).update(update).run(); -} - -struct Model { - ui: Ui, - color_list: widget::Id, - selected_color_index: usize, -} - -fn model(app: &App) -> Model { - check_color_list_lengths(); - app.set_loop_mode(LoopMode::Wait); - let w_id = app - .new_window() - .raw_event(raw_window_event) - .view(view) - .build() - .unwrap(); - let mut ui = ui::builder(app).window(w_id).build().unwrap(); - let color_list = ui.generate_widget_id(); - let selected_color_index = 0; - Model { - ui, - color_list, - selected_color_index, - } -} - -fn raw_window_event(app: &App, model: &mut Model, event: &ui::RawWindowEvent) { - model.ui.handle_raw_event(app, event); -} - -fn update(app: &App, model: &mut Model, _update: Update) { - let Model { - ref mut ui, - ref mut selected_color_index, - color_list, - } = *model; - - // Calling `set_widgets` allows us to instantiate some widgets. - let ui = &mut ui.set_widgets(); - - let win_rect = app.main_window().rect(); - - // The list of colours. - let (mut events, scrollbar) = widget::ListSelect::single(ALL_NAMED_COLORS.len()) - .flow_down() - .item_size(30.0) - .scrollbar_on_top() - .w_h(200.0, win_rect.h() as _) - .top_left() - .set(color_list, ui); - - while let Some(event) = events.next(ui, |i| i == *selected_color_index) { - use nannou_conrod::widget::list_select::Event; - match event { - Event::Item(item) => { - let label = &ALL_NAMED_COLOR_NAMES[item.i]; - let (r, g, b) = ALL_NAMED_COLORS[item.i].into(); - let color = ui::color::rgb_bytes(r, g, b); - let button = widget::Button::new() - .border(0.0) - .color(color) - .label(label) - .label_font_size(10) - .label_x(Relative::Place(Place::Start(Some(20.0)))) - .label_color(color.plain_contrast()); - item.set(button, ui); - } - - Event::Selection(new_index) => *selected_color_index = new_index, - - _ => (), - } - } - - if let Some(scrollbar) = scrollbar { - scrollbar.set(ui); - } -} - -// Draw the state of your `Model` into the given `Frame` here. -fn view(app: &App, model: &Model, frame: Frame) { - let draw = app.draw(); - - // Draw the background with the color. - draw.background() - .color(ALL_NAMED_COLORS[model.selected_color_index]); - - // Also draw a rectangle with the same color to ensure our vertex data is accurate too! - // If we can see this rectangle on the bottom half of the window, something has gone wrong. - let win = app.main_window().rect(); - draw.rect() - .w_h(win.w(), win.h() * 0.5) - .x_y(0.0, -win.h() * 0.25) - .color(ALL_NAMED_COLORS[model.selected_color_index]); - - // Clear the background and draw the rect. - draw.to_frame(app, &frame).unwrap(); - - // Draw the color list to the frame. - model.ui.draw_to_frame(app, &frame).unwrap(); -} - -fn check_color_list_lengths() { - assert_eq!( - ALL_NAMED_COLORS.len(), - ALL_NAMED_COLOR_NAMES.len(), - "Woops! It looks like either `ALL_NAMED_COLORS` or `ALL_NAMED_COLOR_NAMES` was updated \ - without updating the other!", - ) -} - -pub const ALL_NAMED_COLORS: &[nannou::color::Srgb] = &[ - ALICEBLUE, - ANTIQUEWHITE, - AQUA, - AQUAMARINE, - AZURE, - BEIGE, - BISQUE, - BLACK, - BLANCHEDALMOND, - BLUE, - BLUEVIOLET, - BROWN, - BURLYWOOD, - CADETBLUE, - CHARTREUSE, - CHOCOLATE, - CORAL, - CORNFLOWERBLUE, - CORNSILK, - CRIMSON, - CYAN, - DARKBLUE, - DARKCYAN, - DARKGOLDENROD, - DARKGRAY, - DARKGREEN, - DARKGREY, - DARKKHAKI, - DARKMAGENTA, - DARKOLIVEGREEN, - DARKORANGE, - DARKORCHID, - DARKRED, - DARKSALMON, - DARKSEAGREEN, - DARKSLATEBLUE, - DARKSLATEGRAY, - DARKSLATEGREY, - DARKTURQUOISE, - DARKVIOLET, - DEEPPINK, - DEEPSKYBLUE, - DIMGRAY, - DIMGREY, - DODGERBLUE, - FIREBRICK, - FLORALWHITE, - FORESTGREEN, - FUCHSIA, - GAINSBORO, - GHOSTWHITE, - GOLD, - GOLDENROD, - GRAY, - GREEN, - GREENYELLOW, - GREY, - HONEYDEW, - HOTPINK, - INDIANRED, - INDIGO, - IVORY, - KHAKI, - LAVENDER, - LAVENDERBLUSH, - LAWNGREEN, - LEMONCHIFFON, - LIGHTBLUE, - LIGHTCORAL, - LIGHTCYAN, - LIGHTGOLDENRODYELLOW, - LIGHTGRAY, - LIGHTGREEN, - LIGHTGREY, - LIGHTPINK, - LIGHTSALMON, - LIGHTSEAGREEN, - LIGHTSKYBLUE, - LIGHTSLATEGRAY, - LIGHTSLATEGREY, - LIGHTSTEELBLUE, - LIGHTYELLOW, - LIME, - LIMEGREEN, - LINEN, - MAGENTA, - MAROON, - MEDIUMAQUAMARINE, - MEDIUMBLUE, - MEDIUMORCHID, - MEDIUMPURPLE, - MEDIUMSEAGREEN, - MEDIUMSLATEBLUE, - MEDIUMSPRINGGREEN, - MEDIUMTURQUOISE, - MEDIUMVIOLETRED, - MIDNIGHTBLUE, - MINTCREAM, - MISTYROSE, - MOCCASIN, - NAVAJOWHITE, - NAVY, - OLDLACE, - OLIVE, - OLIVEDRAB, - ORANGE, - ORANGERED, - ORCHID, - PALEGOLDENROD, - PALEGREEN, - PALETURQUOISE, - PALEVIOLETRED, - PAPAYAWHIP, - PEACHPUFF, - PERU, - PINK, - PLUM, - POWDERBLUE, - PURPLE, - REBECCAPURPLE, - RED, - ROSYBROWN, - ROYALBLUE, - SADDLEBROWN, - SALMON, - SANDYBROWN, - SEAGREEN, - SEASHELL, - SIENNA, - SILVER, - SKYBLUE, - SLATEBLUE, - SLATEGRAY, - SLATEGREY, - SNOW, - SPRINGGREEN, - STEELBLUE, - TAN, - TEAL, - THISTLE, - TOMATO, - TURQUOISE, - VIOLET, - WHEAT, - WHITE, - WHITESMOKE, - YELLOW, - YELLOWGREEN, -]; - -pub const ALL_NAMED_COLOR_NAMES: &[&str] = &[ - "ALICEBLUE", - "ANTIQUEWHITE", - "AQUA", - "AQUAMARINE", - "AZURE", - "BEIGE", - "BISQUE", - "BLACK", - "BLANCHEDALMOND", - "BLUE", - "BLUEVIOLET", - "BROWN", - "BURLYWOOD", - "CADETBLUE", - "CHARTREUSE", - "CHOCOLATE", - "CORAL", - "CORNFLOWERBLUE", - "CORNSILK", - "CRIMSON", - "CYAN", - "DARKBLUE", - "DARKCYAN", - "DARKGOLDENROD", - "DARKGRAY", - "DARKGREEN", - "DARKGREY", - "DARKKHAKI", - "DARKMAGENTA", - "DARKOLIVEGREEN", - "DARKORANGE", - "DARKORCHID", - "DARKRED", - "DARKSALMON", - "DARKSEAGREEN", - "DARKSLATEBLUE", - "DARKSLATEGRAY", - "DARKSLATEGREY", - "DARKTURQUOISE", - "DARKVIOLET", - "DEEPPINK", - "DEEPSKYBLUE", - "DIMGRAY", - "DIMGREY", - "DODGERBLUE", - "FIREBRICK", - "FLORALWHITE", - "FORESTGREEN", - "FUCHSIA", - "GAINSBORO", - "GHOSTWHITE", - "GOLD", - "GOLDENROD", - "GRAY", - "GREEN", - "GREENYELLOW", - "GREY", - "HONEYDEW", - "HOTPINK", - "INDIANRED", - "INDIGO", - "IVORY", - "KHAKI", - "LAVENDER", - "LAVENDERBLUSH", - "LAWNGREEN", - "LEMONCHIFFON", - "LIGHTBLUE", - "LIGHTCORAL", - "LIGHTCYAN", - "LIGHTGOLDENRODYELLOW", - "LIGHTGRAY", - "LIGHTGREEN", - "LIGHTGREY", - "LIGHTPINK", - "LIGHTSALMON", - "LIGHTSEAGREEN", - "LIGHTSKYBLUE", - "LIGHTSLATEGRAY", - "LIGHTSLATEGREY", - "LIGHTSTEELBLUE", - "LIGHTYELLOW", - "LIME", - "LIMEGREEN", - "LINEN", - "MAGENTA", - "MAROON", - "MEDIUMAQUAMARINE", - "MEDIUMBLUE", - "MEDIUMORCHID", - "MEDIUMPURPLE", - "MEDIUMSEAGREEN", - "MEDIUMSLATEBLUE", - "MEDIUMSPRINGGREEN", - "MEDIUMTURQUOISE", - "MEDIUMVIOLETRED", - "MIDNIGHTBLUE", - "MINTCREAM", - "MISTYROSE", - "MOCCASIN", - "NAVAJOWHITE", - "NAVY", - "OLDLACE", - "OLIVE", - "OLIVEDRAB", - "ORANGE", - "ORANGERED", - "ORCHID", - "PALEGOLDENROD", - "PALEGREEN", - "PALETURQUOISE", - "PALEVIOLETRED", - "PAPAYAWHIP", - "PEACHPUFF", - "PERU", - "PINK", - "PLUM", - "POWDERBLUE", - "PURPLE", - "REBECCAPURPLE", - "RED", - "ROSYBROWN", - "ROYALBLUE", - "SADDLEBROWN", - "SALMON", - "SANDYBROWN", - "SEAGREEN", - "SEASHELL", - "SIENNA", - "SILVER", - "SKYBLUE", - "SLATEBLUE", - "SLATEGRAY", - "SLATEGREY", - "SNOW", - "SPRINGGREEN", - "STEELBLUE", - "TAN", - "TEAL", - "THISTLE", - "TOMATO", - "TURQUOISE", - "VIOLET", - "WHEAT", - "WHITE", - "WHITESMOKE", - "YELLOW", - "YELLOWGREEN", -]; diff --git a/examples/ui/conrod/simple_ui.rs b/examples/ui/conrod/simple_ui.rs deleted file mode 100644 index 7ea63df9d..000000000 --- a/examples/ui/conrod/simple_ui.rs +++ /dev/null @@ -1,159 +0,0 @@ -use nannou::prelude::*; -use nannou_conrod as ui; -use nannou_conrod::prelude::*; - -fn main() { - nannou::app(model).update(update).run(); -} - -struct Model { - ui: Ui, - ids: Ids, - resolution: f32, - scale: f32, - rotation: f32, - color: Rgb, - position: Point2, -} - -widget_ids! { - struct Ids { - resolution, - scale, - rotation, - random_color, - position, - } -} - -fn model(app: &App) -> Model { - // Set the loop mode to wait for events, an energy-efficient option for pure-GUI apps. - app.set_loop_mode(LoopMode::Wait); - - // Create a window. - let w_id = app - .new_window() - .raw_event(raw_window_event) - .view(view) - .build() - .unwrap(); - - // Create the UI for our window. - let mut ui = ui::builder(app).window(w_id).build().unwrap(); - - // Generate some ids for our widgets. - let ids = Ids::new(ui.widget_id_generator()); - - // Init our variables - let resolution = 6.0; - let scale = 200.0; - let rotation = 0.0; - let position = pt2(0.0, 0.0); - let color = rgb(1.0, 0.0, 1.0); - - Model { - ui, - ids, - resolution, - scale, - rotation, - position, - color, - } -} - -fn raw_window_event(app: &App, model: &mut Model, event: &ui::RawWindowEvent) { - model.ui.handle_raw_event(app, event); -} - -fn update(_app: &App, model: &mut Model, _update: Update) { - // Calling `set_widgets` allows us to instantiate some widgets. - let ui = &mut model.ui.set_widgets(); - - fn slider(val: f32, min: f32, max: f32) -> widget::Slider<'static, f32> { - widget::Slider::new(val, min, max) - .w_h(200.0, 30.0) - .label_font_size(15) - .rgb(0.3, 0.3, 0.3) - .label_rgb(1.0, 1.0, 1.0) - .border(0.0) - } - - for value in slider(model.resolution, 3.0, 15.0) - .top_left_with_margin(20.0) - .label("Resolution") - .set(model.ids.resolution, ui) - { - model.resolution = value.round(); - } - - for value in slider(model.scale, 10.0, 500.0) - .down(10.0) - .label("Scale") - .set(model.ids.scale, ui) - { - model.scale = value; - } - - for value in slider(model.rotation, -PI, PI) - .down(10.0) - .label("Rotation") - .set(model.ids.rotation, ui) - { - model.rotation = value; - } - - for _click in widget::Button::new() - .down(10.0) - .w_h(200.0, 60.0) - .label("Random Color") - .label_font_size(15) - .rgb(0.3, 0.3, 0.3) - .label_rgb(1.0, 1.0, 1.0) - .border(0.0) - .set(model.ids.random_color, ui) - { - model.color = rgb(random(), random(), random()); - } - - for (x, y) in widget::XYPad::new( - model.position.x, - -200.0, - 200.0, - model.position.y, - -200.0, - 200.0, - ) - .down(10.0) - .w_h(200.0, 200.0) - .label("Position") - .label_font_size(15) - .rgb(0.3, 0.3, 0.3) - .label_rgb(1.0, 1.0, 1.0) - .border(0.0) - .set(model.ids.position, ui) - { - model.position = Point2::new(x, y); - } -} - -// Draw the state of your `Model` into the given `Frame` here. -fn view(app: &App, model: &Model, frame: Frame) { - // Begin drawing - let draw = app.draw(); - - draw.background().rgb(0.02, 0.02, 0.02); - - draw.ellipse() - .xy(model.position) - .radius(model.scale) - .resolution(model.resolution) - .rotate(model.rotation) - .color(model.color); - - // Write the result of our drawing to the window's frame. - draw.to_frame(app, &frame).unwrap(); - - // Draw the state of the `Ui` to the frame. - model.ui.draw_to_frame(app, &frame).unwrap(); -} diff --git a/examples/ui/conrod/timeline_demo.rs b/examples/ui/conrod/timeline_demo.rs deleted file mode 100644 index b695f1a3f..000000000 --- a/examples/ui/conrod/timeline_demo.rs +++ /dev/null @@ -1,424 +0,0 @@ -use nannou::prelude::*; -use nannou_conrod as ui; -use nannou_conrod::prelude::*; -use nannou_timeline as timeline; -use pitch_calc as pitch; -use std::iter::once; -use time_calc as time; -use timeline::track::automation::{BangValue as Bang, Envelope, Point, ToggleValue as Toggle}; -use timeline::track::piano_roll; -use timeline::{bars, track}; - -const BPM: time::calc::Bpm = 140.0; -const ONE_SECOND_MS: time::calc::Ms = 1_000.0; -const PPQN: time::Ppqn = 9600; -const WIDTH: u32 = 800; -const HEIGHT: u32 = 600; - -fn main() { - nannou::app(model).update(update).run(); -} - -struct Model { - ui: Ui, - ids: Ids, - timeline_data: TimelineData, - playing: bool, -} - -// Implement the Serialize and Deserialize traits only if the serde feature is enabled. -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -struct TimelineData { - playhead_ticks: time::Ticks, - bars: Vec, - notes: Vec, - tempo_envelope: track::automation::numeric::Envelope, - octave_envelope: track::automation::numeric::Envelope, - toggle_envelope: track::automation::toggle::Envelope, - bang_envelope: track::automation::bang::Envelope, -} - -// Create all of our unique `WidgetId`s with the `widget_ids!` macro. -widget_ids! { - struct Ids { - window, - ruler, - timeline, - } -} - -fn model(app: &App) -> Model { - let window_id = app - .new_window() - .key_pressed(key_pressed) - .raw_event(raw_window_event) - .size(WIDTH, HEIGHT) - .title("Timeline Demo") - .view(view) - .build() - .unwrap(); - - // Create the UI. - let mut ui = ui::builder(app).window(window_id).build().unwrap(); - let ids = Ids::new(ui.widget_id_generator()); - - // Start the playhead at the beginning. - let playhead_ticks = time::Ticks::from(0); - - // A sequence of bars with varying time signatures. - let bars = vec![ - time::TimeSig { top: 4, bottom: 4 }, - time::TimeSig { top: 4, bottom: 4 }, - time::TimeSig { top: 6, bottom: 8 }, - time::TimeSig { top: 6, bottom: 8 }, - time::TimeSig { top: 4, bottom: 4 }, - time::TimeSig { top: 4, bottom: 4 }, - time::TimeSig { top: 7, bottom: 8 }, - time::TimeSig { top: 7, bottom: 8 }, - ]; - - let notes = bars::WithStarts::new(bars.iter().cloned(), PPQN) - .enumerate() - .map(|(i, (time_sig, start))| { - let end = start + time_sig.ticks_per_bar(PPQN); - let period = timeline::Period { start, end }; - let pitch = pitch::Step((24 + (i * 5) % 12) as f32).to_letter_octave(); - piano_roll::Note { period, pitch } - }) - .collect(); - - let tempo_envelope = { - let start = Point { - ticks: time::Ticks(0), - value: 20.0, - }; - let points = bars::Periods::new(bars.iter().cloned(), PPQN) - .enumerate() - .map(|(i, period)| Point { - ticks: period.end, - value: 20.0 + (i + 1) as f32 * 60.0 % 220.0, - }); - Envelope::from_points(once(start).chain(points), 20.0, 240.0) - }; - - let octave_envelope = { - let start = Point { - ticks: time::Ticks(0), - value: 0, - }; - let points = bars::WithStarts::new(bars.iter().cloned(), PPQN) - .enumerate() - .flat_map(|(i, (ts, mut start))| { - let bar_end = start + ts.ticks_per_bar(PPQN); - let mut j = 0; - std::iter::from_fn(move || { - if start >= bar_end { - return None; - } - - let end = start + time::Ticks(PPQN as _); - let end = if end > bar_end { bar_end } else { end }; - let point = Point { - ticks: end, - value: 1 + ((i as i32 + j as i32) * 3) % 12, - }; - start = end; - j += 1; - Some(point) - }) - }); - Envelope::from_points(once(start).chain(points), 0, 12) - }; - - let toggle_envelope = { - let start = Point { - ticks: time::Ticks(0), - value: Toggle(random()), - }; - let points = bars::Periods::new(bars.iter().cloned(), PPQN).map(|period| Point { - ticks: period.end, - value: Toggle(random()), - }); - Envelope::from_points(once(start).chain(points), Toggle(false), Toggle(true)) - }; - - let bang_envelope = { - let points = bars::Periods::new(bars.iter().cloned(), PPQN).map(|period| Point { - ticks: period.start, - value: Bang, - }); - Envelope::from_points(points, Bang, Bang) - }; - - let timeline_data = TimelineData { - playhead_ticks, - bars, - notes, - tempo_envelope, - octave_envelope, - toggle_envelope, - bang_envelope, - }; - - Model { - ui, - ids, - timeline_data, - playing: false, - } -} - -fn raw_window_event(app: &App, model: &mut Model, event: &ui::RawWindowEvent) { - model.ui.handle_raw_event(app, event); -} - -fn update(_app: &App, model: &mut Model, update: Update) { - let Model { - ids, - ui, - timeline_data, - playing, - .. - } = model; - - // Update the user interface. - set_widgets(&mut ui.set_widgets(), ids, timeline_data); - - // Get the current bpm from the tempo_envelope automation track. - use timeline::track::automation::EnvelopeTrait; // needed to use the .y(Ticks) method on the envelope - let tempo_value = timeline_data.tempo_envelope.y(timeline_data.playhead_ticks); - let current_bpm = tempo_value.unwrap_or(BPM as f32) as f64; - - // Update the playhead. - let delta_secs = if *playing { - update.since_last.secs() - } else { - 0.0 - }; - let delta_ticks = time::Ms(delta_secs * ONE_SECOND_MS).to_ticks(current_bpm, PPQN); - let total_duration_ticks = - timeline::bars_duration_ticks(timeline_data.bars.iter().cloned(), PPQN); - let previous_playhead_ticks = timeline_data.playhead_ticks.clone(); - timeline_data.playhead_ticks = - (timeline_data.playhead_ticks + delta_ticks) % total_duration_ticks; - - // Check if a bang in the bang_envelope has banged. - for bang_point in timeline_data.bang_envelope.points() { - if bang_point.ticks > previous_playhead_ticks - && bang_point.ticks <= timeline_data.playhead_ticks - { - println!("BANG!"); - } - } - - // Check if a note is playing - for note in &timeline_data.notes { - if timeline_data.playhead_ticks >= note.period.start - && timeline_data.playhead_ticks < note.period.end - { - println!("Note playing: {:?}", note.pitch); - } - } -} - -fn view(app: &App, model: &Model, frame: Frame) { - model.ui.draw_to_frame(app, &frame).unwrap(); -} - -// Update / draw the Ui. -fn set_widgets(ui: &mut UiCell, ids: &Ids, data: &mut TimelineData) { - use timeline::Timeline; - - // Main window canvas. - widget::Canvas::new() - .border(0.0) - .color(ui::color::DARK_CHARCOAL.alpha(0.5)) - .set(ids.window, ui); - - let TimelineData { - playhead_ticks, - bars, - notes, - tempo_envelope, - octave_envelope, - toggle_envelope, - bang_envelope, - } = data; - - let ticks = playhead_ticks.clone(); - let color = ui::color::LIGHT_BLUE; - - //////////////////// - ///// TIMELINE ///// - //////////////////// - // - // Set the `Timeline` widget. - // - // This returns a context on which we can begin setting our tracks, playhead and scrollbar. - // - // The context is used in three stages: - // - // 1. `PinnedTracks` for setting tracks that should be pinned to the top of the timeline. - // 2. `Tracks` for setting regular tracks. - // 3. `Final` for setting the `Playhead` and `Scrollbar` widgets after all tracks are set. - - let context = Timeline::new(bars.iter().cloned(), PPQN) - .playhead(ticks) - .color(color) - .wh_of(ids.window) - .middle_of(ids.window) - .border(1.0) - .border_color(ui::color::CHARCOAL) - .set(ids.timeline, ui); - - ///////////////////////// - ///// PINNED TRACKS ///// - ///////////////////////// - // - // Pin the ruler track to the top of the timeline. - // - // All pinned tracks must be `set` prior to non-pinned tracks. - { - let ruler = track::Ruler::new(context.ruler, &context.bars, PPQN).color(color); - let track = context.set_next_pinned_track(ruler, ui); - for triggered in track.event { - *playhead_ticks = triggered.ticks; - } - } - - ////////////////// - ///// TRACKS ///// - ////////////////// - - // Now that we've finished setting the pinned tracks, move on to the `Tracks` context. - let context = context.start_tracks(ui); - - { - // Piano roll. - let piano_roll = track::PianoRoll::new(&context.bars, PPQN, ¬es[..]).color(color); - let track = context.set_next_track(piano_roll, ui); - for event in track.event { - use timeline::track::piano_roll::Event; - match event { - Event::NoteOn(_note_idx) => (), - Event::NoteOff(_note_idx) => (), - Event::NotePlayed(_note_idx) => (), - } - } - - // A macro for common logic between tempo and octave "numeric" envelopes. - macro_rules! numeric_automation { - ($envelope:expr) => { - let track = { - let automation = - track::automation::Numeric::new(&context.bars, PPQN, $envelope) - .color(color); - context.set_next_track(automation, ui) - }; - for event in track.event { - use timeline::track::automation::numeric::Event; - match event { - Event::Interpolate(number) => println!("{}", number), - Event::Mutate(mutate) => mutate.apply($envelope), - } - } - }; - } - - // Tempo automation. - numeric_automation!(tempo_envelope); - // Octave automation. - numeric_automation!(octave_envelope); - - // Toggle automation. - let track = { - let automation = - track::automation::Toggle::new(&context.bars, PPQN, toggle_envelope).color(color); - context.set_next_track(automation, ui) - }; - for event in track.event { - use timeline::track::automation::toggle::Event; - match event { - Event::Interpolate(_toggle) => (), - Event::SwitchTo(_toggle) => (), - Event::Mutate(mutate) => mutate.apply(toggle_envelope), - } - } - - // Bang automation. - let track = { - let automation = - track::automation::Bang::new(&context.bars, PPQN, bang_envelope).color(color); - context.set_next_track(automation, ui) - }; - for event in track.event { - use timeline::track::automation::bang::Event; - match event { - Event::Mutate(mutate) => mutate.apply(bang_envelope), - _ => (), - } - } - } - - //////////////////////////////// - ///// PLAYHEAD & SCROLLBAR ///// - //////////////////////////////// - - // Now that all tracks have been set, finish up and set the `Playhead` and `Scrollbar`. - let context = context.end_tracks(); - - // Set the playhead after all tracks have been set. - for event in context.set_playhead(ui) { - use timeline::playhead::Event; - match event { - Event::Pressed => println!("Playhead pressed!"), - Event::DraggedTo(ticks) => *playhead_ticks = ticks, - Event::Released => println!("Playhead released!"), - } - } - - // Set the scrollbar if it is visible. - context.set_scrollbar(ui); -} - -fn key_pressed(_app: &App, model: &mut Model, key: Key) { - match key { - // Toggle play when space is pressed. - Key::Space => { - model.playing = !model.playing; - } - Key::R => { - let bars = model.timeline_data.bars.clone(); - model.timeline_data.notes = bars::WithStarts::new(bars.iter().cloned(), PPQN) - .enumerate() - .map(|(i, (time_sig, start))| { - let end = start + time_sig.ticks_per_bar(PPQN); - let period = timeline::Period { start, end }; - let pitch = pitch::Step((24 + (i * (random::() % 11)) % 12) as f32) - .to_letter_octave(); - piano_roll::Note { period, pitch } - }) - .collect(); - } - Key::S => { - // Save model.timeline_data to a JSON file. - // This part is only included if you compile with the serde feature enabled. - #[cfg(feature = "serde1")] - { - nannou::io::save_to_json("./saved_timeline_data.json", &model.timeline_data) - .expect("Error saving file"); - } - } - Key::L => { - // Load the model.timeline_data from a JSON file. - // This part is only included if you compile with the serde feature enabled. - #[cfg(feature = "serde1")] - { - if let Ok(new_data) = nannou::io::load_from_json("./saved_timeline_data.json") { - model.timeline_data = new_data; - } - } - } - _ => {} - } -} diff --git a/guide/src/changelog.md b/guide/src/changelog.md index 0a8ba49ab..79f4b6fb8 100644 --- a/guide/src/changelog.md +++ b/guide/src/changelog.md @@ -11,6 +11,10 @@ back to the origins. - Add CI for testing the `wasm32-unknown-unknown` target. - Enable `wgpu/webgl` when `wasm` feature is enabled. - Update minimum wgpu version to `0.11.1`, update winit to `0.26`. +- Merge the `nannou_egui` repo into the main `nannou` repo. +- Move `nannou_conrod` and `nannou_timeline` into a new repository: + https://github.com/nannou-org/nannou_conrod. Both crates are deprecated in + favour of `nannou_egui`. --- diff --git a/guide/src/why_nannou.md b/guide/src/why_nannou.md index b9e4e134a..40799fc43 100644 --- a/guide/src/why_nannou.md +++ b/guide/src/why_nannou.md @@ -19,8 +19,8 @@ need: output streams. Duplex are not yet supported.* - [ ] **Video** input, playback and processing (*would love suggestions and ideas*). -- [x] **GUI** via [conrod](https://crates.io/crates/conrod). *May switch to a - custom nannou solution [in the +- [x] **GUI** via [egui](https://crates.io/crates/egui). *May switch to a custom + nannou solution [in the future](https://github.com/nannou-org/nannou/issues/383)*. - **Geometry** with functions and iterators for producing vertices and indices: - [x] 1D - `Scalar`, `Range`. diff --git a/nannou_conrod/Cargo.toml b/nannou_conrod/Cargo.toml deleted file mode 100644 index f741a8c88..000000000 --- a/nannou_conrod/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "nannou_conrod" -version ="0.18.0" -authors = ["mitchmindtree "] -description = "Simplifies creating conrod GUIs in nannou apps." -readme = "README.md" -license = "MIT" -repository = "https://github.com/nannou-org/nannou.git" -homepage = "https://nannou.cc" -edition = "2018" - -[dependencies] -conrod_core = "0.76" -conrod_wgpu = "0.76" -conrod_winit = "0.76" -nannou = { version ="0.18.0", path = "../nannou", default-features = false } -# Must be synchronised with the version used in the nannou dependency. -# Required for the winit event conversion function macro to work. -winit = "0.26" - -[features] -default = ["notosans"] -notosans = ["nannou/notosans"] diff --git a/nannou_conrod/README.md b/nannou_conrod/README.md deleted file mode 100644 index 4d6f4d1ee..000000000 --- a/nannou_conrod/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# nannou_conrod - -Simplifies creating conrod GUIs in nannou apps. - -See the `examples/ui/conrod` examples demonstration. diff --git a/nannou_conrod/src/lib.rs b/nannou_conrod/src/lib.rs deleted file mode 100644 index f29917535..000000000 --- a/nannou_conrod/src/lib.rs +++ /dev/null @@ -1,553 +0,0 @@ -//! A library for simplifying using the conrod 2D GUI library with nannou. - -pub extern crate conrod_core; -pub extern crate conrod_wgpu; -pub extern crate conrod_winit; - -pub use conrod_core::event::Input; -pub use conrod_core::{ - color, cursor, event, graph, image, input, position, scroll, text, theme, utils, widget, - widget_ids, -}; -pub use conrod_core::{ - Borderable, Bordering, Color, Colorable, Dimensions, FontSize, Labelable, Point, Positionable, - Range, Rect, Scalar, Sizeable, Theme, UiCell, Widget, -}; -pub use nannou::winit::event::WindowEvent as RawWindowEvent; - -/// Simplify inclusion of common traits with a `nannou::ui::prelude` module. -pub mod prelude { - // Traits. - pub use super::{Borderable, Colorable, Labelable, Positionable, Sizeable, Widget}; - // Types. - pub use super::{ - Bordering, Dimensions, FontSize, Input, Point, Range, Rect, Scalar, Theme, Ui, UiCell, - }; - // Modules. - pub use super::{color, image, position, text, widget, widget_ids}; -} - -use self::conrod_core::text::rt::gpu_cache::CacheWriteErr; -use nannou::frame::Frame; -use nannou::text::{font, Font}; -use nannou::window::{self, Window}; -use nannou::{wgpu, App}; -use std::error::Error as StdError; -use std::fmt; -use std::ops::Deref; -use std::path::{Path, PathBuf}; -use std::sync::{Arc, Mutex}; - -/// A handle to the `Ui` for a specific window. -pub struct Ui { - /// The `Id` of the window upon which this `Ui` is instantiated. - window_id: window::Id, - ui: conrod_core::Ui, - pub image_map: ImageMap, - renderer: Mutex, -} - -/// A type used for building a new `Ui`. -pub struct Builder<'a> { - app: &'a App, - window_id: Option, - dimensions: Option<[Scalar; 2]>, - theme: Option, - default_font_path: Option, - glyph_cache_dimensions: Option<(u32, u32)>, -} - -/// Failed to build the `Ui`. -#[derive(Debug)] -pub enum BuildError { - /// Either the given window `Id` is not associated with any open windows or the window was - /// closed during the build process. - InvalidWindow, - FailedToLoadFont(text::font::Error), -} - -/// An error that might occur while drawing to a `Frame`. -#[derive(Debug)] -pub enum DrawToFrameError { - InvalidWindow, - RendererPoisoned, - RenderModePoisoned, - InvalidRenderMode, - RendererFill(CacheWriteErr), -} - -/// A map from `image::Id`s to their associated `Texture2d`. -pub type ImageMap = conrod_core::image::Map; - -impl<'a> Builder<'a> { - /// Begin building a new `Ui`. - pub fn new(app: &'a App) -> Self { - Builder { - app, - window_id: None, - dimensions: None, - theme: None, - default_font_path: None, - glyph_cache_dimensions: None, - } - } - - /// Specify the window on which the **Ui** will be instantiated. - /// - /// By default, this is the currently focused window, aka the window returned via - /// **App::window_id**. - pub fn window(mut self, window_id: window::Id) -> Self { - self.window_id = Some(window_id); - self - } - - /// Build the `Ui` with the given dimensions. - /// - /// By default, the `Ui` will have the dimensions of the specified window. - pub fn with_dimensions(mut self, dimensions: [Scalar; 2]) -> Self { - self.dimensions = Some(dimensions); - self - } - - /// Build the `Ui` with the given theme. - /// - /// By default, nannou uses conrod's default theme. - pub fn with_theme(mut self, theme: Theme) -> Self { - self.theme = Some(theme); - self - } - - /// Specify the path to the default font. - /// - /// By default this is `None` and the `notosans::REGULAR_TTF` font will be used. If the - /// `notosans` feature is disabled, then the default font will be the first font detected - /// within the `assets/fonts` directory. - /// - /// Fonts can also be specified manually after `Ui` creation using the `fonts_mut` method. - pub fn default_font_path(mut self, path: PathBuf) -> Self { - self.default_font_path = Some(path); - self - } - - /// Specify the dimensions of the texture used to cache glyphs on the GPU. - /// - /// By default this is equal to the framebuffer dimensions of the associated window at the time - /// of building the `UI`. - /// - /// If you notice any glitching of UI text, this may be due to exceeding the bounds of the - /// texture used to cache glyphs. Try using this to specify a larger glyph cache size to fix - /// this. - pub fn with_glyph_cache_dimensions(mut self, width: u32, height: u32) -> Self { - self.glyph_cache_dimensions = Some((width, height)); - self - } - - /// Build a `Ui` with the specified parameters. - /// - /// Returns `None` if the window at the given `Id` is closed or if the inner `Renderer` returns - /// an error upon creation. - pub fn build(self) -> Result { - let Builder { - app, - window_id, - dimensions, - theme, - default_font_path, - glyph_cache_dimensions, - } = self; - - // If the user didn't specify a window, use the "main" one. - let window_id = window_id.unwrap_or(app.window_id()); - - // The window on which the `Ui` will exist. - let window = app.window(window_id).ok_or(BuildError::InvalidWindow)?; - let msaa_samples = window.msaa_samples(); - - // The dimensions of the `Ui`. - let dimensions = dimensions.unwrap_or_else(|| { - let (win_w, win_h) = window.inner_size_points(); - [win_w as Scalar, win_h as Scalar] - }); - - // Build the conrod `Ui`. - let theme = theme.unwrap_or_else(Theme::default); - let ui = conrod_core::UiBuilder::new(dimensions).theme(theme).build(); - - // The device with which to create the `Ui` renderer. - let device = window.device().clone(); - - // Initialise the renderer which draws conrod::render::Primitives to the frame. - let texture_format = Frame::TEXTURE_FORMAT; - let renderer = match glyph_cache_dimensions { - Some((w, h)) => { - let dims = [w as _, h as _]; - conrod_wgpu::Renderer::with_glyph_cache_dimensions( - device, - msaa_samples, - texture_format, - dims, - ) - } - None => conrod_wgpu::Renderer::new(device, msaa_samples, texture_format), - }; - let renderer = Mutex::new(renderer); - - // Initialise the image map. - let image_map = image::Map::new(); - - // Initialise the `Ui`. - let mut ui = Ui { - window_id, - ui, - image_map, - renderer, - }; - - // If no font was specified use one from the notosans crate, otherwise load the given font. - let default_font = default_font(default_font_path.as_ref().map(|path| path.as_path()))?; - ui.fonts_mut().insert(default_font); - - Ok(ui) - } -} - -impl Ui { - /// Generate a new, unique `widget::Id` into a Placeholder node within the widget graph. This - /// should only be called once for each unique widget needed to avoid unnecessary bloat within - /// the `Ui`'s internal widget graph. - /// - /// When using this method, be sure to store the returned `widget::Id` somewhere so that it can - /// be re-used on next update. - /// - /// **Panics** if adding another node would exceed the maximum capacity for node indices. - pub fn generate_widget_id(&mut self) -> widget::Id { - self.widget_id_generator().next() - } - - /// Produces the type that may be used to generate new unique `widget::Id`s. - pub fn widget_id_generator(&mut self) -> widget::id::Generator { - self.ui.widget_id_generator() - } - - /// Handle a raw UI input event and update the **Ui** state accordingly. - /// - /// This method *drives* the **Ui** forward and interprets input into higher-level events (like - /// clicks and drags) for widgets. - /// - /// Note: By default, this will be called automatically by the nannou `App`, so most of the - /// time you should not need to call this (otherwise received inputs may double up). This - /// method is particularly useful in the case that automatic input handling has been disabled, - /// as this can be used to manually submit inputs. - pub fn handle_input(&mut self, input: Input) { - self.ui.handle_event(input) - } - - /// Check the given raw event for a valid conrod `Input` and if there is one, call - /// `self.handle_input(input)`. - /// - /// Returns `false` in the case that no `Input` could be parsed, or if there was no `Window` - /// for the `Ui`'s associated `window::Id`. - pub fn handle_raw_event(&mut self, app: &App, event: &RawWindowEvent) -> bool { - if let Some(window) = app.window(self.window_id) { - if let Some(input) = winit_window_event_to_input(event, &*window) { - self.handle_input(input); - return true; - } - } - false - } - - /// Returns a context upon which UI widgets can be instantiated. - /// - /// The **UiCell** simply acts as a wrapper around the **Ui** for the period over which widgets - /// are instantiated. Once the **UiCell** is dropped, it does some cleanup and sorting that is - /// required after widget instantiation. - pub fn set_widgets(&mut self) -> UiCell { - self.ui.set_widgets() - } - - /// Mutable access to the `Ui`'s font map. - /// - /// This allows for adding and removing fonts to the UI. - pub fn fonts_mut(&mut self) -> &mut text::font::Map { - &mut self.ui.fonts - } - - /// Mutable access to the `Ui`'s `Theme`. - /// - /// This allows for making changes to the active theme. - pub fn theme_mut(&mut self) -> &mut Theme { - &mut self.ui.theme - } - - /// The first of the `Primitives` yielded by `Ui::draw` will always be a `Rectangle` the size - /// of the window in which the Ui is instantiated. - /// - /// This method sets the colour with which this `Rectangle` is drawn (the default being - /// `color::TRANSPARENT`). - pub fn clear_with(&mut self, color: Color) { - self.ui.clear_with(color) - } - - /// Draws the current state of the `Ui` to the given `Frame`. - /// - /// The `Ui` will automatically draw to its associated window within the given `Frame`. - /// - /// If you require more control over where the `Ui` is drawn within the `Frame`, the `draw` - /// method offers more flexibility. - /// - /// This has no effect if the window originally associated with the `Ui` no longer exists. - pub fn draw_to_frame(&self, app: &App, frame: &Frame) -> Result<(), DrawToFrameError> { - let primitives = self.ui.draw(); - let color_attachment_desc = frame.color_attachment_descriptor(); - let mut command_encoder = frame.command_encoder(); - let window = app - .window(self.window_id) - .ok_or(DrawToFrameError::InvalidWindow)?; - encode_render_pass( - self, - &*window, - primitives, - color_attachment_desc, - &mut *command_encoder, - ) - } - - /// Draws the current state of the `Ui` to the given `Frame` but only if the `Ui` has changed - /// since last time either `draw_to_frame` or `draw_to_frame_if_changed` was called. - /// - /// The `Ui` will automatically draw to its associated window within the given `Frame`. - /// - /// If you require more control over where the `Ui` is drawn within the `Frame`, the `draw` - /// method offers more flexibility. - /// - /// This has no effect if the window originally associated with the `Ui` no longer exists. - /// - /// Returns `true` if the call resulted in re-drawing the `Ui` due to changes. - pub fn draw_to_frame_if_changed( - &self, - app: &App, - frame: &Frame, - ) -> Result { - match self.ui.draw_if_changed() { - None => Ok(false), - Some(primitives) => { - let window = app - .window(self.window_id) - .ok_or(DrawToFrameError::InvalidWindow)?; - let color_attachment_desc = frame.color_attachment_descriptor(); - let mut command_encoder = frame.command_encoder(); - encode_render_pass( - self, - &*window, - primitives, - color_attachment_desc, - &mut *command_encoder, - )?; - Ok(true) - } - } - } -} - -/// Begin building a new `Ui`. -/// -/// This is short-hand for the `Builder::new`. -pub fn builder(app: &App) -> Builder { - Builder::new(app) -} - -/// Convert the texture into an image compatible with the UI's image map. -/// -/// **Panic**s if the texture's `Arc` has been cloned and more than one unique -/// reference to the inner data still exists. -pub fn image_from_texture(texture: wgpu::Texture) -> conrod_wgpu::Image { - let texture_format = texture.format(); - let [width, height] = texture.size(); - let texture = Arc::try_unwrap(texture.into_inner()) - .expect("converting to UI image requires unique access to texture"); - conrod_wgpu::Image { - texture, - texture_format, - width, - height, - } -} - -/// Encode commands for drawing the given primitives. -pub fn encode_render_pass( - ui: &Ui, - window: &Window, - primitives: conrod_core::render::Primitives, - color_attachment_desc: wgpu::RenderPassColorAttachment, - encoder: &mut wgpu::CommandEncoder, -) -> Result<(), DrawToFrameError> { - // Feed the renderer primitives and update glyph cache texture if necessary. - let mut renderer = ui - .renderer - .lock() - .ok() - .ok_or(DrawToFrameError::RendererPoisoned)?; - let device = window.device(); - let scale_factor = window.scale_factor(); - let (win_w, win_h) = window.inner_size_pixels(); - let viewport = [0.0, 0.0, win_w as f32, win_h as f32]; - if let Some(cmd) = renderer - .fill(&ui.image_map, viewport, scale_factor as f64, primitives) - .unwrap() - { - cmd.load_buffer_and_encode(&device, encoder); - } - - // Begin the render pass and add the draw commands. - let render_pass_desc = wgpu::RenderPassDescriptor { - label: Some("nannou_ui_render_pass_descriptor"), - color_attachments: &[color_attachment_desc], - depth_stencil_attachment: None, - }; - let render = renderer.render(&device, &ui.image_map); - { - let mut render_pass = encoder.begin_render_pass(&render_pass_desc); - render_pass.set_vertex_buffer(0, render.vertex_buffer.slice(..)); - let instance_range = 0..1; - for cmd in render.commands { - match cmd { - conrod_wgpu::RenderPassCommand::SetPipeline { pipeline } => { - render_pass.set_pipeline(pipeline); - } - conrod_wgpu::RenderPassCommand::SetBindGroup { bind_group } => { - render_pass.set_bind_group(0, bind_group, &[]); - } - conrod_wgpu::RenderPassCommand::SetScissor { - top_left, - dimensions, - } => { - let [x, y] = top_left; - let [w, h] = dimensions; - render_pass.set_scissor_rect(x, y, w, h); - } - conrod_wgpu::RenderPassCommand::Draw { vertex_range } => { - render_pass.draw(vertex_range, instance_range.clone()); - } - } - } - } - - Ok(()) -} - -mod conrod_winit_conv { - conrod_winit::v023_conversion_fns!(); -} - -/// Convert the given window event to a UI Input. -/// -/// Returns `None` if there's no associated UI Input for the given event. -pub fn winit_window_event_to_input( - event: &winit::event::WindowEvent, - window: &Window, -) -> Option { - conrod_winit_conv::convert_window_event(event, window.winit_window()) -} - -impl Deref for Ui { - type Target = conrod_core::Ui; - fn deref(&self) -> &Self::Target { - &self.ui - } -} - -impl From for BuildError { - fn from(err: text::font::Error) -> Self { - BuildError::FailedToLoadFont(err) - } -} - -impl From for DrawToFrameError { - fn from(err: CacheWriteErr) -> Self { - DrawToFrameError::RendererFill(err) - } -} - -impl StdError for BuildError { - fn cause(&self) -> Option<&dyn StdError> { - match *self { - BuildError::InvalidWindow => None, - BuildError::FailedToLoadFont(ref err) => Some(err), - } - } -} - -impl StdError for DrawToFrameError { - fn cause(&self) -> Option<&dyn StdError> { - match *self { - DrawToFrameError::InvalidWindow => None, - DrawToFrameError::RendererPoisoned => None, - DrawToFrameError::RenderModePoisoned => None, - DrawToFrameError::InvalidRenderMode => None, - DrawToFrameError::RendererFill(ref err) => Some(err), - } - } -} - -impl fmt::Display for BuildError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - BuildError::InvalidWindow => { - write!(f, "no open window associated with the given `window_id`") - } - BuildError::FailedToLoadFont(ref err) => fmt::Display::fmt(err, f), - } - } -} - -impl fmt::Display for DrawToFrameError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let s = match *self { - DrawToFrameError::InvalidWindow => { - "no open window associated with the given `window_id`" - } - DrawToFrameError::RendererPoisoned => "`Mutex` containing `Renderer` was poisoned", - DrawToFrameError::RenderModePoisoned => "`Mutex` containing `RenderMode` was poisoned", - DrawToFrameError::InvalidRenderMode => { - "`draw_to_frame` was called while `Ui` was in `Subpass` render mode" - } - DrawToFrameError::RendererFill(ref err) => return fmt::Display::fmt(err, f), - }; - write!(f, "{}", s) - } -} - -// Retrieve the default font. -// -// Accepts an optional default font path if one was provided by the user. -fn default_font(default_font_path: Option<&Path>) -> Result { - // Convert the nannou text error to a conrod one. - fn conv_err(err: font::Error) -> text::font::Error { - match err { - font::Error::Io(err) => text::font::Error::IO(err), - font::Error::NoFont => text::font::Error::NoFont, - } - } - - let font = match default_font_path { - None => { - #[cfg(feature = "notosans")] - { - font::default_notosans() - } - #[cfg(not(feature = "notosans"))] - { - match nannou::app::find_assets_path() { - Err(_err) => return Err(text::font::Error::NoFont)?, - Ok(assets) => font::default(&assets).map_err(conv_err)?, - } - } - } - Some(path) => { - let font = font::from_file(path).map_err(conv_err)?; - font - } - }; - Ok(font) -} diff --git a/nannou_timeline/.gitignore b/nannou_timeline/.gitignore deleted file mode 100644 index cab5f9a1b..000000000 --- a/nannou_timeline/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/target/ -Cargo.lock -saved_timeline_data.json diff --git a/nannou_timeline/Cargo.toml b/nannou_timeline/Cargo.toml deleted file mode 100644 index 8c9838209..000000000 --- a/nannou_timeline/Cargo.toml +++ /dev/null @@ -1,32 +0,0 @@ -[package] -name = "nannou_timeline" -version ="0.18.0" -authors = ["mitchmindtree "] -description = "A timeline widget, compatible with all conrod GUI projects." -readme = "README.md" -keywords = ["timeline", "automation", "GUI", "conrod", "envelope"] -license = "MIT OR Apache-2.0" -repository = "https://github.com/nannou-org/nannou_timeline.git" -homepage = "https://nannou.cc" - -[dependencies] -conrod_derive = "0.76" -conrod_core = "0.76" -envelope = "0.8" -itertools = "0.4.16" -num = "0.1.27" -pitch_calc = { version = "0.12", features = ["serde"] } -time_calc = { version= "0.13", features = ["serde"] } -serde = { version = "1", features = ["derive"], optional = true } - -[features] -serde1 = [ - "serde", - "pitch_calc/serde", - "time_calc/serde", -] - -[dev-dependencies] -nannou = { version ="0.18.0", path = "../nannou" } -rand = "0.3.12" -serde_json = "1.0" diff --git a/nannou_timeline/LICENSE-APACHE b/nannou_timeline/LICENSE-APACHE deleted file mode 100644 index c6232de03..000000000 --- a/nannou_timeline/LICENSE-APACHE +++ /dev/null @@ -1,13 +0,0 @@ -Copyright 2019 nannou-org. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/nannou_timeline/LICENSE-MIT b/nannou_timeline/LICENSE-MIT deleted file mode 100644 index a81fb5ef5..000000000 --- a/nannou_timeline/LICENSE-MIT +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2019 nannou-org. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/nannou_timeline/README.md b/nannou_timeline/README.md deleted file mode 100644 index 67b4a8b7a..000000000 --- a/nannou_timeline/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# nannou_timeline [![Crates.io](https://img.shields.io/crates/v/nannou_timeline.svg)](https://crates.io/crates/nannou_timeline) [![Crates.io](https://img.shields.io/crates/l/nannou_timeline.svg)](https://github.com/nannou-org/nannou_timeline/blob/master/LICENSE-MIT) [![docs.rs](https://docs.rs/nannou_timeline/badge.svg)](https://docs.rs/nannou_timeline/) - -A widget designed for controlling and viewing data over time. This crate was -developed for a generative music workstation but has abstracted for general use. - -![nannou_timeline demo.rs example](https://i.imgur.com/IGnzfKy.png) - -While this is designed and developed by the nannou organisation, this widget -should be compatible with all conrod GUI project. - -Please see [**the nannou guide**](https://guide.nannou.cc) for more information -on how to get started with nannou! - -## Features - -- Continuous and discrete numeric automation. -- A set of readily available track types: - - Piano roll. - - Toggle automation. - - Bang automation. - - Numeric automation (continuous and discrete). -- Playhead widget. -- Easy-to-use API. -- Resizable tracks. -- Track pinning. -- Musical structure grid display (supports varying time signatures). -- Compatible with any conrod project. - -## TODO - -- [ ] Update to Rust 2018. -- [ ] Add support for free-form time (currently only supports musically - structured time). -- [ ] Add ability to continuously scroll. -- [ ] Move tracks into a separate crate. -- [ ] Add example demonstrating how to create a custom track widget. -- [ ] Finish making toggle automation interactive. -- [ ] Add bezier curve support to numeric automation tracks. -- [ ] Smart cursor "snap-to-grid" functionality. -- [ ] Many track type ideas: - - [ ] Plotter track (useful for waveforms / generic 1D data). - - [ ] Audio waveform track. - - [ ] Video preview track. diff --git a/nannou_timeline/src/bars.rs b/nannou_timeline/src/bars.rs deleted file mode 100644 index 4d7233b98..000000000 --- a/nannou_timeline/src/bars.rs +++ /dev/null @@ -1,149 +0,0 @@ -//! Helper items related to working with sequences of musical bars. - -use period::Period; -use time_calc as time; - -/// An iterator that converts some iterator of ticks to their simplest possible musical divisions. -/// -/// NOTE: This is ported from an old version of jen/core. -#[derive(Clone)] -pub struct SimplestDivisions { - ppqn: time::Ppqn, - duration_ticks: time::Ticks, - ticks_iter: I, -} - -/// An iterator yielding each bar's time signature along with its starting position in ticks. -#[derive(Clone)] -pub struct WithStarts { - bars: I, - next_start: time::Ticks, - ppqn: time::Ppqn, -} - -/// Convert an iterator yielding bar time signatures into their consecutive periods over time. -#[derive(Clone)] -pub struct Periods { - bars_with_starts: I, - ppqn: time::Ppqn, -} - -impl SimplestDivisions { - /// Produce an iterator that converts some iterator of ticks to their simplest possible musical - /// divisions. - pub fn new(ticks: It, ppqn: time::Ppqn, duration_ticks: time::Ticks) -> Self - where - It: IntoIterator, - It::IntoIter: Iterator, - { - let ticks_iter = ticks.into_iter(); - SimplestDivisions { - ticks_iter, - ppqn, - duration_ticks, - } - } -} - -impl WithStarts { - /// Convert an iterator yielding a time signature each bar to also yield the ticks at which - /// that bar would begin. - /// - /// Assumes the first bar starts at `Ticks(0)`. - pub fn new(bars: It, ppqn: time::Ppqn) -> Self - where - It: IntoIterator, - It::IntoIter: Iterator, - { - let bars = bars.into_iter(); - WithStarts { - bars, - ppqn, - next_start: time::Ticks(0), - } - } -} - -impl Periods> { - pub fn new(bars: It, ppqn: time::Ppqn) -> Self - where - It: IntoIterator, - It::IntoIter: Iterator, - { - Periods { - bars_with_starts: WithStarts::new(bars, ppqn), - ppqn, - } - } -} - -impl Iterator for SimplestDivisions -where - I: Iterator, -{ - type Item = Option; - fn next(&mut self) -> Option> { - match self.ticks_iter.next() { - None => None, - Some(ticks) => { - // If the ticks exceeds the duration of our bar, we'll stop iteration. - if ticks > self.duration_ticks { - return None; - } - - // If the ticks is 0, we can assume we're on the start of the Bar. - if ticks == time::Ticks(0) { - return Some(Some(time::Division::Bar)); - } - - // We'll start at a `Minim` division and zoom in until we find a division that - // would divide our ticks and return a whole number. - let mut div = time::Division::Minim; - while div.to_u8() <= time::Division::OneThousandTwentyFourth.to_u8() { - let div_in_beats = 2.0f64.powi(time::Division::Beat as i32 - div as i32); - let div_in_ticks = time::Ticks((div_in_beats * self.ppqn as f64).floor() as _); - if ticks % div_in_ticks == time::Ticks(0) { - return Some(Some(div)); - } - div = div - .zoom_in(1) - .expect("Zoomed in too far when finding the simplest div"); - } - - // If we didn't find any matching divisions, we'll indicate this by returning None. - Some(None) - } - } - } -} - -impl Iterator for WithStarts -where - I: Iterator, -{ - type Item = (time::TimeSig, time::Ticks); - fn next(&mut self) -> Option { - if let Some(ts) = self.bars.next() { - let ticks = ts.ticks_per_bar(self.ppqn); - let start = self.next_start; - self.next_start += ticks; - return Some((ts, start)); - } - None - } -} - -impl Iterator for Periods -where - I: Iterator, -{ - type Item = Period; - fn next(&mut self) -> Option { - if let Some((ts, start)) = self.bars_with_starts.next() { - let duration = ts.ticks_per_bar(self.ppqn); - let end = start + duration; - return Some(Period { start, end }); - } - None - } -} diff --git a/nannou_timeline/src/diff.rs b/nannou_timeline/src/diff.rs deleted file mode 100644 index 5c977eb48..000000000 --- a/nannou_timeline/src/diff.rs +++ /dev/null @@ -1,97 +0,0 @@ -//! "Diff"ing iterators for caching elements to sequential collections without requiring the new -//! elements' iterator to be `Clone`. -//! -//! - [**IterDiff**](./enum.IterDiff.html) (produced by the [**iter_diff**](./fn.iter_diff.html) -//! function) describes the difference between two non-`Clone` iterators `a` and `b` after breaking -//! ASAP from a comparison with enough data to update `a`'s collection. -//! - [**copy_on_diff**](./fn.copy_on_diff.html) is an application of [**iter_diff**] that compares -//! two iterators `a` and `b`, borrowing the source of `a` if they are the same or creating a new -//! owned collection with `b`'s elements if they are different. - -use std::borrow::{Cow, ToOwned}; -use std::iter; - -/// A type returned by the [`iter_diff`](./fn.iter_diff.html) function. -/// -/// `IterDiff` represents the way in which the elements (of type `E`) yielded by the iterator `I` -/// differ to some other iterator yielding borrowed elements of the same type. -/// -/// `I` is some `Iterator` yielding elements of type `E`. -pub enum IterDiff { - /// The index of the first non-matching element along with the iterator's remaining elements - /// starting with the first mis-matched element. - FirstMismatch(usize, iter::Chain, I>), - /// The remaining elements of the iterator. - Longer(iter::Chain, I>), - /// The total number of elements that were in the iterator. - Shorter(usize), -} - -/// Compares every element yielded by both elems and new_elems in lock-step and returns an -/// `IterDiff` which describes how `b` differs from `a`. -/// -/// This function is useful for caching some iterator `b` in some sequential collection without -/// requiring `b` to be `Clone` in order to compare it to the collection before determining if the -/// collection needs to be updated. The returned function returns as soon as a difference is found, -/// producing an `IterDiff` that provides the data necessary to update the collection without ever -/// requiring `B` to be `Clone`. This allows for efficiently caching iterators like `Map` or -/// `Filter` that do not implement `Clone`. -/// -/// If the number of elements yielded by `b` is less than the number of elements yielded by `a`, -/// the number of `b` elements yielded will be returned as `IterDiff::Shorter`. -/// -/// If the two elements of a step differ, the index of those elements along with the remaining -/// elements of `b` are returned as `IterDiff::FirstMismatch`. -/// -/// If `a` becomes exhausted before `b` becomes exhausted, the remaining `b` elements will be -/// returned as `IterDiff::Longer`. -/// -/// See [`copy_on_diff`](./fn.copy_on_diff.html) for an application of `iter_diff`. -pub fn iter_diff<'a, A, B>(a: A, b: B) -> Option> -where - A: IntoIterator, - B: IntoIterator, - B::Item: PartialEq + 'a, -{ - let mut b = b.into_iter(); - for (i, a_elem) in a.into_iter().enumerate() { - match b.next() { - None => return Some(IterDiff::Shorter(i)), - Some(b_elem) => { - if *a_elem != b_elem { - return Some(IterDiff::FirstMismatch(i, iter::once(b_elem).chain(b))); - } - } - } - } - b.next() - .map(|elem| IterDiff::Longer(iter::once(elem).chain(b))) -} - -/// Returns `Cow::Borrowed` `a` if `a` contains the same elements as yielded by `b`'s iterator. -/// -/// Collects into a new `A::Owned` and returns `Cow::Owned` if either the number of elements or the -/// elements themselves differ. -/// ``` -#[allow(dead_code)] -pub fn copy_on_diff<'a, A, B, T: 'a>(a: &'a A, b: B) -> Cow<'a, A> -where - &'a A: IntoIterator, - <&'a A as IntoIterator>::IntoIter: Clone, - A: ToOwned, - ::Owned: iter::FromIterator, - B: IntoIterator, - T: Clone + PartialEq, -{ - let a_iter = a.into_iter(); - match iter_diff(a_iter.clone(), b.into_iter()) { - Some(IterDiff::FirstMismatch(i, mismatch)) => { - Cow::Owned(a_iter.take(i).cloned().chain(mismatch).collect()) - } - Some(IterDiff::Longer(remaining)) => Cow::Owned(a_iter.cloned().chain(remaining).collect()), - Some(IterDiff::Shorter(num_new_elems)) => { - Cow::Owned(a_iter.cloned().take(num_new_elems).collect()) - } - None => Cow::Borrowed(a), - } -} diff --git a/nannou_timeline/src/env/bang.rs b/nannou_timeline/src/env/bang.rs deleted file mode 100644 index 459130e32..000000000 --- a/nannou_timeline/src/env/bang.rs +++ /dev/null @@ -1,19 +0,0 @@ -use envelope; - -/// A type to use for `Point`s that have no value. -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct Bang; - -impl envelope::interpolation::Spatial for Bang { - type Scalar = f32; - fn add(&self, _: &Bang) -> Bang { - Bang - } - fn sub(&self, _: &Bang) -> Bang { - Bang - } - fn scale(&self, _scalar: &f32) -> Bang { - Bang - } -} diff --git a/nannou_timeline/src/env/bounded.rs b/nannou_timeline/src/env/bounded.rs deleted file mode 100644 index 7d15b00f7..000000000 --- a/nannou_timeline/src/env/bounded.rs +++ /dev/null @@ -1,147 +0,0 @@ -use super::bang::Bang; -use super::toggle::Toggle; -use super::{Number, ValueKind}; -use super::{Point, Trait}; -use std; -use time_calc as time; - -/// An envelope with some min and max for the value range. -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct Envelope { - pub min: T, - pub max: T, - pub env: super::Envelope, -} - -impl Envelope { - /// Construct a new, empty, default Envelope. - pub fn new(min: T, max: T) -> Envelope { - Envelope::from_points(::std::iter::empty(), min, max) - } - - /// Construct a new Envelope from the given min, max and points. - pub fn from_points(points: I, min: T, max: T) -> Envelope - where - I: IntoIterator>, - { - Envelope { - min: min, - max: max, - env: points.into_iter().collect(), - } - } -} - -impl super::Envelope { - /// Convert the unbounded envelope to a bounded Envelope with the given `min` and `max`. - pub fn bounded(self, min: T, max: T) -> Envelope { - Envelope { - min: min, - max: max, - env: self, - } - } -} - -impl<'a, T: 'a> Trait<'a> for Envelope -where - T: PartialEq + super::Spatial, - Point: super::PointTrait, -{ - type X = time::Ticks; - type Y = T; - type Point = Point; - type Points = std::slice::Iter<'a, Point>; - fn points(&'a self) -> Self::Points { - self.env.points.iter() - } -} - -/// A wrapper around the various kinds of bounded automation envelopes. -#[derive(Clone, Debug, PartialEq)] -pub enum Dynamic { - Bang(Envelope), - Toggle(Envelope), - I8(Envelope), - I16(Envelope), - I32(Envelope), - I64(Envelope), - U8(Envelope), - U16(Envelope), - U32(Envelope), - U64(Envelope), - F32(Envelope), - F64(Envelope), -} - -impl Dynamic { - /// Return the parameter value for the automation envelope at the given time in ticks. - pub fn value_at_ticks(&self, x: time::Ticks) -> ValueKind { - fn expect(t: Option) -> T { - t.expect("Given `x` was out of range") - } - match *self { - Dynamic::Bang(ref env) => ValueKind::Bang(env.closest_point(x).map(|p| p.ticks - x)), - Dynamic::Toggle(ref env) => ValueKind::Toggle(*expect(env.y(x))), - Dynamic::I8(ref env) => ValueKind::Number(Number::I8(expect(env.y(x)))), - Dynamic::I16(ref env) => ValueKind::Number(Number::I16(expect(env.y(x)))), - Dynamic::I32(ref env) => ValueKind::Number(Number::I32(expect(env.y(x)))), - Dynamic::I64(ref env) => ValueKind::Number(Number::I64(expect(env.y(x)))), - Dynamic::U8(ref env) => ValueKind::Number(Number::U8(expect(env.y(x)))), - Dynamic::U16(ref env) => ValueKind::Number(Number::U16(expect(env.y(x)))), - Dynamic::U32(ref env) => ValueKind::Number(Number::U32(expect(env.y(x)))), - Dynamic::U64(ref env) => ValueKind::Number(Number::U64(expect(env.y(x)))), - Dynamic::F32(ref env) => ValueKind::Number(Number::F32(expect(env.y(x)))), - Dynamic::F64(ref env) => ValueKind::Number(Number::F64(expect(env.y(x)))), - } - } -} - -macro_rules! impl_from_envelope_for_dynamic { - ($($T:ident $variant:ident),* $(,)*) => { - $( - impl From> for Dynamic { - fn from(env: Envelope<$T>) -> Self { - Dynamic::$variant(env) - } - } - )* - }; -} - -impl_from_envelope_for_dynamic! { - Bang Bang, - Toggle Toggle, - i8 I8, - i16 I16, - i32 I32, - i64 I64, - u8 U8, - u16 U16, - u32 U32, - u64 U64, - f32 F32, - f64 F64, -} - -impl Dynamic { - /// Construct a bounded dynamic envelope from the given typed bounded envelope. - pub fn from_envelope(env: Envelope) -> Dynamic - where - Self: From>, - { - Dynamic::from(env) - } - - /// Construct a bounded dynamic envelope from the given points. - pub fn from_points(points: I, min: T, max: T) -> Self - where - I: IntoIterator>, - super::Envelope: std::iter::FromIterator>, - Self: From>, - { - let envelope = Envelope::from_points(points, min, max); - Dynamic::from_envelope(envelope) - } -} diff --git a/nannou_timeline/src/env/mod.rs b/nannou_timeline/src/env/mod.rs deleted file mode 100644 index ff89f1ea2..000000000 --- a/nannou_timeline/src/env/mod.rs +++ /dev/null @@ -1,249 +0,0 @@ -use std; -use time_calc as time; - -pub use self::bang::Bang; -pub use self::point::Point; -pub use self::point::Trait as PointTrait; -pub use self::toggle::Toggle; -pub use envelope::interpolation::Spatial; -pub use envelope::Envelope as Trait; - -pub mod bang; -pub mod bounded; -pub mod point; -pub mod points; -pub mod toggle; - -/// A generic envelope type. -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct Envelope { - pub points: Vec>, -} - -/// A wrapper around the various kinds of automation envelopes. -#[derive(Clone, Debug, PartialEq)] -pub enum Dynamic { - Bang(Envelope), - Toggle(Envelope), - I8(Envelope), - I16(Envelope), - I32(Envelope), - I64(Envelope), - U8(Envelope), - U16(Envelope), - U32(Envelope), - U64(Envelope), - F32(Envelope), - F64(Envelope), -} - -/// A wrapper around the different kinds of automatable numeric types. -#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] -pub enum Number { - I8(i8), - I16(i16), - I32(i32), - I64(i64), - U8(u8), - U16(u16), - U32(u32), - U64(u64), - F32(f32), - F64(f64), -} - -/// A wrapper around the different kinds of automatable value types. -#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] -pub enum ValueKind { - /// The offset from the closest `Bang` in Ticks (if there is a `Bang`). - Bang(Option), - Toggle(bool), - Number(Number), -} - -impl std::iter::FromIterator> for Envelope { - fn from_iter(iter: I) -> Self - where - I: IntoIterator>, - { - Envelope { - points: iter.into_iter().collect(), - } - } -} - -impl From>> for Envelope { - fn from(points: Vec>) -> Self { - Envelope { points } - } -} - -impl<'a, T: 'a> Trait<'a> for Envelope -where - T: PartialEq + Spatial, - Point: PointTrait, -{ - type X = time::Ticks; - type Y = T; - type Point = Point; - type Points = std::slice::Iter<'a, Point>; - fn points(&'a self) -> Self::Points { - self.points.iter() - } -} - -macro_rules! impl_from_for_number { - ($num:ty, $variant:ident) => { - impl From<$num> for Number { - fn from(n: $num) -> Self { - Number::$variant(n) - } - } - }; -} - -impl_from_for_number!(i8, I8); -impl_from_for_number!(i16, I16); -impl_from_for_number!(i32, I32); -impl_from_for_number!(i64, I64); -impl_from_for_number!(u8, U8); -impl_from_for_number!(u16, U16); -impl_from_for_number!(u32, U32); -impl_from_for_number!(u64, U64); -impl_from_for_number!(f32, F32); -impl_from_for_number!(f64, F64); - -macro_rules! impl_from_for_value_kind { - ($value_ty:ty, $variant:ident) => { - impl From<$value_ty> for ValueKind { - fn from(v: $value_ty) -> Self { - ValueKind::$variant(v) - } - } - }; -} - -impl_from_for_value_kind!(bool, Toggle); -impl_from_for_value_kind!(Option, Bang); - -impl From for ValueKind -where - N: Into, -{ - fn from(n: N) -> Self { - ValueKind::Number(n.into()) - } -} - -macro_rules! impl_from_envelope_for_dynamic { - ($value_type:ty, $variant:ident) => { - impl From> for Dynamic { - fn from(env: Envelope<$value_type>) -> Self { - Dynamic::$variant(env) - } - } - }; -} - -impl_from_envelope_for_dynamic!(Bang, Bang); -impl_from_envelope_for_dynamic!(Toggle, Toggle); -impl_from_envelope_for_dynamic!(i8, I8); -impl_from_envelope_for_dynamic!(i16, I16); -impl_from_envelope_for_dynamic!(i32, I32); -impl_from_envelope_for_dynamic!(i64, I64); -impl_from_envelope_for_dynamic!(u8, U8); -impl_from_envelope_for_dynamic!(u16, U16); -impl_from_envelope_for_dynamic!(u32, U32); -impl_from_envelope_for_dynamic!(u64, U64); -impl_from_envelope_for_dynamic!(f32, F32); -impl_from_envelope_for_dynamic!(f64, F64); - -impl std::iter::FromIterator> for Dynamic -where - Envelope: Into, -{ - fn from_iter(points: I) -> Self - where - I: IntoIterator>, - { - let env: Envelope = points.into_iter().collect(); - env.into() - } -} - -/// A macro to simplify implementation of `expect_$type` methods on the ValueKind type. -macro_rules! fn_expect_num { - ($method_name:ident, $return_type:ty, $variant:ident) => { - /// Forces the specified number type from the `ValueKind`. - /// - /// **Panics** if the ValueKind variant was of a different type. - pub fn $method_name(&self) -> $return_type { - match *self { - ValueKind::Number(Number::$variant(value)) => value, - _ => panic!("`ValueKind` expected a {:?}", stringify!($method_name)), - } - } - }; -} - -/// A macro to simplify implementation of casting methods on the ValueKind type. -macro_rules! fn_as_type { - ($method_name:ident, $return_type:ty) => { - /// Casts the value from the current variant to the specified type. - /// - /// The `Bang` variant will always be cast to 0. - /// - /// The `Toggle` variant will always be cast to u8 before being cast to the specified type. - pub fn $method_name(&self) -> $return_type { - match *self { - ValueKind::Bang(_) => 0 as $return_type, - ValueKind::Toggle(b) => b as u8 as $return_type, - ValueKind::Number(Number::I8(n)) => n as $return_type, - ValueKind::Number(Number::I16(n)) => n as $return_type, - ValueKind::Number(Number::I32(n)) => n as $return_type, - ValueKind::Number(Number::I64(n)) => n as $return_type, - ValueKind::Number(Number::U8(n)) => n as $return_type, - ValueKind::Number(Number::U16(n)) => n as $return_type, - ValueKind::Number(Number::U32(n)) => n as $return_type, - ValueKind::Number(Number::U64(n)) => n as $return_type, - ValueKind::Number(Number::F32(n)) => n as $return_type, - ValueKind::Number(Number::F64(n)) => n as $return_type, - } - } - }; -} - -impl ValueKind { - fn_expect_num!(expect_u8, u8, U8); - fn_expect_num!(expect_u16, u16, U16); - fn_expect_num!(expect_u32, u32, U32); - fn_expect_num!(expect_u64, u64, U64); - fn_expect_num!(expect_i8, i8, I8); - fn_expect_num!(expect_i16, i16, I16); - fn_expect_num!(expect_i32, i32, I32); - fn_expect_num!(expect_i64, i64, I64); - fn_expect_num!(expect_f32, f32, F32); - fn_expect_num!(expect_f64, f64, F64); - - /// Forces the specified type from the `ValueKind`. - /// - /// **Panics** if the ValueKind variant was of a different type. - pub fn expect_bool(&self) -> bool { - match *self { - ValueKind::Toggle(b) => b, - _ => panic!("`ValueKind` expected a bool"), - } - } - - fn_as_type!(as_u8, u8); - fn_as_type!(as_u16, u16); - fn_as_type!(as_u32, u32); - fn_as_type!(as_u64, u64); - fn_as_type!(as_i8, i8); - fn_as_type!(as_i16, i16); - fn_as_type!(as_i32, i32); - fn_as_type!(as_i64, i64); - fn_as_type!(as_f32, f32); - fn_as_type!(as_f64, f64); -} diff --git a/nannou_timeline/src/env/point.rs b/nannou_timeline/src/env/point.rs deleted file mode 100644 index 761329d6b..000000000 --- a/nannou_timeline/src/env/point.rs +++ /dev/null @@ -1,113 +0,0 @@ -use super::bang::Bang; -use super::toggle::Toggle; -use time; - -pub use envelope::Point as Trait; - -/// An automation point. -#[derive(Copy, Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct Point { - pub ticks: time::Ticks, - pub value: T, -} - -impl Point { - /// Constructor for a new Point. - pub fn new(ticks: time::Ticks, value: T) -> Point { - Point { - ticks: ticks, - value: value, - } - } - - /// Map a `Point`'s value field from one type to another. - pub fn map_value(self, mut f: F) -> Point - where - F: FnMut(T) -> U, - { - let Point { ticks, value } = self; - Point { - ticks: ticks, - value: f(value), - } - } -} - -/// Fast implementation of envelope::Point for most Point types. -macro_rules! impl_point_for { - ($T: ident, $Scalar: ident) => { - /// Implement envelope::Point for Points with floating point parameters. - impl Trait for Point<$T> { - type X = time::Ticks; - type Y = $T; - fn x_to_scalar(x: time::Ticks) -> $Scalar { - x.ticks() as $Scalar - } - fn x(&self) -> time::Ticks { - self.ticks - } - fn y(&self) -> $T { - self.value - } - } - }; -} - -impl_point_for!(i8, f32); -impl_point_for!(i16, f32); -impl_point_for!(i32, f32); -impl_point_for!(i64, f64); - -impl_point_for!(u8, f32); -impl_point_for!(u16, f32); -impl_point_for!(u32, f32); -impl_point_for!(u64, f64); - -impl_point_for!(f32, f32); -impl_point_for!(f64, f64); - -/// A bang doesn't yet have a Point implementation, so create one. -impl ::envelope::Point for Point { - type X = time::Ticks; - type Y = Bang; - fn x_to_scalar(_: time::Ticks) -> f32 { - 0.0 - } - fn x(&self) -> time::Ticks { - self.ticks - } - fn y(&self) -> Bang { - self.value - } - fn interpolate(_: time::Ticks, _: &Point, _: &Point) -> Bang { - Bang - } -} - -/// A bool doesn't yet have a Point implementation, so create one. -impl ::envelope::Point for Point { - type X = time::Ticks; - type Y = Toggle; - fn x_to_scalar(x: time::Ticks) -> f32 { - x.ticks() as f32 - } - fn x(&self) -> time::Ticks { - self.ticks - } - fn y(&self) -> Toggle { - self.value - } - fn interpolate(x: time::Ticks, start: &Point, end: &Point) -> Toggle { - if x == end.ticks { - end.value - } else if x >= start.ticks { - start.value - } else { - panic!( - "Failed to interpolate toggle envelope - ticks {:?} out of range.", - x - ); - } - } -} diff --git a/nannou_timeline/src/env/points.rs b/nannou_timeline/src/env/points.rs deleted file mode 100644 index af74a17d6..000000000 --- a/nannou_timeline/src/env/points.rs +++ /dev/null @@ -1,47 +0,0 @@ -use super::{Bang, Point, Toggle}; - -/// A dynamically dispatched iterator yielding references to `T`. -pub type Points<'a, T> = Box>>; - -/// A type representing a series of points of some kind supported by Jen's automation. -pub enum Dynamic<'a> { - Bang(Points<'a, Bang>), - Toggle(Points<'a, Toggle>), - I8(Points<'a, i8>), - I16(Points<'a, i16>), - I32(Points<'a, i32>), - I64(Points<'a, i64>), - U8(Points<'a, u8>), - U16(Points<'a, u16>), - U32(Points<'a, u32>), - U64(Points<'a, u64>), - F32(Points<'a, f32>), - F64(Points<'a, f64>), -} - -macro_rules! impl_from_points_for_dynamic { - ($($T:ident $variant:ident),* $(,)*) => { - $( - impl<'a> From> for Dynamic<'a> { - fn from(points: Points<'a, $T>) -> Self { - Dynamic::$variant(points) - } - } - )* - } -} - -impl_from_points_for_dynamic! { - Bang Bang, - Toggle Toggle, - i8 I8, - i16 I16, - i32 I32, - i64 I64, - u8 U8, - u16 U16, - u32 U32, - u64 U64, - f32 F32, - f64 F64, -} diff --git a/nannou_timeline/src/env/toggle.rs b/nannou_timeline/src/env/toggle.rs deleted file mode 100644 index 8c0a177c0..000000000 --- a/nannou_timeline/src/env/toggle.rs +++ /dev/null @@ -1,54 +0,0 @@ -use envelope; - -/// A wrapper around a boolean value for a Point implementation. -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct Toggle(pub bool); - -impl ::std::ops::Deref for Toggle { - type Target = bool; - fn deref<'a>(&'a self) -> &'a bool { - let Toggle(ref b) = *self; - b - } -} - -impl ::std::ops::DerefMut for Toggle { - fn deref_mut<'a>(&'a mut self) -> &'a mut bool { - let Toggle(ref mut b) = *self; - b - } -} - -impl envelope::interpolation::Spatial for Toggle { - type Scalar = f32; - fn add(&self, other: &Toggle) -> Toggle { - if !**other { - *self - } else { - Toggle(!**self) - } - } - fn sub(&self, other: &Toggle) -> Toggle { - if !**other { - *self - } else { - Toggle(!**self) - } - } - fn scale(&self, _scalar: &f32) -> Toggle { - *self - } -} - -impl From for bool { - fn from(Toggle(b): Toggle) -> Self { - b - } -} - -impl From for Toggle { - fn from(b: bool) -> Self { - Toggle(b) - } -} diff --git a/nannou_timeline/src/lib.rs b/nannou_timeline/src/lib.rs deleted file mode 100644 index a7ed6c09b..000000000 --- a/nannou_timeline/src/lib.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! A multi-media timeline library. -//! -//! The primary type is **Timeline** - a conrod **Widget** implementation that can take an -//! arbitrary number of **Track**s in any order. - -#[macro_use] -extern crate conrod_core; -#[macro_use] -extern crate conrod_derive; -extern crate envelope; -extern crate itertools; -extern crate num; -extern crate pitch_calc; -extern crate time_calc; -#[cfg(feature = "serde1")] -#[macro_use] -extern crate serde; - -pub use period::Period; -pub use playhead::Playhead; -pub use ruler::Ruler; -pub use timeline::{Context, Final, PinnedTracks, Timeline, Track, TrackStyle, Tracks}; - -pub mod bars; -mod diff; // temporary until diff.rs lands in iter-tools. -pub(crate) mod env; -pub mod period; -pub mod playhead; -mod ruler; -mod timeline; -pub mod track; - -use time_calc as time; - -/// The duration of a sequence of bars in ticks. -pub fn bars_duration_ticks(bars: I, ppqn: time::Ppqn) -> time::Ticks -where - I: IntoIterator, -{ - bars.into_iter() - .fold(time::Ticks(0), |acc, ts| acc + ts.ticks_per_bar(ppqn)) -} diff --git a/nannou_timeline/src/period.rs b/nannou_timeline/src/period.rs deleted file mode 100644 index cf1676583..000000000 --- a/nannou_timeline/src/period.rs +++ /dev/null @@ -1,38 +0,0 @@ -use std::ops::Sub; -use time_calc::Ticks; - -/// A period of time in ticks. -#[derive(Copy, Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct Period { - pub start: T, - pub end: T, -} - -impl Period { - /// The duration of the period. - pub fn duration(&self) -> T - where - T: Clone + Sub, - { - self.end.clone() - self.start.clone() - } - - /// Does the given ticks fall within the period. - #[inline] - pub fn contains(&self, t: T) -> bool - where - T: PartialOrd + PartialEq, - { - t >= self.start && t < self.end - } - - /// Whether or not self intersects with the other period. - #[inline] - pub fn intersects(&self, other: &Self) -> bool - where - T: PartialOrd, - { - !(other.start > self.end || self.start > other.end) - } -} diff --git a/nannou_timeline/src/playhead.rs b/nannou_timeline/src/playhead.rs deleted file mode 100644 index 0acbbd5e6..000000000 --- a/nannou_timeline/src/playhead.rs +++ /dev/null @@ -1,156 +0,0 @@ -//! A widget representing a snappable playhead over some given range. - -use conrod_core::{self as conrod, widget}; -use ruler; -use time_calc as time; - -/// A widget representing a snappable playhead over some given range. -#[derive(WidgetCommon)] -pub struct Playhead { - #[conrod(common_builder)] - common: widget::CommonBuilder, - ruler: ruler::Ruler, - ppqn: time::Ppqn, - /// The x dimensional range of the visible area of the tracks. - visible_tracks_x: conrod::Range, - style: Style, -} - -/// The unique playhead state to be cached within the `Ui` between updates. -pub struct State { - ids: Ids, -} - -widget_ids! { - struct Ids { line } -} - -#[derive(Copy, Clone, Debug, Default, PartialEq, WidgetStyle)] -pub struct Style { - #[conrod(default = "theme.border_color")] - color: Option, -} - -/// Events that may occur within the Playhead widget. -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum Event { - Pressed, - DraggedTo(time::Ticks), - Released, -} - -impl Playhead { - /// Start building a new Playhead widget. - pub fn new(ruler: ruler::Ruler, ppqn: time::Ppqn, visible_tracks_x: conrod::Range) -> Self { - Playhead { - ruler: ruler, - ppqn: ppqn, - visible_tracks_x: visible_tracks_x, - common: widget::CommonBuilder::default(), - style: Style::default(), - } - } -} - -impl conrod::Widget for Playhead { - type State = State; - type Style = Style; - type Event = Vec; - - fn init_state(&self, id_gen: widget::id::Generator) -> Self::State { - State { - ids: Ids::new(id_gen), - } - } - - fn style(&self) -> Self::Style { - self.style.clone() - } - - /// Update the state of the Playhead. - fn update(self, args: widget::UpdateArgs) -> Self::Event { - use conrod_core::{Colorable, Positionable}; - - let widget::UpdateArgs { - id, - rect, - state, - style, - ui, - .. - } = args; - let Playhead { - ruler, - ppqn, - visible_tracks_x, - .. - } = self; - - let mut events = Vec::new(); - - // Check for widget events: - // - Press to - for widget_event in ui.widget_input(id).events() { - use conrod_core::{event, input}; - - match widget_event { - // If the `Playhead` was pressed with the left mouse button, react with a `Pressed` - // event. - event::Widget::Press(press) => { - if let event::Button::Mouse(input::MouseButton::Left, _) = press.button { - events.push(Event::Pressed); - } - } - - // If the left mouse button was released from the playhead, reacti with a - // `Released` event. - event::Widget::Release(release) => { - if let event::Button::Mouse(input::MouseButton::Left, _) = release.button { - events.push(Event::Released); - } - } - - _ => (), - } - } - - // If the playhead was dragged with the left mouse button, emit a drag event. - if let Some(mouse) = ui.widget_input(id).mouse() { - if mouse.buttons.left().is_down() { - let mouse_abs_x = mouse.abs_xy()[0]; - let new_x = visible_tracks_x.clamp_value(mouse_abs_x); - // Only react if the new position is different to the current position. - if (rect.x() - new_x).abs() > 0.5 { - let x_offset = new_x - visible_tracks_x.start; - let target_position = - time::Ticks((ruler.ticks_per_width(ppqn) * x_offset) as _); - events.push(Event::DraggedTo(target_position)); - } - } - } - - // Instantiate the Line widget as the graphic representation of the playhead. - let color = style.color(&ui.theme); - let (color, thickness) = match ui.widget_input(id).mouse() { - Some(mouse) => match mouse.buttons.left().is_down() { - false => (color.highlighted(), 3.0), - true => (color.clicked(), 3.0), - }, - None => (color, 1.0), - }; - let start = [0.0, 0.0]; - let end = [0.0, rect.h()]; - widget::Line::centred(start, end) - .middle_of(id) - .graphics_for(id) - .color(color) - .thickness(thickness) - .set(state.ids.line, ui); - - events - } -} - -impl conrod::Colorable for Playhead { - builder_method!(color { style.color = Some(conrod::Color) }); -} diff --git a/nannou_timeline/src/ruler.rs b/nannou_timeline/src/ruler.rs deleted file mode 100644 index 92de85ca4..000000000 --- a/nannou_timeline/src/ruler.rs +++ /dev/null @@ -1,359 +0,0 @@ -use bars; -use conrod_core::Scalar; -use std::iter::{Enumerate, Zip}; -use time_calc as time; - -/// For converting a duration in bars to a viewable grid. -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct Ruler { - pub width_per_beat: Scalar, - marker_step: (time::NumDiv, time::Division), -} - -/// The information required to construct a new `Ruler` instance. -#[derive(Clone, Debug)] -pub struct RangeDescription { - pub ppqn: time::Ppqn, - pub duration_ticks: time::Ticks, - pub duration_bars: time::Bars, - pub time_sigs: T, -} - -// Iterators - -/// An iterator that yields an iterator of marker positions (in ticks) for each Bar. -#[derive(Clone)] -pub struct MarkersInTicks { - ppqn: time::Ppqn, - marker_step: (time::NumDiv, time::Division), - bars_with_starts_enumerated: Enumerate, -} - -/// An iterator that yields each marker position on a ruler in ticks. -#[derive(Clone)] -pub struct BarMarkersInTicks { - maybe_next: Option, - marker_step_ticks: time::Ticks, - end_ticks: time::Ticks, -} - -/// An alias for markers in ticks per bar along with the TimeSig for each bar. -type MarkersInTicksWithBarsAndStarts = Zip, I>; - -/// An iterator that yields an iterator producing the simplest Division for each marker in a Bar. -#[derive(Clone)] -pub struct MarkersInDivisions { - ppqn: time::Ppqn, - markers_in_ticks_with_bars_and_starts: MarkersInTicksWithBarsAndStarts, - marker_div: time::Division, -} - -/// An iterator that yields the simplest Division for each marker in a Bar suitable for the Ruler. -#[derive(Clone)] -pub struct BarMarkersInDivisions { - time_sig_bottom: u16, - simplest_divisions: bars::SimplestDivisions, - marker_div: time::Division, -} - -// Implementations - -impl Ruler { - /// Constructor for a Ruler. - pub fn new(total_width: f64, desc: RangeDescription) -> Ruler - where - T: Iterator + Clone, - { - let RangeDescription { - ppqn, - duration_ticks, - duration_bars, - time_sigs, - } = desc; - - let total_ticks = duration_ticks; - let total_beats = total_ticks.ticks() as f64 / ppqn as f64; - let width_per_beat = total_width / total_beats; - let total_num_bars = duration_bars.0 as usize; - - // If we don't have a duration, our Ruler is meaningless. - if total_num_bars == 0 { - return Ruler { - width_per_beat: 0.0, - marker_step: (0, time::Division::Bar), - }; - } - - // In order to determine the measure we need, we first need to define a - // resolution limit for the steps in the ruler. - const MIN_STEP_WIDTH: f64 = 30.0; - - // If we find a suitable step as a number of bars, we'll bind it to this. - let mut maybe_bars_step: Option = None; - - // We'll start by checking steps of large numbers of bars first. - let mut pow_two: u32 = 8; - loop { - use itertools::Itertools; - - let num_bars = 2usize.pow(pow_two); - - // We should only bother testing num_bars that are less than or equal to - // our total number of bars. - if num_bars > total_num_bars { - if pow_two > 0 { - pow_two -= 1; - continue; - } else { - break; - } - } - - // Check for the smallest step in terms of width that would be produced by a ruler - // with markers divided by the current `num_bars`. - let step_in_width = time_sigs.clone().chunks_lazy(num_bars).into_iter().fold( - ::std::f64::MAX, - |smallest_step, time_sigs| { - let step = time_sigs.fold(0.0, |total, ts| { - total + (ts.beats_per_bar() * width_per_beat) - }); - step.min(smallest_step) - }, - ); - - // If our step is still greater than the MIN_STEP_WIDTH, we'll keep - // searching smaller and smaller num_bars steps. - if step_in_width >= MIN_STEP_WIDTH { - if pow_two > 0 { - pow_two -= 1; - continue; - } else { - break; - } - } else { - // Otherwise, we've found our step as a number of bars. - pow_two += 1; - let num_bars = 2usize.pow(pow_two); - maybe_bars_step = Some(num_bars as time::NumDiv); - break; - } - } - - // If maybe_bars_step is some, then we've already found our smallest step. - // Otherwise, we still need to check bar divisions for a suitable step. - let mut use_quaver_step = false; - let mut maybe_div_step = None; - if maybe_bars_step.is_none() { - // First we need to check if the time_sig denominator measure would create - // a suitable step, before trying smaller measures. - let quaver_step_in_pixels = width_per_beat / 2.0; - if quaver_step_in_pixels < MIN_STEP_WIDTH { - use_quaver_step = true; - } else { - // We want to loop into finer resolutions by having a divider that - // doubles every iteration. To do this, we'll raise two to some power - // starting at 2.0 (4th of a beat aka semi_quaver) going smaller until - // we reach the most suitable step. - let mut pow_two = 2.0; - loop { - let divider = 2.0f64.powf(pow_two); - let measure_step_in_pixels = width_per_beat / divider; - // If this measure step would be smaller, the previous measure step - // is the one that we're after. - if measure_step_in_pixels < MIN_STEP_WIDTH { - pow_two -= 1.0; - maybe_div_step = time::Division::Beat.zoom_in(pow_two as u8); - break; - } - pow_two += 1.0; - } - } - } - - Ruler { - width_per_beat: width_per_beat, - marker_step: if let Some(div) = maybe_div_step { - (1, div) - } else if use_quaver_step { - (1, time::Division::Quaver) - } else if let Some(num) = maybe_bars_step { - (num, time::Division::Bar) - } else { - unreachable!(); - }, - } - } - - /// Produce an iterator that yields an iterator for each bar along with its start position in - /// ticks. - pub fn markers_in_ticks( - &self, - bars: I, - ppqn: time::Ppqn, - ) -> MarkersInTicks> - where - I: IntoIterator, - I::IntoIter: Clone, - { - let bars_with_starts = bars::WithStarts::new(bars, ppqn); - MarkersInTicks { - marker_step: self.marker_step, - bars_with_starts_enumerated: bars_with_starts.into_iter().enumerate(), - ppqn, - } - } - - /// Produce an iterator that yields an iterator for each bar that yields each marker's simplest - /// division representation suitable for the Ruler. - pub fn markers_in_divisions( - &self, - bars: I, - ppqn: time::Ppqn, - ) -> MarkersInDivisions> - where - I: IntoIterator, - I::IntoIter: Clone, - { - let bars = bars.into_iter(); - let bars_with_starts = bars::WithStarts::new(bars.clone(), ppqn); - MarkersInDivisions { - ppqn, - markers_in_ticks_with_bars_and_starts: self - .markers_in_ticks(bars, ppqn) - .zip(bars_with_starts), - marker_div: self.marker_step.1, - } - } - - /// Produces the number of visible markers on the `Ruler` for the given bars. - pub fn marker_count(&self, bars: I, ppqn: time::Ppqn) -> usize - where - I: IntoIterator, - I::IntoIter: Clone, - { - // TODO: Could probably do this more efficiently, but this is easy for now. - self.markers_in_ticks(bars, ppqn) - .flat_map(|bar_markers| bar_markers) - .count() - } - - // /// The total spatial width representing the duration of a single tick. - // pub fn width_per_tick(&self) -> Scalar { - // self.width_per_beat / core::PPQN as Scalar - // } - - /// The fractional number of ticks that may fit within one unit of space. - pub fn ticks_per_width(&self, ppqn: time::Ppqn) -> Scalar { - (1.0 / self.width_per_beat) * ppqn as Scalar - } -} - -/// Maps the given `ticks` to some offset along a given `width`. -/// -/// This is often used for translating `Ticks` values into useful `Scalar` coordinates. -pub fn x_offset_from_ticks(ticks: time::Ticks, total: time::Ticks, width: Scalar) -> Scalar { - (ticks.ticks() as Scalar / total.ticks() as Scalar) * width - width / 2.0 -} - -// Iterator implementations. - -impl Iterator for MarkersInTicks -where - I: Iterator + Clone, -{ - type Item = BarMarkersInTicks; - fn next(&mut self) -> Option { - self.bars_with_starts_enumerated - .next() - .map(|(i, (time_sig, start))| match self.marker_step { - (n, time::Division::Bar) => { - let bar_ticks = time_sig.ticks_per_bar(self.ppqn); - BarMarkersInTicks { - maybe_next: if i % n as usize == 0 { - Some(start) - } else { - None - }, - marker_step_ticks: bar_ticks, - end_ticks: start + bar_ticks, - } - } - (1, div) => BarMarkersInTicks { - maybe_next: Some(start), - marker_step_ticks: time::Measure(1, div, time::DivType::Whole) - .to_ticks(time_sig, self.ppqn), - end_ticks: start + time_sig.ticks_per_bar(self.ppqn), - }, - _ => unreachable!(), - }) - } -} - -impl Iterator for BarMarkersInTicks { - type Item = time::Ticks; - fn next(&mut self) -> Option { - match self.maybe_next { - None => None, - Some(this_marker_ticks) => { - let next_marker_ticks = this_marker_ticks + self.marker_step_ticks; - self.maybe_next = if next_marker_ticks < self.end_ticks { - Some(next_marker_ticks) - } else { - None - }; - Some(this_marker_ticks) - } - } - } -} - -impl Iterator for MarkersInDivisions -where - I: Iterator + Clone, -{ - type Item = BarMarkersInDivisions; - fn next(&mut self) -> Option { - self.markers_in_ticks_with_bars_and_starts.next().map( - |(markers_in_ticks, (time_sig, start))| { - let time_sig_bottom = time_sig.bottom; - let markers_in_ticks = BarMarkersInTicks { - maybe_next: markers_in_ticks.maybe_next.map(|ticks| ticks - start), - end_ticks: markers_in_ticks.end_ticks - start, - ..markers_in_ticks - }; - let ppqn = self.ppqn; - let duration_ticks = time_sig.ticks_per_bar(ppqn); - let ticks_iter = markers_in_ticks; - let simplest_divisions = - bars::SimplestDivisions::new(ticks_iter, ppqn, duration_ticks); - BarMarkersInDivisions { - time_sig_bottom: time_sig_bottom, - simplest_divisions: simplest_divisions, - marker_div: self.marker_div, - } - }, - ) - } -} - -impl Iterator for BarMarkersInDivisions { - type Item = time::Division; - fn next(&mut self) -> Option { - self.simplest_divisions.next().map(|maybe_div| { - let div = maybe_div.expect("No simplest division found."); - match self.time_sig_bottom { - 4 => match div { - time::Division::Bar | time::Division::Beat | time::Division::Quaver => div, - time::Division::Minim => time::Division::Beat, - _ => self.marker_div, - }, - 8 => match div { - time::Division::Bar | time::Division::Quaver => div, - time::Division::Minim | time::Division::Beat => time::Division::Quaver, - _ => self.marker_div, - }, - _ => unreachable!(), - } - }) - } -} diff --git a/nannou_timeline/src/timeline.rs b/nannou_timeline/src/timeline.rs deleted file mode 100644 index 0e741be63..000000000 --- a/nannou_timeline/src/timeline.rs +++ /dev/null @@ -1,757 +0,0 @@ -use bars_duration_ticks; -use conrod_core::{self as conrod, widget, Colorable, Positionable, Sizeable, Widget}; -use playhead::{self, Playhead}; -use ruler::{self, Ruler}; -use std; -use std::collections::HashMap; -use time_calc as time; -use track; - -/// A widget for viewing and controlling time related data. -#[derive(WidgetCommon)] -pub struct Timeline { - /// Data required by all conrod Widget types. - #[conrod(common_builder)] - common: widget::CommonBuilder, - /// Style information unique to Timeline. - style: Style, - /// The position of the playhead in ticks. - playhead: Option, - /// The duration of the timeline given as a list of Bars. - bars: B, - /// The resolution of a single quarter note. - ppqn: time::Ppqn, - /// A height for tracks that haven't been given some uniquely specified height. - maybe_track_height: Option, -} - -/// All state to be cached within the Conrod `Ui` between updates. -pub struct State { - ids: Ids, - /// The position of the playhead in ticks. - playhead: Option, - /// State shared with the `Context` (used for setting tracks, playhead and scrollbar). - shared: std::sync::Arc>, -} - -/// State shared between the `Timeline`'s cached `State` and the `Context` used for setting tracks. -#[derive(Debug)] -struct Shared { - /// All track heights that have been overridden by manually dragging the separator. - overridden_track_heights: HashMap, - /// A map of the unique identifiers available for use for each type of `Track`. - track_ids: HashMap>, - /// A unique identifier for the separator that goes under each track. - separator_ids: Vec, - /// The index of the next available `widget::Id` for each `Track` type. - next_track_id_indices: HashMap, - /// The duration of the timeline given as a cached list of Bars. - bars: Vec, -} - -widget_ids! { - struct Ids { - // The backdrop for all widgets whose kid area is the inner, non-bordered rect. - canvas, - // A subtle background reference line for each visible marker in the ruler. - grid_lines[], - // The scrollable surface upon which all non-pinned tracks are placed. - scrollable_rectangle, - // Scrollbar for the scrollable_rectangle. - scrollbar, - // If one is given, this is used for the `Playhead` widget. - playhead, - } -} - -#[derive(Copy, Clone, Debug, Default, PartialEq, WidgetStyle)] -pub struct Style { - #[conrod(default = "theme.shape_color")] - pub color: Option, - #[conrod(default = "theme.border_width")] - pub border: Option, - #[conrod(default = "theme.border_color")] - pub border_color: Option, - #[conrod(default = "theme.label_color")] - pub label_color: Option, - #[conrod(default = "theme.font_size_medium")] - pub label_font_size: Option, - #[conrod(default = "theme.border_width")] - pub separator_thickness: Option, - #[conrod(default = "theme.border_color")] - pub separator_color: Option, -} - -/// Styling attributes for the tracks that make up the timeline. -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct TrackStyle { - pub border: conrod::Scalar, - pub label_color: conrod::Color, - pub font_size: conrod::FontSize, - pub color: conrod::Color, - pub separator_thickness: conrod::Scalar, - pub separator_color: conrod::Color, - pub width: conrod::Scalar, - pub maybe_height: Option, -} - -/// A `Context` returned by the `Timeline` widget for setting tracks, `Playhead` and `Scrollbar`. -#[derive(Debug)] -pub struct Context { - /// The list of musical `Bar`s that describes the temporal structure. - /// - /// To avoid unnecessary allocations, this `Vec` is "taken" from the `Timeline`'s `State` before - /// the `Context` is returned. The `Vec` is then swapped back to the `Timeline`'s `State` when - /// the `Context` is `drop`ped. - pub bars: Vec, - /// The resolution of a single quarter note. - pub ppqn: time::Ppqn, - /// The `Ruler` constructed by the `Timeline`. - pub ruler: Ruler, - /// Track-specific styling attributes. - pub track_style: TrackStyle, - - /// The unique identifier used to instantiate the parent `Timeline` for this `Context`. - pub timeline_id: widget::Id, - /// The transparent upon which pinned tracks and the scrollable area are placed. - pub canvas_id: widget::Id, - /// The unique identifier for the scrollable canvas upon which tracks are placed. - pub scrollable_rectangle_id: widget::Id, - /// The unique identifier for the `Timeline`'s `Playhead` widget. - pub playhead_id: widget::Id, - /// The unique identifier for the `Timeline`'s `Scrollbar` widget. - pub scrollbar_id: widget::Id, - - /// Whether or not the `Timeline` is scrollable. - is_scrollable: bool, - /// The index of the next track. - track_index: std::cell::Cell, - /// The height of all tracks and their separators combined. - combined_track_height: std::cell::Cell, - /// The playhead position in `Ticks` and the delta `Ticks` if a `Playhead` was given. - maybe_playhead: Option<(time::Ticks, time::Ticks)>, - - /// State borrowed from the `Timeline`'s cached state. - /// - /// This will be `Some` for as long as the `Timeline`'s `State` exists within the `Ui`. - shared: std::sync::Weak>, -} - -/// The first context stage returned by the `Timeline`. -/// -/// Allows for setting pinned tracks which must be complete before setting non-pinned tracks. -#[derive(Debug)] -pub struct PinnedTracks { - context: Context, -} - -/// The timeline context stage that follows `PinnedTracks`. -/// -/// Allows for setting tracks upon a scrollable area underneath the pinned tracks. -#[derive(Debug)] -pub struct Tracks { - context: Context, - next_non_pinned_track_index: std::cell::Cell, - /// The height of all pinned tracks and their separators combined. - pub pinned_tracks_height: conrod::Scalar, -} - -/// The final timeline context stage. Follows the `Tracks` stage. -/// -/// Allows for setting the `Playhead` and `Scrollbar` for the timeline. -pub struct Final { - context: Context, -} - -/// Information related to an instantiated `Track`. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct Track { - /// The unique identifier for the `Track`. - pub id: widget::Id, - /// The unique identifier for the `Track`'s separator (under the track). - pub separator_id: widget::Id, - /// The index of the `Track` within all tracks on the `Timeline`. - pub index: usize, - /// The index of the `Track` in relation to all sibling tracks. - /// - /// For non-pinned tracks, this represents the index starting from the first non-pinned track. - pub sibling_index: usize, - /// The event produced by the `Track`. - pub event: E, -} - -/// The thickness of the scrollbar. -pub const SCROLLBAR_THICKNESS: conrod::Scalar = 10.0; - -impl Context { - /// Instantiate the next `Track` in the `Timeline`'s list of tracks. - /// - /// The user never calls this directly. Instead, this method is called via - /// `PinnedTracks::set_next_pinned_track` or `Tracks::set_next_track`. - fn set_next_track( - &self, - widget: T, - parent_id: widget::Id, - track_sibling_index: usize, - ui: &mut conrod::UiCell, - ) -> Track - where - T: track::Widget, - { - // Retrieve the state that is shared with the `Timeline`. - let shared = self.shared.upgrade().expect( - "No shared timeline state found. Check that the \ - `Ui` has not been dropped and that the \ - timeline's state has not been dropped from \ - the widget graph.", - ); - let mut shared = shared.lock().unwrap(); - - // Retrieve the index for this track within the list of all tracks. - let track_index = self.track_index.get(); - - // Retrieve the `widget::Id` to use for this `Track`. - let track_id = { - let type_id = std::any::TypeId::of::(); - let index_of_next_id = { - let index_of_next_id = shared.next_track_id_indices.entry(type_id).or_insert(0); - let index = *index_of_next_id; - *index_of_next_id += 1; - index - }; - - // Check for an existing `widget::Id` that can be used. - let existing_id = { - let track_ids = shared.track_ids.entry(type_id).or_insert(Vec::new()); - track_ids.get(index_of_next_id).map(|&id| id) - }; - - match existing_id { - Some(id) => id, - None => { - // Create a new `widget::Id` for the track if there are none available. - let new_id = ui.widget_id_generator().next(); - let track_ids = shared.track_ids.get_mut(&type_id).unwrap(); - track_ids.push(new_id); - track_ids[index_of_next_id] - } - } - }; - - // Retrieve the `widget::Id` for the track separator that goes under this track. - let separator_id = { - while shared.separator_ids.len() <= track_index { - shared.separator_ids.push(ui.widget_id_generator().next()); - } - shared.separator_ids[track_index] - }; - - // Check to see whether the track separator has been moved and whether or not the - // height of this track should be adjusted. - if let Some(drag) = ui.widget_input(separator_id).drags().left().last() { - let separator_y_range = ui.rect_of(separator_id).unwrap().y; - let separator_h = separator_y_range.len(); - let half_separator_h = separator_h / 2.0; - let y_top_max = match track_sibling_index { - 0 => ui.kid_area_of(parent_id).unwrap().top(), - _ => ui - .rect_of(shared.separator_ids[track_index - 1]) - .unwrap() - .bottom(), - }; - let y_middle_max = y_top_max - half_separator_h; - const MIN_TRACK_HEIGHT: conrod::Scalar = 1.0; - let drag_y = separator_y_range.middle() + drag.to[1]; - let new_middle_y = drag_y.min(y_middle_max - MIN_TRACK_HEIGHT); - let new_height = (y_middle_max + half_separator_h) - (new_middle_y - half_separator_h); - shared.overridden_track_heights.insert(track_id, new_height); - } - - // If the track does not yet have a specified height, check to see whether the `TrackStyle` - // specifies some default height that should be used. - let maybe_height = { - shared - .overridden_track_heights - .get(&track_id) - .map(|&h| h) - .or_else(|| match widget.common().style.maybe_y_dimension { - None => self.track_style.maybe_height, - Some(_) => None, - }) - }; - - // Instantiate the track widget given by the user. - let event = widget - .and_then(self.maybe_playhead, |w, p| track::Widget::playhead(w, p)) - .w(self.track_style.width) - .parent(parent_id) - .and(|w| match track_sibling_index { - 0 => w.top_left_of(parent_id), - _ => { - let last_separator_id = shared.separator_ids[track_index - 1]; - w.down_from(last_separator_id, 0.0) - } - }) - .and_then(maybe_height, |w, h| w.h(h)) - .crop_kids() - .set(track_id, ui); - - // The track separator. Goes underneath the track we are setting. - { - const MIN_HOVERED_SEPARATOR_H: conrod::Scalar = 6.0; - const MIN_NEAR_SEPARATOR_H: conrod::Scalar = 3.0; - - // Expand the height of the separator slightly when the mouse is nearby. - let separator_h = match ui.widget_input(separator_id).mouse() { - Some(_) => self - .track_style - .separator_thickness - .max(MIN_HOVERED_SEPARATOR_H), - None => match ui.global_input().current.widget_capturing_mouse { - Some(widget) => match ui.widget_input(track_id).mouse().is_some() - || ui - .widget_graph() - .does_recursive_depth_edge_exist(track_id, widget) - { - true => self - .track_style - .separator_thickness - .max(MIN_NEAR_SEPARATOR_H), - false => self.track_style.separator_thickness, - }, - None => self.track_style.separator_thickness, - }, - }; - - // Highlight the separator when the mouse interacts with it. - let separator_color = match ui.widget_input(separator_id).mouse() { - Some(mouse) => match mouse.buttons.left().is_down() { - true => self.track_style.separator_color.clicked(), - false => self.track_style.separator_color.highlighted(), - }, - None => self.track_style.separator_color, - }; - - widget::Rectangle::fill([self.track_style.width, separator_h]) - .down_from(track_id, 0.0) - .parent(parent_id) - .color(separator_color) - .set(separator_id, ui); - } - - let track_h = ui.h_of(track_id).unwrap(); - let separator_h = ui.h_of(separator_id).unwrap(); - self.combined_track_height - .set(self.combined_track_height.get() + track_h + separator_h); - self.track_index.set(track_index + 1); - - Track { - id: track_id, - separator_id: separator_id, - index: track_index, - sibling_index: track_sibling_index, - event: event, - } - } -} - -impl Drop for Context { - fn drop(&mut self) { - // When the `Context` is dropped, pass the list of `Bar`s back to the `Timeline` so that - // the `Vec` may be re-used. - if let Some(shared) = self.shared.upgrade() { - if let Ok(mut shared) = shared.lock() { - std::mem::swap(&mut shared.bars, &mut self.bars); - } - } - } -} - -impl PinnedTracks { - /// Set the given `widget` as the next pinned track. - /// - /// Returns information about the `Track` as well as the `widget`'s `Event`. - pub fn set_next_pinned_track(&self, widget: T, ui: &mut conrod::UiCell) -> Track - where - T: track::Widget, - { - let parent_id = self.context.canvas_id; - let sibling_track_index = self.context.track_index.get(); - self.context - .set_next_track(widget, parent_id, sibling_track_index, ui) - } - - /// Finalizes the `PinnedTracksContext` and returns a `TracksContext` that allows for setting - /// regular, non-pinned tracks. - pub fn start_tracks(self, ui: &mut conrod::UiCell) -> Tracks { - let PinnedTracks { context } = self; - - // Place the scrollable canvas in the area underneath the pinned tracks. - let inner_rect = ui - .rect_of(context.timeline_id) - .unwrap() - .pad(context.track_style.border); - let pinned_tracks_h = context.combined_track_height.get(); - let scrollable_rectangle_w = inner_rect.w(); - let scrollable_rectangle_h = inner_rect.h() - pinned_tracks_h; - widget::Rectangle::fill([scrollable_rectangle_w, scrollable_rectangle_h]) - .color(conrod::color::TRANSPARENT) - .bottom_left_of(context.canvas_id) - .scroll_kids_vertically() - .set(context.scrollable_rectangle_id, ui); - - Tracks { - context: context, - next_non_pinned_track_index: std::cell::Cell::new(0), - pinned_tracks_height: pinned_tracks_h, - } - } -} - -impl Tracks { - /// Set the given `widget` as the next track. - /// - /// Returns information about the `Track` as well as the `widget`'s `Event`. - pub fn set_next_track(&self, widget: T, ui: &mut conrod::UiCell) -> Track - where - T: track::Widget, - { - let parent_id = self.context.scrollable_rectangle_id; - let sibling_track_index = self.next_non_pinned_track_index.get(); - self.next_non_pinned_track_index - .set(sibling_track_index + 1); - self.context - .set_next_track(widget, parent_id, sibling_track_index, ui) - } - - /// To be called when all tracks have been instantiated. - /// - /// Returns a context which can be used to set the `Playhead` and `Scrollbar`. - pub fn end_tracks(self) -> Final { - let Tracks { context, .. } = self; - Final { context } - } -} - -impl Final { - /// Instantiate the `Playhead` widget, visible over all tracks that have been instantiated so - /// far. To show the `Playhead` over all tracks, call this after all tracks have been set. - pub fn set_playhead(&self, ui: &mut conrod::UiCell) -> Vec { - let Final { ref context } = *self; - - let playhead = match context.maybe_playhead { - Some((playhead, _)) => playhead, - None => return Vec::new(), - }; - - // Get the position and height of the timeline widget. - let timeline_rect = ui.rect_of(context.timeline_id).unwrap(); - - const PLAYHEAD_WIDTH: conrod::Scalar = 6.0; - let total_duration = bars_duration_ticks(context.bars.iter().cloned(), self.ppqn); - let clamped_playhead = conrod::utils::clamp(playhead, time::Ticks(0), total_duration); - let playhead_weight = clamped_playhead.ticks() as f64 / total_duration.ticks() as f64; - let half_combined_track_height = context.combined_track_height.get() / 2.0; - let border = context.track_style.border; - let y_offset = (timeline_rect.h() - border * 2.0) / 2.0 - half_combined_track_height; - let playhead_y = timeline_rect.y() + y_offset; - let left_of_timeline = timeline_rect.left() + border; - let track_w = context.track_style.width; - let x_from_left = playhead_weight * track_w; - let playhead_x = left_of_timeline + x_from_left; - let visible_tracks_x = conrod::Range::from_pos_and_len(timeline_rect.x(), track_w); - let playhead_h = context.combined_track_height.get() - border * 2.0; - - Playhead::new(context.ruler, self.ppqn, visible_tracks_x) - .w_h(PLAYHEAD_WIDTH, playhead_h) - .x_y(playhead_x, playhead_y) - .color(context.track_style.color.complement()) - .parent(context.canvas_id) - .set(context.playhead_id, ui) - } - - /// If the timeline is scrollable, this sets the scrollbar on top. - /// - /// Returns the `widget::Id` of the scrollbar if it is currently scrollable. - pub fn set_scrollbar(&self, ui: &mut conrod::UiCell) -> Option { - let Final { ref context } = *self; - if context.is_scrollable { - // Scrollbar for the scrollable canvas. - let luminance = context.track_style.color.luminance(); - widget::Scrollbar::y_axis(context.scrollable_rectangle_id) - .auto_hide(false) - .thickness(SCROLLBAR_THICKNESS) - .color(conrod::color::rgb(luminance, luminance, luminance)) - .set(context.scrollbar_id, ui); - Some(context.scrollbar_id) - } else { - None - } - } -} - -impl std::ops::Deref for PinnedTracks { - type Target = Context; - fn deref(&self) -> &Self::Target { - &self.context - } -} - -impl std::ops::Deref for Tracks { - type Target = Context; - fn deref(&self) -> &Self::Target { - &self.context - } -} - -impl std::ops::Deref for Final { - type Target = Context; - fn deref(&self) -> &Self::Target { - &self.context - } -} - -impl Timeline { - /// Construct a new Timeline widget in it's default state. - pub fn new(bars: B, ppqn: time::Ppqn) -> Self - where - B: IntoIterator, - { - Timeline { - common: widget::CommonBuilder::default(), - style: Style::default(), - playhead: None, - bars: bars, - ppqn: ppqn, - maybe_track_height: None, - } - } - - builder_methods! { - pub playhead { playhead = Some(time::Ticks) } - pub track_height { maybe_track_height = Some(conrod::Scalar) } - pub label_color { style.label_color = Some(conrod::Color) } - pub label_font_size { style.label_font_size = Some(conrod::FontSize) } - pub separator_thickness { style.separator_thickness = Some(conrod::Scalar) } - pub separator_color { style.separator_color = Some(conrod::Color) } - } -} - -impl conrod::Widget for Timeline -where - B: IntoIterator, -{ - type State = State; - type Style = Style; - type Event = PinnedTracks; - - fn init_state(&self, id_gen: widget::id::Generator) -> State { - let shared = Shared { - overridden_track_heights: HashMap::new(), - track_ids: HashMap::new(), - separator_ids: Vec::new(), - next_track_id_indices: HashMap::new(), - bars: Vec::new(), - }; - State { - ids: Ids::new(id_gen), - playhead: None, - shared: std::sync::Arc::new(std::sync::Mutex::new(shared)), - } - } - - fn style(&self) -> Style { - self.style.clone() - } - - /// The area of the widget below the title bar, upon which child widgets will be placed. - fn kid_area(&self, args: widget::KidAreaArgs) -> widget::KidArea { - let widget::KidAreaArgs { - rect, style, theme, .. - } = args; - widget::KidArea { - rect: rect.pad(style.border(theme) / 2.0), - pad: conrod::position::Padding::none(), - } - } - - /// Update the state of the Timeline. - fn update(self, args: widget::UpdateArgs) -> Self::Event { - use conrod_core::Borderable; - use diff::{iter_diff, IterDiff}; - - let widget::UpdateArgs { - id, - state, - rect, - style, - ui, - .. - } = args; - let Timeline { - playhead, - bars, - ppqn, - maybe_track_height, - .. - } = self; - let color = style.color(&ui.theme); - let border = style.border(&ui.theme) / 2.0; - let border_color = style.border_color(&ui.theme); - let label_color = style.label_color(&ui.theme); - let font_size = style.label_font_size(&ui.theme); - let separator_thickness = style.separator_thickness(&ui.theme); - let separator_color = style.separator_color(&ui.theme); - let inner_rect = rect.pad(border); - - // The `shared` state is only ever shared with the `Context` which should not exist yet, so - // it should be safe to unwrap. - let temp_shared = state.shared.clone(); - let mut shared = temp_shared.lock().unwrap(); - - // First, ensure that our `state`'s `bars` is up to date. - if let Some(diff) = iter_diff(&shared.bars, bars) { - match diff { - IterDiff::FirstMismatch(i, bs) => { - shared.bars = shared.bars.iter().cloned().take(i).chain(bs).collect() - } - IterDiff::Longer(bs) => shared.bars.extend(bs), - IterDiff::Shorter(len) => shared.bars.truncate(len), - } - } - - // Use a `Canvas` as the backdrop for the Tracks. - widget::Canvas::new() - .color(conrod::color::TRANSPARENT) - .border_color(conrod::color::TRANSPARENT) - .border(0.0) - .middle_of(id) - .wh_of(id) - .pad(border) - .set(state.ids.canvas, ui); - - // If the timeline is scrollable, adjust the width of the tracks to fit the Scrollbar. - let is_scrollable = ui - .widget_graph() - .widget(state.ids.scrollable_rectangle) - .and_then(|w| w.maybe_y_scroll_state.as_ref()) - .map(|scroll_state| scroll_state.offset_bounds.magnitude().is_sign_negative()) - .unwrap_or(false); - - // The scrollbar should not overlap with the tracks. - let tracks_w = match is_scrollable { - true => inner_rect.w() - SCROLLBAR_THICKNESS, - false => inner_rect.w(), - }; - - // Construct the ruler for the Timeline in it's current state. - let total_ticks = bars_duration_ticks(shared.bars.iter().cloned(), ppqn); - let duration_bars = time::Bars(shared.bars.len() as _); - let ruler = { - let desc = ruler::RangeDescription { - ppqn, - duration_ticks: total_ticks, - duration_bars, - time_sigs: shared.bars.iter().cloned(), - }; - Ruler::new(tracks_w, desc) - }; - - // Draw a light grid over the background to clarify ruler divisions. - let tracks_x = { - let start = inner_rect.left(); - let end = start + tracks_w; - conrod::position::Range::new(start, end) - }; - - // Ensure there are enough grid line `widget::Id`s. - let num_markers = ruler.marker_count(shared.bars.iter().cloned(), ppqn); - if state.ids.grid_lines.len() < num_markers { - state.update(|state| { - state - .ids - .grid_lines - .resize(num_markers, &mut ui.widget_id_generator()); - }); - } - let mut grid_line_idx = 0; - for bar_markers in ruler.markers_in_ticks(shared.bars.iter().cloned(), ppqn) { - for (i, ticks) in bar_markers.enumerate() { - let x_offset = super::ruler::x_offset_from_ticks(ticks, total_ticks, tracks_w); - let line_x = tracks_x.middle() + x_offset; - let a = [line_x, inner_rect.top()]; - let b = [line_x, inner_rect.bottom()]; - let color = match i { - 0 => border_color.alpha(0.5), - _ => border_color.alpha(0.125), - }; - let line_id = state.ids.grid_lines[grid_line_idx]; - widget::Line::abs(a, b) - .graphics_for(state.ids.canvas) - .parent(state.ids.canvas) - .color(color) - .thickness(1.0) - .set(line_id, ui); - grid_line_idx += 1; - } - } - - let maybe_playhead = playhead.map(|playhead| { - let delta = state - .playhead - .map(|old| playhead - old) - .unwrap_or(time::Ticks(0)); - (playhead, delta) - }); - - // Reset all the next track indices to `0`. - for index in shared.next_track_id_indices.values_mut() { - *index = 0; - } - - if state.playhead != playhead { - state.update(|state| state.playhead = playhead); - } - - let track_style = TrackStyle { - border: border, - label_color: label_color, - font_size: font_size, - color: color, - separator_thickness: separator_thickness, - separator_color: separator_color, - width: tracks_w, - maybe_height: maybe_track_height, - }; - - let context = Context { - bars: std::mem::replace(&mut shared.bars, Vec::new()), - ppqn, - ruler: ruler, - track_style: track_style, - shared: std::sync::Arc::downgrade(&state.shared), - timeline_id: id, - scrollable_rectangle_id: state.ids.scrollable_rectangle, - playhead_id: state.ids.playhead, - scrollbar_id: state.ids.scrollbar, - canvas_id: state.ids.canvas, - is_scrollable: is_scrollable, - track_index: std::cell::Cell::new(0), - combined_track_height: std::cell::Cell::new(0.0), - maybe_playhead: maybe_playhead, - }; - - PinnedTracks { context } - } -} - -impl conrod::Colorable for Timeline { - builder_method!(color { style.color = Some(conrod::Color) }); -} - -impl conrod::Borderable for Timeline { - builder_methods! { - border { style.border = Some(conrod::Scalar) } - border_color { style.border_color = Some(conrod::Color) } - } -} diff --git a/nannou_timeline/src/track/automation/bang.rs b/nannou_timeline/src/track/automation/bang.rs deleted file mode 100644 index a2cbb9db6..000000000 --- a/nannou_timeline/src/track/automation/bang.rs +++ /dev/null @@ -1,353 +0,0 @@ -use super::EnvelopeTrait; -use bars_duration_ticks; -use conrod_core::{self as conrod, widget}; -use env; -use ruler; -use time_calc::{self as time, Ticks}; -use track; - -pub use env::{Bang as BangValue, Point, PointTrait}; - -/// The bounded envelope type compatible with the `Bang` automation track. -pub type Envelope = env::bounded::Envelope; - -/// For viewing and manipulating a series of discrete points over time. -#[derive(WidgetCommon)] -pub struct Bang<'a> { - #[conrod(common_builder)] - common: widget::CommonBuilder, - envelope: &'a Envelope, - bars: &'a [time::TimeSig], - ppqn: time::Ppqn, - /// The position of the playhead in ticks, along with its change in position. - pub maybe_playhead: Option<(Ticks, Ticks)>, - style: Style, -} - -/// Unique state for the Bang automation. -pub struct State { - /// A `Pole` (collection of indices) for each point in the envelope. - point_poles: Vec, - /// The `Pole` used to instantiate the widgets for the phantom cursor point. - phantom_pole: Pole, - // /// A NodeIndex for drawing the value the point closest to the cursor using a Text widget. - // /// - // /// TODO: use this to draw (Bar, Measure, Ticks) string to closest point. - // closest_point_ticks_text_idx: IndexSlot, -} - -/// The indices necessary for a single envelope point `Pole` representation. -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct Pole { - top_circle_id: widget::Id, - bottom_circle_id: widget::Id, - line_id: widget::Id, -} - -#[derive(Copy, Clone, Debug, Default, PartialEq, WidgetStyle)] -pub struct Style { - #[conrod(default = "theme.shape_color")] - pub color: Option, - #[conrod(default = "4.0")] - pub point_radius: Option, -} - -/// The various kinds of events returned by an automation track. -#[derive(Copy, Clone, Debug)] -pub enum Event { - /// Occurs if the playhead has passed a `Bang`. - Bang, - /// Mutate an envelope point in some manner. - Mutate(super::Mutate), -} - -impl<'a> Bang<'a> { - /// Construct a new default Automation. - pub fn new(bars: &'a [time::TimeSig], ppqn: time::Ppqn, envelope: &'a Envelope) -> Self { - Bang { - bars: bars, - ppqn: ppqn, - maybe_playhead: None, - envelope: envelope, - common: widget::CommonBuilder::default(), - style: Style::default(), - } - } - - /// The Automation with some given point radius to use for the envelope points. - pub fn point_radius(mut self, radius: conrod::Scalar) -> Self { - self.style.point_radius = Some(radius); - self - } -} - -impl<'a> track::Widget for Bang<'a> { - fn playhead(mut self, playhead: (Ticks, Ticks)) -> Self { - self.maybe_playhead = Some(playhead); - self - } -} - -impl Pole { - pub fn new(id_gen: &mut widget::id::Generator) -> Self { - Pole { - top_circle_id: id_gen.next(), - bottom_circle_id: id_gen.next(), - line_id: id_gen.next(), - } - } -} - -impl<'a> conrod::Colorable for Bang<'a> { - fn color(mut self, color: conrod::Color) -> Self { - self.style.color = Some(color); - self - } -} - -impl<'a> conrod::Widget for Bang<'a> { - type State = State; - type Style = Style; - type Event = Vec; - - fn init_state(&self, mut id_gen: widget::id::Generator) -> Self::State { - State { - point_poles: Vec::new(), - phantom_pole: Pole::new(&mut id_gen), - } - } - - fn style(&self) -> Self::Style { - self.style.clone() - } - - fn default_y_dimension(&self, ui: &conrod::Ui) -> conrod::position::Dimension { - ui.theme - .widget_style::