diff --git a/src/app.rs b/src/app.rs index 5b1c051..f538226 100644 --- a/src/app.rs +++ b/src/app.rs @@ -17,65 +17,65 @@ pub struct System { pub font_size: f32 } -pub fn init(title: &str, size: (f64, f64)) -> System { - let event_loop = EventLoop::new(); - let context = glutin::ContextBuilder::new().with_vsync(true); - let builder = WindowBuilder::new() - .with_title(title.to_owned()) - .with_inner_size(glium::glutin::dpi::LogicalSize::new(size.0, size.1)); - let display = Display::new(builder, context, &event_loop) - .expect("Failed to initialize display!"); - - let mut imgui = Context::create(); - imgui.set_ini_filename(None); - - let mut platform = WinitPlatform::init(&mut imgui); - - { - let gl_window = display.gl_window(); - let window = gl_window.window(); - - let dpi_mode = if let Ok(factor) = std::env::var("FORCE_DPI_FACTOR") { - match factor.parse::() { - Ok(f) => HiDpiMode::Locked(f), - Err(e) => panic!("Invalid scaling factor: {}", e), +impl System { + pub fn init(title: &str, size: (f64, f64)) -> System { + let event_loop = EventLoop::new(); + let context = glutin::ContextBuilder::new().with_vsync(true); + let builder = WindowBuilder::new() + .with_title(title.to_owned()) + .with_inner_size(glium::glutin::dpi::LogicalSize::new(size.0, size.1)); + let display = Display::new(builder, context, &event_loop) + .expect("Failed to initialize display!"); + + let mut imgui = Context::create(); + imgui.set_ini_filename(None); + + let mut platform = WinitPlatform::init(&mut imgui); + + { + let gl_window = display.gl_window(); + let window = gl_window.window(); + + let dpi_mode = if let Ok(factor) = std::env::var("FORCE_DPI_FACTOR") { + match factor.parse::() { + Ok(f) => HiDpiMode::Locked(f), + Err(e) => panic!("Invalid scaling factor: {}", e), + } + } else { + HiDpiMode::Default + }; + + platform.attach_window(imgui.io_mut(), window, dpi_mode); + } + + let font_size = 13.0; + + imgui.fonts().add_font(&[ + FontSource::TtfData { + data: include_bytes!("/home/skybl/code/rs/imgui-test/assets/SFProDisplay_Regular.ttf"), + size_pixels: font_size, + config: Some(FontConfig { + rasterizer_multiply: 1.5, + oversample_h: 4, + oversample_v: 4, + ..FontConfig::default() + }), } - } else { - HiDpiMode::Default - }; + ]); - platform.attach_window(imgui.io_mut(), window, dpi_mode); - } + let renderer = Renderer::init(&mut imgui, &display) + .expect("failed to initialize renderer!"); - let font_size = 13.0; - - imgui.fonts().add_font(&[ - FontSource::TtfData { - data: include_bytes!("/home/skybl/code/rs/imgui-test/assets/SFProDisplay_Regular.ttf"), - size_pixels: font_size, - config: Some(FontConfig { - rasterizer_multiply: 1.5, - oversample_h: 4, - oversample_v: 4, - ..FontConfig::default() - }), + System { + event_loop, + display, + imgui, + platform, + renderer, + font_size } - ]); - - let renderer = Renderer::init(&mut imgui, &display) - .expect("failed to initialize renderer!"); - - System { - event_loop, - display, - imgui, - platform, - renderer, - font_size } -} -impl System { pub fn main_loop(self, mut run_ui: F) { let System { event_loop, diff --git a/src/drag_area.rs b/src/drag_area.rs index 8868522..fb5cde3 100644 --- a/src/drag_area.rs +++ b/src/drag_area.rs @@ -12,14 +12,11 @@ pub struct DragArea { pub offset: [f32; 2], pub size: [f32; 2], pub border_color: ImColor32, - default_offset: [f32; 2], + pub default_offset: [f32; 2], default_size: [f32; 2] } -pub fn init( - offset: [f32; 2], - size: [f32; 2] -) -> DragArea { +pub fn init(offset: [f32; 2], size: [f32; 2]) -> DragArea { DragArea { offset, size, diff --git a/src/main.rs b/src/main.rs index 677ff98..7d6e002 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,61 +1,42 @@ use glium::glutin::dpi::LogicalSize; -use imgui::*; -use imgui::color::ImColor32; +//use glium::glutin::window::Window as GlutinWindow; +//use imgui::Ui; mod app; -mod drag_area; +use app::System; + +mod storage; +use storage::Storage; + +mod main_window; +use main_window::MainWindow; + +mod ui_generic; const INIT_SIZE: (f64, f64) = (640.0, 480.0); const MIN_SIZE: (f64, f64) = (300.0, 250.0); - fn main() { - let main_app = app::init("muh window", INIT_SIZE); + let main_app = System::init("muh window", INIT_SIZE); main_app.display .gl_window() .window() .set_min_inner_size(Some(LogicalSize::new(MIN_SIZE.0, MIN_SIZE.1))); - let mut main_drag_area = drag_area::init( - [30.0, 30.0], - [200.0, 200.0] - ); + let mut main_window = MainWindow::init(); - main_app.main_loop(move |_, ui, display| { - Window::new("Node Editor") - .size([300.0, 110.0], Condition::FirstUseEver) - .build(&ui, || { - ui.text("Basic Drag Area"); - ui.separator(); - - main_drag_area.build( - &ui, - &display.gl_window().window(), - move |draw_list, area_min, area_max, offset| { - let text_pos: [f32; 2] = [ - area_min[0] + offset[0], - area_min[1] + offset[1] - ]; - - draw_list.add_text( - text_pos, - ImColor32::WHITE, - "Basic Drag Area Contents" - ); - } - ); - - draw_mouse_pos_text(ui); - }); - }); -} + let win_size = main_app.display.gl_window().window().inner_size(); + main_window.size = [ + win_size.width as f32, + 300.0 + ]; + + let mut main_storage = Storage::init(); -fn draw_mouse_pos_text(ui: &Ui) { - let mouse_pos = ui.io().mouse_pos; + main_storage.add_bpm_change(0, 120); - ui.text(format!( - "mouse pos: ({:.1}, {:.1})", - mouse_pos[0], mouse_pos[1] - )); + main_app.main_loop(move |_, ui, display| { + main_window.build(ui, display, &mut main_storage); + }); } diff --git a/src/main_window.rs b/src/main_window.rs new file mode 100644 index 0000000..747248f --- /dev/null +++ b/src/main_window.rs @@ -0,0 +1,112 @@ +//use glium::glutin::window::Window as GlutinWindow; +use glium::Display; +use imgui::{Window, Ui, Condition}; + +use crate::ui_generic::drag_area::DragArea; +use crate::ui_generic::keyframe_editor::KeyframeEditor; +use crate::storage::Storage; + +const DRAG_AREA_MIN_SIZE: [f32; 2] = [300.0, 300.0]; + +const WIN_SIZE: [f32; 2] = [ + DRAG_AREA_MIN_SIZE[0] + 15.0, + DRAG_AREA_MIN_SIZE[1] + 100.0 +]; + +const WIN_MIN_SIZE: [f32; 2] = WIN_SIZE; + +pub struct MainWindow { + pub size: [f32; 2], + default_size: [f32; 2], + min_size: [f32; 2], + editor: KeyframeEditor, + string_buf: String +} + +impl MainWindow { + pub fn init() -> MainWindow { + MainWindow { + size: WIN_SIZE, + default_size: WIN_SIZE, + min_size: WIN_MIN_SIZE, + editor: KeyframeEditor::new(DRAG_AREA_MIN_SIZE), + string_buf: String::new() + } + } + + pub fn build( + &mut self, + ui: &Ui, + display: &Display, + storage: &mut Storage + ) { + Window::new("Main Window") + .size(self.size, Condition::FirstUseEver) + .size_constraints(self.min_size, [1000.0, 1000.0]) + .build(&ui, || { + MainWindow::build_window_contents( + self, + ui, + display, + storage + ) + }); + } + + fn build_window_contents( + self: &mut MainWindow, + ui: &Ui, + display: &Display, + storage: &mut Storage + ) { + let win_size = ui.window_size(); + + self.editor.drag_area.size = [ + win_size[0] - 20.0, + win_size[1] - 100.0 + ]; + + ui.same_line(); + let dec_bpm = ui.button("-"); + + ui.same_line(); + ui.text(format!("{} bpm", storage.initial_bpm())); + + ui.same_line(); + let inc_bpm = ui.button("+"); + + if dec_bpm { storage.bpm_changes[0].bpm -= 1; } + if inc_bpm { storage.bpm_changes[0].bpm += 1; } + + ui.same_line(); + let kf_name_input = ui + .input_text("", &mut self.string_buf) + .hint(format!( + "name new keyframe channel ยท {} in current project", + storage.keyframe_channels.len() + )) + .enter_returns_true(true) + .build(); + + ui.same_line(); + let add_kf_chan = ui.button("add keyframe channel"); + + if (kf_name_input || add_kf_chan) && !self.string_buf.is_empty() { + storage.add_keyframe_channel(String::from(&self.string_buf)); + self.string_buf.clear(); + } + + ui.separator(); + + self.editor.build(ui, display, storage); + draw_mouse_pos_text(ui); + } +} + +fn draw_mouse_pos_text(ui: &Ui) { + let mouse_pos = ui.io().mouse_pos; + ui.text(format!( + "mouse pos: ({:.1}, {:.1})", + mouse_pos[0], mouse_pos[1] + )); +} diff --git a/src/storage.rs b/src/storage.rs new file mode 100644 index 0000000..5c33e04 --- /dev/null +++ b/src/storage.rs @@ -0,0 +1,133 @@ +#[allow(dead_code)] +pub struct Storage { + // TODO: add custom timestamp class which can be queried by + // beats/measures/etc. + pub timestamp: u64, + pub playback_mode: PlaybackMode, + pub bpm_changes: Vec, + pub time_changes: Vec, + pub keyframe_channels: Vec +} + +pub enum PlaybackMode { + Playing, + Stopped, + Paused +} + +pub struct BpmChange { + pub timestamp: u64, + pub bpm: u32 +} + +pub struct TimeChange { + pub timestamp: u64, + pub time: (u8, u8) +} + +pub struct KeyframeChannel { + pub name: String, + pub enabled: bool, + pub keyframes: Vec +} + +pub enum KeyframeType { + Tick +} + +pub struct Keyframe { + pub kf_type: KeyframeType, + pub timestamp: u64 +} + +impl Storage { + pub fn init() -> Storage { + Storage { + timestamp: 0, + playback_mode: PlaybackMode::Stopped, + bpm_changes: Vec::::new(), + time_changes: Vec::::new(), + keyframe_channels: Vec::::new() + } + } + + pub fn add_bpm_change(&mut self, timestamp: u64, bpm: u32) { + self.bpm_changes.push(BpmChange::new(timestamp, bpm)); + } + + pub fn delete_last_bpm_change(&mut self) { + self.bpm_changes.pop(); + } + + pub fn get_bpm_change_index(&self, timestamp: u64) -> Option { + for (idx, bc) in self.bpm_changes.iter().enumerate() { + if (bc.timestamp) == timestamp { + return Some(idx); + } + } + None + } + + pub fn initial_bpm(&self) -> u32 { + self.bpm_changes[0].bpm + } + + pub fn add_time_change(&mut self, timestamp: u64, time: (u8, u8)) { + self.time_changes.push(TimeChange::new(timestamp, time)); + } + + pub fn add_keyframe_channel(&mut self, name: String) { + self.keyframe_channels.push(KeyframeChannel::new(name)); + } + + pub fn keyframe_channel_by_name( + &self, + name: String + ) -> Option<&KeyframeChannel> { + for chan in self.keyframe_channels.iter() { + if chan.name == name { + return Some(chan); + } + } + None + } +} + +impl BpmChange { + pub fn new(timestamp: u64, bpm: u32) -> BpmChange { + BpmChange { timestamp, bpm } + } +} + +impl TimeChange { + pub fn new(timestamp: u64, time: (u8, u8)) -> TimeChange { + TimeChange { timestamp, time } + } +} + +impl KeyframeChannel { + pub fn new(name: String) -> KeyframeChannel { + KeyframeChannel { + name, + enabled: true, + keyframes: Vec::::new() + } + } + + pub fn add_keyframe(&mut self, timestamp: u64) { + self.keyframes.push(Keyframe { + kf_type: KeyframeType::Tick, + timestamp + }); + } + + // TODO: improve efficiency of this method + pub fn keyframe_at_timestamp(&self, timestamp: u64) -> Option<&Keyframe> { + for keyframe in self.keyframes.iter() { + if keyframe.timestamp == timestamp { + return Some(keyframe); + } + } + None + } +} diff --git a/src/ui_generic.rs b/src/ui_generic.rs new file mode 100644 index 0000000..e333af3 --- /dev/null +++ b/src/ui_generic.rs @@ -0,0 +1,2 @@ +pub mod drag_area; +pub mod keyframe_editor; diff --git a/src/ui_generic/drag_area.rs b/src/ui_generic/drag_area.rs new file mode 100644 index 0000000..d454494 --- /dev/null +++ b/src/ui_generic/drag_area.rs @@ -0,0 +1,98 @@ +// Node editor UI element + +#[allow(dead_code)] + +use glium::glutin::window::Window as GlutinWindow; +use glium::glutin::dpi::LogicalPosition; +use imgui::{Ui, MouseButton}; +use imgui::color::ImColor32; +use imgui::draw_list::DrawListMut; + +pub struct DragArea { + pub offset: [f32; 2], + pub size: [f32; 2], + pub border_color: ImColor32, + pub default_offset: [f32; 2], + default_size: [f32; 2], + active: bool +} + +impl DragArea { + pub fn new(size: [f32; 2], offset: [f32; 2]) -> DragArea { + DragArea { + offset, + size, + border_color: ImColor32::from_rgb(15u8, 20u8, 25u8), + default_offset: offset, + default_size: size, + active: false + } + } + + pub fn build + + (&mut self, ui: &Ui, window: &GlutinWindow, mut build_children: F) + { + let mouse_delta = ui.io().mouse_delta; + let mouse_pos = ui.io().mouse_pos; + + if ui.button("reset view") { + self.offset = self.default_offset; + } + + ui.invisible_button("##empty", self.size); + + let rect_min = ui.item_rect_min(); + let rect_max = ui.item_rect_max(); + + let lmb_down = ui.is_mouse_dragging(MouseButton::Left); + let mmb_down = ui.is_mouse_dragging(MouseButton::Middle); + let hovered = ui.is_item_hovered(); + + if !self.active && ((lmb_down || mmb_down) && hovered) { + self.active = true; + } else if self.active && !(lmb_down || mmb_down) { + self.active = false; + } + + if self.active { + if rect_min[0] > mouse_pos[0] { + window.set_cursor_position( + LogicalPosition { x: rect_max[0], y: mouse_pos[1] } + ).unwrap(); + self.offset[0] -= self.size[0]; + } + + if rect_max[0] < mouse_pos[0] { + window.set_cursor_position( + LogicalPosition { x: rect_min[0], y: mouse_pos[1] } + ).unwrap(); + self.offset[0] += self.size[0]; + } + + if rect_min[1] > mouse_pos[1] { + window.set_cursor_position( + LogicalPosition { x: mouse_pos[0], y: rect_max[1] } + ).unwrap(); + self.offset[1] -= self.size[1]; + } + + if rect_max[1] < mouse_pos[1] { + window.set_cursor_position( + LogicalPosition { x: mouse_pos[0], y: rect_min[1] } + ).unwrap(); + self.offset[1] += self.size[1]; + } + + self.offset[0] += mouse_delta[0]; + self.offset[1] += mouse_delta[1]; + } + + let draw_list = ui.get_window_draw_list(); + + draw_list.with_clip_rect(rect_min, rect_max, || { + build_children(&draw_list, &rect_min, &rect_max, &self.offset); + draw_list.add_rect(rect_min, rect_max, self.border_color).build(); + }); + } +} diff --git a/src/ui_generic/keyframe_editor.rs b/src/ui_generic/keyframe_editor.rs new file mode 100644 index 0000000..0c211e1 --- /dev/null +++ b/src/ui_generic/keyframe_editor.rs @@ -0,0 +1,153 @@ +use glium::Display; +use imgui::Ui; +use imgui::color::ImColor32; + +use crate::ui_generic::drag_area::DragArea; +use crate::storage::Storage; + +const KF_CHAN_HEIGHT: f32 = 20.0; +const KF_CHAN_TITLE_DEFAULT_COLOR: ImColor32 = + ImColor32::from_rgb(38, 38, 38); +const KF_CHAN_TITLE_HOVER_COLOR: ImColor32 = + ImColor32::from_rgb(79, 79, 79); +const KF_CHAN_CONTENT_DEFAULT_COLOR: ImColor32 = + ImColor32::from_rgb(16, 16, 16); +const KF_CHAN_CONTENT_HOVER_COLOR: ImColor32 = + ImColor32::from_rgb(54, 54, 54); + +pub struct KeyframeEditor { + pub drag_area: DragArea +} + +impl KeyframeEditor { + pub fn new(size: [f32; 2]) -> KeyframeEditor { + KeyframeEditor { + drag_area: DragArea::new(size, [0.0, 0.0]) + } + } + + pub fn build( + &mut self, + ui: &Ui, + display: &Display, + storage: &mut Storage + ) { + self.drag_area.build( + &ui, + &display.gl_window().window(), + |draw_list, area_min, area_max, offset| { + let mouse_y = ui.io().mouse_pos[1]; + + let origin: [f32; 2]; + + if storage.keyframe_channels.len() == 0 { + let text_pos: [f32; 2] = [ + area_min[0] + (area_max[0] - area_min[0]) / 2.0 - 50.0, + area_min[1] + (area_max[1] - area_min[1]) / 2.0 - 2.5 + ]; + + draw_list.add_text( + text_pos, + KF_CHAN_TITLE_HOVER_COLOR, + "No keyframes to show." + ); + + return; + } + + let origin = [ + area_min[0] + offset[0], + area_min[1] + offset[1] + ]; + + let mut chan_y: f32 = 1.0; + let mut next_chan_y: f32 = chan_y + KF_CHAN_HEIGHT; + + for (idx, chan) in storage.keyframe_channels.iter().enumerate() { + let title_bg: ImColor32; + let content_bg: ImColor32; + + if ( + (mouse_y < origin[1] + next_chan_y) && + (mouse_y > origin[1] + chan_y) + ) { + content_bg = KF_CHAN_CONTENT_HOVER_COLOR; + title_bg = KF_CHAN_TITLE_HOVER_COLOR; + } else { + content_bg = KF_CHAN_CONTENT_DEFAULT_COLOR; + title_bg = KF_CHAN_TITLE_DEFAULT_COLOR; + } + + let chan_title_start: [f32; 2] = [ + origin[0] + 1.0, + origin[1] + chan_y + 1.0 + ]; + + let chan_title_end: [f32; 2] = [ + origin[0] + 100.0, + origin[1] + next_chan_y + 1.0 + ]; + + draw_list + .add_rect( + chan_title_start, + chan_title_end, + title_bg + ) + .filled(true) + .thickness(0.0) + .build(); + + let title_pos: [f32; 2] = [ + origin[0] + 1.0 + 3.0, + origin[1] + chan_y + 2.0 + ]; + + draw_list.add_text( + title_pos, + ImColor32::WHITE, + String::from(&chan.name) + ); + + let chan_content_end: [f32; 2] = [ + area_max[0], + chan_title_start[1] + ]; + + draw_list + .add_rect( + chan_title_end, + chan_content_end, + content_bg + ) + .filled(true) + .thickness(0.0) + .build(); + + if true { + let border_start = [ + origin[0], + origin[1] + next_chan_y + ]; + + let border_end = [ + area_max[0], + origin[1] + next_chan_y + ]; + + draw_list + .add_line( + border_start, + border_end, + title_bg + ) + .build(); + } + + chan_y += KF_CHAN_HEIGHT; + next_chan_y += KF_CHAN_HEIGHT; + } + } + ); + } +}