diff --git a/Cargo.toml b/Cargo.toml index c3f151d..30c441b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "terminal/display_server", "terminal/ansi_parser", "shell", + "sdf_editor", "math/str2int", "math/int2str", "math/radix_transform", diff --git a/nested/src/integer/editor.rs b/nested/src/integer/editor.rs index 0281a94..aee6c37 100644 --- a/nested/src/integer/editor.rs +++ b/nested/src/integer/editor.rs @@ -6,7 +6,7 @@ use { crate::{ core::{ViewPort, OuterViewPort, Observer}, singleton::{SingletonView, SingletonBuffer}, - sequence::{SequenceView}, + sequence::{SequenceView, SequenceViewExt}, vec::VecBuffer, terminal::{TerminalAtom, TerminalStyle, TerminalView, TerminalEvent, TerminalEditor, TerminalEditorResult}, tree_nav::{TreeNav, TreeNavResult, TerminalTreeEditor, TreeCursor}, @@ -102,6 +102,17 @@ impl PosIntEditor { self.digits_editor.get_data_port() .filter_map(move |digit_editor| digit_editor.read().unwrap().data.get()?.to_digit(radix)) } + + pub fn get_value(&self) -> u32 { + let mut value = 0; + let mut weight = 1; + for digit_value in self.get_data_port().get_view().unwrap().iter().collect::>().into_iter().rev() { + value += digit_value * weight; + weight *= self.radix; + } + + value + } } impl TreeNav for PosIntEditor { diff --git a/nested/src/list/editor.rs b/nested/src/list/editor.rs index f29638b..736d27d 100644 --- a/nested/src/list/editor.rs +++ b/nested/src/list/editor.rs @@ -147,7 +147,7 @@ where ItemEditor: TerminalTreeEditor + ?Sized + Send + Sync + 'static, ); return TreeNavResult::Continue; } - + if i < self.data.len() { match cur.mode { ListCursorMode::Insert => { @@ -327,6 +327,8 @@ where ItemEditor: TerminalTreeEditor + ?Sized + Send + Sync + 'static, } ); } + } else { + self.goto_home(); } TreeNavResult::Continue } @@ -424,7 +426,7 @@ where ItemEditor: TerminalTreeEditor + ?Sized + Send + Sync + 'static, TreeNavResult::Exit => { drop(cur_edit); drop(ce); - self.up(); + self.up(); if i+1 < self.data.len() { @@ -477,10 +479,10 @@ where ItemEditor: TerminalTreeEditor + ?Sized + Send + Sync + 'static, ListCursorMode::Insert => { match event { TerminalEvent::Input(Event::Key(Key::Backspace)) => { - if idx > 0 { - self.data.remove(idx-1); + if idx > 0 && idx <= self.data.len() { cur.idx = Some(idx-1); self.cursor.set(cur); + self.data.remove(idx-1); TerminalEditorResult::Continue } else { TerminalEditorResult::Exit @@ -551,24 +553,26 @@ where ItemEditor: TerminalTreeEditor + ?Sized + Send + Sync + 'static, TerminalEditorResult::Exit => { cur_edit.up(); drop(cur_edit); + drop(ce); match event { - TerminalEvent::Input(Event::Key(Key::Char(' '))) => { - // split.. - self.cursor.set(ListCursor { - mode: ListCursorMode::Insert, - idx: Some(idx+1) - }); - } TerminalEvent::Input(Event::Key(Key::Backspace)) => { + // todo: join instead of remove self.cursor.set(ListCursor { mode: ListCursorMode::Insert, idx: Some(idx) }); - self.data.remove(idx); // todo: join instead of remove + self.data.remove(idx); + } + _ => { + // todo: split + + self.cursor.set(ListCursor { + mode: ListCursorMode::Insert, + idx: Some(idx+1) + }); } - _ => {} } }, TerminalEditorResult::Continue => {} @@ -611,11 +615,11 @@ where ItemEditor: TerminalEditor + ?Sized + Send + Sync + 'static, atom.add_style_front(TerminalStyle::bg_color((90,60,200))) ), ListEditorViewSegment::Modify(sub_view) => { - sub_view.clone()/*.map_item( + sub_view.clone().map_item( |_pt, atom| - atom//.add_style_back(TerminalStyle::bg_color((0,0,0))) + atom.add_style_back(TerminalStyle::bg_color((22,15,50))) //.add_style_back(TerminalStyle::bold(true)) - )*/ + ) }, ListEditorViewSegment::View(sub_view) => sub_view.clone() @@ -624,7 +628,7 @@ where ItemEditor: TerminalEditor + ?Sized + Send + Sync + 'static, } pub fn horizontal_sexpr_view(&self) -> OuterViewPort { - self.get_seg_seq_view().horizontal_sexpr_view(0) + self.get_seg_seq_view().horizontal_sexpr_view(1) } pub fn vertical_sexpr_view(&self) -> OuterViewPort { @@ -664,7 +668,7 @@ where ItemEditor: TerminalEditor + ?Sized + Send + Sync + 'static, .to_grid_horizontal() .flatten() } - + pub fn new(make_item_editor: FnMakeItemEditor, style: ListEditorStyle) -> Self { let cursor_port = ViewPort::new(); let data_port = ViewPort::new(); diff --git a/sdf_editor/Cargo.toml b/sdf_editor/Cargo.toml new file mode 100644 index 0000000..d0224df --- /dev/null +++ b/sdf_editor/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "sdf_editor" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nako = {git= "https://git.exobiont.de/senvas/nako.git"} +nako_std = {git= "https://git.exobiont.de/senvas/nako.git"} +nakorender = {git="https://git.exobiont.de/senvas/nako.git", default-features = false} +nested = { path = "../nested" } +cgmath = "*" +termion = "*" +font-kit = "*" + +[dependencies.async-std] +version = "1.9.0" +features = ["unstable", "attributes"] diff --git a/sdf_editor/src/main.rs b/sdf_editor/src/main.rs new file mode 100644 index 0000000..4cd1609 --- /dev/null +++ b/sdf_editor/src/main.rs @@ -0,0 +1,438 @@ + +use{ + std::{ + sync::{Arc, RwLock, Mutex}, + collections::HashMap + }, + cgmath::{Point2, Vector2}, + termion::event::{Event, Key}, + nested::{ + core::{ + View, + ViewPort, + Observer, + ObserverExt, + OuterViewPort, + port::UpdateTask + }, + singleton::{SingletonBuffer, SingletonView}, + sequence::{SequenceView}, + integer::{PosIntEditor}, + terminal::{Terminal, TerminalAtom, TerminalStyle, TerminalView, TerminalCompositor, TerminalEvent, TerminalEditor}, + list::{ListEditor}, + tree_nav::{TreeNav} + }, + nako::{ + stream::{SecondaryStream2d, PrimaryStream2d}, + glam::{Vec2, Vec3, UVec2}, + operations::{ + planar::primitives2d::Box2d, + volumetric::{Color, Union, Round}, + }, + }, + nakorender::{ + backend::{Backend, LayerId, LayerId2d, LayerInfo}, + marp::MarpBackend, + winit, camera::Camera2d + }, + nako_std::{ + text::Character + }, + std::{fs::File, io::Read, mem::needs_drop, path::Path}, + font_kit::font::Font, +}; + +// projects a Sequence of ints to a color tuple +struct ColorCollector { + src_view: Option>>, + color: SingletonBuffer<(u8, u8, u8)> +} + +impl ColorCollector { + fn update(&mut self) { + if let Some(l) = self.src_view.as_ref() { + let r = l.get(&0).unwrap_or(0); + let g = l.get(&1).unwrap_or(0); + let b = l.get(&2).unwrap_or(0); + + self.color.set((r as u8, g as u8, b as u8)); + } + } +} + +impl Observer> for ColorCollector { + fn reset(&mut self, new_view: Option>>) { + self.src_view = new_view; + self.update(); + } + + fn notify(&mut self, idx: &usize) { + self.update(); + } +} + +struct SdfTerm { + src_view: Option>, + bg_layers: HashMap, (bool, LayerId2d)>, + fg_layers: HashMap, (bool, LayerId2d)>, + + font_height: u32, + //font: Mutex, + + renderer: Arc> +} + +impl SdfTerm { + pub fn new(renderer: Arc>) -> Self { + SdfTerm { + src_view: None, + bg_layers: HashMap::new(), + fg_layers: HashMap::new(), + font_height: 60, + //font: Mutex::new(Font::from_path(Path::new("/usr/share/fonts/TTF/FiraCode-Medium.ttf"),0).unwrap()), + renderer + } + } + + pub fn get_order(&self) -> Vec { + vec![ + self.bg_layers.iter(), + self.fg_layers.iter() + ] + .into_iter() + .flatten() + .filter_map( + |(_pt,(active,id))| + if *active { + Some((*id).into()) + } else { + None + }) + .collect::>() + } + + pub fn update(&mut self, pt: &Point2) { + if self.bg_layers.get(pt).is_none() { + let id = self.renderer.lock().unwrap().new_layer_2d(); + + self.renderer.lock().unwrap().update_camera_2d( + id.into(), + Camera2d { + extent: Vec2::new(0.5, 1.0), + location: Vec2::new(0.0, 0.0), + rotation: 0.0 + }); + self.renderer.lock().unwrap().set_layer_info( + id.into(), + LayerInfo { + extent: UVec2::new(1 + self.font_height / 2, self.font_height), + location: UVec2::new(pt.x as u32 * self.font_height / 2, pt.y as u32 * self.font_height) + }); + + self.bg_layers.insert(*pt, (false, id)); + } + if self.fg_layers.get(pt).is_none() { + let id = self.renderer.lock().unwrap().new_layer_2d(); + + self.renderer.lock().unwrap().update_camera_2d( + id.into(), + Camera2d { + extent: Vec2::new(0.5, 1.0), + location: Vec2::new(0.0, 0.0), + rotation: 0.0 + }); + self.renderer.lock().unwrap().set_layer_info( + id.into(), + LayerInfo { + extent: UVec2::new(1 + self.font_height / 2, self.font_height), + location: UVec2::new(pt.x as u32 * self.font_height / 2, pt.y as u32 * self.font_height) + }); + + self.fg_layers.insert(*pt, (false, id)); + } + + if let Some(atom) = self.src_view.get(pt) { + + // background layer + if let Some((r,g,b)) = atom.style.bg_color { + let mut stream = PrimaryStream2d::new() + .push( + SecondaryStream2d::new( + Union, + Box2d { + extent: Vec2::new(0.6, 1.0) + } + ).push_mod( + Color( + Vec3::new( + (r as f32 / 255.0).clamp(0.0, 1.0), + (g as f32 / 255.0).clamp(0.0, 1.0), + (b as f32 / 255.0).clamp(0.0, 1.0), + ) + ) + ).build() + ); + + self.renderer.lock().unwrap().update_sdf_2d(self.bg_layers.get(pt).unwrap().1, stream.build()); + self.bg_layers.get_mut(pt).unwrap().0 = true; + } else { + self.bg_layers.get_mut(pt).unwrap().0 = false; + } + + // foreground layer + if let Some(c) = atom.c { + let font = Font::from_path(Path::new("/usr/share/fonts/TTF/FiraCode-Light.ttf"),0).unwrap(); + let mut ch = Character::from_font(&font, c).with_size(1.0).with_tesselation_factor(0.01); + + let (r,g,b) = atom.style.fg_color.unwrap_or((0, 0, 0)); + + ch.color = Vec3::new( + (r as f32 / 255.0).clamp(0.0, 1.0), + (g as f32 / 255.0).clamp(0.0, 1.0), + (b as f32 / 255.0).clamp(0.0, 1.0), + ); + + let mut stream = PrimaryStream2d::new(); + stream = ch.record_character(stream); + + self.renderer.lock().unwrap().update_sdf_2d(self.fg_layers.get(pt).unwrap().1, stream.build()); + self.fg_layers.get_mut(pt).unwrap().0 = true; + } else { + self.fg_layers.get_mut(pt).unwrap().0 = false; + } + + } else { + self.bg_layers.get_mut(pt).unwrap().0 = false; + self.fg_layers.get_mut(pt).unwrap().0 = false; + } + } +} + +impl Observer for SdfTerm { + fn reset(&mut self, new_view: Option>) { + self.src_view = new_view; + + for pt in self.src_view.area().unwrap_or(vec![]) { + self.notify(&pt); + } + } + + fn notify(&mut self, pt: &Point2) { + self.update(pt); + } +} + +#[async_std::main] +async fn main() { + let term_port = ViewPort::new(); + let compositor = TerminalCompositor::new(term_port.inner()); + + let mut color_editor = ListEditor::new( + || { + Arc::new(RwLock::new(PosIntEditor::new(16))) + }, + nested::list::ListEditorStyle::HorizontalSexpr + ); + + color_editor.goto(nested::tree_nav::TreeCursor { + leaf_mode: nested::list::ListCursorMode::Insert, + tree_addr: vec![ 0 ] + }); + + let color_port = ViewPort::new(); + let color_collector = Arc::new(RwLock::new(ColorCollector { + src_view: None, + color: SingletonBuffer::new((200, 200, 0), color_port.inner()) + })); + + let col_seq_port = color_editor.get_data_port().map( + |sub_editor| sub_editor.read().unwrap().get_value() + ); + color_port.add_update_hook(Arc::new(col_seq_port.0.clone())); + col_seq_port.add_observer( + color_collector.clone() + ); + + compositor.write().unwrap().push(color_editor.get_term_view().offset(Vector2::new(0, 0))); + + let event_loop = nakorender::winit::event_loop::EventLoop::new(); + let window = nakorender::winit::window::Window::new(&event_loop).unwrap(); + let mut renderer = Arc::new(Mutex::new(nakorender::marp::MarpBackend::new(&window, &event_loop))); + + // terminal view + let mut sdf_term = Arc::new(RwLock::new(SdfTerm::new(renderer.clone()))); + term_port.outer().add_observer(sdf_term.clone()); + + // color preview + let color_view = color_port.outer().get_view(); + let color_layer_id = renderer.lock().unwrap().new_layer_2d(); + renderer.lock().unwrap().update_camera_2d(color_layer_id, Camera2d{ + extent: Vec2::new(4.0, 4.0), + location: Vec2::new(-2.0, -2.0), + rotation: 0.0 + }); + renderer.lock().unwrap().set_layer_info(color_layer_id.into(), LayerInfo{ + extent: UVec2::new(600, 600), + location: UVec2::new(200,100) + }); + + event_loop.run(move |event, _target, control_flow|{ + //Set to polling for now, might be overwritten + //TODO: Maybe we want to use "WAIT" for the ui thread? However, the renderer.lock().unwrap()s don't work that hard + //if nothing changes. So should be okay for a alpha style programm. + *control_flow = winit::event_loop::ControlFlow::Poll; + + //now check if a rerender was requested, or if we worked on all + //events on that batch + match event{ + winit::event::Event::WindowEvent{window_id: _, event: winit::event::WindowEvent::Resized(newsize)} => { + + } + winit::event::Event::WindowEvent{window_id: _, event: winit::event::WindowEvent::KeyboardInput{ device_id, input, is_synthetic }} => { + if input.state == winit::event::ElementState::Pressed { + if let Some(kc) = input.virtual_keycode { + match kc { + winit::event::VirtualKeyCode::Space | + winit::event::VirtualKeyCode::Return => { + color_editor.handle_terminal_event(&TerminalEvent::Input(Event::Key(Key::Char(' ')))); + } + winit::event::VirtualKeyCode::Key0 | + winit::event::VirtualKeyCode::Numpad0 => { + color_editor.handle_terminal_event(&TerminalEvent::Input(Event::Key(Key::Char('0')))); + } + winit::event::VirtualKeyCode::Key1 | + winit::event::VirtualKeyCode::Numpad1 => { + color_editor.handle_terminal_event(&TerminalEvent::Input(Event::Key(Key::Char('1')))); + } + winit::event::VirtualKeyCode::Key2 | + winit::event::VirtualKeyCode::Numpad2 => { + color_editor.handle_terminal_event(&TerminalEvent::Input(Event::Key(Key::Char('2')))); + } + winit::event::VirtualKeyCode::Key3 | + winit::event::VirtualKeyCode::Numpad3 => { + color_editor.handle_terminal_event(&TerminalEvent::Input(Event::Key(Key::Char('3')))); + } + winit::event::VirtualKeyCode::Key4 | + winit::event::VirtualKeyCode::Numpad4 => { + color_editor.handle_terminal_event(&TerminalEvent::Input(Event::Key(Key::Char('4')))); + } + winit::event::VirtualKeyCode::Key5 | + winit::event::VirtualKeyCode::Numpad5 => { + color_editor.handle_terminal_event(&TerminalEvent::Input(Event::Key(Key::Char('5')))); + } + winit::event::VirtualKeyCode::Key6 | + winit::event::VirtualKeyCode::Numpad6 => { + color_editor.handle_terminal_event(&TerminalEvent::Input(Event::Key(Key::Char('6')))); + } + winit::event::VirtualKeyCode::Key7 | + winit::event::VirtualKeyCode::Numpad7 => { + color_editor.handle_terminal_event(&TerminalEvent::Input(Event::Key(Key::Char('7')))); + } + winit::event::VirtualKeyCode::Key8 | + winit::event::VirtualKeyCode::Numpad8 => { + color_editor.handle_terminal_event(&TerminalEvent::Input(Event::Key(Key::Char('8')))); + } + winit::event::VirtualKeyCode::Key9 | + winit::event::VirtualKeyCode::Numpad9 => { + color_editor.handle_terminal_event(&TerminalEvent::Input(Event::Key(Key::Char('9')))); + } + winit::event::VirtualKeyCode::A => { + color_editor.handle_terminal_event(&TerminalEvent::Input(Event::Key(Key::Char('a')))); + } + winit::event::VirtualKeyCode::B => { + color_editor.handle_terminal_event(&TerminalEvent::Input(Event::Key(Key::Char('b')))); + } + winit::event::VirtualKeyCode::C => { + color_editor.handle_terminal_event(&TerminalEvent::Input(Event::Key(Key::Char('c')))); + } + winit::event::VirtualKeyCode::D => { + color_editor.handle_terminal_event(&TerminalEvent::Input(Event::Key(Key::Char('d')))); + } + winit::event::VirtualKeyCode::E => { + color_editor.handle_terminal_event(&TerminalEvent::Input(Event::Key(Key::Char('e')))); + } + winit::event::VirtualKeyCode::F => { + color_editor.handle_terminal_event(&TerminalEvent::Input(Event::Key(Key::Char('f')))); + } + winit::event::VirtualKeyCode::Tab => { + color_editor.handle_terminal_event(&TerminalEvent::Input(Event::Key(Key::Insert))); + } + winit::event::VirtualKeyCode::Delete => { + color_editor.handle_terminal_event(&TerminalEvent::Input(Event::Key(Key::Delete))); + } + winit::event::VirtualKeyCode::Back => { + color_editor.handle_terminal_event(&TerminalEvent::Input(Event::Key(Key::Backspace))); + } + winit::event::VirtualKeyCode::Left => { + color_editor.pxev(); + } + winit::event::VirtualKeyCode::Right => { + color_editor.nexd(); + } + winit::event::VirtualKeyCode::Up => { + color_editor.up(); + } + winit::event::VirtualKeyCode::Down => { + color_editor.dn(); + color_editor.goto_home(); + } + winit::event::VirtualKeyCode::Home => { + color_editor.goto_home(); + } + winit::event::VirtualKeyCode::End => { + color_editor.goto_end(); + } + _ => { + } + } + } + } + } + winit::event::Event::MainEventsCleared => { + window.request_redraw(); + } + winit::event::Event::RedrawRequested(_) => { + color_port.update(); + term_port.update(); + + let c = color_view.get(); + let color_stream = PrimaryStream2d::new() + .push( + SecondaryStream2d::new( + Union, + Box2d { + extent: Vec2::new(0.5, 0.5) + } + ).push_mod( + Color( + Vec3::new( + (c.0 as f32 / 255.0).clamp(0.0, 1.0), + (c.1 as f32 / 255.0).clamp(0.0, 1.0), + (c.2 as f32 / 255.0).clamp(0.0, 1.0), + ) + ) + ).push_mod( + Round{radius: 0.2} + ).build() + ).build(); + + renderer.lock().unwrap().update_sdf_2d(color_layer_id, color_stream); + renderer.lock().unwrap().set_layer_order( + vec![ + vec![ color_layer_id.into() ].into_iter(), + sdf_term.read().unwrap().get_order().into_iter() + ] + .into_iter() + .flatten() + .collect::>() + .as_slice() + ); + + renderer.lock().unwrap().render(&window); + } + _ => {}, + } + + }) +} +