mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
wip8 (22e)
This commit is contained in:
parent
f0192bd8f4
commit
2f772eceef
26 changed files with 926 additions and 908 deletions
|
|
@ -1,460 +1,10 @@
|
|||
use crate::*;
|
||||
|
||||
submod! {
|
||||
clip
|
||||
clock
|
||||
color
|
||||
jack
|
||||
phrase
|
||||
player
|
||||
scene
|
||||
track
|
||||
|
||||
//api_mixer
|
||||
//api_channel
|
||||
//api_plugin
|
||||
//api_plugin_kind
|
||||
//api_plugin_lv2
|
||||
//api_sampler
|
||||
//api_sampler_sample
|
||||
//api_sampler_voice
|
||||
}
|
||||
|
||||
pub trait JackActivate: Sized {
|
||||
fn activate_with <T: Audio + 'static> (
|
||||
self,
|
||||
init: impl FnOnce(&Arc<RwLock<JackClient>>)->Usually<T>
|
||||
)
|
||||
-> Usually<Arc<RwLock<T>>>;
|
||||
}
|
||||
|
||||
impl JackActivate for JackClient {
|
||||
fn activate_with <T: Audio + 'static> (
|
||||
self,
|
||||
init: impl FnOnce(&Arc<RwLock<JackClient>>)->Usually<T>
|
||||
)
|
||||
-> Usually<Arc<RwLock<T>>>
|
||||
{
|
||||
let client = Arc::new(RwLock::new(self));
|
||||
let target = Arc::new(RwLock::new(init(&client)?));
|
||||
let event = Box::new(move|_|{/*TODO*/}) as Box<dyn Fn(JackEvent) + Send + Sync>;
|
||||
let events = Notifications(event);
|
||||
let frame = Box::new({
|
||||
let target = target.clone();
|
||||
move|c: &_, s: &_|if let Ok(mut target) = target.write() {
|
||||
target.process(c, s)
|
||||
} else {
|
||||
Control::Quit
|
||||
}
|
||||
});
|
||||
let frames = ClosureProcessHandler::new(frame as BoxedAudioHandler);
|
||||
let mut buffer = Self::Activating;
|
||||
std::mem::swap(&mut*client.write().unwrap(), &mut buffer);
|
||||
*client.write().unwrap() = Self::Active(Client::from(buffer).activate_async(events, frames)?);
|
||||
Ok(target)
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for things that have a JACK process callback.
|
||||
pub trait Audio: Send + Sync {
|
||||
fn process(&mut self, _: &Client, _: &ProcessScope) -> Control {
|
||||
Control::Continue
|
||||
}
|
||||
fn callback(
|
||||
state: &Arc<RwLock<Self>>, client: &Client, scope: &ProcessScope
|
||||
) -> Control where Self: Sized {
|
||||
if let Ok(mut state) = state.write() {
|
||||
state.process(client, scope)
|
||||
} else {
|
||||
Control::Quit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A UI component that may be associated with a JACK client by the `Jack` factory.
|
||||
pub trait AudioComponent<E: Engine>: Component<E> + Audio {
|
||||
/// Perform type erasure for collecting heterogeneous devices.
|
||||
fn boxed(self) -> Box<dyn AudioComponent<E>>
|
||||
where
|
||||
Self: Sized + 'static,
|
||||
{
|
||||
Box::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// All things that implement the required traits can be treated as `AudioComponent`.
|
||||
impl<E: Engine, W: Component<E> + Audio> AudioComponent<E> for W {}
|
||||
|
||||
/// Trait for things that may expose JACK ports.
|
||||
pub trait Ports {
|
||||
fn audio_ins(&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||
Ok(vec![])
|
||||
}
|
||||
fn audio_outs(&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||
Ok(vec![])
|
||||
}
|
||||
fn midi_ins(&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||
Ok(vec![])
|
||||
}
|
||||
fn midi_outs(&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
fn register_ports<T: PortSpec + Copy>(
|
||||
client: &Client,
|
||||
names: Vec<String>,
|
||||
spec: T,
|
||||
) -> Usually<BTreeMap<String, Port<T>>> {
|
||||
names
|
||||
.into_iter()
|
||||
.try_fold(BTreeMap::new(), |mut ports, name| {
|
||||
let port = client.register_port(&name, spec)?;
|
||||
ports.insert(name, port);
|
||||
Ok(ports)
|
||||
})
|
||||
}
|
||||
|
||||
fn query_ports(client: &Client, names: Vec<String>) -> BTreeMap<String, Port<Unowned>> {
|
||||
names.into_iter().fold(BTreeMap::new(), |mut ports, name| {
|
||||
let port = client.port_by_name(&name).unwrap();
|
||||
ports.insert(name, port);
|
||||
ports
|
||||
})
|
||||
}
|
||||
|
||||
///// A [AudioComponent] bound to a JACK client and a set of ports.
|
||||
//pub struct JackDevice<E: Engine> {
|
||||
///// The active JACK client of this device.
|
||||
//pub client: DynamicAsyncClient,
|
||||
///// The device state, encapsulated for sharing between threads.
|
||||
//pub state: Arc<RwLock<Box<dyn AudioComponent<E>>>>,
|
||||
///// Unowned copies of the device's JACK ports, for connecting to the device.
|
||||
///// The "real" readable/writable `Port`s are owned by the `state`.
|
||||
//pub ports: UnownedJackPorts,
|
||||
//}
|
||||
|
||||
//impl<E: Engine> std::fmt::Debug for JackDevice<E> {
|
||||
//fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
//f.debug_struct("JackDevice")
|
||||
//.field("ports", &self.ports)
|
||||
//.finish()
|
||||
//}
|
||||
//}
|
||||
|
||||
//impl<E: Engine> Render for JackDevice<E> {
|
||||
//type Engine = E;
|
||||
//fn min_size(&self, to: E::Size) -> Perhaps<E::Size> {
|
||||
//self.state.read().unwrap().layout(to)
|
||||
//}
|
||||
//fn render(&self, to: &mut E::Output) -> Usually<()> {
|
||||
//self.state.read().unwrap().render(to)
|
||||
//}
|
||||
//}
|
||||
|
||||
//impl<E: Engine> Handle<E> for JackDevice<E> {
|
||||
//fn handle(&mut self, from: &E::Input) -> Perhaps<E::Handled> {
|
||||
//self.state.write().unwrap().handle(from)
|
||||
//}
|
||||
//}
|
||||
|
||||
//impl<E: Engine> Ports for JackDevice<E> {
|
||||
//fn audio_ins(&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||
//Ok(self.ports.audio_ins.values().collect())
|
||||
//}
|
||||
//fn audio_outs(&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||
//Ok(self.ports.audio_outs.values().collect())
|
||||
//}
|
||||
//fn midi_ins(&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||
//Ok(self.ports.midi_ins.values().collect())
|
||||
//}
|
||||
//fn midi_outs(&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||
//Ok(self.ports.midi_outs.values().collect())
|
||||
//}
|
||||
//}
|
||||
|
||||
//impl<E: Engine> JackDevice<E> {
|
||||
///// Returns a locked mutex of the state's contents.
|
||||
//pub fn state(&self) -> LockResult<RwLockReadGuard<Box<dyn AudioComponent<E>>>> {
|
||||
//self.state.read()
|
||||
//}
|
||||
///// Returns a locked mutex of the state's contents.
|
||||
//pub fn state_mut(&self) -> LockResult<RwLockWriteGuard<Box<dyn AudioComponent<E>>>> {
|
||||
//self.state.write()
|
||||
//}
|
||||
//pub fn connect_midi_in(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
|
||||
//Ok(self
|
||||
//.client
|
||||
//.as_client()
|
||||
//.connect_ports(port, self.midi_ins()?[index])?)
|
||||
//}
|
||||
//pub fn connect_midi_out(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
|
||||
//Ok(self
|
||||
//.client
|
||||
//.as_client()
|
||||
//.connect_ports(self.midi_outs()?[index], port)?)
|
||||
//}
|
||||
//pub fn connect_audio_in(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
|
||||
//Ok(self
|
||||
//.client
|
||||
//.as_client()
|
||||
//.connect_ports(port, self.audio_ins()?[index])?)
|
||||
//}
|
||||
//pub fn connect_audio_out(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
|
||||
//Ok(self
|
||||
//.client
|
||||
//.as_client()
|
||||
//.connect_ports(self.audio_outs()?[index], port)?)
|
||||
//}
|
||||
//}
|
||||
|
||||
///// Collection of JACK ports as [AudioIn]/[AudioOut]/[MidiIn]/[MidiOut].
|
||||
//#[derive(Default, Debug)]
|
||||
//pub struct JackPorts {
|
||||
//pub audio_ins: BTreeMap<String, Port<AudioIn>>,
|
||||
//pub midi_ins: BTreeMap<String, Port<MidiIn>>,
|
||||
//pub audio_outs: BTreeMap<String, Port<AudioOut>>,
|
||||
//pub midi_outs: BTreeMap<String, Port<MidiOut>>,
|
||||
//}
|
||||
|
||||
///// Collection of JACK ports as [Unowned].
|
||||
//#[derive(Default, Debug)]
|
||||
//pub struct UnownedJackPorts {
|
||||
//pub audio_ins: BTreeMap<String, Port<Unowned>>,
|
||||
//pub midi_ins: BTreeMap<String, Port<Unowned>>,
|
||||
//pub audio_outs: BTreeMap<String, Port<Unowned>>,
|
||||
//pub midi_outs: BTreeMap<String, Port<Unowned>>,
|
||||
//}
|
||||
|
||||
//impl JackPorts {
|
||||
//pub fn clone_unowned(&self) -> UnownedJackPorts {
|
||||
//let mut unowned = UnownedJackPorts::default();
|
||||
//for (name, port) in self.midi_ins.iter() {
|
||||
//unowned.midi_ins.insert(name.clone(), port.clone_unowned());
|
||||
//}
|
||||
//for (name, port) in self.midi_outs.iter() {
|
||||
//unowned.midi_outs.insert(name.clone(), port.clone_unowned());
|
||||
//}
|
||||
//for (name, port) in self.audio_ins.iter() {
|
||||
//unowned.audio_ins.insert(name.clone(), port.clone_unowned());
|
||||
//}
|
||||
//for (name, port) in self.audio_outs.iter() {
|
||||
//unowned
|
||||
//.audio_outs
|
||||
//.insert(name.clone(), port.clone_unowned());
|
||||
//}
|
||||
//unowned
|
||||
//}
|
||||
//}
|
||||
|
||||
///// Implement the `Ports` trait.
|
||||
//#[macro_export]
|
||||
//macro_rules! ports {
|
||||
//($T:ty $({ $(audio: {
|
||||
//$(ins: |$ai_arg:ident|$ai_impl:expr,)?
|
||||
//$(outs: |$ao_arg:ident|$ao_impl:expr,)?
|
||||
//})? $(midi: {
|
||||
//$(ins: |$mi_arg:ident|$mi_impl:expr,)?
|
||||
//$(outs: |$mo_arg:ident|$mo_impl:expr,)?
|
||||
//})?})?) => {
|
||||
//impl Ports for $T {$(
|
||||
//$(
|
||||
//$(fn audio_ins <'a> (&'a self) -> Usually<Vec<&'a Port<Unowned>>> {
|
||||
//let cb = |$ai_arg:&'a Self|$ai_impl;
|
||||
//cb(self)
|
||||
//})?
|
||||
//)?
|
||||
//$(
|
||||
//$(fn audio_outs <'a> (&'a self) -> Usually<Vec<&'a Port<Unowned>>> {
|
||||
//let cb = (|$ao_arg:&'a Self|$ao_impl);
|
||||
//cb(self)
|
||||
//})?
|
||||
//)?
|
||||
//)? $(
|
||||
//$(
|
||||
//$(fn midi_ins <'a> (&'a self) -> Usually<Vec<&'a Port<Unowned>>> {
|
||||
//let cb = (|$mi_arg:&'a Self|$mi_impl);
|
||||
//cb(self)
|
||||
//})?
|
||||
//)?
|
||||
//$(
|
||||
//$(fn midi_outs <'a> (&'a self) -> Usually<Vec<&'a Port<Unowned>>> {
|
||||
//let cb = (|$mo_arg:&'a Self|$mo_impl);
|
||||
//cb(self)
|
||||
//})?
|
||||
//)?
|
||||
//)?}
|
||||
//};
|
||||
//}
|
||||
|
||||
///// `JackDevice` factory. Creates JACK `Client`s, performs port registration
|
||||
///// and activation, and encapsulates a `AudioComponent` into a `JackDevice`.
|
||||
//pub struct Jack {
|
||||
//pub client: Client,
|
||||
//pub midi_ins: Vec<String>,
|
||||
//pub audio_ins: Vec<String>,
|
||||
//pub midi_outs: Vec<String>,
|
||||
//pub audio_outs: Vec<String>,
|
||||
//}
|
||||
|
||||
//impl Jack {
|
||||
//pub fn new(name: &str) -> Usually<Self> {
|
||||
//Ok(Self {
|
||||
//midi_ins: vec![],
|
||||
//audio_ins: vec![],
|
||||
//midi_outs: vec![],
|
||||
//audio_outs: vec![],
|
||||
//client: Client::new(name, ClientOptions::NO_START_SERVER)?.0,
|
||||
//})
|
||||
//}
|
||||
//pub fn run<'a: 'static, D, E>(
|
||||
//self,
|
||||
//state: impl FnOnce(JackPorts) -> Box<D>,
|
||||
//) -> Usually<JackDevice<E>>
|
||||
//where
|
||||
//D: AudioComponent<E> + Sized + 'static,
|
||||
//E: Engine + 'static,
|
||||
//{
|
||||
//let owned_ports = JackPorts {
|
||||
//audio_ins: register_ports(&self.client, self.audio_ins, AudioIn::default())?,
|
||||
//audio_outs: register_ports(&self.client, self.audio_outs, AudioOut::default())?,
|
||||
//midi_ins: register_ports(&self.client, self.midi_ins, MidiIn::default())?,
|
||||
//midi_outs: register_ports(&self.client, self.midi_outs, MidiOut::default())?,
|
||||
//};
|
||||
//let midi_outs = owned_ports
|
||||
//.midi_outs
|
||||
//.values()
|
||||
//.map(|p| Ok(p.name()?))
|
||||
//.collect::<Usually<Vec<_>>>()?;
|
||||
//let midi_ins = owned_ports
|
||||
//.midi_ins
|
||||
//.values()
|
||||
//.map(|p| Ok(p.name()?))
|
||||
//.collect::<Usually<Vec<_>>>()?;
|
||||
//let audio_outs = owned_ports
|
||||
//.audio_outs
|
||||
//.values()
|
||||
//.map(|p| Ok(p.name()?))
|
||||
//.collect::<Usually<Vec<_>>>()?;
|
||||
//let audio_ins = owned_ports
|
||||
//.audio_ins
|
||||
//.values()
|
||||
//.map(|p| Ok(p.name()?))
|
||||
//.collect::<Usually<Vec<_>>>()?;
|
||||
//let state = Arc::new(RwLock::new(state(owned_ports) as Box<dyn AudioComponent<E>>));
|
||||
//let client = self.client.activate_async(
|
||||
//Notifications(Box::new({
|
||||
//let _state = state.clone();
|
||||
//move |_event| {
|
||||
//// FIXME: this deadlocks
|
||||
////state.lock().unwrap().handle(&event).unwrap();
|
||||
//}
|
||||
//}) as Box<dyn Fn(JackEvent) + Send + Sync>),
|
||||
//contrib::ClosureProcessHandler::new(Box::new({
|
||||
//let state = state.clone();
|
||||
//move |c: &Client, s: &ProcessScope| state.write().unwrap().process(c, s)
|
||||
//}) as BoxedAudioHandler),
|
||||
//)?;
|
||||
//Ok(JackDevice {
|
||||
//ports: UnownedJackPorts {
|
||||
//audio_ins: query_ports(&client.as_client(), audio_ins),
|
||||
//audio_outs: query_ports(&client.as_client(), audio_outs),
|
||||
//midi_ins: query_ports(&client.as_client(), midi_ins),
|
||||
//midi_outs: query_ports(&client.as_client(), midi_outs),
|
||||
//},
|
||||
//client,
|
||||
//state,
|
||||
//})
|
||||
//}
|
||||
//pub fn audio_in(mut self, name: &str) -> Self {
|
||||
//self.audio_ins.push(name.to_string());
|
||||
//self
|
||||
//}
|
||||
//pub fn audio_out(mut self, name: &str) -> Self {
|
||||
//self.audio_outs.push(name.to_string());
|
||||
//self
|
||||
//}
|
||||
//pub fn midi_in(mut self, name: &str) -> Self {
|
||||
//self.midi_ins.push(name.to_string());
|
||||
//self
|
||||
//}
|
||||
//pub fn midi_out(mut self, name: &str) -> Self {
|
||||
//self.midi_outs.push(name.to_string());
|
||||
//self
|
||||
//}
|
||||
//}
|
||||
|
||||
//impl Command<ArrangerModel> for ArrangerSceneCommand {
|
||||
//}
|
||||
//Edit(phrase) => { state.state.phrase = phrase.clone() },
|
||||
//ToggleViewMode => { state.state.mode.to_next(); },
|
||||
//Delete => { state.state.delete(); },
|
||||
//Activate => { state.state.activate(); },
|
||||
//ZoomIn => { state.state.zoom_in(); },
|
||||
//ZoomOut => { state.state.zoom_out(); },
|
||||
//MoveBack => { state.state.move_back(); },
|
||||
//MoveForward => { state.state.move_forward(); },
|
||||
//RandomColor => { state.state.randomize_color(); },
|
||||
//Put => { state.state.phrase_put(); },
|
||||
//Get => { state.state.phrase_get(); },
|
||||
//AddScene => { state.state.scene_add(None, None)?; },
|
||||
//AddTrack => { state.state.track_add(None, None)?; },
|
||||
//ToggleLoop => { state.state.toggle_loop() },
|
||||
//pub fn zoom_in (&mut self) {
|
||||
//if let ArrangerEditorMode::Vertical(factor) = self.mode {
|
||||
//self.mode = ArrangerEditorMode::Vertical(factor + 1)
|
||||
//}
|
||||
//}
|
||||
//pub fn zoom_out (&mut self) {
|
||||
//if let ArrangerEditorMode::Vertical(factor) = self.mode {
|
||||
//self.mode = ArrangerEditorMode::Vertical(factor.saturating_sub(1))
|
||||
//}
|
||||
//}
|
||||
//pub fn move_back (&mut self) {
|
||||
//match self.selected {
|
||||
//ArrangerEditorFocus::Scene(s) => {
|
||||
//if s > 0 {
|
||||
//self.scenes.swap(s, s - 1);
|
||||
//self.selected = ArrangerEditorFocus::Scene(s - 1);
|
||||
//}
|
||||
//},
|
||||
//ArrangerEditorFocus::Track(t) => {
|
||||
//if t > 0 {
|
||||
//self.tracks.swap(t, t - 1);
|
||||
//self.selected = ArrangerEditorFocus::Track(t - 1);
|
||||
//// FIXME: also swap clip order in scenes
|
||||
//}
|
||||
//},
|
||||
//_ => todo!("arrangement: move forward")
|
||||
//}
|
||||
//}
|
||||
//pub fn move_forward (&mut self) {
|
||||
//match self.selected {
|
||||
//ArrangerEditorFocus::Scene(s) => {
|
||||
//if s < self.scenes.len().saturating_sub(1) {
|
||||
//self.scenes.swap(s, s + 1);
|
||||
//self.selected = ArrangerEditorFocus::Scene(s + 1);
|
||||
//}
|
||||
//},
|
||||
//ArrangerEditorFocus::Track(t) => {
|
||||
//if t < self.tracks.len().saturating_sub(1) {
|
||||
//self.tracks.swap(t, t + 1);
|
||||
//self.selected = ArrangerEditorFocus::Track(t + 1);
|
||||
//// FIXME: also swap clip order in scenes
|
||||
//}
|
||||
//},
|
||||
//_ => todo!("arrangement: move forward")
|
||||
//}
|
||||
//}
|
||||
|
||||
//impl From<Moment> for Clock {
|
||||
//fn from (current: Moment) -> Self {
|
||||
//Self {
|
||||
//playing: Some(TransportState::Stopped).into(),
|
||||
//started: None.into(),
|
||||
//quant: 24.into(),
|
||||
//sync: (current.timebase.ppq.get() * 4.).into(),
|
||||
//current,
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
mod phrase; pub(crate) use phrase::*;
|
||||
mod jack; pub(crate) use jack::*;
|
||||
mod clip; pub(crate) use clip::*;
|
||||
mod color; pub(crate) use color::*;
|
||||
mod clock; pub(crate) use clock::*;
|
||||
mod player; pub(crate) use player::*;
|
||||
mod scene; pub(crate) use scene::*;
|
||||
mod track; pub(crate) use track::*;
|
||||
|
|
|
|||
|
|
@ -20,3 +20,444 @@ pub trait HasMidiOuts {
|
|||
self.midi_outs().len() > 0
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
pub trait JackActivate: Sized {
|
||||
fn activate_with <T: Audio + 'static> (
|
||||
self,
|
||||
init: impl FnOnce(&Arc<RwLock<JackClient>>)->Usually<T>
|
||||
)
|
||||
-> Usually<Arc<RwLock<T>>>;
|
||||
}
|
||||
|
||||
impl JackActivate for JackClient {
|
||||
fn activate_with <T: Audio + 'static> (
|
||||
self,
|
||||
init: impl FnOnce(&Arc<RwLock<JackClient>>)->Usually<T>
|
||||
)
|
||||
-> Usually<Arc<RwLock<T>>>
|
||||
{
|
||||
let client = Arc::new(RwLock::new(self));
|
||||
let target = Arc::new(RwLock::new(init(&client)?));
|
||||
let event = Box::new(move|_|{/*TODO*/}) as Box<dyn Fn(JackEvent) + Send + Sync>;
|
||||
let events = Notifications(event);
|
||||
let frame = Box::new({
|
||||
let target = target.clone();
|
||||
move|c: &_, s: &_|if let Ok(mut target) = target.write() {
|
||||
target.process(c, s)
|
||||
} else {
|
||||
Control::Quit
|
||||
}
|
||||
});
|
||||
let frames = ClosureProcessHandler::new(frame as BoxedAudioHandler);
|
||||
let mut buffer = Self::Activating;
|
||||
std::mem::swap(&mut*client.write().unwrap(), &mut buffer);
|
||||
*client.write().unwrap() = Self::Active(Client::from(buffer).activate_async(events, frames)?);
|
||||
Ok(target)
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for things that have a JACK process callback.
|
||||
pub trait Audio: Send + Sync {
|
||||
fn process(&mut self, _: &Client, _: &ProcessScope) -> Control {
|
||||
Control::Continue
|
||||
}
|
||||
fn callback(
|
||||
state: &Arc<RwLock<Self>>, client: &Client, scope: &ProcessScope
|
||||
) -> Control where Self: Sized {
|
||||
if let Ok(mut state) = state.write() {
|
||||
state.process(client, scope)
|
||||
} else {
|
||||
Control::Quit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A UI component that may be associated with a JACK client by the `Jack` factory.
|
||||
pub trait AudioComponent<E: Engine>: Component<E> + Audio {
|
||||
/// Perform type erasure for collecting heterogeneous devices.
|
||||
fn boxed(self) -> Box<dyn AudioComponent<E>>
|
||||
where
|
||||
Self: Sized + 'static,
|
||||
{
|
||||
Box::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// All things that implement the required traits can be treated as `AudioComponent`.
|
||||
impl<E: Engine, W: Component<E> + Audio> AudioComponent<E> for W {}
|
||||
|
||||
/// Trait for things that may expose JACK ports.
|
||||
pub trait Ports {
|
||||
fn audio_ins(&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||
Ok(vec![])
|
||||
}
|
||||
fn audio_outs(&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||
Ok(vec![])
|
||||
}
|
||||
fn midi_ins(&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||
Ok(vec![])
|
||||
}
|
||||
fn midi_outs(&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
fn register_ports<T: PortSpec + Copy>(
|
||||
client: &Client,
|
||||
names: Vec<String>,
|
||||
spec: T,
|
||||
) -> Usually<BTreeMap<String, Port<T>>> {
|
||||
names
|
||||
.into_iter()
|
||||
.try_fold(BTreeMap::new(), |mut ports, name| {
|
||||
let port = client.register_port(&name, spec)?;
|
||||
ports.insert(name, port);
|
||||
Ok(ports)
|
||||
})
|
||||
}
|
||||
|
||||
fn query_ports(client: &Client, names: Vec<String>) -> BTreeMap<String, Port<Unowned>> {
|
||||
names.into_iter().fold(BTreeMap::new(), |mut ports, name| {
|
||||
let port = client.port_by_name(&name).unwrap();
|
||||
ports.insert(name, port);
|
||||
ports
|
||||
})
|
||||
}
|
||||
|
||||
///// A [AudioComponent] bound to a JACK client and a set of ports.
|
||||
//pub struct JackDevice<E: Engine> {
|
||||
///// The active JACK client of this device.
|
||||
//pub client: DynamicAsyncClient,
|
||||
///// The device state, encapsulated for sharing between threads.
|
||||
//pub state: Arc<RwLock<Box<dyn AudioComponent<E>>>>,
|
||||
///// Unowned copies of the device's JACK ports, for connecting to the device.
|
||||
///// The "real" readable/writable `Port`s are owned by the `state`.
|
||||
//pub ports: UnownedJackPorts,
|
||||
//}
|
||||
|
||||
//impl<E: Engine> std::fmt::Debug for JackDevice<E> {
|
||||
//fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
//f.debug_struct("JackDevice")
|
||||
//.field("ports", &self.ports)
|
||||
//.finish()
|
||||
//}
|
||||
//}
|
||||
|
||||
//impl<E: Engine> Render for JackDevice<E> {
|
||||
//type Engine = E;
|
||||
//fn min_size(&self, to: E::Size) -> Perhaps<E::Size> {
|
||||
//self.state.read().unwrap().layout(to)
|
||||
//}
|
||||
//fn render(&self, to: &mut E::Output) -> Usually<()> {
|
||||
//self.state.read().unwrap().render(to)
|
||||
//}
|
||||
//}
|
||||
|
||||
//impl<E: Engine> Handle<E> for JackDevice<E> {
|
||||
//fn handle(&mut self, from: &E::Input) -> Perhaps<E::Handled> {
|
||||
//self.state.write().unwrap().handle(from)
|
||||
//}
|
||||
//}
|
||||
|
||||
//impl<E: Engine> Ports for JackDevice<E> {
|
||||
//fn audio_ins(&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||
//Ok(self.ports.audio_ins.values().collect())
|
||||
//}
|
||||
//fn audio_outs(&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||
//Ok(self.ports.audio_outs.values().collect())
|
||||
//}
|
||||
//fn midi_ins(&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||
//Ok(self.ports.midi_ins.values().collect())
|
||||
//}
|
||||
//fn midi_outs(&self) -> Usually<Vec<&Port<Unowned>>> {
|
||||
//Ok(self.ports.midi_outs.values().collect())
|
||||
//}
|
||||
//}
|
||||
|
||||
//impl<E: Engine> JackDevice<E> {
|
||||
///// Returns a locked mutex of the state's contents.
|
||||
//pub fn state(&self) -> LockResult<RwLockReadGuard<Box<dyn AudioComponent<E>>>> {
|
||||
//self.state.read()
|
||||
//}
|
||||
///// Returns a locked mutex of the state's contents.
|
||||
//pub fn state_mut(&self) -> LockResult<RwLockWriteGuard<Box<dyn AudioComponent<E>>>> {
|
||||
//self.state.write()
|
||||
//}
|
||||
//pub fn connect_midi_in(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
|
||||
//Ok(self
|
||||
//.client
|
||||
//.as_client()
|
||||
//.connect_ports(port, self.midi_ins()?[index])?)
|
||||
//}
|
||||
//pub fn connect_midi_out(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
|
||||
//Ok(self
|
||||
//.client
|
||||
//.as_client()
|
||||
//.connect_ports(self.midi_outs()?[index], port)?)
|
||||
//}
|
||||
//pub fn connect_audio_in(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
|
||||
//Ok(self
|
||||
//.client
|
||||
//.as_client()
|
||||
//.connect_ports(port, self.audio_ins()?[index])?)
|
||||
//}
|
||||
//pub fn connect_audio_out(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
|
||||
//Ok(self
|
||||
//.client
|
||||
//.as_client()
|
||||
//.connect_ports(self.audio_outs()?[index], port)?)
|
||||
//}
|
||||
//}
|
||||
|
||||
///// Collection of JACK ports as [AudioIn]/[AudioOut]/[MidiIn]/[MidiOut].
|
||||
//#[derive(Default, Debug)]
|
||||
//pub struct JackPorts {
|
||||
//pub audio_ins: BTreeMap<String, Port<AudioIn>>,
|
||||
//pub midi_ins: BTreeMap<String, Port<MidiIn>>,
|
||||
//pub audio_outs: BTreeMap<String, Port<AudioOut>>,
|
||||
//pub midi_outs: BTreeMap<String, Port<MidiOut>>,
|
||||
//}
|
||||
|
||||
///// Collection of JACK ports as [Unowned].
|
||||
//#[derive(Default, Debug)]
|
||||
//pub struct UnownedJackPorts {
|
||||
//pub audio_ins: BTreeMap<String, Port<Unowned>>,
|
||||
//pub midi_ins: BTreeMap<String, Port<Unowned>>,
|
||||
//pub audio_outs: BTreeMap<String, Port<Unowned>>,
|
||||
//pub midi_outs: BTreeMap<String, Port<Unowned>>,
|
||||
//}
|
||||
|
||||
//impl JackPorts {
|
||||
//pub fn clone_unowned(&self) -> UnownedJackPorts {
|
||||
//let mut unowned = UnownedJackPorts::default();
|
||||
//for (name, port) in self.midi_ins.iter() {
|
||||
//unowned.midi_ins.insert(name.clone(), port.clone_unowned());
|
||||
//}
|
||||
//for (name, port) in self.midi_outs.iter() {
|
||||
//unowned.midi_outs.insert(name.clone(), port.clone_unowned());
|
||||
//}
|
||||
//for (name, port) in self.audio_ins.iter() {
|
||||
//unowned.audio_ins.insert(name.clone(), port.clone_unowned());
|
||||
//}
|
||||
//for (name, port) in self.audio_outs.iter() {
|
||||
//unowned
|
||||
//.audio_outs
|
||||
//.insert(name.clone(), port.clone_unowned());
|
||||
//}
|
||||
//unowned
|
||||
//}
|
||||
//}
|
||||
|
||||
///// Implement the `Ports` trait.
|
||||
//#[macro_export]
|
||||
//macro_rules! ports {
|
||||
//($T:ty $({ $(audio: {
|
||||
//$(ins: |$ai_arg:ident|$ai_impl:expr,)?
|
||||
//$(outs: |$ao_arg:ident|$ao_impl:expr,)?
|
||||
//})? $(midi: {
|
||||
//$(ins: |$mi_arg:ident|$mi_impl:expr,)?
|
||||
//$(outs: |$mo_arg:ident|$mo_impl:expr,)?
|
||||
//})?})?) => {
|
||||
//impl Ports for $T {$(
|
||||
//$(
|
||||
//$(fn audio_ins <'a> (&'a self) -> Usually<Vec<&'a Port<Unowned>>> {
|
||||
//let cb = |$ai_arg:&'a Self|$ai_impl;
|
||||
//cb(self)
|
||||
//})?
|
||||
//)?
|
||||
//$(
|
||||
//$(fn audio_outs <'a> (&'a self) -> Usually<Vec<&'a Port<Unowned>>> {
|
||||
//let cb = (|$ao_arg:&'a Self|$ao_impl);
|
||||
//cb(self)
|
||||
//})?
|
||||
//)?
|
||||
//)? $(
|
||||
//$(
|
||||
//$(fn midi_ins <'a> (&'a self) -> Usually<Vec<&'a Port<Unowned>>> {
|
||||
//let cb = (|$mi_arg:&'a Self|$mi_impl);
|
||||
//cb(self)
|
||||
//})?
|
||||
//)?
|
||||
//$(
|
||||
//$(fn midi_outs <'a> (&'a self) -> Usually<Vec<&'a Port<Unowned>>> {
|
||||
//let cb = (|$mo_arg:&'a Self|$mo_impl);
|
||||
//cb(self)
|
||||
//})?
|
||||
//)?
|
||||
//)?}
|
||||
//};
|
||||
//}
|
||||
|
||||
///// `JackDevice` factory. Creates JACK `Client`s, performs port registration
|
||||
///// and activation, and encapsulates a `AudioComponent` into a `JackDevice`.
|
||||
//pub struct Jack {
|
||||
//pub client: Client,
|
||||
//pub midi_ins: Vec<String>,
|
||||
//pub audio_ins: Vec<String>,
|
||||
//pub midi_outs: Vec<String>,
|
||||
//pub audio_outs: Vec<String>,
|
||||
//}
|
||||
|
||||
//impl Jack {
|
||||
//pub fn new(name: &str) -> Usually<Self> {
|
||||
//Ok(Self {
|
||||
//midi_ins: vec![],
|
||||
//audio_ins: vec![],
|
||||
//midi_outs: vec![],
|
||||
//audio_outs: vec![],
|
||||
//client: Client::new(name, ClientOptions::NO_START_SERVER)?.0,
|
||||
//})
|
||||
//}
|
||||
//pub fn run<'a: 'static, D, E>(
|
||||
//self,
|
||||
//state: impl FnOnce(JackPorts) -> Box<D>,
|
||||
//) -> Usually<JackDevice<E>>
|
||||
//where
|
||||
//D: AudioComponent<E> + Sized + 'static,
|
||||
//E: Engine + 'static,
|
||||
//{
|
||||
//let owned_ports = JackPorts {
|
||||
//audio_ins: register_ports(&self.client, self.audio_ins, AudioIn::default())?,
|
||||
//audio_outs: register_ports(&self.client, self.audio_outs, AudioOut::default())?,
|
||||
//midi_ins: register_ports(&self.client, self.midi_ins, MidiIn::default())?,
|
||||
//midi_outs: register_ports(&self.client, self.midi_outs, MidiOut::default())?,
|
||||
//};
|
||||
//let midi_outs = owned_ports
|
||||
//.midi_outs
|
||||
//.values()
|
||||
//.map(|p| Ok(p.name()?))
|
||||
//.collect::<Usually<Vec<_>>>()?;
|
||||
//let midi_ins = owned_ports
|
||||
//.midi_ins
|
||||
//.values()
|
||||
//.map(|p| Ok(p.name()?))
|
||||
//.collect::<Usually<Vec<_>>>()?;
|
||||
//let audio_outs = owned_ports
|
||||
//.audio_outs
|
||||
//.values()
|
||||
//.map(|p| Ok(p.name()?))
|
||||
//.collect::<Usually<Vec<_>>>()?;
|
||||
//let audio_ins = owned_ports
|
||||
//.audio_ins
|
||||
//.values()
|
||||
//.map(|p| Ok(p.name()?))
|
||||
//.collect::<Usually<Vec<_>>>()?;
|
||||
//let state = Arc::new(RwLock::new(state(owned_ports) as Box<dyn AudioComponent<E>>));
|
||||
//let client = self.client.activate_async(
|
||||
//Notifications(Box::new({
|
||||
//let _state = state.clone();
|
||||
//move |_event| {
|
||||
//// FIXME: this deadlocks
|
||||
////state.lock().unwrap().handle(&event).unwrap();
|
||||
//}
|
||||
//}) as Box<dyn Fn(JackEvent) + Send + Sync>),
|
||||
//contrib::ClosureProcessHandler::new(Box::new({
|
||||
//let state = state.clone();
|
||||
//move |c: &Client, s: &ProcessScope| state.write().unwrap().process(c, s)
|
||||
//}) as BoxedAudioHandler),
|
||||
//)?;
|
||||
//Ok(JackDevice {
|
||||
//ports: UnownedJackPorts {
|
||||
//audio_ins: query_ports(&client.as_client(), audio_ins),
|
||||
//audio_outs: query_ports(&client.as_client(), audio_outs),
|
||||
//midi_ins: query_ports(&client.as_client(), midi_ins),
|
||||
//midi_outs: query_ports(&client.as_client(), midi_outs),
|
||||
//},
|
||||
//client,
|
||||
//state,
|
||||
//})
|
||||
//}
|
||||
//pub fn audio_in(mut self, name: &str) -> Self {
|
||||
//self.audio_ins.push(name.to_string());
|
||||
//self
|
||||
//}
|
||||
//pub fn audio_out(mut self, name: &str) -> Self {
|
||||
//self.audio_outs.push(name.to_string());
|
||||
//self
|
||||
//}
|
||||
//pub fn midi_in(mut self, name: &str) -> Self {
|
||||
//self.midi_ins.push(name.to_string());
|
||||
//self
|
||||
//}
|
||||
//pub fn midi_out(mut self, name: &str) -> Self {
|
||||
//self.midi_outs.push(name.to_string());
|
||||
//self
|
||||
//}
|
||||
//}
|
||||
|
||||
//impl Command<ArrangerModel> for ArrangerSceneCommand {
|
||||
//}
|
||||
//Edit(phrase) => { state.state.phrase = phrase.clone() },
|
||||
//ToggleViewMode => { state.state.mode.to_next(); },
|
||||
//Delete => { state.state.delete(); },
|
||||
//Activate => { state.state.activate(); },
|
||||
//ZoomIn => { state.state.zoom_in(); },
|
||||
//ZoomOut => { state.state.zoom_out(); },
|
||||
//MoveBack => { state.state.move_back(); },
|
||||
//MoveForward => { state.state.move_forward(); },
|
||||
//RandomColor => { state.state.randomize_color(); },
|
||||
//Put => { state.state.phrase_put(); },
|
||||
//Get => { state.state.phrase_get(); },
|
||||
//AddScene => { state.state.scene_add(None, None)?; },
|
||||
//AddTrack => { state.state.track_add(None, None)?; },
|
||||
//ToggleLoop => { state.state.toggle_loop() },
|
||||
//pub fn zoom_in (&mut self) {
|
||||
//if let ArrangerEditorMode::Vertical(factor) = self.mode {
|
||||
//self.mode = ArrangerEditorMode::Vertical(factor + 1)
|
||||
//}
|
||||
//}
|
||||
//pub fn zoom_out (&mut self) {
|
||||
//if let ArrangerEditorMode::Vertical(factor) = self.mode {
|
||||
//self.mode = ArrangerEditorMode::Vertical(factor.saturating_sub(1))
|
||||
//}
|
||||
//}
|
||||
//pub fn move_back (&mut self) {
|
||||
//match self.selected {
|
||||
//ArrangerEditorFocus::Scene(s) => {
|
||||
//if s > 0 {
|
||||
//self.scenes.swap(s, s - 1);
|
||||
//self.selected = ArrangerEditorFocus::Scene(s - 1);
|
||||
//}
|
||||
//},
|
||||
//ArrangerEditorFocus::Track(t) => {
|
||||
//if t > 0 {
|
||||
//self.tracks.swap(t, t - 1);
|
||||
//self.selected = ArrangerEditorFocus::Track(t - 1);
|
||||
//// FIXME: also swap clip order in scenes
|
||||
//}
|
||||
//},
|
||||
//_ => todo!("arrangement: move forward")
|
||||
//}
|
||||
//}
|
||||
//pub fn move_forward (&mut self) {
|
||||
//match self.selected {
|
||||
//ArrangerEditorFocus::Scene(s) => {
|
||||
//if s < self.scenes.len().saturating_sub(1) {
|
||||
//self.scenes.swap(s, s + 1);
|
||||
//self.selected = ArrangerEditorFocus::Scene(s + 1);
|
||||
//}
|
||||
//},
|
||||
//ArrangerEditorFocus::Track(t) => {
|
||||
//if t < self.tracks.len().saturating_sub(1) {
|
||||
//self.tracks.swap(t, t + 1);
|
||||
//self.selected = ArrangerEditorFocus::Track(t + 1);
|
||||
//// FIXME: also swap clip order in scenes
|
||||
//}
|
||||
//},
|
||||
//_ => todo!("arrangement: move forward")
|
||||
//}
|
||||
//}
|
||||
|
||||
//impl From<Moment> for Clock {
|
||||
//fn from (current: Moment) -> Self {
|
||||
//Self {
|
||||
//playing: Some(TransportState::Stopped).into(),
|
||||
//started: None.into(),
|
||||
//quant: 24.into(),
|
||||
//sync: (current.timebase.ppq.get() * 4.).into(),
|
||||
//current,
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,13 @@
|
|||
use crate::*;
|
||||
|
||||
submod! {
|
||||
//tui
|
||||
audio
|
||||
color
|
||||
collect
|
||||
command
|
||||
edn
|
||||
engine
|
||||
focus
|
||||
input
|
||||
output
|
||||
pitch
|
||||
space
|
||||
time
|
||||
}
|
||||
|
||||
testmod! {
|
||||
test
|
||||
}
|
||||
mod audio; pub(crate) use audio::*;
|
||||
mod color; pub(crate) use color::*;
|
||||
mod command; pub(crate) use command::*;
|
||||
mod edn; pub(crate) use edn::*;
|
||||
mod engine; pub(crate) use engine::*;
|
||||
mod focus; pub(crate) use focus::*;
|
||||
mod input; pub(crate) use input::*;
|
||||
mod output; pub(crate) use output::*;
|
||||
mod pitch; pub(crate) use pitch::*;
|
||||
mod space; pub(crate) use space::*;
|
||||
mod time; pub(crate) use time::*;
|
||||
|
|
|
|||
|
|
@ -1,78 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
pub enum Collect<'a, E: Engine, const N: usize> {
|
||||
Callback(CallbackCollection<'a, E>),
|
||||
//Iterator(IteratorCollection<'a, E>),
|
||||
Array(ArrayCollection<'a, E, N>),
|
||||
Slice(SliceCollection<'a, E>),
|
||||
}
|
||||
|
||||
impl<'a, E: Engine, const N: usize> Collect<'a, E, N> {
|
||||
pub fn iter (&'a self) -> CollectIterator<'a, E, N> {
|
||||
CollectIterator(0, &self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, E: Engine, const N: usize> From<CallbackCollection<'a, E>> for Collect<'a, E, N> {
|
||||
fn from (callback: CallbackCollection<'a, E>) -> Self {
|
||||
Self::Callback(callback)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, E: Engine, const N: usize> From<SliceCollection<'a, E>> for Collect<'a, E, N> {
|
||||
fn from (slice: SliceCollection<'a, E>) -> Self {
|
||||
Self::Slice(slice)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, E: Engine, const N: usize> From<ArrayCollection<'a, E, N>> for Collect<'a, E, N>{
|
||||
fn from (array: ArrayCollection<'a, E, N>) -> Self {
|
||||
Self::Array(array)
|
||||
}
|
||||
}
|
||||
|
||||
type CallbackCollection<'a, E> =
|
||||
&'a dyn Fn(&'a mut dyn FnMut(&dyn Render<E>)->Usually<()>);
|
||||
|
||||
//type IteratorCollection<'a, E> =
|
||||
//&'a mut dyn Iterator<Item = dyn Render<E>>;
|
||||
|
||||
type SliceCollection<'a, E> =
|
||||
&'a [&'a dyn Render<E>];
|
||||
|
||||
type ArrayCollection<'a, E, const N: usize> =
|
||||
[&'a dyn Render<E>; N];
|
||||
|
||||
pub struct CollectIterator<'a, E: Engine, const N: usize>(usize, &'a Collect<'a, E, N>);
|
||||
|
||||
impl<'a, E: Engine, const N: usize> Iterator for CollectIterator<'a, E, N> {
|
||||
type Item = &'a dyn Render<E>;
|
||||
fn next (&mut self) -> Option<Self::Item> {
|
||||
match self.1 {
|
||||
Collect::Callback(callback) => {
|
||||
todo!()
|
||||
},
|
||||
//Collection::Iterator(iterator) => {
|
||||
//iterator.next()
|
||||
//},
|
||||
Collect::Array(array) => {
|
||||
if let Some(item) = array.get(self.0) {
|
||||
self.0 += 1;
|
||||
//Some(item)
|
||||
None
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Collect::Slice(slice) => {
|
||||
if let Some(item) = slice.get(self.0) {
|
||||
self.0 += 1;
|
||||
//Some(item)
|
||||
None
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,190 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
struct TestEngine([u16;4], Vec<Vec<char>>);
|
||||
|
||||
impl Engine for TestEngine {
|
||||
type Unit = u16;
|
||||
type Size = [Self::Unit;2];
|
||||
type Area = [Self::Unit;4];
|
||||
type Input = Self;
|
||||
type Handled = bool;
|
||||
fn exited (&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn area (&self) -> Self::Area {
|
||||
self.0
|
||||
}
|
||||
fn area_mut (&mut self) -> &mut Self::Area {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct TestArea(u16, u16);
|
||||
|
||||
impl Render for TestArea {
|
||||
type Engine = TestEngine;
|
||||
fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
|
||||
Ok(Some([to[0], to[1], self.0, self.1]))
|
||||
}
|
||||
fn render (&self, to: &mut Self::Engine) -> Perhaps<[u16;4]> {
|
||||
if let Some(layout) = self.layout(to.area())? {
|
||||
for y in layout.y()..layout.y()+layout.h()-1 {
|
||||
for x in layout.x()..layout.x()+layout.w()-1 {
|
||||
to.1[y as usize][x as usize] = '*';
|
||||
}
|
||||
}
|
||||
Ok(Some(layout))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_plus_minus () -> Usually<()> {
|
||||
let area = [0, 0, 10, 10];
|
||||
let engine = TestEngine(area, vec![vec![' ';10];10]);
|
||||
let test = TestArea(4, 4);
|
||||
assert_eq!(test.layout(area)?, Some([0, 0, 4, 4]));
|
||||
assert_eq!(Push::X(1, test).layout(area)?, Some([1, 0, 4, 4]));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_outset_align () -> Usually<()> {
|
||||
let area = [0, 0, 10, 10];
|
||||
let engine = TestEngine(area, vec![vec![' ';10];10]);
|
||||
let test = TestArea(4, 4);
|
||||
assert_eq!(test.layout(area)?, Some([0, 0, 4, 4]));
|
||||
assert_eq!(Outset::X(1, test).layout(area)?, Some([0, 0, 6, 4]));
|
||||
assert_eq!(Align::X(test).layout(area)?, Some([3, 0, 4, 4]));
|
||||
assert_eq!(Align::X(Outset::X(1, test)).layout(area)?, Some([2, 0, 6, 4]));
|
||||
assert_eq!(Outset::X(1, Align::X(test)).layout(area)?, Some([2, 0, 6, 4]));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
//#[test]
|
||||
//fn test_misc () -> Usually<()> {
|
||||
//let area: [u16;4] = [0, 0, 10, 10];
|
||||
//let test = TestArea(4, 4);
|
||||
//assert_eq!(test.layout(area)?,
|
||||
//Some([0, 0, 4, 4]));
|
||||
//assert_eq!(Align::Center(test).layout(area)?,
|
||||
//Some([3, 3, 4, 4]));
|
||||
//assert_eq!(Align::Center(Stack::down(|add|{
|
||||
//add(&test)?;
|
||||
//add(&test)
|
||||
//})).layout(area)?,
|
||||
//Some([3, 1, 4, 8]));
|
||||
//assert_eq!(Align::Center(Stack::down(|add|{
|
||||
//add(&Outset::XY(2, 2, test))?;
|
||||
//add(&test)
|
||||
//})).layout(area)?,
|
||||
//Some([2, 0, 6, 10]));
|
||||
//assert_eq!(Align::Center(Stack::down(|add|{
|
||||
//add(&Outset::XY(2, 2, test))?;
|
||||
//add(&Inset::XY(2, 2, test))
|
||||
//})).layout(area)?,
|
||||
//Some([2, 1, 6, 8]));
|
||||
//assert_eq!(Stack::down(|add|{
|
||||
//add(&Outset::XY(2, 2, test))?;
|
||||
//add(&Inset::XY(2, 2, test))
|
||||
//}).layout(area)?,
|
||||
//Some([0, 0, 6, 8]));
|
||||
//assert_eq!(Stack::right(|add|{
|
||||
//add(&Stack::down(|add|{
|
||||
//add(&Outset::XY(2, 2, test))?;
|
||||
//add(&Inset::XY(2, 2, test))
|
||||
//}))?;
|
||||
//add(&Align::Center(TestArea(2 ,2)))
|
||||
//}).layout(area)?,
|
||||
//Some([0, 0, 8, 8]));
|
||||
//Ok(())
|
||||
//}
|
||||
|
||||
//#[test]
|
||||
//fn test_offset () -> Usually<()> {
|
||||
//let area: [u16;4] = [50, 50, 100, 100];
|
||||
//let test = TestArea(3, 3);
|
||||
//assert_eq!(Push::X(1, test).layout(area)?, Some([51, 50, 3, 3]));
|
||||
//assert_eq!(Push::Y(1, test).layout(area)?, Some([50, 51, 3, 3]));
|
||||
//assert_eq!(Push::XY(1, 1, test).layout(area)?, Some([51, 51, 3, 3]));
|
||||
//Ok(())
|
||||
//}
|
||||
|
||||
//#[test]
|
||||
//fn test_outset () -> Usually<()> {
|
||||
//let area: [u16;4] = [50, 50, 100, 100];
|
||||
//let test = TestArea(3, 3);
|
||||
//assert_eq!(Outset::X(1, test).layout(area)?, Some([49, 50, 5, 3]));
|
||||
//assert_eq!(Outset::Y(1, test).layout(area)?, Some([50, 49, 3, 5]));
|
||||
//assert_eq!(Outset::XY(1, 1, test).layout(area)?, Some([49, 49, 5, 5]));
|
||||
//Ok(())
|
||||
//}
|
||||
|
||||
//#[test]
|
||||
//fn test_inset () -> Usually<()> {
|
||||
//let area: [u16;4] = [50, 50, 100, 100];
|
||||
//let test = TestArea(3, 3);
|
||||
//assert_eq!(Inset::X(1, test).layout(area)?, Some([51, 50, 1, 3]));
|
||||
//assert_eq!(Inset::Y(1, test).layout(area)?, Some([50, 51, 3, 1]));
|
||||
//assert_eq!(Inset::XY(1, 1, test).layout(area)?, Some([51, 51, 1, 1]));
|
||||
//Ok(())
|
||||
//}
|
||||
|
||||
//#[test]
|
||||
//fn test_stuff () -> Usually<()> {
|
||||
//let area: [u16;4] = [0, 0, 100, 100];
|
||||
//assert_eq!("1".layout(area)?,
|
||||
//Some([0, 0, 1, 1]));
|
||||
//assert_eq!("333".layout(area)?,
|
||||
//Some([0, 0, 3, 1]));
|
||||
//assert_eq!(Layers::new(|add|{add(&"1")?;add(&"333")}).layout(area)?,
|
||||
//Some([0, 0, 3, 1]));
|
||||
//assert_eq!(Stack::down(|add|{add(&"1")?;add(&"333")}).layout(area)?,
|
||||
//Some([0, 0, 3, 2]));
|
||||
//assert_eq!(Stack::right(|add|{add(&"1")?;add(&"333")}).layout(area)?,
|
||||
//Some([0, 0, 4, 1]));
|
||||
//assert_eq!(Stack::down(|add|{
|
||||
//add(&Stack::right(|add|{add(&"1")?;add(&"333")}))?;
|
||||
//add(&"55555")
|
||||
//}).layout(area)?,
|
||||
//Some([0, 0, 5, 2]));
|
||||
//let area: [u16;4] = [1, 1, 100, 100];
|
||||
//assert_eq!(Outset::X(1, Stack::right(|add|{add(&"1")?;add(&"333")})).layout(area)?,
|
||||
//Some([0, 1, 6, 1]));
|
||||
//assert_eq!(Outset::Y(1, Stack::right(|add|{add(&"1")?;add(&"333")})).layout(area)?,
|
||||
//Some([1, 0, 4, 3]));
|
||||
//assert_eq!(Outset::XY(1, 1, Stack::right(|add|{add(&"1")?;add(&"333")})).layout(area)?,
|
||||
//Some([0, 0, 6, 3]));
|
||||
//assert_eq!(Stack::down(|add|{
|
||||
//add(&Outset::XY(1, 1, "1"))?;
|
||||
//add(&Outset::XY(1, 1, "333"))
|
||||
//}).layout(area)?,
|
||||
//Some([1, 1, 5, 6]));
|
||||
//let area: [u16;4] = [1, 1, 95, 100];
|
||||
//assert_eq!(Align::Center(Stack::down(|add|{
|
||||
//add(&Outset::XY(1, 1, "1"))?;
|
||||
//add(&Outset::XY(1, 1, "333"))
|
||||
//})).layout(area)?,
|
||||
//Some([46, 48, 5, 6]));
|
||||
//assert_eq!(Align::Center(Stack::down(|add|{
|
||||
//add(&Layers::new(|add|{
|
||||
////add(&Outset::XY(1, 1, Background(Color::Rgb(0,128,0))))?;
|
||||
//add(&Outset::XY(1, 1, "1"))?;
|
||||
//add(&Outset::XY(1, 1, "333"))?;
|
||||
////add(&Background(Color::Rgb(0,128,0)))?;
|
||||
//Ok(())
|
||||
//}))?;
|
||||
//add(&Layers::new(|add|{
|
||||
////add(&Outset::XY(1, 1, Background(Color::Rgb(0,0,128))))?;
|
||||
//add(&Outset::XY(1, 1, "555"))?;
|
||||
//add(&Outset::XY(1, 1, "777777"))?;
|
||||
////add(&Background(Color::Rgb(0,0,128)))?;
|
||||
//Ok(())
|
||||
//}))
|
||||
//})).layout(area)?,
|
||||
//Some([46, 48, 5, 6]));
|
||||
//Ok(())
|
||||
//}
|
||||
|
|
@ -1,22 +1,17 @@
|
|||
// manja s grozde; ikebana s chiaroscuro
|
||||
|
||||
use crate::*;
|
||||
|
||||
submod! {
|
||||
align
|
||||
bsp
|
||||
cond
|
||||
debug
|
||||
fill
|
||||
fixed
|
||||
inset_outset
|
||||
layers
|
||||
map_reduce
|
||||
measure
|
||||
min_max
|
||||
push_pull
|
||||
scroll
|
||||
shrink_grow
|
||||
split
|
||||
stack
|
||||
}
|
||||
mod align; pub(crate) use align::*;
|
||||
mod bsp; pub(crate) use bsp::*;
|
||||
mod cond; pub(crate) use cond::*;
|
||||
mod debug; pub(crate) use debug::*;
|
||||
mod fill; pub(crate) use fill::*;
|
||||
mod fixed; pub(crate) use fixed::*;
|
||||
mod inset_outset; pub(crate) use inset_outset::*;
|
||||
mod layers; pub(crate) use layers::*;
|
||||
mod measure; pub(crate) use measure::*;
|
||||
mod min_max; pub(crate) use min_max::*;
|
||||
mod push_pull; pub(crate) use push_pull::*;
|
||||
mod scroll; pub(crate) use scroll::*;
|
||||
mod shrink_grow; pub(crate) use shrink_grow::*;
|
||||
mod split; pub(crate) use split::*;
|
||||
mod stack; pub(crate) use stack::*;
|
||||
|
|
|
|||
|
|
@ -50,6 +50,24 @@ pub struct ToEast<E: Engine, A, B>(Option<E::Unit>, A, B);
|
|||
|
||||
pub struct ToWest<E: Engine, A: Render<E>, B: Render<E>>(Option<E::Unit>, A, B);
|
||||
|
||||
impl<E: Engine, A: Render<E>, B: Render<E>> Render<E> for Over<E, A, B> {
|
||||
fn min_size (&self, _: E::Size) -> Perhaps<E::Size> {
|
||||
todo!();
|
||||
}
|
||||
fn render (&self, _: &mut E::Output) -> Usually<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine, A: Render<E>, B: Render<E>> Render<E> for Under<E, A, B> {
|
||||
fn min_size (&self, _: E::Size) -> Perhaps<E::Size> {
|
||||
todo!();
|
||||
}
|
||||
fn render (&self, _: &mut E::Output) -> Usually<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine, A: Render<E>, B: Render<E>> Render<E> for ToNorth<E, A, B> {
|
||||
fn min_size (&self, _: E::Size) -> Perhaps<E::Size> {
|
||||
todo!();
|
||||
|
|
|
|||
79
crates/tek/src/layout/collect.rs
Normal file
79
crates/tek/src/layout/collect.rs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
use crate::*;
|
||||
|
||||
pub enum Collect<'a, E: Engine, const N: usize> {
|
||||
Callback(CallbackCollection<'a, E>),
|
||||
//Iterator(IteratorCollection<'a, E>),
|
||||
Array(ArrayCollection<'a, E, N>),
|
||||
Slice(SliceCollection<'a, E>),
|
||||
}
|
||||
|
||||
impl<'a, E: Engine, const N: usize> Collect<'a, E, N> {
|
||||
pub fn iter (&'a self) -> CollectIterator<'a, E, N> {
|
||||
CollectIterator(0, &self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, E: Engine, const N: usize> From<CallbackCollection<'a, E>> for Collect<'a, E, N> {
|
||||
fn from (callback: CallbackCollection<'a, E>) -> Self {
|
||||
Self::Callback(callback)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, E: Engine, const N: usize> From<SliceCollection<'a, E>> for Collect<'a, E, N> {
|
||||
fn from (slice: SliceCollection<'a, E>) -> Self {
|
||||
Self::Slice(slice)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, E: Engine, const N: usize> From<ArrayCollection<'a, E, N>> for Collect<'a, E, N>{
|
||||
fn from (array: ArrayCollection<'a, E, N>) -> Self {
|
||||
Self::Array(array)
|
||||
}
|
||||
}
|
||||
|
||||
type CallbackCollection<'a, E> =
|
||||
&'a dyn Fn(&'a mut dyn FnMut(&dyn Render<E>)->Usually<()>);
|
||||
|
||||
//type IteratorCollection<'a, E> =
|
||||
//&'a mut dyn Iterator<Item = dyn Render<E>>;
|
||||
|
||||
type SliceCollection<'a, E> =
|
||||
&'a [&'a dyn Render<E>];
|
||||
|
||||
type ArrayCollection<'a, E, const N: usize> =
|
||||
[&'a dyn Render<E>; N];
|
||||
|
||||
pub struct CollectIterator<'a, E: Engine, const N: usize>(usize, &'a Collect<'a, E, N>);
|
||||
|
||||
impl<'a, E: Engine, const N: usize> Iterator for CollectIterator<'a, E, N> {
|
||||
type Item = &'a dyn Render<E>;
|
||||
fn next (&mut self) -> Option<Self::Item> {
|
||||
match self.1 {
|
||||
Collect::Callback(callback) => {
|
||||
todo!()
|
||||
},
|
||||
//Collection::Iterator(iterator) => {
|
||||
//iterator.next()
|
||||
//},
|
||||
Collect::Array(array) => {
|
||||
if let Some(item) = array.get(self.0) {
|
||||
self.0 += 1;
|
||||
//Some(item)
|
||||
None
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Collect::Slice(slice) => {
|
||||
if let Some(item) = slice.get(self.0) {
|
||||
self.0 += 1;
|
||||
//Some(item)
|
||||
None
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -12,20 +12,19 @@ pub trait LayoutShrinkGrow<E: Engine> {
|
|||
fn shrink_xy <W: Render<E>> (x: E::Unit, y: E::Unit, w: W) -> Shrink<E, W> {
|
||||
Shrink::XY(x, y, w)
|
||||
}
|
||||
fn grow_x <W: Render<E>> (x: E::Unit, w: W) -> Grow<E::Unit, W> {
|
||||
fn grow_x <W: Render<E>> (x: E::Unit, w: W) -> Grow<E, W> {
|
||||
Grow::X(x, w)
|
||||
}
|
||||
fn grow_y <W: Render<E>> (y: E::Unit, w: W) -> Grow<E::Unit, W> {
|
||||
fn grow_y <W: Render<E>> (y: E::Unit, w: W) -> Grow<E, W> {
|
||||
Grow::Y(y, w)
|
||||
}
|
||||
fn grow_xy <W: Render<E>> (x: E::Unit, y: E::Unit, w: W) -> Grow<E::Unit, W> {
|
||||
fn grow_xy <W: Render<E>> (x: E::Unit, y: E::Unit, w: W) -> Grow<E, W> {
|
||||
Grow::XY(x, y, w)
|
||||
}
|
||||
}
|
||||
|
||||
/// Shrink drawing area
|
||||
pub enum Shrink<E: Engine, T> {
|
||||
_Unused(PhantomData<E>),
|
||||
pub enum Shrink<E: Engine, T: Render<E>> {
|
||||
/// Decrease width
|
||||
X(E::Unit, T),
|
||||
/// Decrease height
|
||||
|
|
@ -34,6 +33,16 @@ pub enum Shrink<E: Engine, T> {
|
|||
XY(E::Unit, E::Unit, T),
|
||||
}
|
||||
|
||||
/// Expand drawing area
|
||||
pub enum Grow<E: Engine, T: Render<E>> {
|
||||
/// Increase width
|
||||
X(E::Unit, T),
|
||||
/// Increase height
|
||||
Y(E::Unit, T),
|
||||
/// Increase width and height
|
||||
XY(E::Unit, E::Unit, T)
|
||||
}
|
||||
|
||||
impl<E: Engine, T: Render<E>> Shrink<E, T> {
|
||||
fn inner (&self) -> &T {
|
||||
match self {
|
||||
|
|
@ -45,6 +54,16 @@ impl<E: Engine, T: Render<E>> Shrink<E, T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<E: Engine, T: Render<E>> Grow<E, T> {
|
||||
fn inner (&self) -> &T {
|
||||
match self {
|
||||
Self::X(_, i) => i,
|
||||
Self::Y(_, i) => i,
|
||||
Self::XY(_, _, i) => i,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine, T: Render<E>> Render<E> for Shrink<E, T> {
|
||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||
Ok(self.inner().min_size(to)?.map(|to|match *self {
|
||||
|
|
@ -60,7 +79,6 @@ impl<E: Engine, T: Render<E>> Render<E> for Shrink<E, T> {
|
|||
if to.w() > w { to.w() - w } else { 0.into() },
|
||||
if to.h() > h { to.h() - h } else { 0.into() }
|
||||
],
|
||||
_ => unreachable!(),
|
||||
}.into()))
|
||||
}
|
||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||
|
|
@ -70,23 +88,7 @@ impl<E: Engine, T: Render<E>> Render<E> for Shrink<E, T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Expand drawing area
|
||||
pub enum Grow<N: Coordinate, T> {
|
||||
/// Increase width
|
||||
X(N, T),
|
||||
/// Increase height
|
||||
Y(N, T),
|
||||
/// Increase width and height
|
||||
XY(N, N, T)
|
||||
}
|
||||
|
||||
impl<N: Coordinate, T> Grow<N, T> {
|
||||
fn inner (&self) -> &T {
|
||||
match self { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, }
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine, T: Render<E>> Render<E> for Grow<E::Unit, T> {
|
||||
impl<E: Engine, T: Render<E>> Render<E> for Grow<E, T> {
|
||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||
Ok(self.inner().min_size(to)?.map(|to|match *self {
|
||||
Self::X(w, _) => [to.w() + w, to.h()],
|
||||
|
|
|
|||
|
|
@ -1,17 +1,23 @@
|
|||
use crate::*;
|
||||
|
||||
#[macro_export] macro_rules! col {
|
||||
($(move)?|$add:ident|$expr:expr) => { Stack::down($(move)?|$add|$expr) };
|
||||
($($move:ident)?|$add:ident|$expr:expr) => {
|
||||
Stack::down($(move)?|$add|$expr)
|
||||
};
|
||||
($pat:pat in $collection:expr => $item:expr) => {
|
||||
Stack::down(move |add|{
|
||||
for $pat in $collection { add(&$item)?; }
|
||||
Ok(())
|
||||
})
|
||||
};
|
||||
($($expr:expr),* $(,)?) => { Stack::down(move|add|{ $(add(&$expr)?;)* Ok(()) }) };
|
||||
($($expr:expr),* $(,)?) => {
|
||||
Stack::down(move|add|{ $(add(&$expr)?;)* Ok(()) })
|
||||
};
|
||||
}
|
||||
#[macro_export] macro_rules! col_up {
|
||||
($(move)?|$add:ident|$expr:expr) => { Stack::up($(move)?|$add|$expr) };
|
||||
($($move:ident)?|$add:ident|$expr:expr) => {
|
||||
Stack::up($(move)?|$add|$expr)
|
||||
};
|
||||
($pat:pat in $collection:expr => $item:expr) => {
|
||||
Stack::up(move |add|{
|
||||
for $pat in $collection { add(&$item)?; }
|
||||
|
|
@ -21,7 +27,7 @@ use crate::*;
|
|||
($($expr:expr),* $(,)?) => { Stack::up(move|add|{ $(add(&$expr)?;)* Ok(()) }) };
|
||||
}
|
||||
#[macro_export] macro_rules! row {
|
||||
($(move)?|$add:ident|$expr:expr) => {
|
||||
($($move:ident)?|$add:ident|$expr:expr) => {
|
||||
Stack::right($(move)?|$add|$expr)
|
||||
};
|
||||
($pat:pat in $collection:expr => $item:expr) => {
|
||||
|
|
@ -104,7 +110,7 @@ where
|
|||
(self.0)(&mut |component: &dyn Render<E>| {
|
||||
let max = to.h().minus(h);
|
||||
if max > E::Unit::ZERO() {
|
||||
let item = Tui::max_y(to.h() - h, component);
|
||||
let item = E::max_y(to.h() - h, component);
|
||||
let size = item.min_size(to)?.map(|size|size.wh());
|
||||
if let Some([width, height]) = size {
|
||||
h = h + height.into();
|
||||
|
|
@ -138,7 +144,7 @@ where
|
|||
Direction::Down => {
|
||||
(self.0)(&mut |item| {
|
||||
if h < area.h() {
|
||||
let item = Tui::max_y(area.h() - h, Tui::push_y(h, item));
|
||||
let item = E::max_y(area.h() - h, E::push_y(h, item));
|
||||
let show = item.min_size(area.wh().into())?.map(|s|s.wh());
|
||||
if let Some([width, height]) = show {
|
||||
item.render(to)?;
|
||||
|
|
@ -152,7 +158,7 @@ where
|
|||
Direction::Right => {
|
||||
(self.0)(&mut |item| {
|
||||
if w < area.w() {
|
||||
let item = Tui::max_x(area.w() - w, Tui::push_x(w, item));
|
||||
let item = E::max_x(area.w() - w, E::push_x(w, item));
|
||||
let show = item.min_size(area.wh().into())?.map(|s|s.wh());
|
||||
if let Some([width, height]) = show {
|
||||
item.render(to)?;
|
||||
|
|
@ -168,7 +174,8 @@ where
|
|||
if h < area.h() {
|
||||
let show = item.min_size([area.w(), area.h().minus(h)].into())?.map(|s|s.wh());
|
||||
if let Some([width, height]) = show {
|
||||
item.push_y(area.h() - height).shrink_y(height).render(to)?;
|
||||
E::shrink_y(height, E::push_y(area.h() - height, item))
|
||||
.render(to)?;
|
||||
h = h + height;
|
||||
if width > w { w = width }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -73,12 +73,10 @@ pub type Perhaps<T> = Result<Option<T>, Box<dyn Error>>;
|
|||
($($name:ident)*) => { $(#[cfg(test)] mod $name;)* };
|
||||
}
|
||||
|
||||
submod! {
|
||||
api
|
||||
core
|
||||
layout
|
||||
tui
|
||||
}
|
||||
mod core; pub(crate) use core::*;
|
||||
mod layout; pub(crate) use layout::*;
|
||||
mod api; pub(crate) use api::*;
|
||||
mod tui; pub(crate) use tui::*;
|
||||
|
||||
testmod! {
|
||||
test
|
||||
|
|
|
|||
|
|
@ -0,0 +1,190 @@
|
|||
use crate::*;
|
||||
|
||||
struct TestEngine([u16;4], Vec<Vec<char>>);
|
||||
|
||||
impl Engine for TestEngine {
|
||||
type Unit = u16;
|
||||
type Size = [Self::Unit;2];
|
||||
type Area = [Self::Unit;4];
|
||||
type Input = Self;
|
||||
type Handled = bool;
|
||||
fn exited (&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn area (&self) -> Self::Area {
|
||||
self.0
|
||||
}
|
||||
fn area_mut (&mut self) -> &mut Self::Area {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct TestArea(u16, u16);
|
||||
|
||||
impl Render for TestArea {
|
||||
type Engine = TestEngine;
|
||||
fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
|
||||
Ok(Some([to[0], to[1], self.0, self.1]))
|
||||
}
|
||||
fn render (&self, to: &mut Self::Engine) -> Perhaps<[u16;4]> {
|
||||
if let Some(layout) = self.layout(to.area())? {
|
||||
for y in layout.y()..layout.y()+layout.h()-1 {
|
||||
for x in layout.x()..layout.x()+layout.w()-1 {
|
||||
to.1[y as usize][x as usize] = '*';
|
||||
}
|
||||
}
|
||||
Ok(Some(layout))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_plus_minus () -> Usually<()> {
|
||||
let area = [0, 0, 10, 10];
|
||||
let engine = TestEngine(area, vec![vec![' ';10];10]);
|
||||
let test = TestArea(4, 4);
|
||||
assert_eq!(test.layout(area)?, Some([0, 0, 4, 4]));
|
||||
assert_eq!(Push::X(1, test).layout(area)?, Some([1, 0, 4, 4]));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_outset_align () -> Usually<()> {
|
||||
let area = [0, 0, 10, 10];
|
||||
let engine = TestEngine(area, vec![vec![' ';10];10]);
|
||||
let test = TestArea(4, 4);
|
||||
assert_eq!(test.layout(area)?, Some([0, 0, 4, 4]));
|
||||
assert_eq!(Outset::X(1, test).layout(area)?, Some([0, 0, 6, 4]));
|
||||
assert_eq!(Align::X(test).layout(area)?, Some([3, 0, 4, 4]));
|
||||
assert_eq!(Align::X(Outset::X(1, test)).layout(area)?, Some([2, 0, 6, 4]));
|
||||
assert_eq!(Outset::X(1, Align::X(test)).layout(area)?, Some([2, 0, 6, 4]));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
//#[test]
|
||||
//fn test_misc () -> Usually<()> {
|
||||
//let area: [u16;4] = [0, 0, 10, 10];
|
||||
//let test = TestArea(4, 4);
|
||||
//assert_eq!(test.layout(area)?,
|
||||
//Some([0, 0, 4, 4]));
|
||||
//assert_eq!(Align::Center(test).layout(area)?,
|
||||
//Some([3, 3, 4, 4]));
|
||||
//assert_eq!(Align::Center(Stack::down(|add|{
|
||||
//add(&test)?;
|
||||
//add(&test)
|
||||
//})).layout(area)?,
|
||||
//Some([3, 1, 4, 8]));
|
||||
//assert_eq!(Align::Center(Stack::down(|add|{
|
||||
//add(&Outset::XY(2, 2, test))?;
|
||||
//add(&test)
|
||||
//})).layout(area)?,
|
||||
//Some([2, 0, 6, 10]));
|
||||
//assert_eq!(Align::Center(Stack::down(|add|{
|
||||
//add(&Outset::XY(2, 2, test))?;
|
||||
//add(&Inset::XY(2, 2, test))
|
||||
//})).layout(area)?,
|
||||
//Some([2, 1, 6, 8]));
|
||||
//assert_eq!(Stack::down(|add|{
|
||||
//add(&Outset::XY(2, 2, test))?;
|
||||
//add(&Inset::XY(2, 2, test))
|
||||
//}).layout(area)?,
|
||||
//Some([0, 0, 6, 8]));
|
||||
//assert_eq!(Stack::right(|add|{
|
||||
//add(&Stack::down(|add|{
|
||||
//add(&Outset::XY(2, 2, test))?;
|
||||
//add(&Inset::XY(2, 2, test))
|
||||
//}))?;
|
||||
//add(&Align::Center(TestArea(2 ,2)))
|
||||
//}).layout(area)?,
|
||||
//Some([0, 0, 8, 8]));
|
||||
//Ok(())
|
||||
//}
|
||||
|
||||
//#[test]
|
||||
//fn test_offset () -> Usually<()> {
|
||||
//let area: [u16;4] = [50, 50, 100, 100];
|
||||
//let test = TestArea(3, 3);
|
||||
//assert_eq!(Push::X(1, test).layout(area)?, Some([51, 50, 3, 3]));
|
||||
//assert_eq!(Push::Y(1, test).layout(area)?, Some([50, 51, 3, 3]));
|
||||
//assert_eq!(Push::XY(1, 1, test).layout(area)?, Some([51, 51, 3, 3]));
|
||||
//Ok(())
|
||||
//}
|
||||
|
||||
//#[test]
|
||||
//fn test_outset () -> Usually<()> {
|
||||
//let area: [u16;4] = [50, 50, 100, 100];
|
||||
//let test = TestArea(3, 3);
|
||||
//assert_eq!(Outset::X(1, test).layout(area)?, Some([49, 50, 5, 3]));
|
||||
//assert_eq!(Outset::Y(1, test).layout(area)?, Some([50, 49, 3, 5]));
|
||||
//assert_eq!(Outset::XY(1, 1, test).layout(area)?, Some([49, 49, 5, 5]));
|
||||
//Ok(())
|
||||
//}
|
||||
|
||||
//#[test]
|
||||
//fn test_inset () -> Usually<()> {
|
||||
//let area: [u16;4] = [50, 50, 100, 100];
|
||||
//let test = TestArea(3, 3);
|
||||
//assert_eq!(Inset::X(1, test).layout(area)?, Some([51, 50, 1, 3]));
|
||||
//assert_eq!(Inset::Y(1, test).layout(area)?, Some([50, 51, 3, 1]));
|
||||
//assert_eq!(Inset::XY(1, 1, test).layout(area)?, Some([51, 51, 1, 1]));
|
||||
//Ok(())
|
||||
//}
|
||||
|
||||
//#[test]
|
||||
//fn test_stuff () -> Usually<()> {
|
||||
//let area: [u16;4] = [0, 0, 100, 100];
|
||||
//assert_eq!("1".layout(area)?,
|
||||
//Some([0, 0, 1, 1]));
|
||||
//assert_eq!("333".layout(area)?,
|
||||
//Some([0, 0, 3, 1]));
|
||||
//assert_eq!(Layers::new(|add|{add(&"1")?;add(&"333")}).layout(area)?,
|
||||
//Some([0, 0, 3, 1]));
|
||||
//assert_eq!(Stack::down(|add|{add(&"1")?;add(&"333")}).layout(area)?,
|
||||
//Some([0, 0, 3, 2]));
|
||||
//assert_eq!(Stack::right(|add|{add(&"1")?;add(&"333")}).layout(area)?,
|
||||
//Some([0, 0, 4, 1]));
|
||||
//assert_eq!(Stack::down(|add|{
|
||||
//add(&Stack::right(|add|{add(&"1")?;add(&"333")}))?;
|
||||
//add(&"55555")
|
||||
//}).layout(area)?,
|
||||
//Some([0, 0, 5, 2]));
|
||||
//let area: [u16;4] = [1, 1, 100, 100];
|
||||
//assert_eq!(Outset::X(1, Stack::right(|add|{add(&"1")?;add(&"333")})).layout(area)?,
|
||||
//Some([0, 1, 6, 1]));
|
||||
//assert_eq!(Outset::Y(1, Stack::right(|add|{add(&"1")?;add(&"333")})).layout(area)?,
|
||||
//Some([1, 0, 4, 3]));
|
||||
//assert_eq!(Outset::XY(1, 1, Stack::right(|add|{add(&"1")?;add(&"333")})).layout(area)?,
|
||||
//Some([0, 0, 6, 3]));
|
||||
//assert_eq!(Stack::down(|add|{
|
||||
//add(&Outset::XY(1, 1, "1"))?;
|
||||
//add(&Outset::XY(1, 1, "333"))
|
||||
//}).layout(area)?,
|
||||
//Some([1, 1, 5, 6]));
|
||||
//let area: [u16;4] = [1, 1, 95, 100];
|
||||
//assert_eq!(Align::Center(Stack::down(|add|{
|
||||
//add(&Outset::XY(1, 1, "1"))?;
|
||||
//add(&Outset::XY(1, 1, "333"))
|
||||
//})).layout(area)?,
|
||||
//Some([46, 48, 5, 6]));
|
||||
//assert_eq!(Align::Center(Stack::down(|add|{
|
||||
//add(&Layers::new(|add|{
|
||||
////add(&Outset::XY(1, 1, Background(Color::Rgb(0,128,0))))?;
|
||||
//add(&Outset::XY(1, 1, "1"))?;
|
||||
//add(&Outset::XY(1, 1, "333"))?;
|
||||
////add(&Background(Color::Rgb(0,128,0)))?;
|
||||
//Ok(())
|
||||
//}))?;
|
||||
//add(&Layers::new(|add|{
|
||||
////add(&Outset::XY(1, 1, Background(Color::Rgb(0,0,128))))?;
|
||||
//add(&Outset::XY(1, 1, "555"))?;
|
||||
//add(&Outset::XY(1, 1, "777777"))?;
|
||||
////add(&Background(Color::Rgb(0,0,128)))?;
|
||||
//Ok(())
|
||||
//}))
|
||||
//})).layout(area)?,
|
||||
//Some([46, 48, 5, 6]));
|
||||
//Ok(())
|
||||
//}
|
||||
|
|
@ -1,46 +1,55 @@
|
|||
use crate::*;
|
||||
|
||||
submod! {
|
||||
engine_focus
|
||||
engine_input
|
||||
engine_output
|
||||
engine_style
|
||||
mod jack_arranger; pub(crate) use app_arranger::*;
|
||||
mod jack_sequencer; pub(crate) use app_sequencer::*;
|
||||
mod jack_transport; pub(crate) use app_transport::*;
|
||||
|
||||
app_arranger
|
||||
app_sequencer
|
||||
app_transport
|
||||
mod engine_focus; pub(crate) use engine_focus::*;
|
||||
mod engine_input; pub(crate) use engine_input::*;
|
||||
mod engine_style; pub(crate) use engine_style::*;
|
||||
mod engine_output; pub(crate) use engine_output::*;
|
||||
|
||||
jack_transport
|
||||
jack_sequencer
|
||||
jack_arranger
|
||||
mod app_arranger; pub(crate) use app_arranger::*;
|
||||
mod app_sequencer; pub(crate) use app_sequencer::*;
|
||||
mod app_transport; pub(crate) use app_transport::*;
|
||||
|
||||
ctrl_arranger
|
||||
ctrl_file_browser
|
||||
ctrl_phrase_editor
|
||||
ctrl_phrase_length
|
||||
ctrl_phrase_list
|
||||
ctrl_phrase_rename
|
||||
ctrl_sequencer
|
||||
ctrl_transport
|
||||
mod view_arranger; pub(crate) use view_arranger::*;
|
||||
mod view_sequencer; pub(crate) use view_sequencer::*;
|
||||
mod view_transport; pub(crate) use view_transport::*;
|
||||
|
||||
model_arranger
|
||||
model_clock
|
||||
model_file_browser
|
||||
model_phrase_editor
|
||||
model_phrase_length
|
||||
model_phrase_list
|
||||
model_phrase_player
|
||||
mod ctrl_arranger; pub(crate) use ctrl_arranger::*;
|
||||
mod ctrl_sequencer; pub(crate) use ctrl_sequencer::*;
|
||||
mod ctrl_transport; pub(crate) use ctrl_transport::*;
|
||||
|
||||
view_arranger
|
||||
view_file_browser
|
||||
view_phrase_editor
|
||||
view_phrase_length
|
||||
view_phrase_list
|
||||
view_phrase_selector
|
||||
view_sequencer
|
||||
view_status_bar
|
||||
view_transport
|
||||
}
|
||||
mod model_arranger; pub(crate) use model_arranger::*;
|
||||
|
||||
////
|
||||
|
||||
mod model_clock; pub(crate) use model_clock::*;
|
||||
|
||||
mod view_status_bar; pub(crate) use view_status_bar::*;
|
||||
|
||||
mod model_file_browser; pub(crate) use model_file_browser::*;
|
||||
mod view_file_browser; pub(crate) use view_file_browser::*;
|
||||
mod ctrl_file_browser; pub(crate) use ctrl_file_browser::*;
|
||||
|
||||
mod model_phrase_editor; pub(crate) use model_phrase_editor::*;
|
||||
mod view_phrase_editor; pub(crate) use view_phrase_editor::*;
|
||||
mod ctrl_phrase_editor; pub(crate) use ctrl_phrase_editor::*;
|
||||
|
||||
mod model_phrase_length; pub(crate) use model_phrase_length::*;
|
||||
mod view_phrase_length; pub(crate) use view_phrase_length::*;
|
||||
mod ctrl_phrase_length; pub(crate) use ctrl_phrase_length::*;
|
||||
|
||||
mod model_phrase_list; pub(crate) use model_phrase_list::*;
|
||||
mod view_phrase_list; pub(crate) use view_phrase_list::*;
|
||||
mod ctrl_phrase_list; pub(crate) use ctrl_phrase_list::*;
|
||||
|
||||
mod model_phrase_player; pub(crate) use model_phrase_player::*;
|
||||
|
||||
mod view_phrase_selector; pub(crate) use view_phrase_selector::*;
|
||||
|
||||
mod ctrl_phrase_rename; pub(crate) use ctrl_phrase_rename::*;
|
||||
|
||||
#[macro_export] macro_rules! render {
|
||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
use crate::*;
|
||||
use SequencerFocus::*;
|
||||
use super::app_transport::TransportFocus::*;
|
||||
|
||||
/// Root view for standalone `tek_sequencer`.
|
||||
pub struct SequencerTui {
|
||||
|
|
@ -135,8 +137,6 @@ impl StatusBar for SequencerStatusBar {
|
|||
|
||||
impl From<&SequencerTui> for SequencerStatusBar {
|
||||
fn from (state: &SequencerTui) -> Self {
|
||||
use SequencerFocus::*;
|
||||
use TransportFocus::*;
|
||||
let samples = state.clock.chunk.load(Ordering::Relaxed);
|
||||
let rate = state.clock.timebase.sr.get() as f64;
|
||||
let buffer = samples as f64 / rate;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,12 @@
|
|||
use crate::*;
|
||||
use super::model_arranger::ArrangerSelection;
|
||||
use crate::{
|
||||
*,
|
||||
api::{
|
||||
ArrangerTrackCommand,
|
||||
ArrangerSceneCommand,
|
||||
ArrangerClipCommand
|
||||
}
|
||||
};
|
||||
|
||||
impl Handle<Tui> for ArrangerTui {
|
||||
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
use crate::*;
|
||||
use KeyCode::{Up, Down, Right, Left, Enter, Esc, Char, Backspace};
|
||||
use FileBrowserCommand::*;
|
||||
use super::model_phrase_list::PhrasesMode::{Import, Export};
|
||||
|
||||
/// Commands supported by [FileBrowser]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
|
|
@ -13,8 +16,6 @@ pub enum FileBrowserCommand {
|
|||
|
||||
impl Command<PhraseListModel> for FileBrowserCommand {
|
||||
fn execute (self, state: &mut PhraseListModel) -> Perhaps<Self> {
|
||||
use FileBrowserCommand::*;
|
||||
use PhrasesMode::{Import, Export};
|
||||
let mode = state.phrases_mode_mut();
|
||||
match mode {
|
||||
Some(Import(index, ref mut browser)) => match self {
|
||||
|
|
@ -59,8 +60,6 @@ impl Command<PhraseListModel> for FileBrowserCommand {
|
|||
|
||||
impl InputToCommand<Tui, PhraseListModel> for FileBrowserCommand {
|
||||
fn input_to_command (state: &PhraseListModel, from: &TuiInput) -> Option<Self> {
|
||||
use KeyCode::{Up, Down, Right, Left, Enter, Esc, Char, Backspace};
|
||||
use FileBrowserCommand::*;
|
||||
if let Some(PhrasesMode::Import(_index, browser)) = state.phrases_mode() {
|
||||
Some(match from.event() {
|
||||
key!(Up) => Select(
|
||||
|
|
@ -97,7 +96,6 @@ impl InputToCommand<Tui, PhraseListModel> for FileBrowserCommand {
|
|||
|
||||
impl InputToCommand<Tui, PhraseListModel> for PhraseLengthCommand {
|
||||
fn input_to_command (state: &PhraseListModel, from: &TuiInput) -> Option<Self> {
|
||||
use KeyCode::{Up, Down, Right, Left, Enter, Esc};
|
||||
if let Some(PhrasesMode::Length(_, length, _)) = state.phrases_mode() {
|
||||
Some(match from.event() {
|
||||
key!(Up) => Self::Inc,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
use crate::*;
|
||||
use super::model_phrase_list::{PhraseListModel, PhrasesMode};
|
||||
use super::model_phrase_length::PhraseLengthFocus::*;
|
||||
use PhraseLengthCommand::*;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum PhraseLengthCommand {
|
||||
|
|
@ -13,8 +16,6 @@ pub enum PhraseLengthCommand {
|
|||
|
||||
impl Command<PhraseListModel> for PhraseLengthCommand {
|
||||
fn execute (self, state: &mut PhraseListModel) -> Perhaps<Self> {
|
||||
use PhraseLengthFocus::*;
|
||||
use PhraseLengthCommand::*;
|
||||
match state.phrases_mode_mut().clone() {
|
||||
Some(PhrasesMode::Length(phrase, ref mut length, ref mut focus)) => match self {
|
||||
Cancel => { *state.phrases_mode_mut() = None; },
|
||||
|
|
|
|||
|
|
@ -1,13 +1,21 @@
|
|||
use crate::*;
|
||||
use crate::{
|
||||
*,
|
||||
api::PhrasePoolCommand as Pool,
|
||||
tui::{
|
||||
ctrl_phrase_rename::PhraseRenameCommand as Rename,
|
||||
ctrl_phrase_length::PhraseLengthCommand as Length,
|
||||
ctrl_file_browser::FileBrowserCommand as Browse,
|
||||
}
|
||||
};
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum PhrasesCommand {
|
||||
Select(usize),
|
||||
Phrase(PhrasePoolCommand),
|
||||
Rename(PhraseRenameCommand),
|
||||
Length(PhraseLengthCommand),
|
||||
Import(FileBrowserCommand),
|
||||
Export(FileBrowserCommand),
|
||||
Phrase(Pool),
|
||||
Rename(Rename),
|
||||
Length(Length),
|
||||
Import(Browse),
|
||||
Export(Browse),
|
||||
}
|
||||
|
||||
impl Command<PhraseListModel> for PhrasesCommand {
|
||||
|
|
@ -72,9 +80,6 @@ impl HasPhrases for PhraseListModel {
|
|||
|
||||
impl InputToCommand<Tui, PhraseListModel> for PhrasesCommand {
|
||||
fn input_to_command (state: &PhraseListModel, input: &TuiInput) -> Option<Self> {
|
||||
use PhraseRenameCommand as Rename;
|
||||
use PhraseLengthCommand as Length;
|
||||
use FileBrowserCommand as Browse;
|
||||
Some(match state.phrases_mode() {
|
||||
Some(PhrasesMode::Rename(..)) => Self::Rename(Rename::input_to_command(state, input)?),
|
||||
Some(PhrasesMode::Length(..)) => Self::Length(Length::input_to_command(state, input)?),
|
||||
|
|
@ -87,11 +92,7 @@ impl InputToCommand<Tui, PhraseListModel> for PhrasesCommand {
|
|||
|
||||
fn to_phrases_command (state: &PhraseListModel, input: &TuiInput) -> Option<PhrasesCommand> {
|
||||
use KeyCode::{Up, Down, Delete, Char};
|
||||
use PhrasesCommand as Cmd;
|
||||
use PhrasePoolCommand as Pool;
|
||||
use PhraseRenameCommand as Rename;
|
||||
use PhraseLengthCommand as Length;
|
||||
use FileBrowserCommand as Browse;
|
||||
use PhrasesCommand as Cmd;
|
||||
let index = state.phrase_index();
|
||||
let count = state.phrases().len();
|
||||
Some(match input.event() {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
use crate::*;
|
||||
use crate::{*, api::ClockCommand::{Play, Pause}};
|
||||
use super::ctrl_phrase_editor::PhraseCommand::Show;
|
||||
use KeyCode::{Char, Enter};
|
||||
use SequencerCommand::*;
|
||||
|
||||
impl Handle<Tui> for SequencerTui {
|
||||
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
|
||||
|
|
@ -20,7 +23,6 @@ pub enum SequencerCommand {
|
|||
|
||||
impl Command<SequencerTui> for SequencerCommand {
|
||||
fn execute (self, state: &mut SequencerTui) -> Perhaps<Self> {
|
||||
use SequencerCommand::*;
|
||||
Ok(match self {
|
||||
Focus(cmd) => cmd.execute(state)?.map(Focus),
|
||||
Phrases(cmd) => cmd.execute(&mut state.phrases)?.map(Phrases),
|
||||
|
|
@ -50,10 +52,6 @@ impl InputToCommand<Tui, SequencerTui> for SequencerCommand {
|
|||
}
|
||||
|
||||
pub fn to_sequencer_command (state: &SequencerTui, input: &TuiInput) -> Option<SequencerCommand> {
|
||||
use SequencerCommand::*;
|
||||
use PhraseCommand::Show;
|
||||
use ClockCommand::{Play, Pause};
|
||||
use KeyCode::{Char, Enter};
|
||||
Some(match input.event() {
|
||||
// Play/pause
|
||||
key!(Char(' ')) => Clock(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
use crate::*;
|
||||
use crate::api::ClockCommand::{SetBpm, SetQuant, SetSync};
|
||||
use TransportCommand::{Focus, Clock};
|
||||
use KeyCode::{Enter, Left, Right, Char};
|
||||
|
||||
impl Handle<Tui> for TransportTui {
|
||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||
|
|
@ -60,9 +63,6 @@ pub fn to_transport_command <T> (state: &T, input: &TuiInput) -> Option<Transpor
|
|||
where
|
||||
T: TransportControl
|
||||
{
|
||||
use ClockCommand::{SetBpm, SetQuant, SetSync};
|
||||
use TransportCommand::{Focus, Clock};
|
||||
use KeyCode::{Enter, Left, Right, Char};
|
||||
Some(match input.event() {
|
||||
key!(Left) => Focus(FocusCommand::Prev),
|
||||
key!(Right) => Focus(FocusCommand::Next),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::*;
|
||||
use crate::{*, tui::ArrangerTui};
|
||||
|
||||
impl JackApi for ArrangerTui {
|
||||
fn jack (&self) -> &Arc<RwLock<JackClient>> {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::*;
|
||||
use crate::{*, tui::SequencerTui};
|
||||
|
||||
impl JackApi for SequencerTui {
|
||||
fn jack (&self) -> &Arc<RwLock<JackClient>> {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use crate::*;
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PhraseListModel {
|
||||
|
|
|
|||
|
|
@ -1,23 +1,21 @@
|
|||
use crate::*;
|
||||
|
||||
impl Content<Tui> for FileBrowser {
|
||||
fn content (&self) -> impl Render<Tui> {
|
||||
Stack::down(|add|{
|
||||
let mut i = 0;
|
||||
for (_, name) in self.dirs.iter() {
|
||||
if i >= self.scroll {
|
||||
add(&Tui::bold(i == self.index, name.as_str()))?;
|
||||
}
|
||||
i += 1;
|
||||
render!(|self: FileBrowser|{
|
||||
Stack::down(|add|{
|
||||
let mut i = 0;
|
||||
for (_, name) in self.dirs.iter() {
|
||||
if i >= self.scroll {
|
||||
add(&Tui::bold(i == self.index, name.as_str()))?;
|
||||
}
|
||||
for (_, name) in self.files.iter() {
|
||||
if i >= self.scroll {
|
||||
add(&Tui::bold(i == self.index, name.as_str()))?;
|
||||
}
|
||||
i += 1;
|
||||
i += 1;
|
||||
}
|
||||
for (_, name) in self.files.iter() {
|
||||
if i >= self.scroll {
|
||||
add(&Tui::bold(i == self.index, name.as_str()))?;
|
||||
}
|
||||
add(&format!("{}/{i}", self.index))?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
add(&format!("{}/{i}", self.index))?;
|
||||
Ok(())
|
||||
})
|
||||
});
|
||||
|
|
|
|||
|
|
@ -124,21 +124,21 @@ render!(|self: PhraseView<'a>|{
|
|||
////}
|
||||
//Ok(())
|
||||
//};
|
||||
Tui::layers()
|
||||
.add(Tui::layers()
|
||||
.add(Tui::at_nw(Tui::fg(title_color, upper_left)))
|
||||
.add(Tui::at_sw(Tui::fg(title_color, lower_left)))
|
||||
.add(Tui::fill_xy(Tui::at_ne(Tui::pull_x(1, Tui::fg(title_color, upper_right)))))
|
||||
.add(Tui::fill_xy(Tui::at_se(Tui::pull_x(1, Tui::fg(title_color, lower_right))))))
|
||||
.add(Tui::bg(
|
||||
Color::Rgb(40, 50, 30),
|
||||
Tui::fill_x(Tui::to_east(
|
||||
Tui::push_y(1, Tui::fill_y(Widget::new(|to:[u16;2]|Ok(Some(to.clip_w(2))), keys))),
|
||||
Tui::fill_x(lay!(
|
||||
Tui::push_y(1, Tui::fill_x(Widget::new(|to|Ok(Some(to)), notes))),
|
||||
Tui::push_y(1, Widget::new(|to|Ok(Some(to)), cursor))
|
||||
)),
|
||||
))))
|
||||
let indicators = lay!(|add|{
|
||||
add(&Tui::at_nw(Tui::fg(title_color, upper_left)))?;
|
||||
add(&Tui::at_sw(Tui::fg(title_color, lower_left)))?;
|
||||
add(&Tui::fill_xy(Tui::at_ne(Tui::pull_x(1, Tui::fg(title_color, upper_right)))))?;
|
||||
add(&Tui::fill_xy(Tui::at_se(Tui::pull_x(1, Tui::fg(title_color, lower_right)))))?;
|
||||
Ok(())
|
||||
});
|
||||
let content = Tui::bg(Color::Rgb(40, 50, 30), Tui::fill_x(Tui::to_east(
|
||||
Tui::push_y(1, Tui::fill_y(Widget::new(|to:[u16;2]|Ok(Some(to.clip_w(2))), keys))),
|
||||
Tui::fill_x(lay!(
|
||||
Tui::push_y(1, Tui::fill_x(Widget::new(|to|Ok(Some(to)), notes))),
|
||||
Tui::push_y(1, Widget::new(|to|Ok(Some(to)), cursor))
|
||||
)),
|
||||
)));
|
||||
lay!(indicators, content)
|
||||
});
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::*;
|
||||
use crate::{*, tui::TransportTui};
|
||||
|
||||
impl Render<Tui> for TransportTui {
|
||||
fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue