mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-08 20:56:43 +01:00
wip: generic render
This commit is contained in:
parent
7bd2a70e85
commit
fcd2d16de9
12 changed files with 314 additions and 351 deletions
|
|
@ -1,17 +1,17 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
/// A component that may contain [Focusable] components.
|
/// A component that may contain [Focusable] components.
|
||||||
pub trait Focus <const N: usize>: Render + Handle {
|
pub trait Focus <const N: usize, T, U>: Render<T, U> + Handle {
|
||||||
fn focus (&self) -> usize;
|
fn focus (&self) -> usize;
|
||||||
fn focus_mut (&mut self) -> &mut usize;
|
fn focus_mut (&mut self) -> &mut usize;
|
||||||
fn focusable (&self) -> [&dyn Focusable;N];
|
fn focusable (&self) -> [&dyn Focusable<T, U>;N];
|
||||||
fn focusable_mut (&mut self) -> [&mut dyn Focusable;N];
|
fn focusable_mut (&mut self) -> [&mut dyn Focusable<T, U>;N];
|
||||||
|
|
||||||
fn focused (&self) -> &dyn Focusable {
|
fn focused (&self) -> &dyn Focusable<T, U> {
|
||||||
let focus = self.focus();
|
let focus = self.focus();
|
||||||
self.focusable()[focus]
|
self.focusable()[focus]
|
||||||
}
|
}
|
||||||
fn focused_mut (&mut self) -> &mut dyn Focusable {
|
fn focused_mut (&mut self) -> &mut dyn Focusable<T, U> {
|
||||||
let focus = self.focus();
|
let focus = self.focus();
|
||||||
self.focusable_mut()[focus]
|
self.focusable_mut()[focus]
|
||||||
}
|
}
|
||||||
|
|
@ -33,12 +33,14 @@ pub trait Focus <const N: usize>: Render + Handle {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A component that may be focused.
|
/// A component that may be focused.
|
||||||
pub trait Focusable: Render + Handle {
|
pub trait Focusable<T, U>: Render<T, U> + Handle {
|
||||||
fn is_focused (&self) -> bool;
|
fn is_focused (&self) -> bool;
|
||||||
fn set_focused (&mut self, focused: bool);
|
fn set_focused (&mut self, focused: bool);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Focusable> Focusable for Option<T> {
|
impl<F: Focusable<T, U>, T, U> Focusable<T, U> for Option<F>
|
||||||
|
where Option<F>: Render<T, U>
|
||||||
|
{
|
||||||
fn is_focused (&self) -> bool {
|
fn is_focused (&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Some(focusable) => focusable.is_focused(),
|
Some(focusable) => focusable.is_focused(),
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
use crate::{*, jack::*};
|
use crate::{*, jack::*};
|
||||||
|
|
||||||
/// A UI component that may be associated with a JACK client by the `Jack` factory.
|
/// A UI component that may be associated with a JACK client by the `Jack` factory.
|
||||||
pub trait Device: Render + Handle + Process + Send + Sync {
|
pub trait Device<T, U>: Render<T, U> + Handle + Process + Send + Sync {
|
||||||
/// Perform type erasure for collecting heterogeneous devices.
|
/// Perform type erasure for collecting heterogeneous devices.
|
||||||
fn boxed (self) -> Box<dyn Device> where Self: Sized + 'static {
|
fn boxed (self) -> Box<dyn Device<T, U>> where Self: Sized + 'static {
|
||||||
Box::new(self)
|
Box::new(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// All things that implement the required traits can be treated as `Device`.
|
/// All things that implement the required traits can be treated as `Device`.
|
||||||
impl<T: Render + Handle + Process + Send + Sync> Device for T {}
|
impl<D, T, U> Device<T, U> for D where D: Render<T, U> + Handle + Process {}
|
||||||
|
|
||||||
impl Render for Box<dyn Device> {
|
impl<T, U> Render<T, U> for Box<dyn Device<T, U>> {
|
||||||
fn render (&self, b: &mut Buffer, a: Rect) -> Usually<Rect> {
|
fn render (&self, to: &mut T) -> Result<Option<U>, Box<dyn std::error::Error>> {
|
||||||
(**self).render(b, a)
|
(**self).render(to)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -143,10 +143,8 @@ impl Jack {
|
||||||
)?.0,
|
)?.0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn run <T: Device + Process + Sized + 'static> (
|
pub fn run <D, T, U> (self, state: impl FnOnce(JackPorts)->Box<D>) -> Usually<JackDevice<T, U>>
|
||||||
self, state: impl FnOnce(JackPorts)->Box<T>
|
where D: Device<T, U> + Process + Sized + 'static
|
||||||
)
|
|
||||||
-> Usually<JackDevice>
|
|
||||||
{
|
{
|
||||||
let owned_ports = JackPorts {
|
let owned_ports = JackPorts {
|
||||||
audio_ins: register_ports(&self.client, self.audio_ins, AudioIn)?,
|
audio_ins: register_ports(&self.client, self.audio_ins, AudioIn)?,
|
||||||
|
|
@ -162,7 +160,7 @@ impl Jack {
|
||||||
.map(|p|Ok(p.name()?)).collect::<Usually<Vec<_>>>()?;
|
.map(|p|Ok(p.name()?)).collect::<Usually<Vec<_>>>()?;
|
||||||
let audio_ins = owned_ports.audio_ins.values()
|
let audio_ins = owned_ports.audio_ins.values()
|
||||||
.map(|p|Ok(p.name()?)).collect::<Usually<Vec<_>>>()?;
|
.map(|p|Ok(p.name()?)).collect::<Usually<Vec<_>>>()?;
|
||||||
let state = Arc::new(RwLock::new(state(owned_ports) as Box<dyn Device>));
|
let state = Arc::new(RwLock::new(state(owned_ports) as Box<dyn Device<T, U>>));
|
||||||
let client = self.client.activate_async(
|
let client = self.client.activate_async(
|
||||||
Notifications(Box::new({
|
Notifications(Box::new({
|
||||||
let _state = state.clone();
|
let _state = state.clone();
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,51 @@
|
||||||
use crate::{*, jack::*};
|
use crate::{*, jack::*};
|
||||||
|
|
||||||
/// A [Device] bound to a JACK client and a set of ports.
|
/// A [Device] bound to a JACK client and a set of ports.
|
||||||
pub struct JackDevice {
|
pub struct JackDevice<T, U> {
|
||||||
/// The active JACK client of this device.
|
/// The active JACK client of this device.
|
||||||
pub client: DynamicAsyncClient,
|
pub client: DynamicAsyncClient,
|
||||||
/// The device state, encapsulated for sharing between threads.
|
/// The device state, encapsulated for sharing between threads.
|
||||||
pub state: Arc<RwLock<Box<dyn Device>>>,
|
pub state: Arc<RwLock<Box<dyn Device<T, U>>>>,
|
||||||
/// Unowned copies of the device's JACK ports, for connecting to the device.
|
/// Unowned copies of the device's JACK ports, for connecting to the device.
|
||||||
/// The "real" readable/writable `Port`s are owned by the `state`.
|
/// The "real" readable/writable `Port`s are owned by the `state`.
|
||||||
pub ports: UnownedJackPorts,
|
pub ports: UnownedJackPorts,
|
||||||
}
|
}
|
||||||
impl std::fmt::Debug for JackDevice {
|
impl<T, U> std::fmt::Debug for JackDevice<T, U> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("JackDevice").field("ports", &self.ports).finish()
|
f.debug_struct("JackDevice").field("ports", &self.ports).finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
render!(JackDevice |self, buf, area| self.state.read().unwrap().render(buf, area));
|
impl<'a, T, U> Render<T, U> for JackDevice<T, U> {
|
||||||
handle!(JackDevice |self, event| self.state.write().unwrap().handle(event));
|
fn render (&self, to: &mut T) -> Perhaps<U> {
|
||||||
ports!(JackDevice {
|
self.state.read().unwrap().render(to)
|
||||||
audio: {
|
|
||||||
ins: |s|Ok(s.ports.audio_ins.values().collect()),
|
|
||||||
outs: |s|Ok(s.ports.audio_outs.values().collect()),
|
|
||||||
}
|
}
|
||||||
midi: {
|
}
|
||||||
ins: |s|Ok(s.ports.midi_ins.values().collect()),
|
impl<T, U> Handle for JackDevice<T, U> {
|
||||||
outs: |s|Ok(s.ports.midi_outs.values().collect()),
|
fn handle (&mut self, event: &AppEvent) -> Usually<bool> {
|
||||||
|
self.state.write().unwrap().handle(event)
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
impl JackDevice {
|
impl<T, U> Ports for JackDevice<T, U> {
|
||||||
|
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<T, U> JackDevice<T, U> {
|
||||||
/// Returns a locked mutex of the state's contents.
|
/// Returns a locked mutex of the state's contents.
|
||||||
pub fn state (&self) -> LockResult<RwLockReadGuard<Box<dyn Device>>> {
|
pub fn state (&self) -> LockResult<RwLockReadGuard<Box<dyn Device<T, U>>>> {
|
||||||
self.state.read()
|
self.state.read()
|
||||||
}
|
}
|
||||||
/// Returns a locked mutex of the state's contents.
|
/// Returns a locked mutex of the state's contents.
|
||||||
pub fn state_mut (&self) -> LockResult<RwLockWriteGuard<Box<dyn Device>>> {
|
pub fn state_mut (&self) -> LockResult<RwLockWriteGuard<Box<dyn Device<T, U>>>> {
|
||||||
self.state.write()
|
self.state.write()
|
||||||
}
|
}
|
||||||
pub fn connect_midi_in (&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
|
pub fn connect_midi_in (&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
|
||||||
|
|
|
||||||
|
|
@ -55,11 +55,13 @@ submod! {
|
||||||
render
|
render
|
||||||
render_axis
|
render_axis
|
||||||
render_border
|
render_border
|
||||||
|
render_buffer
|
||||||
render_collect
|
render_collect
|
||||||
render_fill
|
|
||||||
render_layered
|
render_layered
|
||||||
render_split
|
render_split
|
||||||
render_theme
|
render_tui
|
||||||
|
render_tui_fill
|
||||||
|
render_tui_theme
|
||||||
time_base
|
time_base
|
||||||
time_note
|
time_note
|
||||||
time_tick
|
time_tick
|
||||||
|
|
@ -80,59 +82,39 @@ submod! {
|
||||||
/// Standard result type.
|
/// Standard result type.
|
||||||
pub type Usually<T> = Result<T, Box<dyn Error>>;
|
pub type Usually<T> = Result<T, Box<dyn Error>>;
|
||||||
|
|
||||||
|
/// Standard optional result type.
|
||||||
|
pub type Perhaps<T> = Result<Option<T>, Box<dyn Error>>;
|
||||||
|
|
||||||
/// A UI component.
|
/// A UI component.
|
||||||
pub trait Component: Render + Handle + Sync {
|
pub trait Component<T, U>: Render<T, U> + Handle + Sync {
|
||||||
/// Perform type erasure for collecting heterogeneous components.
|
/// Perform type erasure for collecting heterogeneous components.
|
||||||
fn boxed (self) -> Box<dyn Component> where Self: Sized + 'static {
|
fn boxed (self) -> Box<dyn Component<T, U>> where Self: Sized + 'static {
|
||||||
Box::new(self)
|
Box::new(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Render + Handle + Sync> Component for T {}
|
impl<C, T, U> Component<T, U> for C where C: Render<T, U> + Handle + Sync {}
|
||||||
|
|
||||||
/// Marker trait for [Component]s that can [Exit]
|
/// Marker trait for [Component]s that can [Exit]
|
||||||
pub trait ExitableComponent: Exit + Component {
|
pub trait ExitableComponent<T, U>: Exit + Component<T, U> {
|
||||||
/// Perform type erasure for collecting heterogeneous components.
|
/// Perform type erasure for collecting heterogeneous components.
|
||||||
fn boxed (self) -> Box<dyn ExitableComponent> where Self: Sized + 'static {
|
fn boxed (self) -> Box<dyn ExitableComponent<T, U>> where Self: Sized + 'static {
|
||||||
Box::new(self)
|
Box::new(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Exit + Component> ExitableComponent for T {}
|
impl<E: Exit + Component<T, U>, T, U> ExitableComponent<T, U> for E {}
|
||||||
|
|
||||||
/// Run the main loop.
|
/// Run the main loop.
|
||||||
pub fn run <T> (state: Arc<RwLock<T>>) -> Usually<Arc<RwLock<T>>>
|
pub fn run <'a, R> (state: Arc<RwLock<R>>) -> Usually<Arc<RwLock<R>>>
|
||||||
where T: Render + Handle + Send + Sync + Sized + 'static
|
where R: Render<TuiOutput<'a>, Rect> + Handle + Sized + 'static
|
||||||
{
|
{
|
||||||
let exited = Arc::new(AtomicBool::new(false));
|
let exited = Arc::new(AtomicBool::new(false));
|
||||||
let _input_thread = input_thread(&exited, &state);
|
let _input_thread = input_thread(&exited, &state);
|
||||||
terminal_setup()?;
|
terminal_setup()?;
|
||||||
panic_hook_setup();
|
panic_hook_setup();
|
||||||
let main_thread = render_thread(&exited, &state)?;
|
let main_thread = tui_render_thread(&exited, &state)?;
|
||||||
main_thread.join().expect("main thread failed");
|
main_thread.join().expect("main thread failed");
|
||||||
terminal_teardown()?;
|
terminal_teardown()?;
|
||||||
Ok(state)
|
Ok(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set up panic hook
|
|
||||||
pub fn panic_hook_setup () {
|
|
||||||
let better_panic_handler = Settings::auto().verbosity(Verbosity::Full).create_panic_handler();
|
|
||||||
std::panic::set_hook(Box::new(move |info: &std::panic::PanicInfo|{
|
|
||||||
stdout().execute(LeaveAlternateScreen).unwrap();
|
|
||||||
disable_raw_mode().unwrap();
|
|
||||||
better_panic_handler(info);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set up terminal
|
|
||||||
pub fn terminal_setup () -> Usually<()> {
|
|
||||||
stdout().execute(EnterAlternateScreen)?;
|
|
||||||
enable_raw_mode()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
/// Cleanup
|
|
||||||
pub fn terminal_teardown () -> Usually<()> {
|
|
||||||
stdout().execute(LeaveAlternateScreen)?;
|
|
||||||
disable_raw_mode()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -2,230 +2,60 @@
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
pub(crate) use ratatui::prelude::CrosstermBackend;
|
pub(crate) use ratatui::prelude::CrosstermBackend;
|
||||||
pub(crate) use ratatui::style::Style;
|
|
||||||
pub(crate) use ratatui::layout::Rect;
|
|
||||||
pub(crate) use ratatui::buffer::{Buffer, Cell};
|
|
||||||
|
|
||||||
/// Main thread render loop
|
/// Trait for things that are displayed to the user.
|
||||||
pub fn render_thread (
|
pub trait Render<T, U>: Send + Sync {
|
||||||
exited: &Arc<AtomicBool>,
|
fn render (&self, to: &mut T) -> Perhaps<U>;
|
||||||
device: &Arc<RwLock<impl Render + 'static>>
|
|
||||||
) -> Usually<JoinHandle<()>> {
|
|
||||||
let exited = exited.clone();
|
|
||||||
let device = device.clone();
|
|
||||||
let mut terminal = ratatui::Terminal::new(CrosstermBackend::new(stdout()))?;
|
|
||||||
let sleep = Duration::from_millis(20);
|
|
||||||
Ok(spawn(move || loop {
|
|
||||||
|
|
||||||
if let Ok(device) = device.try_read() {
|
|
||||||
terminal.draw(|frame|{
|
|
||||||
let area = frame.size();
|
|
||||||
let buffer = frame.buffer_mut();
|
|
||||||
device
|
|
||||||
.render(buffer, area)
|
|
||||||
.expect("Failed to render content");
|
|
||||||
})
|
|
||||||
.expect("Failed to render frame");
|
|
||||||
}
|
|
||||||
|
|
||||||
if exited.fetch_and(true, Ordering::Relaxed) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
std::thread::sleep(sleep);
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait for things that render to the display.
|
/// Options can be rendered optionally.
|
||||||
pub trait Render: Send + Sync {
|
impl<R, T, U> Render<T, U> for Option<R> where R: Render<T, U> {
|
||||||
// Render something to an area of the buffer.
|
fn render (&self, to: &mut T) -> Perhaps<U> {
|
||||||
// Returns area used by component.
|
|
||||||
// This is insufficient but for the most basic dynamic layout algorithms.
|
|
||||||
fn render (&self, _b: &mut Buffer, _a: Rect) -> Usually<Rect> {
|
|
||||||
Ok(Rect { x: 0, y: 0, width: 0, height: 0 })
|
|
||||||
}
|
|
||||||
fn into_collected <'a> (self) -> Collected<'a> where Self: Sized + 'a {
|
|
||||||
Collected::Box(Box::new(self))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Implement the `Render` trait.
|
|
||||||
#[macro_export] macro_rules! render {
|
|
||||||
($T:ty) => {
|
|
||||||
impl Render for $T {}
|
|
||||||
};
|
|
||||||
($T:ty |$self:ident, $buf:ident, $area:ident|$block:expr) => {
|
|
||||||
impl Render for $T {
|
|
||||||
fn render (&$self, $buf: &mut Buffer, $area: Rect) -> Usually<Rect> {
|
|
||||||
$block
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
($T:ty = $render:path) => {
|
|
||||||
impl Render for $T {
|
|
||||||
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
|
||||||
$render(self, buf, area)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for () {
|
|
||||||
fn render (&self, _: &mut Buffer, a: Rect) -> Usually<Rect> {
|
|
||||||
Ok(Rect { x: a.x, y: a.y, width: 0, height: 0 })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Render> Render for &T {
|
|
||||||
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
|
||||||
(*self).render(buf, area)
|
|
||||||
}
|
|
||||||
fn into_collected <'a> (self) -> Collected<'a> where Self: Sized + 'a {
|
|
||||||
Collected::Ref(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Render> Render for &mut T {
|
|
||||||
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
|
||||||
(**self).render(buf, area)
|
|
||||||
}
|
|
||||||
fn into_collected <'a> (self) -> Collected<'a> where Self: Sized + 'a {
|
|
||||||
Collected::Ref(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Render> Render for Option<T> {
|
|
||||||
fn render (&self, b: &mut Buffer, a: Rect) -> Usually<Rect> {
|
|
||||||
match self {
|
match self {
|
||||||
Some(widget) => widget.render(b, a),
|
Some(component) => component.render(to),
|
||||||
None => ().render(b, a),
|
None => Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Render for Box<dyn Render + 'a> {
|
/// Boxed references can be rendered.
|
||||||
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
impl<'a, T, U> Render<T, U> for Box<dyn Render<T, U> + 'a> {
|
||||||
(**self).render(buf, area)
|
fn render (&self, to: &mut T) -> Perhaps<U> {
|
||||||
}
|
(**self).render(to)
|
||||||
fn into_collected <'b> (self) -> Collected<'b> where Self: Sized + 'b {
|
|
||||||
Collected::Box(self)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Render> Render for Arc<T> {
|
/// Immutable references can be rendered.
|
||||||
fn render (&self, b: &mut Buffer, a: Rect) -> Usually<Rect> {
|
impl<R, T, U> Render<T, U> for &R where R: Render<T, U> {
|
||||||
self.as_ref().render(b, a)
|
fn render (&self, to: &mut T) -> Perhaps<U> {
|
||||||
|
(*self).render(to)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Render> Render for Mutex<T> {
|
/// Mutable references can be rendered.
|
||||||
fn render (&self, b: &mut Buffer, a: Rect) -> Usually<Rect> {
|
impl<R, T, U> Render<T, U> for &mut R where R: Render<T, U> {
|
||||||
self.lock().unwrap().render(b, a)
|
fn render (&self, to: &mut T) -> Perhaps<U> {
|
||||||
|
(**self).render(to)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Render + Sync> Render for RwLock<T> {
|
/// Counted references can be rendered.
|
||||||
fn render (&self, b: &mut Buffer, a: Rect) -> Usually<Rect> {
|
impl<R, T, U> Render<T, U> for Arc<R> where R: Render<T, U> {
|
||||||
self.read().unwrap().render(b, a)
|
fn render (&self, to: &mut T) -> Perhaps<U> {
|
||||||
|
self.as_ref().render(to)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//impl<'a, T: Fn(&mut Buffer, Rect) -> Usually<Rect> + Send + Sync + 'a> Render for T {
|
/// References behind a [Mutex] can be rendered.
|
||||||
//fn render (&self, b: &mut Buffer, a: Rect) -> Usually<Rect> {
|
impl<R, T, U> Render<T, U> for Mutex<R> where R: Render<T, U> {
|
||||||
//(*self)(b, a)
|
fn render (&self, to: &mut T) -> Perhaps<U> {
|
||||||
//}
|
self.lock().unwrap().render(to)
|
||||||
//}
|
|
||||||
|
|
||||||
impl<'a> Render for Box<dyn Fn(&mut Buffer, Rect) -> Usually<Rect> + Send + Sync + 'a> {
|
|
||||||
fn render (&self, b: &mut Buffer, a: Rect) -> Usually<Rect> {
|
|
||||||
(*self)(b, a)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn center_box (area: Rect, w: u16, h: u16) -> Rect {
|
/// References behind a [RwLock] can be rendered.
|
||||||
let width = w.min(area.width * 3 / 5);
|
impl<R, T, U> Render<T, U> for RwLock<R> where R: Render<T, U> {
|
||||||
let height = h.min(area.width * 3 / 5);
|
fn render (&self, to: &mut T) -> Perhaps<U> {
|
||||||
let x = area.x + (area.width - width) / 2;
|
self.read().unwrap().render(to)
|
||||||
let y = area.y + (area.height - height) / 2;
|
|
||||||
Rect { x, y, width, height }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn half_block (lower: bool, upper: bool) -> Option<char> {
|
|
||||||
match (lower, upper) {
|
|
||||||
(true, true) => Some('█'),
|
|
||||||
(true, false) => Some('▄'),
|
|
||||||
(false, true) => Some('▀'),
|
|
||||||
_ => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Blit {
|
|
||||||
// Render something to X, Y coordinates in a buffer, ignoring width/height.
|
|
||||||
fn blit (&self, buf: &mut Buffer, x: u16, y: u16, style: Option<Style>) -> Usually<Rect>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: AsRef<str>> Blit for T {
|
|
||||||
fn blit (&self, buf: &mut Buffer, x: u16, y: u16, style: Option<Style>) -> Usually<Rect> {
|
|
||||||
if x < buf.area.width && y < buf.area.height {
|
|
||||||
buf.set_string(x, y, self.as_ref(), style.unwrap_or(Style::default()));
|
|
||||||
}
|
|
||||||
Ok(Rect { x, y, width: self.as_ref().len() as u16, height: 1 })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//impl WidgetRef for &dyn Render {
|
|
||||||
//fn render_ref (&self, area: Rect, buf: &mut Buffer) {
|
|
||||||
//Render::render(*self, buf, area).expect("Failed to render device.");
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
|
|
||||||
//impl WidgetRef for dyn Render {
|
|
||||||
//fn render_ref (&self, area: Rect, buf: &mut Buffer) {
|
|
||||||
//Render::render(self, buf, area).expect("Failed to render device.");
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct BigBuffer {
|
|
||||||
pub width: usize,
|
|
||||||
pub height: usize,
|
|
||||||
pub content: Vec<Cell>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BigBuffer {
|
|
||||||
pub fn new (width: usize, height: usize) -> Self {
|
|
||||||
Self { width, height, content: vec![Cell::default(); width*height] }
|
|
||||||
}
|
|
||||||
pub fn get (&self, x: usize, y: usize) -> Option<&Cell> {
|
|
||||||
let i = self.index_of(x, y);
|
|
||||||
self.content.get(i)
|
|
||||||
}
|
|
||||||
pub fn get_mut (&mut self, x: usize, y: usize) -> Option<&mut Cell> {
|
|
||||||
let i = self.index_of(x, y);
|
|
||||||
self.content.get_mut(i)
|
|
||||||
}
|
|
||||||
pub fn index_of (&self, x: usize, y: usize) -> usize {
|
|
||||||
y * self.width + x
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct If<'a>(pub bool, pub &'a (dyn Render + Sync));
|
|
||||||
|
|
||||||
impl<'a> Render for If<'a> {
|
|
||||||
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
|
||||||
match self.0 {
|
|
||||||
true => self.1 as &dyn Render,
|
|
||||||
false => &() as &dyn Render
|
|
||||||
}.render(buf, area)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct IfElse<'a>(pub bool, pub &'a (dyn Render + Sync), pub &'a (dyn Render + Sync));
|
|
||||||
|
|
||||||
impl<'a> Render for IfElse<'a> {
|
|
||||||
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
|
||||||
match self.0 {
|
|
||||||
true => self.1 as &dyn Render,
|
|
||||||
false => &() as &dyn Render
|
|
||||||
}.render(buf, area)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
25
crates/tek_core/src/render_buffer.rs
Normal file
25
crates/tek_core/src/render_buffer.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct BigBuffer {
|
||||||
|
pub width: usize,
|
||||||
|
pub height: usize,
|
||||||
|
pub content: Vec<Cell>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BigBuffer {
|
||||||
|
pub fn new (width: usize, height: usize) -> Self {
|
||||||
|
Self { width, height, content: vec![Cell::default(); width*height] }
|
||||||
|
}
|
||||||
|
pub fn get (&self, x: usize, y: usize) -> Option<&Cell> {
|
||||||
|
let i = self.index_of(x, y);
|
||||||
|
self.content.get(i)
|
||||||
|
}
|
||||||
|
pub fn get_mut (&mut self, x: usize, y: usize) -> Option<&mut Cell> {
|
||||||
|
let i = self.index_of(x, y);
|
||||||
|
self.content.get_mut(i)
|
||||||
|
}
|
||||||
|
pub fn index_of (&self, x: usize, y: usize) -> usize {
|
||||||
|
y * self.width + x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,45 +1,40 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
pub enum Collected<'a> {
|
pub enum Collected<'a, T, U> {
|
||||||
Box(Box<dyn Render + 'a>),
|
Box(Box<dyn Render<T, U> + 'a>),
|
||||||
Ref(&'a (dyn Render + 'a)),
|
Ref(&'a (dyn Render<T, U> + 'a)),
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
impl<'a, T, U> Render<T, U> for Collected<'a, T, U> {
|
||||||
impl<'a> Render for Collected<'a> {
|
fn render (&self, to: &mut T) -> Perhaps<U> {
|
||||||
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
match self {
|
||||||
Ok(match self {
|
Self::Box(item) => (*item).render(to),
|
||||||
Self::Box(item) => (*item).render(buf, area)?,
|
Self::Ref(item) => (*item).render(to),
|
||||||
Self::Ref(item) => (*item).render(buf, area)?,
|
}
|
||||||
Self::None => ().render(buf, area)?
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub struct Collection<'a, T, U>(
|
||||||
pub struct Collection<'a>(pub Vec<Collected<'a>>);
|
pub Vec<Collected<'a, T, U>>
|
||||||
|
);
|
||||||
impl<'a> Collection<'a> {
|
impl<'a, T, U> Collection<'a, T, U> {
|
||||||
pub fn new () -> Self {
|
pub fn new () -> Self {
|
||||||
Self(vec![])
|
Self(vec![])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub trait Collect<'a, T, U> {
|
||||||
pub trait Collect<'a> {
|
fn add_box (self, item: Box<dyn Render<T, U> + 'a>) -> Self;
|
||||||
fn add_box (self, item: Box<dyn Render + 'a>) -> Self;
|
fn add_ref (self, item: &'a dyn Render<T, U>) -> Self;
|
||||||
fn add_ref (self, item: &'a dyn Render) -> Self;
|
fn add <R: Render<T, U> + Sized + 'a> (self, item: R) -> Self
|
||||||
fn add <T: Render + Sized + 'a> (self, item: T) -> Self
|
|
||||||
where Self: Sized
|
where Self: Sized
|
||||||
{
|
{
|
||||||
self.add_box(Box::new(item))
|
self.add_box(Box::new(item))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl<'a, T, U> Collect<'a, T, U> for Collection<'a, T, U> {
|
||||||
impl<'a> Collect<'a> for Collection<'a> {
|
fn add_box (mut self, item: Box<dyn Render<T, U> + 'a>) -> Self {
|
||||||
fn add_box (mut self, item: Box<dyn Render + 'a>) -> Self {
|
|
||||||
self.0.push(Collected::Box(item));
|
self.0.push(Collected::Box(item));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
fn add_ref (mut self, item: &'a dyn Render) -> Self {
|
fn add_ref (mut self, item: &'a dyn Render<T, U>) -> Self {
|
||||||
self.0.push(Collected::Ref(item));
|
self.0.push(Collected::Ref(item));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,30 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
pub struct Layered<'a>(Collection<'a>);
|
pub struct Layered<'a, T, U>(Collection<'a, T, U>);
|
||||||
|
|
||||||
impl<'a> Layered<'a> {
|
impl<'a, T, U> Layered<'a, T, U> {
|
||||||
pub fn new () -> Self {
|
pub fn new () -> Self {
|
||||||
Self(Collection::new())
|
Self(Collection::new())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Render for Layered<'a> {
|
impl<'a, T, U> Collect<'a, T, U> for Layered<'a, T, U> {
|
||||||
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
fn add_box (mut self, item: Box<dyn Render<T, U> + 'a>) -> Self {
|
||||||
for layer in self.0.0.iter() {
|
|
||||||
layer.render(buf, area)?;
|
|
||||||
}
|
|
||||||
Ok(area)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Collect<'a> for Layered<'a> {
|
|
||||||
fn add_box (mut self, item: Box<dyn Render + 'a>) -> Self {
|
|
||||||
self.0 = self.0.add_box(item);
|
self.0 = self.0.add_box(item);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
fn add_ref (mut self, item: &'a dyn Render) -> Self {
|
fn add_ref (mut self, item: &'a dyn Render<T, U>) -> Self {
|
||||||
self.0 = self.0.add_ref(item);
|
self.0 = self.0.add_ref(item);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> Render<TuiOutput<'b>, Rect> for Layered<'a, TuiOutput<'b>, Rect> {
|
||||||
|
fn render (&self, to: &mut TuiOutput<'b>) -> Perhaps<Rect> {
|
||||||
|
let area = to.area;
|
||||||
|
for layer in self.0.0.iter() {
|
||||||
|
layer.render(to)?;
|
||||||
|
}
|
||||||
|
Ok(Some(area))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,13 @@ impl Direction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Split<'a> {
|
pub struct Split<'a, T, U> {
|
||||||
items: Collection<'a>,
|
items: Collection<'a, T, U>,
|
||||||
direction: Direction,
|
direction: Direction,
|
||||||
focus: Option<usize>
|
focus: Option<usize>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Split<'a> {
|
impl<'a, T, U> Split<'a, T, U> {
|
||||||
pub fn new (direction: Direction) -> Self {
|
pub fn new (direction: Direction) -> Self {
|
||||||
Self {
|
Self {
|
||||||
items: Collection::new(),
|
items: Collection::new(),
|
||||||
|
|
@ -41,47 +41,53 @@ impl<'a> Split<'a> {
|
||||||
self.focus = focus;
|
self.focus = focus;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
pub fn render_areas (&self, buf: &mut Buffer, area: Rect) -> Usually<(Rect, Vec<Rect>)> {
|
|
||||||
let Rect { mut x, mut y, mut width, mut height } = area;
|
|
||||||
let mut areas = vec![];
|
|
||||||
for (index, item) in self.items.0.iter().enumerate() {
|
|
||||||
if width == 0 || height == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
let result = item.render(buf, Rect { x, y, width, height })?;
|
|
||||||
match self.direction {
|
|
||||||
Direction::Down => {
|
|
||||||
y += result.height;
|
|
||||||
height = height.saturating_sub(result.height);
|
|
||||||
},
|
|
||||||
Direction::Right => {
|
|
||||||
x += result.width;
|
|
||||||
width = width.saturating_sub(result.width);
|
|
||||||
},
|
|
||||||
_ => unimplemented!()
|
|
||||||
};
|
|
||||||
areas.push(result);
|
|
||||||
if self.focus == Some(index) {
|
|
||||||
Corners(Style::default().green().not_dim()).draw(buf, result)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok((area, areas))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Render for Split<'a> {
|
impl<'a, T, U> Collect<'a, T, U> for Split<'a, T, U> {
|
||||||
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
fn add_box (mut self, item: Box<dyn Render<T, U> + 'a>) -> Self {
|
||||||
Ok(self.render_areas(buf, area)?.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Collect<'a> for Split<'a> {
|
|
||||||
fn add_box (mut self, item: Box<dyn Render + 'a>) -> Self {
|
|
||||||
self.items = self.items.add_box(item);
|
self.items = self.items.add_box(item);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
fn add_ref (mut self, item: &'a dyn Render) -> Self {
|
fn add_ref (mut self, item: &'a dyn Render<T, U>) -> Self {
|
||||||
self.items = self.items.add_ref(item);
|
self.items = self.items.add_ref(item);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> Render<TuiOutput<'a>, Rect> for Split<'a, TuiOutput<'a>, Rect> {
|
||||||
|
fn render (&self, to: &mut TuiOutput<'a>) -> Perhaps<Rect> {
|
||||||
|
Ok(Some(self.render_areas(to)?.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Split<'a, TuiOutput<'a>, Rect> {
|
||||||
|
pub fn render_areas (&self, to: &mut TuiOutput<'a>) -> Usually<(Rect, Vec<Rect>)> {
|
||||||
|
let Rect { mut x, mut y, mut width, mut height } = to.area;
|
||||||
|
let mut areas = vec![];
|
||||||
|
let buffer = &mut to.buffer;
|
||||||
|
for (index, item) in self.items.0.iter().enumerate() {
|
||||||
|
if width == 0 || height == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
let target = TuiOutput { buffer, area: Rect { x, y, width, height } };
|
||||||
|
if let Some(result) = item.render(&mut target)? {
|
||||||
|
match self.direction {
|
||||||
|
Direction::Down => {
|
||||||
|
y += result.height;
|
||||||
|
height = height.saturating_sub(result.height);
|
||||||
|
},
|
||||||
|
Direction::Right => {
|
||||||
|
x += result.width;
|
||||||
|
width = width.saturating_sub(result.width);
|
||||||
|
},
|
||||||
|
_ => unimplemented!()
|
||||||
|
};
|
||||||
|
areas.push(result);
|
||||||
|
if self.focus == Some(index) {
|
||||||
|
Corners(Style::default().green().not_dim()).draw(buffer, result)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok((to.area, areas))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
110
crates/tek_core/src/render_tui.rs
Normal file
110
crates/tek_core/src/render_tui.rs
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
pub(crate) use ratatui::buffer::Cell;
|
||||||
|
|
||||||
|
pub struct TuiOutput<'a> {
|
||||||
|
pub buffer: &'a mut Buffer,
|
||||||
|
pub area: Rect
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Main thread render loop
|
||||||
|
pub fn tui_render_thread <'a, T: Render<TuiOutput<'a>, Rect> + 'static> (
|
||||||
|
exited: &Arc<AtomicBool>, device: &Arc<RwLock<T>>,
|
||||||
|
) -> Usually<JoinHandle<()>> {
|
||||||
|
let exited = exited.clone();
|
||||||
|
let device = device.clone();
|
||||||
|
let mut terminal = ratatui::Terminal::new(CrosstermBackend::new(stdout()))?;
|
||||||
|
let sleep = Duration::from_millis(20);
|
||||||
|
Ok(spawn(move || loop {
|
||||||
|
|
||||||
|
if let Ok(device) = device.try_read() {
|
||||||
|
terminal.draw(|frame|{
|
||||||
|
let area = frame.size();
|
||||||
|
let buffer = frame.buffer_mut();
|
||||||
|
let mut output = TuiOutput { buffer, area };
|
||||||
|
device.render(&mut output).expect("Failed to render content");
|
||||||
|
})
|
||||||
|
.expect("Failed to render frame");
|
||||||
|
}
|
||||||
|
|
||||||
|
if exited.fetch_and(true, Ordering::Relaxed) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
std::thread::sleep(sleep);
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set up panic hook
|
||||||
|
pub fn panic_hook_setup () {
|
||||||
|
let better_panic_handler = Settings::auto().verbosity(Verbosity::Full).create_panic_handler();
|
||||||
|
std::panic::set_hook(Box::new(move |info: &std::panic::PanicInfo|{
|
||||||
|
stdout().execute(LeaveAlternateScreen).unwrap();
|
||||||
|
disable_raw_mode().unwrap();
|
||||||
|
better_panic_handler(info);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set up terminal
|
||||||
|
pub fn terminal_setup () -> Usually<()> {
|
||||||
|
stdout().execute(EnterAlternateScreen)?;
|
||||||
|
enable_raw_mode()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cleanup
|
||||||
|
pub fn terminal_teardown () -> Usually<()> {
|
||||||
|
stdout().execute(LeaveAlternateScreen)?;
|
||||||
|
disable_raw_mode()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A simpler analog to [Render].
|
||||||
|
pub trait Blit {
|
||||||
|
// Render something to X, Y coordinates in a buffer, ignoring width/height.
|
||||||
|
fn blit (&self, buf: &mut Buffer, x: u16, y: u16, style: Option<Style>) -> Perhaps<Rect>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Text can be rendered.
|
||||||
|
impl<T: AsRef<str>> Blit for T {
|
||||||
|
fn blit (&self, buf: &mut Buffer, x: u16, y: u16, style: Option<Style>) -> Perhaps<Rect> {
|
||||||
|
if x < buf.area.width && y < buf.area.height {
|
||||||
|
buf.set_string(x, y, self.as_ref(), style.unwrap_or(Style::default()));
|
||||||
|
}
|
||||||
|
Ok(Some(Rect { x, y, width: self.as_ref().len() as u16, height: 1 }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rendering unit struct to Ratatui returns zero-sized [Rect] at render coordinates.
|
||||||
|
impl<'a> Render<TuiOutput<'a>, Rect> for () {
|
||||||
|
fn render (&self, (_, area): (&mut Buffer, Rect)) -> Perhaps<Rect> {
|
||||||
|
Ok(Some(Rect { x: area.x, y: area.y, width: 0, height: 0 }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Boxed closures can be rendered.
|
||||||
|
///
|
||||||
|
/// Rendering unboxed closures should also be possible;
|
||||||
|
/// but in practice implementing the trait for an unboxed
|
||||||
|
/// `Fn` closure causes an impl conflict.
|
||||||
|
impl<'a, T, U> Render<T, U> for Box<dyn Fn(T) -> Usually<U> + Send + Sync + 'a> {
|
||||||
|
fn render (&self, to: T) -> Usually<U> {
|
||||||
|
(*self)(to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn center_box (area: Rect, w: u16, h: u16) -> Rect {
|
||||||
|
let width = w.min(area.width * 3 / 5);
|
||||||
|
let height = h.min(area.width * 3 / 5);
|
||||||
|
let x = area.x + (area.width - width) / 2;
|
||||||
|
let y = area.y + (area.height - height) / 2;
|
||||||
|
Rect { x, y, width, height }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn half_block (lower: bool, upper: bool) -> Option<char> {
|
||||||
|
match (lower, upper) {
|
||||||
|
(true, true) => Some('█'),
|
||||||
|
(true, false) => Some('▄'),
|
||||||
|
(false, true) => Some('▀'),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,10 +2,12 @@ use crate::*;
|
||||||
|
|
||||||
pub struct FillBg(pub Color);
|
pub struct FillBg(pub Color);
|
||||||
|
|
||||||
render!(FillBg |self, buf, area| {
|
impl<'a> Render<TuiOutput<'a>, Rect> for FillBg {
|
||||||
fill_bg(buf, area, self.0);
|
fn render (&self, to: &mut TuiOutput<'a>) -> Perhaps<Rect> {
|
||||||
Ok(area)
|
fill_bg(to.buffer, to.area, self.0);
|
||||||
});
|
Ok(Some(to.area))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn make_dim (buf: &mut Buffer) {
|
pub fn make_dim (buf: &mut Buffer) {
|
||||||
for cell in buf.content.iter_mut() {
|
for cell in buf.content.iter_mut() {
|
||||||
Loading…
Add table
Add a link
Reference in a new issue